A C++ product, end to end
The commands, in order
This chapter is the delivery loop on a C++ product whose manifest declares no task body at all. Here is what a person types, in order:
./cppdemo.sh support toolchain cpp 19 write the four toolchain commands into the manifest, once
./cppdemo.sh build configure generate the build system, cmake inside the pinned image
./cppdemo.sh build compile compile in the same image
./cppdemo.sh build unit the ctest suite
./cppdemo.sh build analyse clang-tidy over the compile database configure wrote
./cppdemo.sh release tag v0.1.0 tag it
./cppdemo.sh dev deploy up env-first: run the compiled binary on this hostThe first line is the support command, and here it really is the first step rather than a hint beside
one: until it has run, the product has no build commands at all. ./cppdemo.sh support install is the
host preflight beside it.
Two things a C++ reader will look for and not find in that list. The CMakeLists.txt files are written
by build:cmake-files, which cppdemo predates and does not declare - the section below has the walk
that produced it. And there is no documentation command, because cppdemo declares no site: section;
the Java chapter drove the documentation render instead.
Everything under this heading is the table below read as a sequence; the table is what says which rows were really run, on which date, and which of them did not work.
The product, and what it proves
The Python chapter walks one delivery loop with the kernel’s own language underneath
it. The Java chapter walks the same loop with no Python in the product at all - and its
product still writes a build body of its own, a Python function ending in a docker run, because when it
was written there was nothing else to write.
This one is the same loop again with no task body at all. cppdemo’s manifest has no tasks: block:
every command in it is an instance of a catalogue coordinate, and the four that build the product are the
same coordinate, toolchain:run, differing only in the argv they hand a pinned silkeh/clang:19.
That is the before/after this chapter exists for, and it is worth stating plainly rather than implying it:
| what the product writes for its build | |
|---|---|
| javademo (Java, si#26) | impl: "orchestrator.gradle:build" plus a Python module that assembles a docker run |
| cppdemo (C++, this chapter) | an image reference and an argv, four times, in YAML |
The second half of the chapter is why that is not yet the whole story. Driving cppdemo was the first time the si#95 path was walked end to end by a product, and it does not survive the walk: the ready-made configuration cannot be asked for, the shape it writes does not assemble, and the command it produces cannot report a verdict. Every one of those has a ticket and a row in the table below. Nothing on this page is a plan.
The product was scaffolded for this chapter and driven on one machine on 2026-09-09, against the kernel
in this checkout rather than a released wheel. That is not a detail: simplon init pinned
simplon==0.7.1 in the product’s own requirements, which is the newest release it knew of and predates
toolchain:run entirely, so the first thing a C++ product has to change is the pin. It is about as
small as a C++ product gets:
cppdemo.yaml the manifest - and there is no `tasks:` block in it
CMakeLists.txt the CMake project, three targets
src/calculator.h src/calculator.cpp three functions
src/main.cpp what `deploy up` runs
tests/calculator_test.cpp three ctest cases
orchestrator/src/python/orchestrator/ five .py files - five scaffolded, zero written hereZero lines of product code outside C++ and YAML. The five Python files are the scaffold’s adapter - the manifest path, the environment provider, the composition root - and this product added nothing to them and named none of their placeholder bodies.
The labels, and why they are on the page
Same rule as the other two cases, for the same reason: a page describing a pipeline nobody has driven is worse than no page, because the reader concludes they misread the site rather than that the site is inventing.
run - somebody executed it. The evidence names where and when.
derived - read out of a manifest, a task body or the catalogue, and not executed for this page. It is a statement about what is declared, not about what was observed.
does not exist yet - the step is named because leaving it out would make the loop look complete. Every such row carries the ticket where the decision is still open.
| # | Step | Command | Label | Evidence |
|---|---|---|---|---|
| 1 | Scaffold the product | simplon init cppdemo |
run | this checkout, 2026-09-09; five Python files and a CLI that runs before there is anything to run |
| 2 | Ask the kernel for the C++ configuration | ./cppdemo.sh support toolchain cpp |
does not exist yet | rc 1, a traceback: the profile is real and the command cannot carry its one parameter, simplon#105 |
| 3 | Write the profile’s four commands into the manifest | support:toolchain |
run | 2026-09-09 06:35:32, rc 0, four commands written - and what it wrote does not assemble, simplon#105 |
| 4 | Generate the build system, in Docker | ./cppdemo.sh build configure |
run | 2026-09-09 06:41:07, rc 0; transcript below |
| 5 | Compile in the pinned toolchain image | ./cppdemo.sh build compile |
run | 2026-09-09 06:41:07, rc 0; transcript below |
| 6 | Run the ctest suite in the same image | ./cppdemo.sh build unit |
run | 2026-09-09 06:41:08, rc 0, three of three |
| 7 | A failing C++ test arrives red | ./cppdemo.sh build unit |
run | 2026-09-09 06:41:38, rc 8; transcript below |
| 8 | A tree that does not compile reports a full green suite | ./cppdemo.sh build unit |
run | 2026-09-09 06:41:47, compile rc 2 and unit rc 0; table below |
| 9 | Static analysis over the compile database | ./cppdemo.sh build analyse |
run | 2026-09-09, rc 1: the profile’s argv named no input file; fixed and re-driven the same day, simplon#111 |
| 10 | Append an argument to the pinned argv | ./cppdemo.sh build analyse src/calculator.cpp |
does not exist yet | rc 2, refused by the command line before docker was asked, simplon#105 |
| 11 | A verdict and an Allure archive for the ctest run | test:accept |
derived | a gate names a command since simplon#106 and really reaches a toolchain:run one since simplon#136, which drove a ctest through one green and red; cppdemo declares no suites: section, so nothing was run on this product |
| 12 | Refuse a toolchain reference that is not pinned | ./cppdemo.sh build configure |
run | 2026-09-09 06:45:25, rc 1; message below |
| 13 | Cut the release tag | ./cppdemo.sh release tag v0.1.0 |
run | 2026-09-09 06:41:28, rc 1; refused, nothing cut |
| 14 | Publish the binary or a container image | release:artifact, release:image |
derived | they read an artifacts: and an images: section, and cppdemo declares neither |
| 15 | Deploy to the local host | ./cppdemo.sh dev deploy up |
run | 2026-09-09 06:41:18, rc 0; output below |
| 16 | Deploy past the local host - Proxmox, Portainer, a cloud | - | does not exist yet | simplon#5 |
| 17 | Render the documentation | docs:render, docs:site |
derived | neither reads anything C++; the Java chapter drove docs:render and cppdemo declares no site: section |
Of the 17 steps above, 11 are run, 3 are derived and 3 does not exist yet.
Three open rows is two more than the Java chapter carries, and that is the honest shape of a capability
one week old: the toolchain itself does everything it promises, and the path from “I have a C++ product”
to “I have those commands” is not joined up yet. Row 11 was the fourth until
simplon#136, and how it stopped being one is worth
the sentence: the key it needs had existed since simplon#106 and could not name the kind of command this
product has, because every test behind that key used a Python stub. It is derived rather than run for
the ordinary reason the other two are - this product declares no suites: section, so nobody ran it
here.
What the kernel carries for C++
simplon.tasks.profiles is a table, and that is a design decision rather than an implementation detail:
the kernel learns no CMake and no MSBuild, it learns to run a pinned image over a product tree as the
calling user. What it carries per language is argv and paths, nothing callable. There are four profiles -
cpp, java, dotnet and python - and the C++ one carried four commands when this
chapter was measured:
proto -
protoc and its plugin in an image of its own, generating gRPC stubs into build/proto/. The
scaffolder writes it alongside the ones below; nothing above changes.| command | what it runs in silkeh/clang:19 |
|---|---|
configure |
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo |
compile |
cmake --build build -j |
unit |
ctest --test-dir build --output-on-failure |
analyse |
run-clang-tidy -p build -quiet -warnings-as-errors=* |
Those are the four cppdemo runs, unedited. The manifest entry for one of them is the whole of what the product writes about compiling C++:
compile:
task: "toolchain:run"
help: "Compile the product in the containerised toolchain."
with:
body:
image: "silkeh/clang:19"
workdir: /src
argv: ["cmake", "--build", "build", "-j"]
where: "build compile"
extra: []Compiler and version are manifest data, so moving to clang 20 is a one-line diff in a file a reviewer
reads, not an edit to a script. And the same coordinate is filed under a different group further down,
which is what makes toolchain:run a family rather than a placement - the binary this product ships is
run by the same task that compiled it:
up:
task: "toolchain:run"
help: "Run the compiled binary on this host (si#5: there is no further target)."
with:
body:
image: "silkeh/clang:19"
workdir: /src
argv: ["./build/cppdemo"]
where: "deploy up"
extra: []The three things between that table and a running command
The design says a product declares { language: cpp, version: "19" }, runs one command and receives the
four. Driven, that path stops three times, and the shape above is what is left after going around all
three. They are one ticket, simplon#105, because
they are one story.
The version cannot be asked for. Every profile’s image is a template, and the catalogue declares
exactly one parameter for the scaffolder - the language. simplon.signatures.bindable drops **kwargs,
so there is no second value to pass and no manifest key that carries one:
$ ./cppdemo.sh support toolchain cpp
KeyError: 'version'Pinning it in the manifest is refused rather than ignored, which is at least loud:
support toolchain: with:/params: name parameter(s) the impl does not take: version.
The block the scaffolder writes does not assemble. Called with the version it cannot receive, the scaffolder does its job and writes four commands in the design’s own shape:
compile:
task: toolchain:run
with:
image: silkeh/clang:19
workdir: /src
argv:
- cmake
- --build
- build
- -jThat manifest LOADS. It does not ASSEMBLE, because toolchain:run’s body takes body, where, extra
and network, and image/workdir/argv are the keys inside the first of those:
build configure: `with:`/`params:` name parameter(s) the impl does not take: argv, image, workdirSo the failure arrives at the first command anybody types afterwards, not at the scaffold - and the nesting in the manifest block above is what a product has to write by hand until that is decided.
yaml.safe_load and
writes it back with yaml.safe_dump, so on the manifest simplon init had just written, forty-two
comment lines became zero and every flow mapping was expanded to block style. The argument for
scaffolding rather than resolving at run time is that the product then reads its own build in its own
file; a writer that keeps the data and drops the prose collects that argument and spends it.
simplon#110 is where the two ways out are weighed.And the toolchain asks for a lab instance it does not use. run_toolchain resolves the instance id
before it looks at whether the command declares a cache volume at all, so a product with no caches still
needs the section, and simplon init writes none:
instance:
env_var: CPPDEMO_INSTANCE
default: dev
max_id_len: 2Since this walk: simplon#105 is closed. All
three faces above were fixed on 2026-09-09 and driven end to end on a throwaway product the same day:
support toolchain cpp 19 takes the version as a declared parameter, toolchain:run takes the
manifest’s keys - image, workdir, argv, env, caches - as its own parameters, so the flat block
the scaffolder writes assembles unchanged, and the lab instance is resolved only where a cache volume
needs one. The caller’s argv reaches the tool again too (build compile --verbose).
The transcripts and the table above are left standing as the record of what cppdemo met on the date they
were measured - which is what makes this chapter evidence rather than documentation. What a product
writes TODAY is the block without body:, where: and extra:; the nesting below is the workaround
that was necessary before the fix.
build
One command, one container, and the kernel does not know what CMake is.
$ ./cppdemo.sh build configure
-- The CXX compiler identification is Clang 19.1.7
-- Configuring done
-- Generating done
-- Build files have been written to: /src/build
$ ./cppdemo.sh build compile
[ 16%] Building CXX object CMakeFiles/calculator.dir/src/calculator.cpp.o
[ 33%] Linking CXX static library libcalculator.a
[100%] Linking CXX executable cppdemo
[100%] Built target cppdemoWhat stands behind both is one assembled argv, and it is worth reading once because every product on this kernel gets the same one:
docker run --rm --user 0:0 -v /tmp/cppdemo:/src -w /src silkeh/clang:19 cmake --build build -j--user is there from the first line, and that is not tidiness. netctl’s hand-written gradle invocation
does not carry it, and on 2026-09-08 that left a repo-root build/ owned by root, after which every CI
run on a freshly cleared runner died at the next step that had to create a directory (netctl#1778). A
product that declares its build here inherits the right answer instead of copying the wrong one - which
is also why the Java chapter’s GRADLE_USER_HOME callout has no counterpart on this page.
The image goes through the kernel’s one image gate, with the toolchain’s own hint on the end of it.
Written as silkeh/clang:latest and run, that manifest line answers:
[06:45:25] ERR build configure: 'image' must pin a version, not the moving tag 'latest' (got 'silkeh/clang:latest') - a build whose output depends on when it ran is not a build; a toolchain must name its versiondocker.pinned_image is the gate the kernel puts its own images through - the Hugo image a site:
section declares, the docToolchain tag docs:render reads. The difference from the Java chapter is
where the product stands relative to it: javademo chose to call the gate from its own task body, and
cppdemo cannot avoid it, because the image reference is a manifest key the kernel reads. The rule reaches
the declaration, so nobody has to remember to keep it.
The argument the caller cannot add
The design’s other half is that the manifest pins the argv and the caller appends: build compile is a
CI step by name, and a person at a terminal keeps the tool’s whole vocabulary behind it. toolchain.argv
implements it, and no command line reaches it. extra is a required parameter of the body, so it has to
be pinned in the manifest, and a pinned parameter leaves the command line entirely; toolchain:run also
carries no passthrough_args, which is the mechanism that would have let the tail through:
$ ./cppdemo.sh build analyse src/calculator.cpp
Got unexpected extra argument (src/calculator.cpp)That is row 10, and it is what made row 9 a refusal rather than a pass: clang-tidy -p build names no
source file, so the profile’s analyse entry could not analyse anything, and the mechanism that was
supposed to complete it was the one that was missing.
Since this walk: simplon#111 is closed, and the
argv in the table above is what the kernel carries today. run-clang-tidy ships in the same image,
reads the compile database configure already wrote and analyses everything the project really compiles -
re-driven on a scaffolded product on 2026-09-09, three files out of three, rc 0.
Two words go with it, and both are measurements rather than taste. -quiet drops the several-hundred-line
dump of every enabled check the driver prints ahead of the run. -warnings-as-errors=* is what makes the
command a check at all: on a deliberate null dereference the driver prints
clang-analyzer-core.NullDereference and still exits 0, because clang-tidy reports its findings as
warnings - so without the flag analyse is green on every tree, and a gate naming it is green forever.
With it, that tree is rc 1 and a clean one is still rc 0.
Row 10 is closed too, by si#105: toolchain:run now declares the variadic tail and
passthrough_args: true, so ./cppdemo.sh build analyse <anything> reaches the tool. The appending rule
is no longer what analyse depends on, which is the point of the driver.
The file the product still wrote by hand
Read the tree listing at the top of this chapter again. Every line of cppdemo’s build is manifest data
except one: CMakeLists.txt, three targets, written by a person. Nothing on this page labelled that a
gap, because when the walk was taken there was nothing else to write - which is exactly the shape the
chapter is about, one file lower down.
Since this walk: build:cmake-files writes that file.
simplon#102 reads the tree as the declaration it
already is - src/<name>/ holding sources is a library, one holding a main.cpp is an executable, and
each tests/<name>_test.cpp is one ctest case - and writes one CMakeLists.txt per directory plus the
root file that adds them. The one thing a directory cannot show is what a target depends on, and an
optional build: section carrying targets: { cppdemo: { depends: [calculator], include: [src/calculator] } }
is the whole of that exception. Guessing it from include paths was refused in the design: it reads a
preprocessor approximately, and an approximate answer fails at link time, in a message about symbols
rather than about the manifest.
Driven end to end on a throwaway product on 2026-09-09, in this same silkeh/clang:19: four files
written, configure rc 0, compile rc 0, ctest 1 of 1 passed, and the binary printed its line.
Two things a product adopting it has to know, and both are consequences rather than caveats. The
generated files are committed and each carries a DO NOT EDIT header, because the generator
overwrites without asking - deliberately the opposite of support:toolchain’s never-clobber rule, since
a manifest is a product’s own statement and a CMakeLists.txt is a rendering of one. And the output
layout moves: add_subdirectory puts a target’s binary under its own directory, so an executable
declared in src/cppdemo/ lands at build/src/cppdemo/cppdemo and not at the build/cppdemo that row
15’s deploy up argv above names.
The table and the transcripts above are left standing as the record of what cppdemo met on the date they were measured.
Since this walk, again: a target can say what it IS. Driving the generator produced its own next
three tickets, and two of them were one decision.
simplon#131 asked whether a nested directory under
src/ is its own target, and simplon#134 asked
where a unit test belongs - and the second answer depends on the first, so they were taken together.
A nested directory folds into the target above it. src/net/tcp/socket.cpp is a source of net, at
any depth, and there is no target called tcp. The other answer needs a name for it, and every name is
an invention - tcp collides with src/util/tcp in the one namespace CMake, the solution and the
depends: key all share. This one needs none, it is what the .NET SDK’s **/*.cs glob already does
whatever a solution file says, and it is the only one of the two a product can escape: a directory that
wants to be its own library is written as src/<name>/. A main BELOW a target’s root is refused rather
than folded, because a static archive carrying a stray main is invisible - the linker gives up an
archive member only to resolve an undefined symbol, and whoever links it defined main already.
A unit test sits beside its unit, and it is not part of it. src/<target>/<name>_test.<ext> is a
unit test; tests/ holds what is about the assembled product. That is a rule about meaning - a reader
has to be able to tell a level from a path without opening the file - and the thing it replaced was not
a missing feature. Every source directly in a directory went into that directory’s target, so
src/net/net_test.cpp was compiled INTO libnet.a: the test’s code and its main shipped inside the
artefact and nothing in any run said a word. A co-located test names neither a dependency nor an include
path, because sitting inside the library’s directory already says both. _test is the one spelling;
test_<name> is deliberately not a second one, or test_helpers.cpp becomes a test target with no
main.
tests/system/ and tests/acceptance/ name a level, and a level is a ctest label. So
ctest --test-dir build -L unit really selects the unit tests, and a test directly in tests/ keeps the
meaning it had and carries no level. Worth knowing before writing a gate around it:
ctest -L <a-label-nothing-carries> exits 0 and prints No tests were found!!!, so a check that
reads the exit code is green for a level that contributed nothing.
A library can be shared, and its headers can be found. add_library was written bare, so every
target followed BUILD_SHARED_LIBS - which nobody sets, so every library was static and no product could
have both. targets: { plugin: { kind: shared } } is the one word a directory of sources cannot say
about itself; static and executable are the other two, and the kind is written into the generated
file rather than left implied. Include directories were PRIVATE, which means used to compile the target
and inherited by nothing, so a library’s headers could not be found by the target one directory over. A
library now exports the directory its sources sit in, and an executable and a test keep theirs private
because nothing links either. The export is usable within the generated project and no further -
there is no install(TARGETS ... EXPORT ...) and no package config, because an export set with no
importer is written for a consumer that does not exist, and the consumer that will exist is
simplon#128.
And simplon#132: the build had no debug symbols
at all. Nothing set CMAKE_BUILD_TYPE, and with a single-config generator an empty one means no
optimisation and no -g. It is said in the profile’s configure argv and NOT in the generated
CMakeLists.txt, because a build type belongs to the run: a committed set(CMAKE_BUILD_TYPE ...) would
decide it for every consumer of that tree forever. RelWithDebInfo is the default because a CI build is
the one run whose artefact ships and whose failure has to be explicable, and
./cppdemo.sh build configure -DCMAKE_BUILD_TYPE=Debug still wins - measured, the last -D is the one
in the cache.
Two things are named rather than solved. A SHARED target gets no -fvisibility=hidden and no generated
export header, so it links on Linux and not on Windows, and the kernel ships no Windows toolchain. And a
co-located unit test is REFUSED on the .NET side: an SDK-style project compiles every .cs beneath
itself, so the file is part of the library whatever the solution says, and a second project in one
directory is not a shape MSBuild has. Saying so beats generating something that builds and is wrong.
Driven end to end in this same silkeh/clang:19, and read off the artefacts rather than off the
generated text - which is the point, because add_library(plugin SHARED ...) appears in the file
whether or not a shared object came out:
$ file build/src/plugin/libplugin.so build/src/calculator/libcalculator.a build/src/e2edemo/e2edemo
build/src/plugin/libplugin.so: ELF 64-bit LSB shared object, x86-64 ... with debug_info, not stripped
build/src/calculator/libcalculator.a: current ar archive
build/src/e2edemo/e2edemo: ELF 64-bit LSB pie executable, x86-64 ... with debug_info, not stripped
$ nm build/src/calculator/libcalculator.a
calculator.cpp.o:
0000000000000000 T _ZN4demo3addEii
mul.cpp.o:
0000000000000000 T _ZN4demo3mulEiiRead the second command twice. The nested detail/mul.cpp is in the archive and the co-located
calculator_test.cpp is not, and neither of those two facts is visible in any CMakeLists.txt. The
executable links two libraries and its manifest names no include path at all.
with debug_info is the phrase that matters and not stripped is not: a build with no build type at all
is also not stripped, so an assertion on the second word would have been green on exactly the tree
simplon#132 was opened about.
test
The suite runs, and it runs in the image the compile ran in - which is the one rule the design states about static analysis and testing, and the reason it states it: a checker that cannot see the dependencies it is checking against degrades into a blanket exception, and a test binary is no different from a checker in that respect.
$ ./cppdemo.sh build unit
Start 1: adds
1/3 Test #1: adds ............................. Passed 0.00 sec
Start 2: multiplies
2/3 Test #2: multiplies ....................... Passed 0.00 sec
Start 3: divides
3/3 Test #3: divides .......................... Passed 0.00 sec
100% tests passed, 0 tests failed out of 3Break one assertion in demo::mul and it arrives red, with ctest’s own accounting of what failed:
2/3 Test #2: multiplies .......................***Failed 0.00 sec
multiplies: expected 20, got 9
67% tests passed, 1 tests failed out of 3
The following tests FAILED:
2 - multiplies (Failed)1 rather than to 0.The green that is not about the product
Three runs, one after another, on the same tree:
| run | build compile |
build unit |
what ctest said |
|---|---|---|---|
| green | 0 |
0 |
100% tests passed, 0 tests failed out of 3 |
| one assertion broken | 0 |
8 |
67% tests passed, 1 tests failed out of 3 |
| a type error in the tree | 2 |
0 |
100% tests passed, 0 tests failed out of 3 |
Read the third row. build compile failed on a cannot initialize a variable of type 'int' with an lvalue of type 'const char[11]', and build unit then ran the binaries the previous build left in
build/ and reported every test passing. Nothing lied: ctest ran what was there, and what was there was
last week’s answer.
The Java chapter has the same hazard and an answer for it. Its gate is an impl: - a callable the kernel
runs for a verdict - so the kernel exports a marker path in SIMPLON_SETUP_FAILED before it, the runner
writes the stage that broke into it, and the report comes out setup-failed and empty rather than
plausible. A toolchain:run command has no callable to point at - and it does not need one, because it
is a command, and a gate may name a command in the product’s own tree
(simplon#106). A gate whose command: is the
ctest command above and whose preamble: is the compile stops on the third row at the compile: the
verdict is setup-failed, ctest is never asked about last week’s binaries, and the archive says the
build fell over rather than that three tests passed. Nothing is copied out of the build commands - the
image, the argv and the caches stay declared once, where they already are.
That pairing has since been driven rather than described - a real ctest in silkeh/clang:19,
behind a real command: gate, green, red, and stopped at a failed compile
(simplon#136, which is also what found that the key
could not name a toolchain:run command at all until then). What it has not been driven on is this
product: cppdemo declares no suites: section, which is why row 11 says derived.
The three runs above were measured before that key existed, and they are still exactly what the two commands do when they are run bare. That is the point of the third row rather than an artefact of it: the pairing is what a gate adds, and two commands nobody paired report the last good result.
cppdemo declares no suites: section, so what is above is what it does today; the block that closes
the gap is three keys in one section and no Python, which leaves the claim this chapter opens with
standing in the test phase as well as in the build.
What a gate is, what a level is and how a
command backs one are all in
Test levels; the five outcomes a gate can report, and which two of them
are statements about the product, are in the Python chapter’s test section.
release
The tag is the version and the guard is the kernel’s, and neither of those has anything to do with the
language. release:tag is placed in cppdemo’s manifest with one line and no body of its own, and it
behaves exactly as it does for a Python or a Java product - which the chapter can show, because it
refused: with no origin/main in the checkout the question “main carries this commit” has no answer, so
./cppdemo.sh release tag v0.1.0 exited 1 and git tag afterwards listed nothing. What the guard checks,
and what it deliberately does not, is in Cutting a release.
What gets published is the part that differs, and cppdemo declares none of it: release:artifact reads
an artifacts: section and release:image an images: one. Row 14 says derived for that reason and
not because anything is missing - a stripped ELF binary and a jar are the same kind of thing to the
kernel, which is the point.
deploy
Same answer as the other two chapters, and it is the state of the kernel rather than an omission here.
What exists is the local host, and for this product it is one more instance of the same coordinate: the binary runs in the image that compiled it, on the machine you typed the command on.
$ ./cppdemo.sh dev deploy up
cppdemo: 2 + 3 = 5There is a real difference from the Java chapter hiding in that line, and it is not in cppdemo’s favour.
javademo runs its jar in a JRE image rather than the JDK that built it - what ships is the artefact, and
the thing that runs it is not the thing that made it. cppdemo runs its binary in the full clang image,
because the product declared the same image twice and nothing suggested otherwise. A C++ product that
cared would name a runtime image in the deploy up body, and the manifest is where that decision would
be visible.
What does not exist is everything past that host. deploy is the rib the catalogue draws all but
empty: the group name, the env-first gate, and two tasks that say which version goes out.
simplon#5 is where that decision is open, and
the empty rib is why leaving the rest of it empty is the
expensive choice rather than the lazy one.
docs
Both documentation commands are language-agnostic, and this chapter drove neither. docs:render runs
docToolchain in Docker and produces HTML and PDF from AsciiDoc; the Java chapter drove
it and its transcript is there, including the ownership trap that makes the order of build and docs
matter on a fresh tree. docs:site reads a site: section cppdemo does not declare. Row 17 says
derived for both, and repeating a run that has nothing C++ about it would have been a third copy of
the same evidence.
What this chapter does not repeat
Each of these is told once, elsewhere, and told properly:
- the same loop with the kernel’s own language underneath it - A Python product, end to end
- the same loop with a hand-written build body - A Java product, end to end
- what a gate is, how a level is declared, and how a foreign runner attaches - Test levels
- why the tag is the version and what the release guard refuses - Cutting a release
- why
deployandmonitorare empty - The five phases - getting a product to exist in the first place - Getting started