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
build.mcpp: three channels a rule package had no way to reach the engine through
Each of these is small, and they share a shape: a rule package already knew
something and nothing carried it to a decision the engine makes.
`mcpp::action` gains `depfile`. An action's inputs are fixed when build.mcpp
runs, before its command has executed, so a compiler that learns its include
graph by parsing the source has no channel to report it: editing a file the
command merely READ rebuilds nothing and `mcpp build` stays green over a stale
artifact. With a depfile declared, ninja reads the file and folds what it names
into the edge. Every device compiler this matters for already emits one --
`glslangValidator --depfile`, `glslc -MD -MF`, `slangc -depfile`, `nvcc` and
`clang` `-MD -MF` -- so the missing half was always the receiving end.
The depfile must not also be declared as an output: `deps = gcc` makes ninja
consume and delete it, so an edge that promised it would be permanently dirty.
The comment at the emission site says so.
`.slang` joins the device-source extension table. A constrained glob's `accel`
key does not make a file a device source; `kDeviceExtensions` does. Until it
listed `.slang`, a rule package could not introduce a device language on its
own: the payload installed, the rule compiled, and the file still fell through
to the ordinary source scan and was refused with "mcpp has no role for the
extension". Measured against 2026.9.6.6 with a working rule and a working
`xim:slang`.
Slang is a language rather than a second driver for GLSL -- its own module
system, generics, and targets beyond SPIR-V -- so it has an extension here and
a rule of its own outside.
`[language] modules` is reported as `MCPP_LANGUAGE_MODULES`. A rule that
GENERATES a consumer-facing declaration has to choose between a module
interface and a header, and the project has already stated which it builds;
deriving it any other way would be a second spelling of one decision. An engine
that does not report it leaves the variable absent, which a rule reads as
"header" -- the behaviour every consumer had before -- so the upgrade needs no
project to declare anything.
Tests. `tests/e2e/631` builds a project whose action writes a depfile naming a
file it never declares as an input, and asserts the action reruns when that
file is touched. It carries its own control: a no-op rebuild must not rerun the
action, which rules out a backend that reruns unconditionally. A separate
reverse control was run by hand against the same binary with `a.depfile`
removed and the flag still passed: the action did not rerun, so the assertion
measures the field rather than some other trigger.
Copy file name to clipboardExpand all lines: docs/07-build-mcpp.md
+22Lines changed: 22 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -470,6 +470,27 @@ package's compile edges to that package's action outputs.
470
470
> package**, because `include_dir` colours only the declaring package's own
471
471
> translation units.
472
472
473
+
**An action whose command discovers its own dependencies declares a depfile**
474
+
(mcpp 2026.9.7.1+). `input()` fixes the edge's inputs when `build.mcpp` runs,
475
+
before the command has executed, so a compiler that learns its `#include` graph
476
+
by parsing the source has no channel to report it — and editing a file the
477
+
command merely *read* reruns nothing, leaving `mcpp build` green over a stale
478
+
artifact.
479
+
480
+
```cpp
481
+
a.depfile = dep.c_str(); // a path the command writes
482
+
a.arg("--depfile").arg(dep.c_str());
483
+
```
484
+
485
+
mcpp emits `depfile =` and `deps = gcc` for that edge, so ninja reads the file
486
+
and folds what it names into the edge's dependencies. Every device compiler this
487
+
matters for already emits one: `glslangValidator --depfile`, `glslc -MD -MF`,
488
+
`slangc -depfile`, `nvcc`/`clang``-MD -MF`.
489
+
490
+
> **Do not also declare the depfile as an `output()`.**`deps = gcc` makes ninja
491
+
> consume and delete it after reading, so an edge that promised it as an output
492
+
> would be permanently dirty.
493
+
473
494
**A check's command does not have to write its stamp** (mcpp 2026.8.29.1+).
474
495
The verdict is the exit code; the stamp is bookkeeping the graph needs, and
475
496
mcpp creates it when the command succeeds. Before this, every check needed a
@@ -637,6 +658,7 @@ The running program receives the build context as `MCPP_*` variables
637
658
|`MCPP_TOOLCHAIN_BINUTILS_DIR`*(2026.9.5.2+)*|`mcpp::toolchain_binutils_dir()`| the directory mcpp names with `-B`; empty when it names none (a musl or MinGW payload brings its own assembler and linker) |
638
659
|`MCPP_CXX_STDLIB`*(2026.9.6.3+)*|`mcpp::cxx_stdlib()`| the C++ standard library the resolved toolchain uses — `libstdc++`, `libc++`, `msvc-stl`; empty when no toolchain resolved. A different question from `MCPP_TARGET_LIBC`, which is the C library |
639
660
|`MCPP_ACCEL`*(2026.9.5.2+)*|`mcpp::accel()`| the device axis of this build, resolved — `--accel` / `--no-accel` over `[build] accel` — in the wire form `cuda12.9+{sm_89} ptx>=89`; empty when the build asks for no accelerator. A rule package derives its own flags (`-gencode`, `--offload-arch`) from it, so the architecture set is written once, in the manifest. The same value feeds the `cfg(accelerator = "…")` layer key |
661
+
|`MCPP_LANGUAGE_MODULES`*(2026.9.7.1+)*| -- |`1` when the declaring package sets `[language] modules`, `0` otherwise. A rule that GENERATES a consumer-facing declaration reads it to choose between a module interface and a header, so a project states that once and never again. An older engine leaves it absent, which a rule reads as `0` -- the behaviour every consumer had before the variable existed |
640
662
|`MCPP_DEVICE_SOURCES`*(2026.9.5.2+)*|`mcpp::device_sources()`| the device-kind sources (`.cu`, `.hip`, …) the package's effective `sources` match, package-root-relative, one per line; empty when there are none. The engine compiles none of them — the rule package this program imports turns each into an `mcpp::action`. Already narrowed: a `{ glob, accel }` entry the build does not cover contributes nothing, so `--no-accel` yields an empty list |
641
663
|`MCPP_OUT_DIR`|`mcpp::out_dir()`| a writable scratch/output dir owned by mcpp |
642
664
|`MCPP_MANIFEST_DIR`|`mcpp::manifest_dir()`| the package root (= CWD) |
|`rules-hip`|`mcpp.rules.hip`| the project's own clang (`-x cuda`) on the NVIDIA platform | the above plus `xim:hip-nvidia`|`hip, cuda12.9+{sm_89}`|
486
487
|`rules-sycl`|`mcpp.rules.sycl`| the `xim:dpcpp` payload's clang (`-fsycl`) |`xim:dpcpp`; on Linux also `xim:gcc`, `xim:glibc`, `xim:linux-headers`; `xim:cuda-nvcc` for an NVIDIA target |`sycl` or `sycl, cuda12.9+{sm_89}`|
487
488
|`rules-spirv`|`mcpp.rules.spirv`|`glslangValidator` or `glslc`|`xim:glslang` on Linux, `xim:shaderc` on macOS and Windows |`vulkan1.2`|
0 commit comments