Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion .agents/docs/2026-09-07-package-identity-and-doc-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ manifest 注释,因为它是唯一能回答「为什么这个还要我写」的
3. **抑制的那一半必须一起写**:带 `DT_RUNPATH` 的对象不使用任何 RPATH,自己的和继承的
都不用。只写正向那条,一个无条件继承的实现同样通过。

更一般的形状:**为了让一段没人跑过的代码被编译一次而去跑它,挖到的往往不是那段代码
更一般的形状:**为了让一段没人跑过的代码被编译一次而去跑它,挖到的往往不是那段代码
的问题。** 五个夹具覆盖五条规则里的四条,而第五条一次都没被编译过。

### 10.7 落地顺序(不可交换)
Expand All @@ -366,3 +366,52 @@ manifest 注释,因为它是唯一能回答「为什么这个还要我写」的

第 5 步是 mcpp 的**第二个** PR。它不是拆分,是两次跨仓库发布夹在中间的必然结果:
示例引用的是已发布的 plugins,而那一版依赖第 1 步的引擎。

## 11. 落地记录

| 步 | 产物 | 判据 |
|---|---|---|
| mcpp 引擎 | PR#584 → main `83c91002` → **2026.9.6.6** | 分支 38/38 绿,main 30/30 绿;e2e 627–630 在 2026.9.6.5 上实测为红 |
| 发布 | tag `v2026.9.6.6`,四平台 | 两端镜像逐字节核过(GitCode 的 HEAD 返 401,ranged GET 返 206,尺寸四个全等) |
| 索引(xim) | openxlings/xim-pkgindex #777 | `Publish Index Artifact` 在合并 commit 上绿;`xlings install mcpp@2026.9.6.6` 成功 |
| mcpp-plugins | PR#8 → main `7a834933` → **0.2.4** | main CI 绿,且新判据打印出四条 `entries declared by dependencies` |
| 索引(mcpp) | mcpplibs/mcpp-index #364 | `lua` 解析通过,三个平台表 `latest = 0.2.4`;`mirror-cn-reachable` 绿 |
| 示例 | mcpp 的第二个 PR | 25 行声明离开 manifest,留下三行(两个设备 + 一个覆盖示范) |

### 11.1 落地期又挖出两处,都不是本方案的对象

**一、`DT_RPATH` 的继承没被建模**(§10.8)。为了让第五条规则的诊断被编译一次而去构建
cann 示例时碰到:引擎拒绝了一个加载器能起来的产物。已修,连同两条腿的单测。

**二、mcpp-plugins 的 CI 缓存从来没生效过。** 已发布的 mcpp 是自包含的 —— 没有
`MCPP_HOME` 时它的 home 就是解开的 tarball,而那个 job 缓存的是 `~/.mcpp`,一个 mcpp
从不碰的目录。**每次运行都是冷的,而 90 分钟的超时预算正是按冷的算的,所以没人觉得
不对。** 顺带把我自己新写的判据也坑了一次:`rm -rf ~/.mcpp/provisioned` 什么都没删,
于是一次构建成功的运行被判红。

**本地预演之所以通过,恰恰因为它用的是开发构建** —— 开发构建被排除在自包含模式
之外,确实用 `~/.mcpp`。**同一段脚本,两个不同的对象。** 预演要用发布物跑。

### 11.2 索引描述符里一句不成立的话

`mcpp.plugins.lua` 原有的措辞说「记录的 floor 会**拒绝**低版本客户端」。**没有这回事**:
描述符与包的 manifest 里都没有 per-package 的引擎下界,而索引级的 `min_mcpp` 是故意
不抬的(抬它会让停在下限的客户端连整个索引都打不开)。floor 是文档。改成写清楚低版本
客户端**实际**会撞上什么:2026.9.6.5 上规则报工具包缺席并点出下界,≤2026.9.6.4 上
`cfg(accelerator = ...)` 的工具表被直接拒绝。

### 11.3 沙箱验证:五节全过,零跳过

`xlings subos use verify-966 --sandbox`,脚本 base64 传进去,mcpp 按 store 路径寻址,
用的全部是**已发布**的东西(2026.9.6.6 + `mcpp:plugins@0.2.4`,CN mirror):

