Commit 0117a9f
fix(build): a package's generated inputs come before its compiles; an empty link is refused (2026.8.30.1) (#536)
* fix(build): a package's generated inputs come before its compiles; an empty link is refused (2026.8.30.1)
Three defects, one change of mind about evidence. Each of them was a place
where something answered "is this ready?" from a proxy instead of from the
artifact.
── mcpp#534: `role = "source"` did not order a generated header ─────────────
The engine's own comment claimed ordering needed no special handling because
"a Source action's outputs ARE the compile edge's inputs". That is true of a
generated `.cpp` and false of a generated `.h`: a header is reached through
`-I`, never appears as an edge input, and the depfile that would record it does
not exist until a compile has already succeeded.
So an action whose outputs were all headers had a node in build.ninja that
nothing could reach — not `default` (Source outputs are excluded on purpose),
not the goal phony (objects and link outputs only), and no consuming edge. It
never ran. The issue was filed as an intermittent race; it is deterministic,
and five consecutive builds reproduce it identically. What made it look like a
race is that `prepare_actions` wrote a zero-byte placeholder for every declared
Source output, headers included — so the file was on disk whether or not the
generator had run.
* `BuildAction` records the package that declared it, spelled the same way
`CompileUnit::packageName` is (`qualified_package_name`, now exported so
the two cannot drift).
* Each package that declares a gating action gets one phony over its outputs,
and every compile edge of that package takes it as an order-only
prerequisite. Per package, not per build: `include_dir` colours only the
declaring package's own TUs, and a build-wide edge would express a
dependency that does not exist while landing on the critical path.
* `check_action_ordering` scans the emitted manifest and fails the build if
any such edge is missing one — including the denominator, because "every
edge that should carry it does" is vacuously true when none does, which was
exactly the previous state. Seven call sites append the string today; being
careful at seven sites is not a mechanism.
* `blocking` on a `check` now does what it has been documented to do since it
was introduced. It was typed, emitted over the build-program protocol,
parsed, documented in two languages and demonstrated in a shipped example —
and read by nothing.
* Placeholders are no longer written for outputs that are not translation
units. The scan never reads a header, and the empty file only ever turned
"the generator did not run" into "the header is empty".
── mcpp#533: an empty link unit, reported as a shell error ──────────────────
A dependency whose `install()` was skipped over a package-identity collision
left a version directory with no sources. mcpp planned its shared library
anyway and the user was shown `/bin/sh: 1: -shared: not found`.
* A link unit with no inputs is refused at plan time, naming the target. The
static case is why this is an error rather than a better linker message:
`ar rcs` with no members exits 0 and writes an 8-byte archive, so the build
REPORTED SUCCESS and every consumer failed later with undefined symbols.
* `cc` is emitted unconditionally, for the reason `c_ldflags` twenty-six
lines below already carried (mcpp#426). The rule had been written down for
one variable of `c_link`/`c_shared` and not for the other.
* `check_rule_commands_name_a_program` scans the manifest for the class: a
rule's command must begin with a program. Deliberately not the more obvious
"no undefined variables" — ninja's empty expansion is a feature several
rules rely on (`$soname_flag`, `$unit_ldflags`), and that check would have
needed an allowlist of exceptions.
* `.mcpp_ok` is no longer written from the installer's exit code plus the
existence of a directory the installer creates before doing any work. It
now requires one entry that neither mcpp nor xlings wrote. Withheld rather
than fatal, because a package may legitimately install no payload.
* The same predicate runs on the fast path, so a store already poisoned by
this bug heals on the next build instead of requiring the user to know
which directory to delete.
* The lib-root warning asked `has_lib_target` — "does this produce a library"
— when the property it wants is "is this a C++ module library". A
source-built C package warned that `src/<name>.cppm` was missing in every
consumer's build.
── Tests ───────────────────────────────────────────────────────────────────
e2e 314 (a dependency generating its only header), 315 (blocking gates the
compile, non-blocking does not), 316 (empty shared AND static targets refused;
a populated one still builds). All three were run against the pre-fix binary
and all three fail there, so they discriminate rather than describe.
Unit: 14 new cases over the two emitter guards, the ordering denominator, and
the install-marker evidence — including the poisoned-store heal.
Analysis and cross-repo plan: .agents/docs/2026-08-30-*.md
Refs #533, #534
* chore(xlings): raise the floor to 2026.8.30.2 — the store-identity fix
openxlings/xlings#576, released as v2026.8.30.2. Below this version xlings
answers "is this package already installed" from the xvm version database keyed
on the bare short name, so a package skips its own `install()` whenever any
other namespace holds the same `<name>@<version>`.
This is a FLOOR and not a preference, which is the whole reason the pin has the
shape it does. The mcpp side of #533 makes the resulting failure legible on any
client — the link unit is refused by name, and `.mcpp_ok` is withheld from a
directory holding nothing the package installed — but only a client at or above
this version INSTALLS correctly.
1 parent ab1da5d commit 0117a9f
27 files changed
Lines changed: 2683 additions & 73 deletions
File tree
- .agents/docs
- .github
- actions
- bootstrap-mcpp
- setup-macos-llvm
- workflows
- docs
- zh
- modules
- buildmcpp/src
- manifest/src
- versioning/src
- src
- build
- fallback
- modgraph
- pm
- xlings
- tests
- e2e
- unit
Lines changed: 929 additions & 0 deletions
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
155 | | - | |
| 155 | + | |
156 | 156 | | |
157 | 157 | | |
158 | 158 | | |
| |||
293 | 293 | | |
294 | 294 | | |
295 | 295 | | |
296 | | - | |
| 296 | + | |
297 | 297 | | |
298 | 298 | | |
299 | 299 | | |
| |||
364 | 364 | | |
365 | 365 | | |
366 | 366 | | |
367 | | - | |
| 367 | + | |
368 | 368 | | |
369 | 369 | | |
370 | 370 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
237 | 237 | | |
238 | 238 | | |
239 | 239 | | |
240 | | - | |
| 240 | + | |
241 | 241 | | |
242 | 242 | | |
243 | 243 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | | - | |
| 125 | + | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| |||
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
266 | | - | |
| 266 | + | |
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| |||
289 | 289 | | |
290 | 290 | | |
291 | 291 | | |
292 | | - | |
| 292 | + | |
293 | 293 | | |
294 | 294 | | |
295 | 295 | | |
| |||
360 | 360 | | |
361 | 361 | | |
362 | 362 | | |
363 | | - | |
| 363 | + | |
364 | 364 | | |
365 | 365 | | |
366 | 366 | | |
| |||
369 | 369 | | |
370 | 370 | | |
371 | 371 | | |
372 | | - | |
| 372 | + | |
373 | 373 | | |
374 | | - | |
| 374 | + | |
375 | 375 | | |
376 | 376 | | |
377 | 377 | | |
| |||
449 | 449 | | |
450 | 450 | | |
451 | 451 | | |
452 | | - | |
| 452 | + | |
453 | 453 | | |
454 | 454 | | |
455 | 455 | | |
| |||
632 | 632 | | |
633 | 633 | | |
634 | 634 | | |
635 | | - | |
| 635 | + | |
636 | 636 | | |
637 | 637 | | |
638 | 638 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
311 | 311 | | |
312 | 312 | | |
313 | 313 | | |
314 | | - | |
315 | | - | |
| 314 | + | |
| 315 | + | |
316 | 316 | | |
317 | 317 | | |
318 | 318 | | |
319 | | - | |
320 | | - | |
321 | | - | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
322 | 335 | | |
323 | 336 | | |
324 | 337 | | |
| |||
0 commit comments