Releases: mcpp-community/mcpp
Release list
v2026.10.3.1
This release is identical to 2026.10.2.1 in code; the bump exists to publish a
fresh GitHub Release and a fresh index entry, because the 2026.10.2.1 release
ran while the canary runners on ubuntu-24.04 were unavailable and the publish
itself could not complete. It carries no behavioural change and is wire-
and-cache-compatible with 2026.10.2.1: a record written by 2026.10.2.1 is
still admitted by 2026.10.3.1, and a record written by 2026.10.3.1 is admitted
by 2026.10.2.1 (the engine field exists in both; its value 2026.10.x matches
its own self).
CI
- Bootstrap fast path.
bootstrap-mcppskips the xlings tarball fetch,
extract andself installwhen the restored cache already holds the pinned
xlings version. The check is the one case the guard would otherwise miss:
a stale xlings cache from before the pin was bumped, or a binary that no
longer runs because its dynamic loader is gone. On Linux it shaved the
Run ./.github/actions/bootstrap-mcppstep from ~5 s to ~2 s on the
bootstrap-mcpp invocation per job, summed over ~30 jobs per run; on Windows
from ~68 s to ~30–60 s. The fast path addresses xlings by$XL_BIN_PATH
rather than by the bare name, because Git Bash re-derivesPATHfrom the
Windows environment on every child shell and drops the mixed-separator
entry theexport PATHwrites;xlings self installwould write it back
via[Environment]::SetEnvironmentVariable, but the fast path skips that
step. - Windows e2e MinGW prewarm guard. Each
ci-windows-e2eshard now
probes the two locationsrun_all.shlooks at forg++.exebefore
invokingmcpp toolchain install mingw 16.1.0, so a sandbox restored
from the build job's prewarm does not pay the install call again.
Measured 68 s → 45 s on shard 1/3; the other two shards were already
cheaper. The install is still run when the probe finds no payload, so
a missing-prewarm shard is unchanged.
Operator-facing note (transient)
release.ymlcarries askip_canariesworkflow_dispatchinput,
documented in the workflow, kept on the main branch during the
ubuntu-24.04 runner outage of 2026-10-02. The canaries design (WS10) is
intact: the input isif: ${{ !inputs.skip_canaries }}on the
canariesjob, and the gate is otherwise unchanged. This release's
GitHub Release was created via the input. The input is removed by the
commit that follows the next successful canary run.
v2026.10.1.3
This release gives every tool a build uses a source that can be declared,
decided by a build program, and read back (mcpp#755;
.agents/docs/2026-10-01-tool-and-toolchain-sources-design.md). A project that
writes none of the new keys builds exactly as before, and its output is
unchanged.
Added
[xlings.overrides]states where a declared payload comes from, in the
root manifest (also under[target.'cfg(..)']), as
MCPP_XLINGS_OVERRIDE_<NS>_<NAME>, or in~/.mcpp/config.toml. An
overridden payload is not provisioned and does not reach the offline gate;
mcpp::xpkg_diranswers the root it implies, and the new
mcpp::xpkg_programandmcpp::xpkg_sourceanswer the program it named and
override. A statedversionis checked against every requirement a package
of the graph made. A dependency that writes the table is refused: which
payloads a package needs is its own statement, where they come from is the
project's.provision = "on-request"installs a payload when a build program asks for
it, withmcpp::xpkg_request. Every request of one invocation is installed
together and only the programs that asked run again, so a build whose program
names its own tool downloads nothing.mcpp emit build-databaseinstalls
nothing and recordsMCPP_BUILD_DATABASE_PAYLOAD_DEFERRED.- A toolchain named by path:
[toolchain] <key> = { path = "<dir>", prefix, sysroot, family, launcher, tools }, orMCPP_TOOLCHAIN=path:<dir>. mcpp
probes the drivers in the tree, identifies them, drives them with its own
link model, and writes nothing into the tree. The driver and each stated tool
enter the fingerprint by content, and a build records them beside its output,
so the fast paths decline once one of them changed. [toolchain] bootstrapnames the toolchain that compiles and runs build
programs when it should not be the one building the project.[toolchain] <key> = { configure = "build.mcpp" }hands the build
toolchain to the root build program: it runs once in a toolchain phase, where
mcpp::phase()is"toolchain", and states the toolchain with
mcpp::toolchain(key, value). That phase may state nothing else.- A build reports its sources. A source that is not the ecosystem's gets a
line of its own (Using … [custom · mcpp.toml:22],Bootstrap …), the
Finishedline summarises them, and the record is written to
resolution.json.mcpp why sources,mcpp why tool <name>and
mcpp why payload <ns:name>report it, including asmcpp.why.sourcesunder
--format json. --managed-only/MCPP_MANAGED_ONLY=1refuses a build whose toolchain,
payload or plugin tool came from anywhere but the ecosystem, naming each.- Protocol 15 for build programs:
xpkg_source,xpkg_program,
xpkg_request,xpkg_pending,phase,decisionandtoolchain.
Changed
mcpp why toolchainstates the source of the toolchain and the origin the
resolution recorded, in place of a sentence listing every way one can be
chosen.
Fixed
- A build program's compile command goes through a response file when it
outgrows the channel it travels. The command carries one
-fmodule-file=<name>=<path>per host module the program imports, with
absolute paths, and on Windows it reaches a shell that tolerates 8191 bytes: a
program importing fifteen modules reported onlyThe command line is too long., naming neither the length nor the cause. The file is written in the
grammar its driver reads -- single quotes for clang and GCC, which treat a
backslash as an escape, Windows quoting for cl and clang-cl -- and stays beside
the program for a failed compile to show. - A manifest key this engine does not know says which engine the package
needs. A package written for a newer mcpp was refused withunknown key '<key>'and nothing about the version, because the engine floor is checked on
the document that very parse failed to produce. The refusal now names the
floor, this engine and the upgrade, which is what a reader meets first after a
plugin collection raises it. which()resolves a name that is also a shell builtin.command -v true
printstrue, not a path, so a bare-name payload override of such a name was
refused as not found on a machine carrying/usr/bin/true.
v2026.10.1.2
This release implements the design for a pack's build and a compile that does
not depend on the member selection
(.agents/docs/2026-10-01-pack-drive-and-selection-independent-compile-design.md),
and resolves mcpp#751 and mcpp#753. A project that imports a module of a
dependency, and every project on an ELF target, is compiled once more after the
upgrade, because its commands change (both under Changed).
Fixed
- A unit is compiled by the same command in every selection of a
workspace. The selections of one configuration share a build directory,
and three facts about the whole graph reached the commands of members they
did not concern, so each switch between--workspaceand-precompiled
them (mcpp#751; GalTranslPP 3.1.3 recompiled its core for 3m22s):- a module name that two members provide moved every BMI of that name below
its provider's directory, and told the importers so, only when the graph
held both providers; - a file that a member lists from outside its own directory was owned by the
workspace's virtual root, whose object census depended on how many members
listed it; - a member that builds a shared library put
-fPICon every unit of the
graph.
A command now depends on the unit's package, the packages it reaches, the
features the selection activates for it and the declarations a selected
member holds as the root, and on nothing else in the graph.mcpp build --workspacefollowed bymcpp build -p <member>compiles nothing, and so
does any alternation of selections (e2e 872).
- a module name that two members provide moved every BMI of that name below
mcpp packstates the build it performs. Its build wrote no package
line, no status row and noFinished, so a release job whose pack
recompiled mcpp showedPlanningfor six minutes (mcpp#753). A pack is now
stated asmcpp buildstates one, withFinishedbefore the firstPacking
line and oneFinishedfor a pack over several configurations; the second
pass of a dispatched--formatis stated in the same way (e2e 871).[build] jobsbounds every command that compiles.mcpp testand
mcpp packran ninja's default number of jobs whatever[build] jobs,
--jobsorMCPP_JOBSsaid, which on a machine with little memory exceeded
the bound the key exists to enforce. The backend reads the job count from
the plan, and the reclaim of stalebmi_schedule = "on"tokens, which ran
only undermcpp build, runs before the first drive of every build
directory (e2e 871).- A pack fills the global dependency cache with the dependencies its
build compiled, asmcpp buildandmcpp testdo.
Changed
- Every package's BMIs lie below the package's directory, except the root
package's. The rule is the one object files have followed since mcpp#233:
gcm.cache/<package>/<module>.gcm(pcm.cachewith clang). A unit that
imports a module of another package reads one module map of the modules it
reaches through its imports: a mapper file with GCC, and an argument file
@<build directory>/modmap/<package>-<hash>.modmapof-fmodule-file=or
/referencelines with clang or MSVC. A project whose modules are all its
own is laid out, and every command spelled, as before. A package whose
module name another package of the graph provides is served from the global
dependency cache again; 2026.9.30.2 compiled it in the project. - Every unit of an ELF target that is not freestanding is compiled with
-fPIC, whether or not the graph links a shared library, as rustc's
default relocation model does on these targets: an object compiled once
serves a program and a shared object. Mach-O compilers produce
position-independent code by default, PE has no such flag, and nothing
changes on those targets or on freestanding ones. The global dependency
cache keys these entries by the flag, so each is compiled once more and
then served again.
v2026.10.1.1
This release implements the plan for member selection, build programs prepared
once, a pack over several members, and the output streams
(.agents/docs/2026-09-30-member-selection-and-build-program-cost-plan.md),
and resolves mcpp#748, mcpp#749 and mcpp#750. Two changes are observable by
scripts: narration moves to standard error, and a mcpp run whose build failed
exits 101 (both under Changed).
Changed
- Narration is written to standard error, and a command's result to
standard output. The narration is what says what a command is doing: the
status lines that begin with a verb (Resolving,Compiling,Finished,
Running,Packed,Downloadingand the others), the progress bars, the
status row of a terminal, and the blank line afterRunning. The result is
what the command was asked to produce: the program's output undermcpp run,
a document or a listing, and formcpp testeach verdict, thetest result
line and theworkspace resultline. Releases up to 2026.9.30.2 wrote the
narration to standard output. A script that read a status line from standard
output changes:mcpp build | tee logbecomesmcpp build 2>&1 | tee log. A
CI log, which captures both streams, is unaffected. The live status row is
drawn on standard error, whether or not standard output is a terminal
(e2e 864 to 866). - A
mcpp runwhose planning or build failed exits 101, ascargo run
does. It exited 1 or 2, which a program that returns 1 also does. The
program's own status still passes through, a refused start keeps 125 to 127,
andbuild,testandpackkeep their statuses. A program or runner that
itself returns 101 reads as a failed build (e2e 863). mcpp testover several members plans once per configuration. The
members of one configuration are one plan with each member's tests, so a
package they share is compiled once with the union of their features and its
build program runs once; then each member's tests run in[workspace] membersorder with that member's runtime directories, continuing past a
member that fails. The JSON stream adds agroup_buildrecord before a
group's first test and abuild_groupfield in each member's summary, whose
build_msis the group's build time; each test record addsbuild_ms, the
build time of its own binary.--workspace-timeoutbounds the runs and
--build-timeoutthe build (e2e 854 to 856).- A workspace's build programs share what they import and compile at the
same time (mcpp#748). The bundledmcppmodule and each host module are
compiled once per key: the host compiler, the standard and every flag of the
compile, the imported BMIs and the interface's content. The engine's module
and the host modules of index packages are kept in the global cache, where
mcpp cache gccollects them; a host module of a path or git dependency or
of a workspace member is kept under the workspace's
target/.build-mcpp/host-modules/. The members' programs are compiled
together, up to the job count, and run in the order they always ran, so the
plan is the one a serial build writes. On a fixture of #748's shape the whole
build takes 0.33 to 0.48 s instead of 0.89 to 1.22 s (e2e 857 to 861).
Added
- A repeated
-pselects every member it names (mcpp#750).build,
test,packandmcpp emit build-databaseread one selection: a set of
members in[workspace] membersorder, whatever the order of the-p
values. A value that names no member is refused before planning, and the
refusal lists the members.mcpp runexecutes one program and refuses a
second-p, naming both;--workspacetogether with-pis refused
(e2e 852, 853). --exclude <member>removes members from a whole-workspace selection
(--workspace, or a virtual root without-p), onbuild,test,pack
andmcpp emit build-database. It is refused with-p, for a name that
matches no member, and when it leaves no member (e2e 853).mcpp packover several members (mcpp#749).--workspace, a repeated
-pand--excludepack several members with one plan per configuration and
one build; each member is staged in a tree of its own, its build program sees
its ownpack_stage_dir(), and a dispatched format runs one second pass per
configuration. A positional target, several--targetvalues, an--output
that is a file, a member no provider of the requested format acts for, and
two members that would write one destination are refused before anything is
compiled. A member whose distribution step fails is reported by name and the
others are packed; the members are reported in[workspace] membersorder,
and${mcpp.target_file:<name>}names the target of the member an action is
for.mcpp pack -p <member>keeps its meaning. The JSON envelope adds
data.stagesand amemberfield per artifact when several members are
packed (e2e 867 to 870).
Fixed
- A repeated
-pno longer keeps only its last value (mcpp#750).
mcpp test -p a -p btestedbalone and exited 0. - Starting a child process is safe for concurrent callers. A pipe created
by one thread could be inherited by a child another thread started, and the
first thread's reader then waited for end of file until the unrelated child
exited. Pipes are created close-on-exec on Linux; on macOS and Windows the
creation of a pipe, the start of the child and the parent's close of the
write end form one critical section. The registry of children for signal
forwarding is serialised and holds 256 entries. mcpp run -qwrites exactly the program's standard output. It began
with an empty line (e2e 862).
v2026.9.30.2
This release answers five reports on 2026.9.30.1 while building xlings
(.agents/docs/2026-09-30-build-wall-time-progress-count-and-hang-plan.md): a
build that did not exit after its status row stopped, a status row whose count
was mostly bookkeeping, planning that preceded every edit's compile by three
seconds, mcpp#744, and mcpp#732. On a clean build of xlings the command starts
ninja at 1.3 s instead of 4.5 s; an edit of one source builds in 8.0 s instead
of 10.5 s; a build with nothing to do is unchanged at 0.05 s.
Fixed
- A build no longer hangs after ninja. The stack animation could spawn
pieces onto cells it already held once its stack reached the right edge
short of its target, and its loop then never ended while the status row held
the terminal's line lock; the build joined the row's thread and waited for
ever (about 1% of interactive builds). Every loop of the animation now grows
the stack or ends. A property test drives every animation over 2000 seeds and
every game over 500 under a watchdog; it fails on the previous animation. - The status row counts the work of the build. A clean build of xlings
counted 1195 steps, of which 503 placed files the global cache serves and 460
were dependency scans, and read 967/1195 when its first compile began. The
cache pass is reported by itsCachedlines and not counted; the scans that
wait on no action run first, shown asScanning f/t; andBuilding f/t
counts the compiles, links, archives and actions: 0/232 at the first compile
of the same build. The fast path runs the same passes (e2e 842, 843). - A vendored xlings is replaced from the newest source (mcpp#744).
MCPP_VENDORED_XLINGSwhen set, otherwise the newer of the xlings released
with mcpp and the xlings onPATH; the note that no newer source is
available was false when a newer xlings was onPATH.UpdatingandNote
are each stated once per process (e2e 846). - A module name is unique within one program, not within one build
(mcpp#732). Anartifactsprogram, or a workspace member that shares no
program with another, may provide a module of the same name as another
program of the build. An import is resolved in the importing package's
closure; two BMIs of one name lie below their packages' directories, and each
compile is told which one a name means (a module map for GCC,
-fmodule-file=for Clang,/referencefor MSVC). Two providers that one
program links are refused, naming that program's package, and one file
reached as two packages is recognised whatever its spelling. When every name
has one provider,build.ninjaandcompile_commands.jsonare byte-identical
to 2026.9.30.1's (e2e 847, 848). - A BMI served from the global cache waits for the modules it imports that
the build compiles. A module that a package's build program generates lies
below the consumer's target directory and is compiled in every build, also
when the rest of the package is staged from the cache (xpkg'slua_stdlib,
imported by its cachedexecutor). Nothing ordered a consumer of the staged
BMI after that compile, so a fresh build could compile the consumer first and
fail withfailed to read compiled module. The stage edge of such a BMI now
waits for those BMIs (e2e 849). - A selected workspace member declares as the root. Since 2026.9.29.1 a
workspace is planned from a virtual root that declares only its members, and
the rules that grant the root's own declarations a privilege read that
root's edges alone. A rooted workspace's own package, and a member selected
with-p, again hold the position each held when planned as its own root:
itspathorgitoverride of a dependency that another package requests
by another kind wins (the release canary on mcpp-language-server, whose root
package overridesopenkal-linuxbypath, was refused with "Pick one");
linkageon its dependency edges is honoured; the identity its declarations
adopt is written back for the lock names; and its registry dependencies are
considered for the index refresh. Two selected members that disagree about
one dependency's checkout (its kind or its reference) or its link form are
refused, naming both (e2e 850). - The clang MSVC row states the compiler version (mcpp#746). The version
of the toolset'scl.exe, read from its file version as clang reads it, is
passed as-fms-compatibility-versionon every command and enters every
key. Without it the driver chose the version itself and wrote it into each
BMI, and a std module compiled under one runner image'scl.exewas served
to another image whosecl.exediffered under the same toolset directory
name (std.pcm was compiled for ... msvc19.51.36260 ... msvc19.51.36257).
A clang build for*-windows-msvcrebuilds once after the upgrade.
Changed
- Planning walks each package tree once. A source pattern with an empty
literal prefix walked the whole package tree: the 127 patterns of
compat.libarchive, expanded about three times per plan, opened its 35
directories 13,406 times. A walk is now kept per tree for the command and
revalidated by its directories' modification times. A planned edit of one
xlings source opens 1,749 package directories instead of 30,372, and its
scan phase takes 43 ms instead of 0.91 s. - The version of the vendored xlings is asked once. It is kept per
process, and under the home keyed by the binary's path, size and
modification time, so a command that loads its configuration no longer runs
xlings --version(0.35 s) when the binary has not changed. - Planning states where its time goes. Each phase of planning, and each
step of its last phase, logs its duration underbuild/stagein the log
file, which--verboseorMCPP_LOG_LEVEL=infoenables. The backend's own
stage lines are recorded in the file under the same condition.
v2026.9.30.1
This release revises what a build prints, from a report on mcpp build in the
xlings repository with 2026.9.29.5
(.agents/docs/2026-09-30-build-output-refinement-design.md): a package is
named when it does work, the live display is one status row drawn in one
write, and a bare key of a path dependency is no longer reported. It also
records one lock entry per identity again.
Added
- A screen in the status row. Beside the phase, 24 braille cells play one
of four animations, chosen per command: a chomper whose position is the
progress, a snake that eats a food in the colour of each package that
starts, Tetris on its side whose stack is the progress, and an emitter whose
ions build the progress bar. Each takes its tempo from the build: slow
while mcpp works, faster as steps finish, still while the build waits.
MCPP_PROGRESSnames one, orplain(the row without the screen) oroff
(no live row) (e2e 843;mcpp.ui.dots_screen). --play-game[=NAME]onbuild,runandtestplayssnake,stack
orrunneron the screen while the build runs, steered from the keyboard at
a speed of its own. The best round is stated afterFinished. Keys are
read without echo; Ctrl-C still stops the build, and the terminal's mode is
restored when the build ends or is interrupted. Where standard input or
output is not a terminal, or mcpp runs as a background job, one line says
why and the build proceeds (e2e 845). An arrow with a modifier is read as
the arrow, and a sequence for any other key is skipped whole
(TerminalKeysunit tests).last N running. Once ninja has no step left to start (its%u
reaches 0), the status row states how many steps remain, all of them
running.
Fixed
- The live display no longer flickers. A frame reached the terminal in two
or three writes (stdout is line-buffered, and a line above the region was
erase, text, redraw), and every frame erased the region before drawing it:
in one build of xlings, 184 of 202 frames left in two parts. A frame now
leaves in one write that overwrites the rows in place, with autowrap off for
the status row, so a terminal that draws East Asian ambiguous characters
wide clips the row instead of wrapping it. The row is first drawn half a
second into the command, so the first lines of output no longer push it
down (e2e 843). - A dependency the global cache serves is named when its units are
placed. Its staging steps ran in a pass of their own that nothing read,
so such a package never completed and the folded dependency line waited
for ninja to exit (27 s late in a first build of xlings), after the root's
line (e2e 842). - The phase returns to planning after the build programs. The status
line readRunning build programsfor as long as planning continued. - A planned build leaves a committed
mcpp.lockunchanged. Since
2026.9.29.1 a planned build of a workspace root wrote the dependencies of a
[dependencies.<ns>]table under a second spelling with another hash,
beside the entries already there; the lock now holds one entry per
identity, spelled as 2026.9.28.3 spelled it, and a lock that holds two is
reduced to one by the next planned build (e2e 844).
Behaviour changes
- A package is named once, when it does work:
Compiling <package>when
the first of its steps that is not a dependency scan finishes, or when its
firstcheckorprepareaction starts, andCached <package> (N units)
when the global cache places its units. The line states no outcome and does
not change; nothing is folded, and a package with nothing to do has no line
(--verbosenames itFresh, and states each package's steps and span as
Compiled) (e2e 842). - A package inside the project is named by its short name, version and
directory (platform v0.1.0 (modules/platform)); any other by its
identity, with(index <name>),(git <kind> <ref>)or its relative
directory. On a terminal the name's colour states the source: the official
index cyan, another index magenta, git blue, the project's own packages the
default colour. - A build program has a line when it runs or fails; a reused program has
one under--verbose. The foldedbuild.mcpp N dependenciesline is gone. - A failure names its package:
error: build failed in <package>. Finishedstates how the time was spent from one minute, where it did
from ten seconds; below a minute it states the profile and the total alone.
The longest step, when it took at least a quarter of the build, is named
by its source file for a compile (longest slow: src/main.cpp 1m08s), not
by its object file. A build with nothing to do states the profile's
descriptor as a full build does (Finished dev [unoptimized + debuginfo] in 0.02s): the step record's header carries it for the fast path.- The status row is aligned with the verbs (
Building 612/707 · 0:35),
its phases arePlanning,Running,Building,StoppingandChecking,
and no blank row separates it from the output. - A bare key of a path or git dependency states only the short name. Its
adoption of the namespace the manifest declares is no longer reported; a
key that states a namespace the manifest contradicts is reported once per
consumer manifest, named by its path, with the TOML that states the
declared identity (e2e 679, 713; package-identity §4.2). - The bundled xlings is 2026.9.30.1.
v2026.9.29.5
This release completes the workspace build graph in the commands around the
build, from the validation project's post-release run of 2026.9.29.4: build
programs are reused across selections and run dependencies first, the build
database and --configure-only plan by configuration, and the output names
what it reports. A build now reports each step when its outcome is known, and
shows what runs while it runs (.agents/docs/2026-09-29-build-progress-display-design.md).
Added
- A build reports each step once, with its outcome. A package's line is
written when every step the graph assigns to it has run (donewith the
span of its steps, read from ninja's log), when the global cache supplied it
(cached N units), when a step of it failed (failed), or when the build
ends. A package with nothing to do has no line. The packages the command was
asked to build are listed; their dependencies are folded into one line, and
a dependency that fails is named.--verboselists every package and prints
each step as ninja reports it (e2e 842). - A status line states the build. On a terminal the steps still running
and one status line,Building 612/1203 · 14:32 · gpp.gui: vcpkg install 6:10, are drawn below the output and updated in place; the status line
names the longest-runningcheckorprepareaction, which the engine's
action wrapper reports when it starts. In a log (CI, a pipe) only final lines
are written, and the status line is written when the log has been silent for
a minute (e2e 842, 843). Finishedstates the whole command's time, and for a command of ten
seconds or more how it was spent (plan,programs,build) and the step
that took at least a quarter of the build.
Fixed
-
A failed step is reported when it fails. Its diagnostics were printed
after ninja exited, that is, after every step still running had finished:
a compile error beside a twenty-minute vcpkg install appeared twenty minutes
late (e2e 842). -
macOS and Windows terminals are terminals. Terminal detection was
compiled only under__unix__, which Apple's compilers do not define, so
the download bar and colours were drawn on Linux alone. A Windows console
now receives mcpp's lines as UTF-16, so·,→and non-ASCII paths appear
as written whatever its code page. -
A member's build program is reused whichever members a command selects.
Its graph document listed every requester in the plan, the virtual root
included, so the program's re-run key followed the selection:-p,mcpp packandmcpp emit build-databasereran the programs a--workspace
build had run (7 to 15 s each in the validation project). A program's
document now lists the requests made inside its own closure (e2e 839). -
A member's build program runs after those of the members it depends on.
They ran in discovery order, so a member's program could run before its
dependency's had applied its directives (e2e 839). -
mcpp emit build-databaseandmcpp build --configure-onlyplan a
workspace by configuration, as the build does. They planned each member
separately, so a package two members use was described once per member,
each time with other arguments (the validation project's core library three
times). A member that is a program is described as one, and its tests as
tests. A configuration whose plan fails is planned member by member, so a
member's failure still affects that member only (e2e 840; SPEC-005 v1.6). -
A command that plans several configurations writes the root
compile_commands.jsononce, as the union of their databases. Each
configuration replaced it, and undermcpp build --workspace, whose
configurations build at the same time, the file was the last one's (e2e 840).
Behaviour changes
- A build program has one line, which names its package and states its
outcome:build.mcpp <package> ran <time>,cachedorfailed, in
place ofbuild.mcpp compiling <package>andrunning <package>, and of
up to date <package> (cached)(e2e 839, 842). The programs of the
requested packages are listed, and those of their dependencies are folded
into one line. Compiling <package>is written when the package's steps have run, with
their outcome, rather than for every direct dependency before ninja starts;
the per-dependencyCached <package> (N units)line is--verboseoutput,
ascached N units(e2e 842).- A selected member is announced by its directory in a
--workspace
build, also where another member depends on it (e2e 839). mcpp packsummarises many outputs. A format that reports more than
eight outputs is reported by the entry each lies in below their common
directory, with a count;--verbosenames every output, and
--message-format jsonlists every one as before (e2e 841).- Build database set names (SPEC-005 v1.6). A set is named by its package;
a document of several configurations prefixes each name with the
configuration's build directory name, instead of<member>/.
v2026.9.29.4
This release links a program that a workspace member ships through
artifacts with the link line of its own package's closure, and expands
${mcpp.bin_dir} to where the declaring member's binaries land. The validation
project's post-release build of 2026.9.29.3 found the first, and its
cross-verification against this release's pull request the second.
Fixed
- A program shipped through
artifactslinks with its own closure's line in
a workspace plan. It was linked with the plan's line, which pools the
dependencies' link flags and not a member's; a library that the program's
package states through its build program (mcpp::link_lib) was therefore
missing, and the link failed with undefined references (e2e 838). The
program now has a link group of its own that holds its closure's line and
runtime contract and places nothing, since the members that ship the program
place it. ${mcpp.bin_dir}in an action a workspace member's build program declares
is that member's product directory. It was the plan'sbin/, where a
member's binaries are not, so an action that named a file beside the
member's program (a.pdb, say) read or wrote the wrong place (e2e 838 A4).
v2026.9.29.3
This release completes the runtime placement of a program that a workspace
member ships through artifacts. The validation project's post-release build
of 2026.9.29.2 found it.
Fixed
- A program shipped through
artifactsno longer waits for a runtime file
that a workspace plan never places. Its link edge depended on the plan's
own deploy set, which a workspace plan does not place (bin/holds products
only), so a workspace whose members declare runtime files stopped with
"missing and no known rule to make it" (e2e 833 G9). - The runtime files of such a program are beside it. A member's runtime
set includes the closure of every package whose program the member ships
throughartifacts, so the program finds its own runtime files in the
member's product directory, as it did beside a root's program inbin/
(e2e 833 G9).
v2026.9.29.2
This release corrects what a workspace plan reads from its members. The plan's
root is a virtual root that holds the values shared by the whole graph; five
statements that a member makes about itself were read from that root in
2026.9.29.1 and were therefore empty, and a member's product directory lacked
the shared libraries that its libraries need. The mcpp-index sweep of
2026.9.29.1 found the first two.
Fixed
- A member's own relative
[indices].pathis anchored at the member. It
was resolved against the workspace root, so a member that declares its own
index (47 members of mcpp-index do) found no package. Two members that name
one tree with different relative paths are now one configuration (e2e 120). - A member whose tests import
stdis tested with the std module built.
The decision read the entry files of the root's targets only, and the virtual
root has none; a member whose only sources are its tests failed to compile
them (e2e 836). - A member's
[resources]andwindows_code_pagereach its own images.
Each selected member's resources are compiled against the member's directory
and include directories, underres/<member>/, and embedded into the
member's programs and shared libraries only (e2e 837). A quoted#include
or resource file in a script is now found through the include directories as
the resource compiler finds it, in every build. - A member's product directory holds every graph-built shared library of its
closure. Only the libraries a member's own units link were placed, so a
library that another library needs (libffi under libwayland-client) was
missing and the program did not start (e2e 835 L3). [build] linkageis a value of the plan. It chooses the C runtime that
every object is compiled against, so members that differ in it are separate
configurations, and the plan takes it from its members.- A member's manifest is checked as a root's. An unknown capability under
the reservedmcpp:prefix is refused, and a cfg() predicate mcpp cannot
evaluate and the other schema warnings are reported, for each selected
member (e2e 836).