== A. identity == ok: mcpp 2026.9.6.6
== B. 范围 == ok: 装上并被回答 / 不可满足被拒绝
== C. 一条边 == ok: 载荷来自规则(entries declared by dependencies)/ 跑通
== D. 覆盖 == ok: 只装一个 glslang
== E. 击穿下界 == ok: 拒绝并点出两侧 / 抬钉后通过
0 assertion(s) failed

**隔离是可证的,而且这次证了**:沙箱 registry 里 **12** 个包(恰好这次验证需要的
那些,含 `mcpp-x-plugins` 与 `xim-x-glslang`),宿主 **222** 个。判据落在一个从没见过
这些东西的 registry 上 —— 这是「看内容不要看变量」的正面用法。
2 changes: 1 addition & 1 deletion .github/tools/build_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ SKIP=(
"examples/09-heterogeneous/hip/app|same, for the HIP payloads"
"examples/09-heterogeneous/sycl/app|needs the dpcpp payload (over a gigabyte) and a device its runtime accepts"
"examples/09-heterogeneous/vulkan/app|built AND RUN by the next step of this job, on the lavapipe payload, which needs no GPU"
"examples/09-heterogeneous/cann/app|its device leg needs the Ascend DRIVER, which a runner does not have: the kernel compiles and the object links, and then `libascend_hal.so` is missing, which is correct on a machine with no NPU. Its CPU leg does build -- and is not built here only because the plugins pin would make this job resolve a fifth rule package for one example. Covered by the measurements in its README"
"examples/09-heterogeneous/cann/app|its device leg needs the Ascend DRIVER, which a runner does not have: the kernel compiles and the object links, and then `libascend_hal.so` is missing -- and only that one, since 2026.9.6.6 models DT_RPATH inheritance. Correct on a machine with no NPU. Its CPU leg does build, and is not built here only because it would make this job resolve a fifth rule package for one example. Covered by the measurements in its README"
)

# Every ROOT manifest in the tree: a directory with an `mcpp.toml` that has no
Expand Down
2 changes: 1 addition & 1 deletion .xlings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"workspace": {
"mcpp": "2026.9.6.4"
"mcpp": "2026.9.6.6"
}
}
9 changes: 5 additions & 4 deletions examples/09-heterogeneous/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ working build stops working on another machine.
|---|---|---|
| engine | the graph, the identity, the axis | mcpp itself |
| rule package | the spelling of one model | `mcpp.rules.cuda` in `mcpp:plugins` |
| payload | the binaries, versioned by the project | `xim:cuda-nvcc`, `xim:dpcpp`, `xim:glslang` |
| payload | the binaries, versioned by the rule and overridable by the project | `xim:cuda-nvcc`, `xim:dpcpp`, `xim:glslang` |
| adapter | a built artifact's reach to something the host owns | `compat:cuda-driver`, `compat:vulkan-runtime`, `compat:sycl-runtime` |

The adapter layer exists for one reason. An mcpp-built program runs under
Expand All @@ -112,9 +112,10 @@ NVIDIA driver, a Vulkan ICD — is reached by an index package that puts a
directory on the artifact's runtime search path. A project declares it as an
ordinary dependency and does not otherwise think about it.

Everything else is a payload, so the version is the project's choice rather
than the machine's, and no example here contains an absolute path or reads the
host's toolkit.
Everything else is a payload. Which package and how old it may be belongs to
the rule; *exactly which version* is the project's to override and nobody's to
discover from the machine. No example here contains an absolute path or reads
the host's toolkit, and only `multi-backend/` names a payload version at all.

## Running them

Expand Down
19 changes: 11 additions & 8 deletions examples/09-heterogeneous/cann/app/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@ import_std = true
# imports it as `mcpp.rules.ascendc`. `[build-dependencies]`, because a rule
# package's library must never reach the target while its rule is wanted.
[build-dependencies.mcpp]
plugins = { version = "0.2.3", features = ["rules-ascendc"], host-module = true }
plugins = { version = "0.2.4", features = ["rules-ascendc"], host-module = true }

