The build plugins the mcpp project maintains, published as one package,
mcpp:plugins. A consumer selects the members it needs through features and
imports each one from build.mcpp under the module name the member declares.
[build-dependencies.mcpp]
plugins = { version = "0.2.3", features = ["rules-spirv"], host-module = true }[build-dependencies], not [dependencies]. The two keys answer separate
questions: host-module = true says which build-time product is wanted, and
the section says whether the package reaches the target. A rule package answers
"no" to the second -- its library must never be linked into the artifact while
its rule is still needed -- which is the case docs/05 section 2.6.1 exists for.
Writing it under [dependencies] also works, which is precisely why the
distinction has to be stated rather than left to a failure to teach.
// build.mcpp
import std;
import mcpp;
import mcpp.rules.spirv;
int main() {
mcpp::rules::spirv::options opt;
opt.includes = { "shaders" };
return mcpp::rules::spirv::compile(opt) ? 0 : 1;
}| family | module name | purpose |
|---|---|---|
| rules | mcpp.rules.<x> |
how one kind of translation unit is compiled by a compiler mcpp does not drive: the spelling of its flags, the probe of its toolkit, the actions it submits |
| tools | mcpp.tools.<x> |
a build-time utility independent of any compiler; see tools/README.md |
| identity | mcpp.plugins |
the lib root, compiled before every member; it states the collection's version |
The mcpp. prefix is reserved for this package: mcpp warns when a module under
it is declared by a package outside the mcpp namespace. mcpp.build.* is the
engine's own module family and is not used here.
| feature | module | since mcpp | what it needs |
|---|---|---|---|
rules-ascendc |
mcpp.rules.ascendc |
2026.9.6.6 | [build] accel = "ascend8.5+{dav-c220}", a constrained glob for *.asc. Compiles with BiSheng in MIXED mode, so the object carries the device binary and a host-callable launcher and joins the ordinary link -- no registration file and no device-link step. Its own engine needs are .asc in the device-source table and mcpp::link_flag for the -rpath-link the toolkit's shared libraries require, both 2026.9.6.5 |
rules-cuda |
mcpp.rules.cuda |
2026.9.6.6 | [build] accel = "cuda…", a constrained glob for *.cu; the clang route with an LLVM toolchain, the nvcc route with a GCC one |
rules-hip |
mcpp.rules.hip |
2026.9.6.6 | [build] accel = "hip, cuda12.9+{sm_89}", a constrained glob for *.hip. On the NVIDIA platform HIP is a header layer over the CUDA runtime, so the compiler is the project's own clang and there is no ROCm on the machine |
rules-spirv |
mcpp.rules.spirv |
2026.9.6.6 | [build] accel = "vulkan1.2", a constrained glob for the shader stages; emits one header per shader through a role = "source" action, and states which of the two compilers produced it |
rules-sycl |
mcpp.rules.sycl |
2026.9.6.6 | [build] accel = "sycl" or "sycl, cuda12.9+{sm_89}", a constrained glob for *.sycl, and compat:sycl-runtime so the artifact can reach libsycl.so.9 at run time. Its own engine need is .sycl in the device-source table, 2026.9.6.1 |
tools-embed |
mcpp.tools.embed |
2026.9.5.4 | nothing beyond mcpp: it reads a file and writes a header while the build program runs. The floor is the release whose fast path compares a declared file input, without which an edit to the data does not reach the binary |
A project names the rule and nothing else:
[build-dependencies.mcpp]
plugins = { version = "0.2.4", features = ["rules-cuda"], host-module = true }The payloads each rule drives are declared here, under the feature that selects the rule and the accelerator it serves:
[target.'cfg(accelerator = "cuda")'.feature-xlings.rules-cuda]
"xim:cuda-nvcc" = "12.9.86"
"xim:cuda-cudart" = "12.9.79"Two gates, and both must open before a byte is downloaded. The feature says whether the rule is wanted; the selector says whether this build compiles for the device. A CPU-only build opens neither.
The shape of each default is a judgement about coupling. An exact version
where the payload is coupled to something the rule cannot see -- a CUDA runtime
must not be newer than the driver it will meet, so the 12.9 line is offered and
a project with newer machines names 13.x itself. A floor (>=) where no such
coupling exists: a shader compiler, a SYCL compiler, a CANN toolkit.
mcpp reads the difference. A bare version is a choice, so a project pinning
a different one wins and the override is reported; a >= is a requirement,
so a project pinning below it is refused naming both sides. Either way one
version is installed. To override:
[target.'cfg(accelerator = "cuda")'.xlings.workspace]
"xim:cuda-nvcc" = "13.3.33"What is not here: anything the produced program chooses to run on. A
Vulkan ICD (xim:mesa-lavapipe) is a device, and a rule that declared one would
force a software renderer onto consumers that have a GPU. The runtime adapters
(compat:cuda-runtime, compat:sycl-runtime, compat:vulkan-runtime) stay in
the project for that reason and for a second one: this package is reached
through a [build-dependencies] edge, so its own [dependencies] deliberately
do not reach the consumer's target.
mcpp::device_sources() is the package's WHOLE device set, not one rule's
share of it, and every rule in a build program reads the same variable. A
project with two backends puts a .cu and a .comp in that one list.
Each rule therefore selects the extensions it claims and leaves the rest:
| feature | claims |
|---|---|
rules-ascendc |
.asc, .cce |
rules-cuda |
.cu |
rules-hip |
.hip |
rules-sycl |
.sycl |
rules-spirv |
.comp .vert .frag .geom .tesc .tese .mesh .task .rgen .rint .rahit .rchit .rmiss .rcall, and .glsl / .hlsl so that a stage-less name is refused by name rather than by absence |
A rule whose backend this build does not name returns immediately, so a build
program may call every rule it imports unconditionally and --no-accel
compiles nothing.
A device source that NO rule claims is not silently dropped: mcpp refuses a device source that reached no action, naming the file. That is the engine's half of this rule and it needs 2026.9.6.5.
The floor is the mcpp release whose engine carries what the member relies on.
From 0.2.4 every rule shares one: 2026.9.6.6, the release in which a payload
a DEPENDENCY declared is both installed and answerable. Before it a rule could
declare >=8.5.0, have it installed, and still be told by xpkg_dir that
nothing was there -- which is why each rule's list used to be repeated in every
project that used it. The earlier per-member floors are still the floors of the
rules themselves (rules-spirv needs the shader extensions in the device-source
table, 2026.9.5.3; tools-embed needs the fast path to compare a declared file
input, 2026.9.5.4; rules-sycl needs .sycl in that table, 2026.9.6.1), and
they are all below the shared one.
The index descriptor states the highest floor among the members, so it is the floor of the collection rather than of any one feature; a project on an older mcpp is refused at resolution rather than at the first shader.
mcpp compiles every module interface unit among a host-module package's
resolved sources as a host module of its own, the lib root first (2026.9.5.3+).
[features.<f>] sources is what puts a member into that set, so the module set
a consumer can import is exactly its feature set. A member is compiled alone,
in the same command as the consumer's build.mcpp, and may import std,
mcpp and mcpp.plugins.
mcpp.toml the package: one feature per member
src/plugins.cppm export module mcpp.plugins;
rules/<x>.cppm export module mcpp.rules.<x>;
tools/<x>.cppm export module mcpp.tools.<x>;
tests/<consumer>/ one project per member, built by CI with the pinned mcpp
A member drives a compiler the ecosystem resolved and no other. xim:dpcpp is
mcpp.rules.sycl's compiler; xim:gcc is the C++ standard library it compiles
against; xim:cuda-nvcc is the back end it emits for. None of the three is a
default: each is read from mcpp::xpkg_dir, and a member that cannot find one
refuses and prints the [xlings.workspace] line that would add it.
Two of those three were added because a build that already worked was found to
be reading the host. Without --gcc-install-dir the SYCL unit compiled against
/usr/include/c++; without --cuda-path clang found the host's CUDA
installation. Neither said anything: both are visible only in the compiler's
own include search list, and only on a machine that has those directories.
- One file,
rules/<x>.cppmortools/<x>.cppm, declaring its module name. The engine owns the graph — the accelerator axis, the constrained globs, the action edges, the fingerprint — and the member owns the spelling: which tool, which flags, what is generated. A member does not read/usr; a tool comes frommcpp::toolchain_dir(),mcpp::xpkg_dir()or an explicit option, and a missing one is refused naming the[xlings.workspace]entry to add. - A feature in
mcpp.tomladding that one source. - A consumer under
tests/that builds through it and asserts on the artefact, and a row in the table above with the mcpp floor. - A version bump: mcpp identifies an installed package by
(name, version), so a changed payload under an unchanged version is not reinstalled.
A tag v<version> publishes the source archive; the index descriptor
(mcpp-index/pkgs/m/mcpp.plugins.lua) names the GitHub archive and its GitCode
mirror with one sha256.