Skip to content

0.2.4: every rule brings its own environment - #8

Merged
Sunrisepeak merged 3 commits into
mainfrom
feat/rules-bring-their-own-environment
Sep 7, 2026
Merged

0.2.4: every rule brings its own environment#8
Sunrisepeak merged 3 commits into
mainfrom
feat/rules-bring-their-own-environment

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

What changes for a project

Before:

[build-dependencies.mcpp]
plugins = { version = "0.2.3", features = ["rules-cuda"], host-module = true }

[xlings.workspace]
"xim:cuda-nvcc"         = "12.9.86"
"xim:cuda-cudart"       = "12.9.79"
"xim:libcurand"         = "10.3.10.19"
"xim:cuda-cccl"         = "12.9.27"
"xim:libcuda-host-link" = { linux = "0.0.1" }

After:

[build-dependencies.mcpp]
plugins = { version = "0.2.4", features = ["rules-cuda"], host-module = true }

The second block was the rule's own knowledge, repeated by everyone who used
it — a copy that goes stale silently, because the rule moves and the projects
do not. Each rule now declares it, 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 of the same project opens neither.

All five rules, with no exception. "Sometimes you have to supply it yourself"
is a rule nothing can state precisely enough to be useful.

The shape of each default is a judgement about coupling

Not a style. An exact version where the payload's version is coupled to
something the rule cannot see — a CUDA runtime must not be newer than the
driver it will meet, and the 12.9 line reaches every driver from r525 onward
where 13.x raises that to r580. A floor (>=) where no such coupling
exists: glslang, dpcpp, the 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 — which is what mcpp 2026.9.6.6 makes true, and why that is the
floor for every rule here.

Overriding is one line in the project:

[target.'cfg(accelerator = "cuda")'.xlings.workspace]
"xim:cuda-nvcc" = "13.3.33"

What is deliberately not moved

Anything the produced program chooses to run on. xim:mesa-lavapipe is a
device, and a rule declaring 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 the
same reason and for a structural one: this package is reached through a
[build-dependencies] edge, so its own [dependencies] deliberately do not
reach the consumer's target.

Two fixtures were short by one accelerator

Raising the CI pin to 2026.9.6.6 turned hip-consumer red immediately: its
glob names accel = "hip, cuda12.9+{sm_89}" while [package] accelerators
listed only hip. sycl-consumer has the same shape, and so do mcpp's own
examples/09-heterogeneous/{hip,sycl}.

Neither CI could see it. mcpp skips those two examples in its example job, and
this repository's engine pin predated the check (2026.9.6.5). It took both
repositories moving.

Criteria

The four device fixtures now declare no payload at all and still build; that
alone does not distinguish "the rule declared it" from "the fixture still
does", so the new CI step asserts both halves:

  • no [xlings.workspace] in the fixture's manifest — a property of a file this
    repository owns;
  • mcpp's own report that the entries came from the graph pass rather than
    the root's (entries declared by dependencies).

That line only prints on a provisioning-stamp miss, and this job restores
~/.mcpp from a cache — so the step clears the stamp first. Without that, the
criterion's "no" and "this ran on a warm cache" are the same reading.

multi-rule-consumer keeps one override (xim:glslang pinned to the rule's own
floor) and the step asserts one version is installed, so the override path is on
the CI graph and not only in mcpp's e2e.

A project that wanted a device island wrote two things: the rule edge, and
the rule's own package list repeated with exact pins. The second is a copy
that goes stale silently -- the rule moves and the projects do not.

Each rule now declares the payloads it drives, under the feature that
selects it and the accelerator it serves, so a project writes one edge:

    [build-dependencies.mcpp]
    plugins = { version = "0.2.4", features = ["rules-cuda"], host-module = true }

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, not a style. An
exact version where the payload's version 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: glslang, dpcpp,
the CANN toolkit. mcpp reads the difference: a bare version is a choice and
a project pinning another wins; a `>=` is a requirement and a pin below it
is refused naming both sides.

Not moved: anything the produced PROGRAM chooses to run on. A Vulkan ICD is
a device, and a rule declaring one would force a software renderer onto
consumers that have a GPU. The runtime adapters stay in the project for that
reason and because this package is a [build-dependencies] edge.

Two fixtures declared fewer accelerators than their globs name: on the
NVIDIA platform HIP and SYCL both compile THROUGH the CUDA back end, so
`cuda` belongs in their `[package] accelerators`. mcpp 2026.9.6.5 refuses
that mismatch; the CI pin was older, so only raising it surfaced them.

The floor for every rule is now 2026.9.6.6, the release in which a payload a
dependency declared is both installed and answerable.
新加的那步在一次**构建成功**的运行上红了:`rm -rf ~/.mcpp/provisioned` 什么都
没删,于是前面几步写下的 stamp 还在,供给那一行不再打印。

已发布的 mcpp 是自包含的:没有 `MCPP_HOME` 时它的 home 就是解开的那个 tarball
目录。所以 `~/.mcpp` 从来不是它的 home——而这个 job 的 cache 正是 `path: ~/.mcpp`,
它保存和恢复的是一个 mcpp 从不碰的目录。**每一次运行都是冷的**,而下面那 90 分钟
的预算正是按冷的算的。

两处一起改:

* `MCPP_HOME: /home/runner/.mcpp` 进 env。mcpp 自己的 ci-linux.yml 为同一个理由
  钉了它并写明「so the cache keys restore into the same path」。
* 判据不再假定路径,改成问 `mcpp self env`。

⚠️ 本地预演之所以通过,是因为**开发构建被排除在自包含模式之外**,它确实用
`~/.mcpp` —— 预演与 CI 清的是两个不同的目录。判据的对象在两个环境里不是同一个。
@Sunrisepeak
Sunrisepeak merged commit 7a83493 into main Sep 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant