0.6.0: a third family for what comes out of the link, and a table shape for tools-embed - #16
Merged
Merged
Conversation
…pe for tools-embed
THREE FAMILIES, AND THE PREFIX SAYS WHICH OF THREE QUESTIONS A MEMBER ANSWERS:
rules-* how is this translation unit compiled
tools-* what does the build program need to do itself
dist-* what comes out of the link, and in what form a user installs it
A `dist-*` member fits neither of the first two definitions. It compiles no
translation unit and does no work while the build program runs: it consumes
LINK OUTPUTS through a `role = "artifact"` action, reached with
`mcpp pack --format <name>` (mcpp 2026.9.11.1+). The prefix matters because the
taxonomy is load-bearing -- a consumer reading `rules-wix` would expect a
compiler it does not drive and a translation unit, and there is neither.
The engine holds the DISPATCH and no format. dpkg's control fields, AppImage's
runtime, WiX's schema and Apple's notarisation each couple a release to a
release mcpp does not control, so none of them is in mcpp. It is the argument
this project already made for languages: Slang is supported without being named
in the engine, and a distribution format has less claim to a name there than a
language does.
DECLARE UNCONDITIONALLY, SUBMIT CONDITIONALLY, and `generate()` does both so a
consumer cannot do one without the other. The declaration is what lets the
engine answer "which formats does this graph provide" on a build that asked for
nothing; the submission must be gated or a plain `mcpp build` grows an edge it
must not have. A member that declared only when asked still works for its
author -- they always pass their own format -- and makes the set unknowable for
everyone else.
`dist-appimage`, Linux. The staged tree is ALREADY an AppDir bar three files,
which is why this member is small: `mcpp pack --mode vendored` stages `bin/`,
`lib/` and a `$ORIGIN`-relocatable launcher, and AppImage additionally wants an
`AppRun`, a top-level `.desktop` entry and an icon. So it writes three small
files into the tree and invokes one tool, and never copies or re-lays-out a
directory that can be hundreds of megabytes. The three are declared as action
inputs, which is what makes a change to `[package] description` reach the
graph. Its payload is `xim:appimagetool`, declared on the `cfg(linux)` axis,
which also carries the type-2 runtime stub carved out of the tool itself --
appimagetool otherwise downloads that stub on every invocation, and a build
must not reach the network.
A SIZE FLOOR WAS THE FIRST VERSION OF ITS SUCCESS CHECK AND WAS WRONG ON ITS
FIRST REAL FIXTURE. A stripped hello-world stages at 14999 bytes, under the
16 KB bound, so a correct AppImage was reported as carrying no program. A size
is a proxy; the question is answerable directly, so it now counts files the
member did not itself write.
`dist-wix`, Windows. Renders a `.wxs` and passes the program in as a
preprocessor variable rather than binding a directory and harvesting it: a bind
path that resolved to nothing produced a valid, empty, 52 KB installer with no
diagnostic, and that measurement is the reason for the shape. The `UpgradeCode`
is derived deterministically from the package identity, because WiX requires
one, a random one breaks upgrades and a hardcoded one makes every mcpp project
the same product. MSI's `ProductVersion` compares only three fields, so the
four-segment date version is repacked and what is lost is stated.
`$(Executable)`, NOT `$(var.Executable)`, AND THE DIFFERENCE IS A MEASUREMENT
RATHER THAN A READING. WiX's documentation spells a `-d` define the second way.
A working implementation that is green on Windows CI uses the bare form; since
neither could be run here, the one with a measurement behind it is the one
shipped, and both are recorded.
`dist-apple`, macOS. A `.app`, its `Info.plist` written directly rather than
through `plutil`, and `codesign` only when an identity is given -- a member that
signed by default would fail every build on a machine with no identity in its
keychain. Four chained actions rather than one, because an icon change must not
force a re-copy of the whole staged tree. iOS is the same shape plus a target
row, which is a payload rather than a redesign.
`tools-embed` GAINS `table()`: N inputs, ONE header, ONE table, where each row
carries the input's key beside its contents and the consumer iterates. `file()`
writes one header per input and `files()` writes several; neither can express
the shape a shader set wants. A duplicate key is refused naming BOTH inputs,
and the bytes are a numeric array rather than a raw string literal -- a raw
literal cannot carry arbitrary binary and its delimiter is terminable by the
input, which is the defect in the CMake code this shape replaces.
THE FIXTURE'S DENOMINATOR IS THE MANIFEST, so `tests/all-rules-compile` names
every member a consumer can activate, `dist-appimage` included. Leaving it out
was the alternative and is worse: that fixture exists so every member's module
is COMPILED on every host, and a member left out is a member whose macOS and
Windows compile is never attempted -- which is exactly what the two
platform-specific members are there to catch. Its payload is gated on
`cfg(linux)`, so the other two runners download nothing.
CI gains three steps, and two of them exist because a plan-level assertion is
not a measurement. `plan_for()` returning `applies == false` on the wrong OS
says the gate works and says nothing about whether the tool accepts what the
member renders. `dist-wix` installs the WiX CLI on windows-2022 and builds a
real MSI, asserting its size against the linked program's rather than that a
file exists -- the measured failure is a valid EMPTY installer, and an
assertion that stops at existence passes on exactly that artifact. `dist-apple`
builds a real bundle on macos-15, lints the plist with Apple's own parser, and
runs the program THROUGH the bundle, because a bundle whose `Info.plist` names
the wrong executable still contains a working program.
A FALSE CLAIM IS CORRECTED IN `dist/wix.cppm` RATHER THAN LEFT TO BE
DISCOVERED. It said WiX is "not a redistributable archive this ecosystem can
vendor". Its own licence file says otherwise: the software is under the
Microsoft Reciprocal License, the fee is a maintenance fee on
revenue-generating use, and sections 2 and 3 state that binaries may be
redistributed and that the source remains freely distributable. The package is
anonymously fetchable and immutable from NuGet's flat container. So `xim:wix`
is legitimate and this member should declare it with no PATH fallback, the way
`dist-appimage` declares its own tool; the comment now records that as a gap
with the measurement behind it.
Verified locally against mcpp 2026.9.11.1: every fixture builds and runs, all
three dist modules compile as host modules, and `mcpp pack --format appimage`
produces an AppImage that executes and prints its program's output.
TWO CI FAILURES, TWO DIFFERENT KINDS. THE LINUX ONE WAS THE CHECK, NOT THE CODE. It grepped for `struct embedded_file`, which is the DEFAULT `row_type` -- while the fixture sets `row_type = "shader_entry"` precisely because that option exists. So the assertion failed with the code correct, which is what a check tied to a spelling the fixture chooses will eventually always do. It now reads the struct's name out of the file and asserts that the table's element type IS that struct, for every generated header rather than whichever `find` listed first -- this fixture writes three, and `head -1` was asserting about a file nothing chose. THE WINDOWS ONE WAS A TOOL THAT REFUSES TO RUN. An unpinned `dotnet tool install wix` gets v7: wix.exe : error WIX7015: You must accept the Open Source Maintenance Fee (OSMF) EULA to use WiX Toolset v7. A runtime gate, not a redistribution term -- 6's own EULA permits redistributing binaries under the Microsoft Reciprocal License. So 6.0.2 is the version this ecosystem can drive, and the version `xim:wix` should carry when that package lands: one pinning 7 would install a tool that cannot run. The dispatch itself was already correct when this failed, which is why the fix is a pin. The same run reported `Distributing msi-consumer v0.3.0 (--format msi)` and the full `wix.exe build -arch x64 -d Executable=... MsiConsumer.wxs` argv, so the `.wxs` was rendered and the request reached the member. Only the tool declined. The macOS lane stays red for a third, unrelated reason -- `error: cannot package the Mach-O program 'app-consumer' yet`, which mcpp 2026.9.11.2 fixes (staging is a service to the provider, not a precondition for dispatch). That one needs the engine release and a `MCPP_VERSION` bump, not a change here.
The pin worked: WIX7015 is gone, `wix build` completed with no warnings, and
`Packed target/.build-mcpp/out/MsiConsumer-x64.msi` appeared. The check then
refused it -- 32768 bytes against a 114688-byte program, under a
`size > exesize / 2` floor whose comment admitted the ratio was invented ("a
cabinet compresses, so this is a floor and not an equality").
32768 is what a stripped hello-world looks like after a cabinet has had it.
Mostly padding, highly compressible, plus the fixed overhead of an OLE
compound document. The floor was refusing a correct artifact.
THIS IS THE SECOND TIME IN THIS ECOSYSTEM. The AppImage floor was a 16 KB size
bound and refused a correct 14999-byte bundle. Both times the check asked a
proxy question. The AppImage one was replaced by running the AppImage and
asserting the program's output; this one is now an administrative install
(`msiexec /a`), which writes the payload out uncompressed, so the assertion is
EQUALITY against the linked binary rather than a ratio -- and it also proves
the package installs, which a size never did.
The macOS lane stays red on `cannot package the Mach-O program`, which needs
mcpp 2026.9.11.2.
…le reachable The red macOS lane was the dependency, not a convention. Under 2026.9.11.1: error: cannot package the Mach-O program 'app-consumer' yet. `mcpp pack` staged unconditionally before dispatching and let a staging failure fail the command, and its built-in closure walk uses `LD_TRACE_LOADED_OBJECTS` -- which dyld answers by RUNNING the program. So every dispatched format was unreachable on macOS, including one that reads no staged tree at all. 2026.9.11.2 makes staging a service to the provider, so `dist-apple` has been unreachable rather than broken since it was written. The README floor for `dist-apple` moves with it and says why it is one release higher than its siblings: the reason is not this member.
…not exist
2026.9.11.2 made the dispatch reachable and the member then refused one layer
up, for the same underlying reason:
error: no action claimed --format 'app'
That message is the ENGINE's. The member's own explanation -- "mcpp reported no
staged tree" -- went to stderr, which is discarded on a successful build, so
the refusal was unreadable.
And the refusal was wrong. `mcpp pack`'s built-in closure walk refuses a Mach-O
program because it uses `LD_TRACE_LOADED_OBJECTS`, which dyld answers by
RUNNING the program -- so on macOS there is no staged tree to have, and a
member that requires one can never run there. A `.app` needs ONE program, not
a tree: `${mcpp.target_file:<name>}` names it, which is the placeholder
dist/wix.cppm uses for exactly this reason and what section 6 of the design
record recommends for a member packaging a named target.
The tree is still preferred when it exists -- a `--mode vendored` tree carries
the program's dependencies beside it and a bundle should keep them. Without
one the bundle carries the program alone, which is correct for a
self-contained Mach-O and is what the platform's own default produces.
`${mcpp.stage_dir}` is now named only on the path that has a tree, because the
engine REFUSES that placeholder when there is none -- that is its contract,
and a member naming it unconditionally is a member that cannot run on a target
whose built-in staging is refused. `CFBundleExecutable` takes the target's own
name on that path, since a placeholder's basename is not knowable before the
engine expands it.
All eleven features still compile against released 2026.9.11.2.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Needs mcpp 2026.9.11.1, released today, which is what CI now pins.
Three families, and the prefix says which of three questions a member answers
A
dist-*member fits neither of the first two definitions: it compiles no translation unit and does no work while the build program runs. It consumes link outputs through arole = "artifact"action, reached withmcpp pack --format <name>. The prefix matters because the taxonomy is load-bearing — a consumer readingrules-wixwould expect a compiler it does not drive and a translation unit, and there is neither.The engine holds the dispatch and no format. dpkg's control fields, AppImage's runtime, WiX's schema and Apple's notarisation each couple a release to a release mcpp does not control. It is the argument this project already made for languages: Slang is supported without being named in the engine, and a distribution format has less claim to a name there than a language does.
Declare unconditionally, submit conditionally
generate()does both, so a consumer cannot do one without the other. The declaration is what lets the engine answer "which formats does this graph provide" on a build that asked for nothing; the submission must be gated or a plainmcpp buildgrows an edge it must not have. A member that declared only when asked still works for its author — they always pass their own format — and makes the set unknowable for everyone else.The members
dist-appimagexim:appimagetoolon thecfg(linux)axisdist-wixdist-applecodesignonly with an identitydist-appimageis small because the staged tree is already an AppDir bar three files.mcpp pack --mode vendoredstagesbin/,lib/and a$ORIGIN-relocatable launcher — which is what an AppImage is. So the member writes anAppRun, a.desktopentry and an icon into it and invokes one tool; it never copies or re-lays-out a directory that can be hundreds of megabytes. The three files are declared as action inputs, which is what makes a change to[package] descriptionreach the graph.A size floor was its first success check and was wrong on its first real fixture. A stripped hello-world stages at 14999 bytes — under the 16 KB bound — so a correct AppImage was reported as carrying no program. A size is a proxy; the question is answerable directly, so it now counts files the member did not itself write.
dist-wixnames the input rather than harvesting a directory, because a bind path that resolved to nothing produced a valid, empty, 52 KB installer with no diagnostic. ItsUpgradeCodeis derived deterministically from package identity: WiX requires one, a random one breaks upgrades, and a hardcoded one makes every mcpp project the same product.$(Executable), not$(var.Executable). WiX's documentation spells a-ddefine the second way; a working implementation green on Windows CI uses the bare form. Neither could be run here, so the one with a measurement behind it ships, and both are recorded.dist-appleuses four chained actions rather than one, so an icon change does not force a re-copy of the whole staged tree.codesignis off by default — a member that signed unconditionally would fail every build on a machine with no identity in its keychain. iOS is the same shape plus a target row, which is a payload rather than a redesign.tools-embedgainstable()N inputs, one header, one table, where each row carries the input's key beside its contents and the consumer iterates.
file()writes one header per input andfiles()writes several; neither can express the shape a shader set wants. A duplicate key is refused naming both inputs, and the bytes are a numeric array rather than a raw string literal — a raw literal cannot carry arbitrary binary and its delimiter is terminable by the input, which is the defect in the CMake code this replaces.The fixture's denominator is the manifest
tests/all-rules-compilenames every member a consumer can activate,dist-appimageincluded. Leaving it out was the alternative and is worse: that fixture exists so every member's module is compiled on every host, and a member left out is one whose macOS and Windows compile is never attempted — exactly what the two platform-specific members are there to catch. Its payload is gated oncfg(linux), so the other two runners download nothing.CI gains three steps, and two exist because a plan-level assertion is not a measurement
plan_for()returningapplies == falseon the wrong OS says the gate works and says nothing about whether the tool accepts what the member renders.Info.plistnames the wrong executable still contains a working program.mcpp pack --format appimageand executes the result.A false claim is corrected rather than left to be discovered
dist/wix.cppmsaid WiX is "not a redistributable archive this ecosystem can vendor". Its own licence file says otherwise: the software is under the Microsoft Reciprocal License, the fee is a maintenance fee on revenue-generating use, and §2 and §3 state that binaries may be redistributed and the source remains freely distributable. The package is anonymously fetchable and immutable from NuGet's flat container (5851349bytes, HTTP/2 200). Soxim:wixis legitimate and this member should declare it with no PATH fallback, the waydist-appimagedeclares its own tool. The comment now records that as a gap, with the measurement behind it.Verified locally against mcpp 2026.9.11.1
No emoji in any of the 29 changed files; no deletions against main.