# The toolkit carries BOTH halves this lane needs, so it is one payload rather
# than two:
# NO [xlings.workspace]. `mcpp.rules.ascendc` declares `xim:cann-toolkit`
# itself, gated on this accelerator and on the feature that selects the rule --
# so a build that names no accelerator installs none of its 2.9 GB, and a
# project that wants the lane writes the edge above and nothing else.
#
# The toolkit carries BOTH halves the rule needs, which is why it is one payload
# rather than two:
#
# <toolkit>/cann/<arch>-linux/ccec_compiler/bin/bisheng the device compiler
# <toolkit>/cann/<arch>-linux/simulator/<SoC>/lib 38 SoCs, no hardware
#
# Gated on the accelerator: it is 2.9 GB installed, and `mcpp build` with no
# accelerator has no use for it. That gating needs mcpp 2026.9.6.5; before it
# the only spellings were "unconditionally" and "not at all".
[target.'cfg(accelerator = "ascend")'.xlings.workspace]
"xim:cann-toolkit" = "8.5.0"
# The rule states `>=8.5.0` rather than an exact version: nothing here is
# coupled to a driver, and that is the release the mixed-mode object and the
# simulators arrive in. Needs mcpp 2026.9.6.6 and mcpp:plugins 0.2.4.

[build]
# `dav-2201` is the device architecture, the role `sm_89` plays for CUDA. The
Expand Down
23 changes: 16 additions & 7 deletions examples/09-heterogeneous/cuda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,26 @@ is compiled, with `accel-mismatch` on the machine-readable channel.

## Where the toolkit comes from

The project names it:
The rule names it. This project writes one edge and no payload list at all:

```toml
[xlings.workspace]
"xim:cuda-nvcc" = "12.9.86"
"xim:cuda-cudart" = "12.9.79"
[build-dependencies.mcpp]
plugins = { version = "0.2.4", features = ["rules-cuda"], host-module = true }
```

These are payloads, so the version is the project's choice and not the
machine's. The rule package resolves them with `mcpp::xpkg_dir` and builds the
whole invocation from what it finds — the compiler, the include directories
`mcpp.rules.cuda` declares nvcc, cudart, cuRAND's headers, CCCL and the driver
sentinel for itself, under `cfg(accelerator = "cuda")` and the feature that
selects it — so a build that names no accelerator installs none of them. To use
a different CUDA line, write the entry in this project and it wins:

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

One version is installed either way, and mcpp says which. The rule resolves
what it declared with `mcpp::xpkg_dir` and builds the whole invocation from what
it finds — the compiler, the include directories
and the library search paths. No path in this example is absolute, and a build
here touches nothing of the host's CUDA:

Expand Down
30 changes: 12 additions & 18 deletions examples/09-heterogeneous/cuda/app/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ default = "llvm@22.1.8"
# The rule that compiles the island lives in the official plugin collection,
# selected by its feature; `build.mcpp` imports it as `mcpp.rules.cuda`.
[build-dependencies.mcpp]
plugins = { version = "0.2.1", features = ["rules-cuda"], host-module = true }
plugins = { version = "0.2.4", features = ["rules-cuda"], host-module = true }

# The driver's userspace library, reached through an index package that owns
# the one hop mcpp needs: a directory on the artifact's runtime search path.
Expand All @@ -31,23 +31,17 @@ plugins = { version = "0.2.1", features = ["rules-cuda"], host-module = true }
[dependencies.compat]
cuda-driver = "2026.09.05"

