codingame_tools.language.languages.cpp¶
cpp
¶
CgCppLanguage: CgLanguage for CodinGame's "C++", compiled and run inside Docker so nothing
has to be installed locally--see codingame_tools.language.registry for the discovery contract
(LANGUAGE below) that finds it.
COMPILED_MARKER
module-attribute
¶
COMPILED_MARKER = 'cg-build:compiled'
Machine markers the build script writes to stdout (diagnostics go to stderr) so the caller can tell a cached no-op from a real compile. Necessary because a clean compile emits no diagnostics at all, making it otherwise indistinguishable from the cached path.
DEBUG_STDIN_FILE_NAME
module-attribute
¶
DEBUG_STDIN_FILE_NAME = 'debug-stdin'
Name of the file start_debug_session writes into the working directory's .meta/ to redirect
the debugged program's stdin from. A copy rather than the test case's own file, so that exactly
the bytes the caller specified reach the program--see start_debug_session.
DEBUG_STDIN_CONTAINER_PATH
module-attribute
¶
DEBUG_STDIN_CONTAINER_PATH = f'{BUILD_DIR}/debug-stdin'
Where the selected test case's input is staged for the debugged program to read.
A fixed path inside the container, not the working directory's own .meta/debug-stdin,
because the launch configuration names it (set args < ...) and must stay identical for every
working directory in the workspace--see codingame_tools.language.vscode. Safe because a
container hosts one debug session at a time.
start_debug_session copies the real input here; the file the user's working directory holds is
still the source of truth.
CgCppLanguage
¶
CgCppLanguage()
Bases: CgLanguage
C++ (CodinGame's cg_id "C++"), compiled and run in a container so no local toolchain is
needed. See codingame_tools.language._docker for the container/image model.
Source code in codingame_tools/language/languages/cpp.py
295 296 | |
toolchain_fragment
property
¶
toolchain_fragment
Installs nothing: C++ is entirely supplied by the shared gcc11 subsystem, which C also
depends on, so an image containing both carries one compiler rather than two.
The flags live here rather than in the image because they are cg's business, not the
toolchain's -- changing a warning flag should not require rebuilding a multi-gigabyte
image. CG_CXXLIBS is separate from CG_CXXFLAGS because link libraries must follow the
translation unit on the command line, not precede it.
The flags are measured, not guessed. A probe run on CodinGame reports __OPTIMIZE__
undefined and __NO_INLINE__ defined, so the platform compiles at -O0 -- while cg
previously used -O2. That asymmetry is the dangerous direction: an O(n^2) solution fast
enough locally at -O2 can exceed the time limit on submission, and the local run would
have said it was fine. Matching means the local run predicts the remote one, which is the
only reason to pin a toolchain at all. See doc/design/codingame-runtime.md.
-O0 explicitly rather than by omission, and deliberately not configurable. Optimizing
past CodinGame buys nothing: puzzles are designed to be solvable in every supported
language, so the time limits are set by the slowest of them and a C++ solution has orders
of magnitude of headroom either way. It also makes single-stepping faithful -- at -O0 the
code you step through is the code you wrote, with nothing reordered or inlined away.
-lm -lpthread -ldl -lcrypt matches what CodinGame links, which cg previously omitted
entirely -- so a solution using pthread_create or dlopen linked remotely and failed
locally, or worse the reverse.
source_path_in_container
¶
source_path_in_container(ctx, profile)
Which path inside the container to compile: the solution file itself, the same for every profile.
There is only one path to choose from now, which is the point. When a solution.<ext>
symlink sat over a fixed data/solution.src this had to pick one, and both choices were
wrong in different ways: gdb reports two paths per stop location--file from the DWARF
and fullname, its own realpath of it--and the editor navigates by fullname.
Compiling the symlink made them disagree, so a breakpoint bound and then yanked the editor
to the target; adding a sourceFileMap to fix the navigation broke the binding instead,
since it applied in both directions. One real file carrying its language's own extension
makes the two paths identical and deletes the problem rather than balancing it.
The host path is the in-container path--the mount root is bind-mounted at its own
location (see codingame_tools.language._docker)--so there is nothing to translate here.
-x c++ is kept although the file is now named solution.cpp: it costs nothing, and it
still compiles a working directory an older cg left holding data/solution.src, whose
extension g++ doesn't recognize and would treat as a
as a linker input ("file format not recognized").
Source code in codingame_tools/language/languages/cpp.py
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | |
build
async
¶
build(ctx, *, profile='run', timeout=DEFAULT_BUILD_TIMEOUT_SECONDS)
Compile the solution inside the container, bringing the image and container up first if needed. Near-free when the source hasn't changed since the last successful build.
Compiler diagnostics come back in the result rather than as an exception--a compile error is a routine thing to display, not a crash. A Docker problem (no daemon, image build failure) is reported the same way, so a caller never has to catch anything here.
Source code in codingame_tools/language/languages/cpp.py
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
run_streaming
async
¶
run_streaming(ctx, input_text, *, timeout=DEFAULT_RUN_TIMEOUT_SECONDS)
Run the already-built binary in the container, streaming its output.
Does not build--that's a separate step (see CgLanguage.build). It does ensure the
container is up, since losing the container also loses the artifacts that live inside it;
if the binary is missing, the run fails with a message saying to build first.
Source code in codingame_tools/language/languages/cpp.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | |
start_debug_session
async
¶
start_debug_session(ctx, stdin_text, *, timeout=DEFAULT_BUILD_TIMEOUT_SECONDS)
Build the debug profile and stage stdin_text where the debugged program will read it.
Despite the name this starts nothing. gdb launches the program itself, the way it does
for any ordinary local target--see this module's docstring for why there is no gdbserver
in the picture. All that is needed beforehand is a current debug build and the input in
place, so this is a preLaunchTask that prepares and exits.
stdin_text is copied rather than the test case's own file being used directly. That is
not incidental: a contribution's test-case file carries a final newline this client added
(see common.text_files), and reading from it would put one extra byte on stdin--
diverging from cg contribution play and from CodinGame, which appends nothing. Copying
also drops the requirement that the caller's file live inside the working directory.
It lands at DEBUG_STDIN_CONTAINER_PATH, a fixed path inside the container, so the launch
configuration that names it stays identical for every working directory. The route is a
cp inside the container rather than a docker cp, because the workspace is already
bind-mounted at its own absolute path--the file cg just wrote on the host is visible there
under the same name.
Source code in codingame_tools/language/languages/cpp.py
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 | |
stop_debug_session
async
¶
stop_debug_session(ctx)
Nothing to tear down: gdb owns the debugged process, so it dies with the debug session.
Kept as an explicit no-op rather than removed, because the base class declares it and a
language whose debugger does leave something running still needs it. It also means
cg debug stop stays safe to run at any time.
Source code in codingame_tools/language/languages/cpp.py
440 441 442 443 444 445 446 | |
build_vscode_provisioning
async
¶
build_vscode_provisioning(request)
A single cppdbg configuration in which gdb launches the program itself, plus the
task that prepares the build and a devcontainer.json for IntelliSense.
gdb runs inside the container, reached by pipeTransport shelling out to docker exec,
so the host needs nothing but Docker. From gdb's point of view this is then an ordinary
local target: it forks and execs the program, wires breakpoints before a single
instruction runs, and owns its stdin, stdout and stderr.
There is no gdbserver, and that is deliberate. gdbserver exists for targets that cannot run gdb--embedded boards, foreign architectures, machines reachable only over a network. Here gdb is already on the target, so a second debugger-side process in the same container, talking to the first over a socket, buys nothing and costs the thing that matters: whoever execs the program owns its descriptors. With gdbserver doing it, the program's output went to gdbserver's terminal and never reached the Debug Console, and its stdin had to be arranged separately. With gdb doing it, the program's I/O is simply the debug session's, exactly as VS Code's own Dev Containers arrangement works.
Everything the program needs is set up before -exec-run--see _SETUP_COMMANDS, notably
the stdin redirection that makes it read the selected test case.
Nothing here is specific to a working directory, so it is written once and never
regenerated. Which directory and which test case are both resolved at launch time by the
preLaunchTask (--file ${file}, plus .meta/selected-test.json), and the container is
per workspace, so its name is a constant--see
codingame_tools.language._docker.container_name_for.
No sourceFileMap at all. Two separate things make that possible: the workspace is
bind-mounted at its own path, so the paths the compiler recorded are already the paths VS
Code has open; and the solution is one real file rather than a solution.<ext> symlink
over a fixed data/solution.src, so gdb's file (from the DWARF) and fullname (its own
realpath) name the same thing. While that symlink existed, a mapping was needed to stop
the editor navigating away from the file the breakpoints were set in--and it applied in
both directions, which then broke binding.
The task passes ${workspaceFolder} explicitly rather than letting cg guess the mount
root, so VS Code's real workspace wins over find_workspace_root's heuristic. A mismatch
is self-correcting: the mount is part of the container spec hash, so a differently-mounted
container is recreated rather than reused.
Source code in codingame_tools/language/languages/cpp.py
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 | |
build_script
¶
build_script(source, profile)
Shell to compile source (a path inside the container) into /build/<profile>/solution,
skipping the work entirely when nothing relevant changed.
Hashes the source file's contents, its path, and the compiler flags--never a
directory tree. The working directory contains a git object database and tests/, both of
which churn constantly and would cause endless spurious rebuilds.
The path belongs in the hash because g++ records it in the debug info, so it is part of what
the build is, not merely how it was made. The toolchain identity belongs there for the
same reason: the compiler and its flags now come from the image's activation script rather
than from this file, so switching images -- or editing custom.dockerfile -- must invalidate
artifacts compiled by the previous one. Omitting it caused a real staleness bug: switching
which of two identical-content paths gets compiled (data/solution.src versus the
solution.<ext> symlink pointing at it) left the previous binary in place, still carrying the
old path in its DWARF, and breakpoints silently failed to bind.
Caches failures as well as successes: rebuilding known-bad source replays the saved diagnostics instead of recompiling, so a repeat is cheap and says exactly the same thing.
Compiler diagnostics go to stderr; stdout carries only a CACHED_MARKER/COMPILED_MARKER
machine marker. They have to be separable because a clean compile with no warnings says
nothing at all, which would otherwise be indistinguishable from the cached fast path.
Source code in codingame_tools/language/languages/cpp.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
run_script
¶
run_script(timeout)
Shell to exec the built binary.
timeout runs inside the container because killing the local docker exec client does
not terminate the process inside it--an infinite-looping solution would otherwise survive its
timeout and keep burning CPU, with runs piling up in a long-lived container. It's set one
second beyond the caller's timeout on purpose: the outer timeout should win the race, so a
runaway is reported as a clean timed_out=True rather than as an opaque exit code 124. This
is the backstop that guarantees cleanup, not the primary mechanism.
stdbuf -o0 -e0 because a C++ binary on a pipe is fully block-buffered, so a solution
printing a few lines would emit nothing until exit--exactly the problem the Python3 plugin
solves with -u/PYTHONUNBUFFERED=1. Without it run_streaming would stream nothing.
Source code in codingame_tools/language/languages/cpp.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
target_architecture
¶
target_architecture()
What to tell the debug adapter the debuggee's architecture is, or None if unrecognized.
Without it cppdbg warns "Debuggee TargetArchitecture not detected, assuming x86_64" and does exactly that--wrong on any Apple Silicon or ARM host, where it silently misreads the disassembly and register views. Breakpoints, stepping and variables are unaffected, which is what makes it easy to miss.
Derived from the host architecture rather than by asking the container. Strictly the
container is the authority--under QEMU emulation a deliberately foreign image would make this
wrong--but asking it would make the generated configuration depend on whether Docker happened
to be running, so provisioning would emit different output at different times and
cg vscode install --check would flap between them. A configuration file should be a function
of the project, not of daemon state.
The host is a sound proxy in every non-emulated case, because cg builds its image from a
multi-arch base with no --platform, so the container matches the host. An unrecognized host
yields None, leaving cppdbg to its own detection rather than asserting something false.
Source code in codingame_tools/language/languages/cpp.py
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | |