You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: link-flag, and an open vocabulary that can say "empty"
Stage one of the general build-infrastructure design
(.agents/docs/2026-09-07-general-build-infrastructure-gaps-design.md).
mcpp:link-flag (protocol v8). link-lib, link-search and link-script each name
one kind of thing, so a flag the program COMPUTED had no outlet: a version
script whose content depends on which features are on, -Wl,--wrap=malloc for a
runtime that takes over a C-library symbol, -Wl,--exclude-libs,ALL so a
statically absorbed third party does not become part of this package's ABI.
It reaches the consumer, and that CORRECTS the design doc, which first ruled it
private by analogy with include-dir. The analogy is false and the code says so:
linkUsage.ldflags is a copy of buildConfig.ldflags, so a private link flag is
not a policy this engine can express, and [build] ldflags -- the declarative
twin -- already propagates. Making the computed form behave differently would
be the inconsistency rather than the safeguard. The hazard that follows is
stated in the docs rather than hidden: a dependency emitting --version-script
puts it on the consumer's link too, which a dependency writing the same flag in
[build] ldflags has always done.
cfg(accelerator = "none"). A CPU fallback could only be written by enumerating
the backends it is not, and accelerator's vocabulary is open by design -- so
that predicate changes meaning the day a fifth backend exists, and every
fallback already written starts treating a build that named the new backend as
having no accelerator. The spelling reuses os = "none", which this manifest
already means "bare metal" by. Not cpu: that puts a second question on an axis
whose job is "which device compiler, which architecture", and leaves
cfg(accelerator = "cpu") under accel = "cuda" with no self-consistent answer.
Both criteria measure the property rather than its shadow. e2e 620 asserts the
LINKER'S BEHAVIOUR -- the program computes -Wl,--defsym=mcpp_e2e_620=42 and the
artifact prints that symbol's address -- because grepping build.ninja would
pass for a flag written down and never handed to the linker. The unit test
SIMULATES the fifth backend arriving: the enumeration starts lying on the spot
and none does not, which is the entire reason the row exists and is invisible
under a single backend.
Copy file name to clipboardExpand all lines: docs/07-build-mcpp.md
+15Lines changed: 15 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -53,6 +53,7 @@ is ignored, so diagnostics may be logged freely.
53
53
|`mcpp:include-dir=<dir>`*(0.0.100+)*| add a **private** include directory (`-I`) for this package's own TUs (absolute, or relative to the package root; normalized). Replaces the `cxxflag=-I` + `cflag=-I` double emission |
54
54
|`mcpp:include-dir-after=<dir>`*(0.0.100+)*| like `include-dir`, but searched **after** the system directories (`-idirafter`) — for payload trees that shadow system headers |
55
55
|`mcpp:runner=<token>`*(2026.8.19.2+)*| one argv token of the command that EXECUTES this build's artifact, when the host cannot. Emitted once per token, in order; the artifact path is appended (or substituted for `{}`). Reaches the **consumer**. Emit the executable as an ABSOLUTE path, and only **one** dependency may supply it |
56
+
|`mcpp:link-flag=<flag>`*(2026.9.6.5+)*| add a **linker flag** this program computed, verbatim. The outlet `link-lib` / `link-search` / `link-script` leave open: a generated version script (`-Wl,--version-script=`), `-Wl,--wrap=malloc` for a runtime that takes over a C-library symbol, `-Wl,--exclude-libs,ALL` so a statically absorbed third party does not become part of this package's ABI. Appended after `[build] ldflags`, in emission order. **Reaches the consumer**, exactly as `[build] ldflags` does — see below |
56
57
|`mcpp:link-script=<path>`*(2026.8.19+)*| link with this **linker script** (`-T`; relative resolves against the package root, and the emitted path is absolute because the link runs in the build directory). Reaches the **consumer**, unlike `include-dir` — a board's memory layout is the one thing a consumer cannot write for itself |
57
58
|`mcpp:warning=<text>`*(2026.8.21.2+)*| say something to the user and **keep going**. The one directive that changes no compile line, no link line and no source set. Survives the build cache — see below |
58
59
|`mcpp:fact=<name>=<version>`*(2026.9.5.2+)*| state something the program **established about the machine** (`cuda.driver=12.4`). Compared against floors before anything is compiled; see below |
@@ -65,6 +66,19 @@ registry dependency — the dependency graph stays declarative in `mcpp.toml`
is for *leaf* decisions: flags, codegen, link requirements.
67
68
69
+
`link-flag` is deliberately **not** private, and the reason is worth stating
70
+
because the opposite looks safer. A compile interface has a declarative public
71
+
counterpart (`[build] include_dirs`), so a build-time program widening it would
72
+
go behind the manifest's back — hence `include-dir`'s privateness. Link flags
73
+
have no such split: `[build] ldflags` already propagates to consumers, so a
74
+
private computed form would behave differently from its own declarative twin.
75
+
76
+
The consequence is stated rather than hidden. A dependency emitting
77
+
`-Wl,--version-script=` puts it on the consumer's link line too, which is
78
+
usually not what that dependency meant. That hazard is not new — a dependency
79
+
writing the same flag in `[build] ldflags` has always done this — so this
80
+
directive widens *who can compute the value*, not *what the value can reach*.
81
+
68
82
`include-dir`/`include-dir-after` are deliberately **private** (Cargo
69
83
discipline): they color only this package's own TUs and are never propagated
70
84
to consumers. An include directory consumers must see is part of the public
@@ -104,6 +118,7 @@ int main() {
104
118
|`mcpp::rerun_if_changed(p)` / `mcpp::rerun_if_env_changed(v)`| the matching `rerun-*` directives |
105
119
|`mcpp::rerun_if_changed_glob(pat)`*(2026.8.6.2+)*|`mcpp:rerun-if-changed-glob=` — re-run when the **set** of files matching `pat` changes (see below) |
106
120
|`mcpp::dep_bin(pkg, tool)`*(2026.8.5.1+)*| reads `MCPP_DEP_<PKG>_BIN_<TOOL>` — the absolute path of a **host tool** built by a dependency (see below) |
|`mcpp::runner(tok)`*(2026.8.19.2+)*|`mcpp:runner=` — see below |
109
124
|`mcpp::xpkg_dir(ns, name)` / `mcpp::xpkg_dir(name)`*(2026.8.19+)*| the payload directory of a package this manifest declared in `[xlings.workspace]`; `""` when it was not declared or is not installed (see below) |
0 commit comments