# The toolkit this project builds with, named rather than discovered. These
# are PAYLOADS, so the version is the project's choice and not the machine's.
# The 12.9 line is named on purpose: a runtime must not be newer than the
# driver it will meet, and 12.x reaches every driver from r525 onward. The
# rule package states the driver relation and mcpp compares it before the
# first compile.
[xlings.workspace]
"xim:cuda-nvcc" = "12.9.86"
"xim:cuda-cudart" = "12.9.79"
# cuRAND's headers and CCCL. clang's CUDA wrapper includes
# curand_mtgp32_kernel.h for every device unit, and that header includes
# <nv/target> from CCCL, so the clang route needs both even though this kernel
# calls neither; on a developer machine the host's /usr/include used to supply
# them silently. 10.3.x and 12.9.27 are the 12.9 line.
"xim:libcurand" = "10.3.10.19"
"xim:cuda-cccl" = "12.9.27"
"xim:libcuda-host-link" = { linux = "0.0.1" }
# NO [xlings.workspace]. The toolkit is declared by the rule itself, under
# `cfg(accelerator = ...)` and the feature that selects it, so this project
# names the rule and nothing else. To pin a different version, write the same
# entry here and it wins -- see `One package, one version` in
# docs/05-mcpp-toml.md. Needs mcpp 2026.9.6.6 and mcpp:plugins 0.2.4.
#
# What the rule declares for this lane: nvcc, cudart, cuRAND's headers, CCCL and
# the driver sentinel. The 12.9 line is an EXACT default rather than a floor,
# because a runtime must not be newer than the driver it will meet -- 12.x
# reaches every driver from r525 onward and 13.x raises that to r580, which is a
# property of the machines the artifact will run on.

[build]
# What this build compiles device code FOR. Written once, here: the rule
Expand Down
30 changes: 11 additions & 19 deletions examples/09-heterogeneous/hip/app/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import_std = true
default = "llvm@22.1.8"

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

# The driver's userspace library. HIP reaches the device through the CUDA
# runtime here, so this is the same one hop the CUDA consumer needs: mcpp's
Expand All @@ -30,24 +30,16 @@ plugins = { version = "0.2.1", features = ["rules-hip"], host-module = true }
[dependencies.compat]
cuda-driver = "2026.09.05"

# The payloads. `hip-nvidia` is headers only -- on this platform that is all
# HIP is -- and the four CUDA entries are the back end it compiles through.
# cuRAND and CCCL are on the list for the reason the CUDA consumer records:
# clang's CUDA wrapper includes curand_mtgp32_kernel.h for every device unit
# and that header includes <nv/target>, so a unit that calls neither still
# needs both.
[xlings.workspace]
"xim:hip-nvidia" = "7.2.4"
"xim:cuda-nvcc" = "12.9.86"
"xim:cuda-cudart" = "12.9.79"
"xim:libcurand" = "10.3.10.19"
"xim:cuda-cccl" = "12.9.27"
# `nvidia_hip_runtime_api.h` includes <cuda_profiler_api.h> at its second line,
# and CUDA ships that header in its own component. A machine with a host CUDA
# installation finds it in /usr/include without saying so, which is how this
# entry came to be missing from a build that worked.
"xim:cuda-profiler-api" = "12.9.79"
"xim:libcuda-host-link" = { linux = "0.0.1" }
# NO [xlings.workspace]. `mcpp.rules.hip` declares the payloads it drives:
# `hip-nvidia` (headers only -- on this platform that is all HIP is) plus the
# CUDA back end it compiles through, including `cuda-profiler-api`, which
# `nvidia_hip_runtime_api.h` includes at its second line. A machine with a host
# CUDA installation finds that header in /usr/include without saying so, which
# is how the entry came to be missing from a build that worked -- and it is now
# the rule's business rather than every consumer's.
#
# To pin a different version, write the same entry here and it wins. Needs mcpp
# 2026.9.6.6 and mcpp:plugins 0.2.4.

[build]
# Two chunks: the programming model, and the device. A device is spelled once
Expand Down
17 changes: 12 additions & 5 deletions examples/09-heterogeneous/multi-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,18 @@ glob out the way `--no-accel` leaves both out, and the `cfg(accelerator =
the two halves stay together. Only an accelerator this build *does* name whose
architecture it does not cover is refused (mcpp 2026.9.6.5).

**Nothing is installed for a device this build did not name.** The payloads sit
under `[target.'cfg(accelerator = ...)'.xlings.workspace]`, so `mcpp run`
fetches neither the CUDA toolkit nor the shader compiler. That gating needs
mcpp 2026.9.6.5; before it, the only spellings available were "unconditionally"
and "not at all", and the cheapest build paid for the most expensive one.
**Nothing is installed for a device this build did not name.** The payloads are
declared by the two rules, under `cfg(accelerator = ...)` and the feature that
selects each — so `mcpp run` fetches neither the CUDA toolkit nor the shader
compiler. That gating needs mcpp 2026.9.6.5; before it, the only spellings
available were "unconditionally" and "not at all", and the cheapest build paid
for the most expensive one.

**This is also the one example that overrides a rule's default.** One line names
`xim:cuda-nvcc`, and it is kept to show the escape hatch working: the
declaration nearer the artifact wins, one version is installed, and a pin that
failed a floor the rule stated would be refused naming both sides. Every other
example writes the rule edge and nothing else.

## The CUDA leg takes the clang route

Expand Down
38 changes: 20 additions & 18 deletions examples/09-heterogeneous/multi-backend/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ default = "llvm@22.1.8"
# The rules are declared unconditionally because `build.mcpp` imports them
# unconditionally -- each returns immediately when its own backend is absent.
[build-dependencies.mcpp]
plugins = { version = "0.2.2", features = ["rules-cuda", "rules-spirv"], host-module = true }
plugins = { version = "0.2.4", features = ["rules-cuda", "rules-spirv"], host-module = true }

# ── the payloads, gated on the device they are for ──────────────────────────
#
Expand All @@ -65,20 +65,21 @@ plugins = { version = "0.2.2", features = ["rules-cuda", "rules-spirv"], host-mo
# to 13.x fixes that and raises the driver floor to r580, which is a machine
# requirement rather than a project decision. The clang route never includes
# that header and imposes no such floor.
# ONE ENTRY, AND IT IS THE ONLY OVERRIDE IN THIS REPOSITORY'S EXAMPLES.
#
# `mcpp.rules.cuda` declares the whole toolkit -- nvcc, cudart, cuRAND, CCCL and
# the driver sentinel -- so the four beside this one are gone and this project
# would build with no `[xlings.workspace]` at all. The line is kept to show the
# escape hatch working: the declaration nearer the artifact wins, one version is
# installed either way, and a pin that failed a floor the rule stated would be
# refused naming both sides.
#
# Which CUDA line matters here is what makes it a plausible override rather than
# a contrived one: a runtime must not be newer than the driver it will meet, so
# a project whose machines are older or newer than the rule's default is exactly
# the project that should say so.
[target.'cfg(accelerator = "cuda")'.xlings.workspace]
"xim:cuda-nvcc" = "12.9.86"
"xim:cuda-cudart" = "12.9.79"
# clang's CUDA wrapper includes a cuRAND header for every device unit, and that
# header includes <nv/target> from CCCL. Neither is called by this kernel; on a
# developer machine the host's /usr/include used to supply them silently.
"xim:libcurand" = "10.3.10.19"
"xim:cuda-cccl" = "12.9.27"

# The host-link stub is Linux-only, and the OS goes in the PREDICATE rather
# than in the value: a value carrying platform keys under an already-predicated
# table would state the condition twice and let the two disagree.
[target.'cfg(all(accelerator = "cuda", linux))'.xlings.workspace]
"xim:libcuda-host-link" = "0.0.1"
"xim:cuda-nvcc" = "12.9.86"

# The driver's userspace library, reached through an index package rather than
# the host: mcpp's private loader does not consult /usr/lib, so a statically
Expand All @@ -88,11 +89,12 @@ plugins = { version = "0.2.2", features = ["rules-cuda", "rules-spirv"], host-mo
[target.'cfg(accelerator = "cuda")'.dependencies.compat]
cuda-driver = "2026.09.05"

# The shader compiler is NOT here: `mcpp.rules.spirv` declares `xim:glslang` for
# itself. What stays is a DEVICE -- a Vulkan driver that is always present
# because it is the CPU, which is what makes the Vulkan leg runnable on a
# machine with no GPU, and what every CI runner in this ecosystem is. A rule
# declares what it needs to COMPILE; a project declares what it needs to RUN.
[target.'cfg(accelerator = "vulkan")'.xlings.workspace]
# The shader compiler, and a Vulkan driver that is always present because it is
# the CPU. The second is what makes the Vulkan leg runnable on a machine with
# no GPU, which is what every CI runner in this ecosystem is.
"xim:glslang" = "15.1.0"
"xim:mesa-lavapipe" = "26.2.1"

# The Khronos loader, built by the index rather than taken from the host, and
Expand Down
Loading
Loading