Skip to content

Commit 1dd2faf

Browse files
committed
xlings: one table, deps superseded, envs removed, and the install-time edge published
`[xlings]` is mcpp's surface for xlings' local project mechanism, and this brings the manifest into line with what that mechanism has. `[xlings.workspace]` becomes the one table. An entry names a package and the version this project uses it at, and produces two projections: an install address for provisioning and a resolution pin for the materialised `.xlings.json`. The namespace may be written on the key or on the version — `"xim:picolibc-riscv" = "1.8.12"` and `picolibc-riscv = "xim:1.8.12"` are one entry — because both spellings already exist in the ecosystem and neither is more natural; writing it on both halves with different values is refused, as is naming one package twice. `""` means present with the version unconstrained, which is the spelling an authored project file already uses. Platform keys are xlings' own, with `macos` kept as an alias. `deps` is superseded and still honoured, reported once with the line to write instead. It is not refused: a refusal would reach a dependency's manifest that a consumer pinning that package cannot edit. One package in both tables with two versions is a hard error, because the two are provisioned in order while the last wins the pin — install one, resolve the other. `envs` is removed. It was materialised into the file and read by nothing: the two `envs` structures xlings has belong to a program's own shim record and to a SubOS's provider sections, and mcpp's run environment comes from the runtime binding. The documentation described an effect that did not occur, which is why this is an error rather than a warning. `mcpp emit xpkg` writes `xpm.<platform>.deps`. Nothing emitted it before, so a package declaring an environment had its install-time edge written by hand — which is how riscv-virt-rt 0.3.0 shipped without the C library its own target row named. The declaration is kept unresolved per platform for it, since a descriptor has a block per platform and the host resolution has discarded two. Documented: the three-row inheritance rule, which has always held and which mcpp stated nowhere — no `subos` means the machine's environment with the project's entries over it, a named one means isolation, and an `xlings use` inside the project outranks both. Verified: e2e 331 asserts on the addresses the provisioning pass names with auto-install off, so every criterion runs on every shard with no network; it fails at its first assertion on 2026.9.2.1. Unit: test_manifest +8. Full unit suite 97 passed; e2e 06, 93, 130, 131, 132, 134 re-run.
1 parent dcd9753 commit 1dd2faf

15 files changed

Lines changed: 782 additions & 242 deletions

.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md

Lines changed: 50 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ Three changes follow, and one of them is a defect rather than a design.
2121
1. **`workspace` becomes the one table**; `deps` is retired from the manifest.
2222
The two state the same thing and differ only in what mcpp then does with the
2323
statement, and the mechanism they map onto has one authored surface.
24-
2. **Provisioning moves into project scope.** mcpp calls xlings with a global
25-
environment today, so the install writes into a workspace shared by every
26-
mcpp project while programs resolve through the project's own layers. That
27-
one argument is behind three measured symptoms.
24+
2. ~~Provisioning moves into project scope.~~ **Withdrawn during
25+
implementation** (§3): the call site records a measurement showing that
26+
project scope puts payloads in a SubOS the compiler's `--sysroot` does not
27+
name. The scope stays global; what the declaration decides is unchanged,
28+
because resolution reads the project's `workspace` layer either way.
2829
3. **`envs` is retired.** It has no reader in xlings and none in mcpp, and
2930
`docs/05` documents an effect it does not have.
3031

@@ -201,78 +202,51 @@ silence. `[xlings]` has no unknown-key sweep — no `kKnownXlings` list exists i
201202
`toml.cppm` — so a removed key would be read by nobody and reported by nobody,
202203
which is the shape #531 exists to prevent.
203204

204-
## 3. Change two: provisioning runs in project scope
205-
206-
### 3.1 The defect
207-
208-
```cpp
209-
mcpp::xlings::call(mcpp::config::make_xlings_env(**cfg2), "install_packages", …)
210-
```
211-
212-
`prepare.cppm:3421`. `make_xlings_env` carries no `projectDir`
213-
(`src/config.cpp:129`), so no `XLINGS_PROJECT_DIR` reaches the child and the
214-
cwd walk finds nothing. The child runs in **global** scope against mcpp's
215-
registry home, and three symptoms follow:
216-
217-
1. **The install's view and the shim's view disagree.** Records land in the
218-
registry's global workspace; a program invoked from the project resolves
219-
through the project's layers. This is the
220-
`qemu-aarch64-static is not installed in this subos (_)` measured during the
221-
2026.9.2.1 verification, and the reason the runner needed a
222-
payload-directory lookup.
223-
2. **`active` is read from a workspace shared by every mcpp project** on the
224-
machine, so the auto-activation decides against a state no project owns.
225-
3. **Forcing the switch there would let two checkouts flip each other**, which
226-
is why the decline is protective in this scope rather than obstructive.
227-
228-
### 3.2 The fix, and why nothing else is needed
229-
230-
Call with `make_project_xlings_env(cfg, root)`. The child then runs in project
231-
scope: the installed set and the activation records land in the project's own
232-
SubOS, and the view the install writes is the view the shim reads.
233-
234-
**No flag, and no explicit switch.** With the declaration materialised into the
235-
project file's `workspace`, §1's merge already makes it authoritative —
236-
Anonymous puts the project layer over the global one, Named does not merge the
237-
global one at all. `activate_requested_targets` does not fire in that situation
238-
and should not: the merged view already answers with the declared version.
239-
240-
An earlier draft proposed `useAfterInstall: true` and an explicit `use_version`
241-
per entry. Both are withdrawn (§15).
242-
243-
**One precondition, checked.** The scope fix works only if xlings accepts
244-
`<project>/.mcpp` as a project directory, and §1's discovery rule rejects any
245-
directory holding a `subos/`. It does not fire here: a project's SubOS lives at
246-
`<projectDir>/.xlings/subos/<name>`, so what would have to exist is
247-
`<project>/.mcpp/subos`, and nothing creates it — measured on
248-
`mcpplibs/riscv-virt-rt/.mcpp/`, which holds only `.xlings.json`. It is worth
249-
an assertion rather than a memory: anything that later writes a bare `subos/`
250-
under `.mcpp` would make mcpp's own project file invisible to xlings, silently
251-
and everywhere at once.
252-
253-
### 3.3 Provisioning otherwise keeps its contract
254-
255-
- The result is read, not assumed: `!called || childRc != 0`
256-
(`prepare.cppm:3448`).
257-
- A failure is a hard build error naming the manual command.
258-
- The stamp is written only on success and keyed on the hash of the declared
259-
set, so the merged table re-provisions once per project after the upgrade and
260-
the common build performs nothing.
261-
- `MCPP_OFFLINE` and `MCPP_NO_AUTO_INSTALL` gate the install action, not the
262-
block.
263-
264-
### 3.4 Two consequences to write down
265-
266-
**The project SubOS layer outranks the project manifest.** It merges last, so
267-
an explicit `xlings use` inside the project beats what `mcpp.toml` declares
268-
until the manifest is re-materialised. Defensible — an action a person took
269-
beats a file — but not obvious.
270-
271-
**A declared version that was never installed already fails with the right
272-
words**: `"{}@{} is the version this project asks for"`
273-
(`src/core/xvm/shim.cpp:495`). Provisioning is what prevents it; that
274-
diagnostic is what happens when provisioning was skipped, and it names the
275-
project rather than the machine.
205+
## 3. Change two, withdrawn during implementation: the scope stays global
206+
207+
The design proposed moving the provisioning call from `make_xlings_env` to
208+
`make_project_xlings_env`, on the reasoning that the install should write where
209+
the shim reads. Implementing it turned up a comment at the call site recording
210+
that this was tried and measured:
211+
212+
> GLOBAL scope, and the scope is the whole point. The obvious alternative —
213+
> `install_packages` against `make_project_xlings_env` — installs at PROJECT
214+
> scope, and that measurably does not work: on a fresh `MCPP_HOME` the headers
215+
> land in `<proj>/.mcpp/.xlings/subos/_/usr/include` while `--sysroot` names
216+
> `<MCPP_HOME>/registry/subos/default`, so `#include <gbm.h>` still failed with
217+
> the dependency installed and declared. Two SubOS views, and the payload in
218+
> the one the compiler does not read.
219+
220+
The premise the design rested on — that the install destination is chosen by
221+
package scope rather than by transport — is contradicted by that measurement.
222+
**Change two is withdrawn.** The call keeps `make_xlings_env`.
223+
224+
### 3.1 What that leaves true, and what it leaves unsolved
225+
226+
**The declaration still wins at resolution.** mcpp materialises
227+
`[xlings.workspace]` into the project file's `workspace` object, and that is a
228+
layer `merged_workspace` applies over the machine's (§1). So a project
229+
declaring a version resolves to it inside the project regardless of where the
230+
payload was installed. Provisioning only has to make the payload exist, which
231+
global scope does.
232+
233+
**Two views still disagree about the installed set.** A payload installed into
234+
the registry is not in the project SubOS's `installed[]`, so a shim invoked in
235+
the project can resolve the declared version and still report
236+
`… is not installed in this subos (_)`. That is the line the 2026.9.2.1
237+
verification recorded, and the reason the runner resolves a program through the
238+
declared payload's `bin/` before consulting `PATH`.
239+
240+
That mismatch is real and is not addressed here. It is a question about which
241+
environment mcpp's own `--sysroot` names — the registry SubOS today — and
242+
answering it means changing where mcpp points the compiler, not where it points
243+
an install. That is a larger change than this document, and the runner lookup
244+
is a working compensation for its user-visible half.
245+
246+
**What is not done, and why it is not a gap in this change:** nothing in the
247+
merged table depends on the scope. `deps` and `workspace` are two projections
248+
of one entry either way, and the entry provisions and resolves exactly as it
249+
did before.
276250

277251
## 4. Change three: `envs` is retired
278252

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,54 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.9.3.1] — 2026-09-03
7+
8+
`[xlings]` 收敛成一张表。`[xlings.workspace]` 说出工程用哪个包、用哪个版本,mcpp
9+
既供给它也把它物化成解析用的钉;`deps` 被取代;`envs` 移除;发布出去的描述符第一次
10+
带上安装期的边。
11+
12+
设计与全部实测见
13+
[`.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md`](.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md)
14+
15+
> **一般形态比它自己的简写弱。** `docs/05` §2.13 写着 `[toolchain]` 是编译器那一项
16+
> 的便捷写法、`[xlings.workspace]` 是一般形态;而 `[toolchain]` 会装
17+
> (`resolve_xpkg_path(…, autoInstall=…)`),一般形态什么都不装。两个键说的是同一件
18+
> 事,差别只在 mcpp 拿这句话去做什么 —— 而它们对应的那套机制,书写面只有一个。
19+
20+
### 变更
21+
22+
- **`[xlings.workspace]` 成为唯一的表。** 一条条目产出两个投影:一个安装地址
23+
(`[<ns>:]<target>[@<version>]`,供给用)与一份解析钉(`[<ns>:]<version>`,写进
24+
`.xlings.json``workspace` 对象)。命名空间写在键上或版本上都接受——
25+
`"xim:picolibc-riscv" = "1.8.12"``picolibc-riscv = "xim:1.8.12"` 是同一条,
26+
写在键上必须带引号(TOML 裸键不能含冒号);两半都写且不一致是错误。`""` 表示
27+
「存在即可,版本不限」,这是手写项目文件里已经在用的拼法。平台键取 xlings 自己的
28+
`linux` / `macosx` / `windows` / `default`,`macos` 作为别名保留。
29+
30+
- **`deps` 被取代,但仍然生效并被报告。** 报告里给出该写的那一行。**不拒绝**——拒绝
31+
会落到依赖的 manifest 上,而钉了那个包精确版本的工程改不了它。同一个包在两张表里
32+
给出两个版本则是硬错误:两者按顺序供给而后者赢得钉,接受它等于装一个、解析另一个。
33+
34+
- **`[xlings.envs]` 移除。** 它曾被物化进 `.xlings.json` 而没有任何东西读它:
35+
xlings 里两处 `envs` 分别属于某个程序的 shim 记录和某个 SubOS 的 provider 段,
36+
都不是这个形状;mcpp 自己给程序的运行环境来自 runtime binding。现在这个键是错误
37+
——**一个什么都不做的键,在有东西声称它有效果时更坏**
38+
39+
- **发布的描述符带上 `xpm.<platform>.deps`** 此前 `mcpp emit xpkg` 一个字都不写,
40+
安装期的边只能手写进 `xpkg.lua` —— `riscv-virt-rt` 0.3.0 因此发出去时没有它自己
41+
目标行指名的 C 库。声明按**未解析**的形态保留在 `workspaceByPlatform` 里,因为
42+
描述符每个平台一块,而按本机解析已经丢掉了另外两个。
43+
44+
- **`[xlings]` 的三档继承写进文档。** 不写 `subos` 就是机器的环境加上工程自己的条目;
45+
写了 `subos` 就是隔离,机器那层不参与;工程内执行的 `xlings use` 压过两者。这条
46+
行为一直如此,而 mcpp 此前一字未提。
47+
48+
### 行为变化
49+
50+
- `[xlings.envs]` 从被忽略变成硬错误。索引里没有任何包用过它。
51+
- `[xlings] deps` 仍然生效,但会打印一条指出替代写法的警告;`--strict` 下它是错误。
52+
- 同一个包在 `deps``[xlings.workspace]` 里给出不同版本,现在被拒绝。
53+
654
## [2026.9.2.1] — 2026-09-02
755

856
`[target.<triple>].runner` 对每一个目标生效,启动失败不再无声,`mcpp test` 把跑不起来

docs/05-mcpp-toml.md

Lines changed: 67 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,63 +1866,80 @@ built" means is the same question `--target` answers (docs/08 §7.4).
18661866
Both are warnings, never errors: coverage is release discipline, and the person
18671867
who can judge it is looking at the release, not at this build.
18681868

1869-
### 2.13 `[xlings]`Build Environment
1869+
### 2.13 `[xlings]`the project's environment
18701870

18711871
```toml
1872-
[xlings]
1873-
deps = ["make@4.4", "cmake@3.28", "python@3.13"] # host build-tools to provision
1874-
subos = "dev" # a named per-project sandbox
1875-
1876-
[xlings.workspace] # pin tool versions (general form of [toolchain])
1877-
clang = "20.1.7"
1872+
[xlings.workspace] # what this project's environment contains
1873+
cmake = "3.28"
1874+
picolibc-riscv = "xim:1.8.12" # namespace on the version
1875+
"xim:qemu-user-aarch64" = "7.2.0" # or on the key - quotes required
1876+
code = "" # present; version unconstrained
1877+
llvm = { macosx = "20", default = "22" }
18781878

1879-
[xlings.envs] # env vars applied to the tool environment
1880-
OPENBLAS_NUM_THREADS = "1"
1879+
subos = "dev" # a named, isolated environment
18811880
```
18821881

1883-
Declares the project's **build environment**, provisioned through xlings (which mcpp
1884-
is built on). The subsection names mirror xlings' own `.xlings.json` schema **1:1**, so
1885-
mcpp materializes them verbatim into `<project>/.mcpp/.xlings.json` (no translation
1886-
layer): `deps` (host build-tools), `[xlings.workspace]` (tool→version pins),
1887-
`subos` (a named sandbox), `[xlings.envs]` (env vars). Use it to declare host tools a
1888-
build needs (`make`/`cmake`/`protoc`/…), pin tool versions per project, or set
1889-
build-time env vars — without hand-editing `.xlings.json`. `[toolchain]` (§2.7) remains
1890-
the ergonomic shorthand for the compiler; `[xlings.workspace]` is the general form.
1882+
`[xlings]` is mcpp's surface for **xlings' local project mechanism**: the
1883+
project `.xlings.json` that gives a directory its own environment. The
1884+
subsection names and their meanings are that file's, and mcpp materializes them
1885+
into `<project>/.mcpp/.xlings.json` with no translation layer.
18911886

1892-
**Values per host platform (2026.9.2.1+).** A `deps` entry and a
1893-
`[xlings.workspace]` value may be a table keyed by platform, the form xlings'
1894-
own `.xlings.json` accepts for `workspace`:
1887+
**`[xlings.workspace]` is the one table.** An entry names a package and the
1888+
version this project uses it at. mcpp provisions it — installing it when the
1889+
machine does not have it, mapping it when it does — and materializes it as a
1890+
resolution pin, so the version the project named is the one its tools resolve
1891+
to.
18951892

1896-
```toml
1897-
[xlings]
1898-
deps = ["xim:ninja", { linux = "qemu-user-aarch64" }, { windows = "nasm", default = "yasm" }]
1899-
1900-
[xlings.workspace]
1901-
gcc = { linux = "15.1.0" }
1902-
llvm = { macos = "20", default = "22" }
1903-
```
1904-
1905-
The keys are `linux`, `macos`, `windows` and `default`; `macosx` is accepted as
1906-
xlings' spelling of `macos`. mcpp resolves the table against the host it runs
1907-
on when the manifest is loaded: the host's key wins, `default` is the fallback,
1908-
and a table with neither declares nothing on that host — the entry is absent,
1909-
not empty. An unknown key is an error rather than a dropped entry. The axis is
1910-
the host operating system only; a package that exists for the OS but not for
1911-
the architecture is still a provisioning error on that host.
1912-
1913-
`subos` selects the root project's **local build/run OS environment**. If the
1914-
key is absent, mcpp uses its initialized, release-verified `McppDefault` SubOS;
1915-
`subos = "default"` is an explicit `NamedSubos("default")` selection. There is
1916-
no CLI/environment override and no implicit following of xlings active/current.
1917-
1918-
On Linux the selected environment also fixes the loader/libc contract, so
1919-
`subos = "el8"` and `subos = "trixie"` can coexist and produce separately
1920-
fingerprinted objects. A workspace root overrides member declarations during a
1921-
workspace build. Dependency/member SubOS declarations are non-transitive: a
1922-
library's declaration applies when it is an independent root, not when its
1923-
sources are consumed by another root. A missing named SubOS or missing/
1924-
incompatible runtime contract is an error, never a fallback. See
1925-
docs/08-toolchain-internals.md §2.1.
1893+
#### Writing an entry
1894+
1895+
| Form | Means |
1896+
|---|---|
1897+
| `cmake = "3.28"` | that version |
1898+
| `llvm = "22"` | the highest installed `22.*`; a version prefix resolves |
1899+
| `code = ""` | present, version unconstrained |
1900+
| `picolibc-riscv = "xim:1.8.12"` | from the `xim` index |
1901+
| `"xim:picolibc-riscv" = "1.8.12"` | the same entry, namespace written on the key |
1902+
| `llvm = { macosx = "20", default = "22" }` | per host platform |
1903+
1904+
The namespace may be written on either half. Writing it on the key requires
1905+
**quotes**, because a TOML bare key cannot contain a colon. Writing it on both
1906+
halves with different values is an error, and so is naming one package twice
1907+
under two spellings.
1908+
1909+
Platform keys are xlings' own — `linux`, `macosx`, `windows` — plus `default`;
1910+
`macos` is accepted as an alias. A table with no key for this host and no
1911+
`default` declares nothing here.
1912+
1913+
#### Which version a tool the project did not name resolves to
1914+
1915+
| The project declares | The version comes from |
1916+
|---|---|
1917+
| `[xlings.workspace]`, no `subos` | the machine's environment, with the project's own entries laid over it |
1918+
| `[xlings.workspace]` and `subos = "<name>"` | that environment's own workspace; the machine's does not apply |
1919+
| neither | the machine's environment |
1920+
1921+
The middle row is not an omission. A named environment has its own installed
1922+
set, and carrying the machine's versions into it would name versions that are
1923+
not there. Naming one is how a project asks for isolation; leaving it out is
1924+
how it asks for the machine's environment with its own entries on top.
1925+
1926+
An `xlings use` performed inside the project outranks this table until mcpp
1927+
rewrites the environment, because it is the layer merged last.
1928+
1929+
#### `deps`, superseded
1930+
1931+
`deps = ["xim:qemu-riscv@9.2.4-1"]` is the pre-2026.9.3 spelling of the same
1932+
statement. It is still honoured and is reported once, with the
1933+
`[xlings.workspace]` line to write instead. It is not refused, because a
1934+
refusal would reach a *dependency's* manifest, which a project that pinned an
1935+
exact version of that package cannot edit.
1936+
1937+
#### `envs`, removed
1938+
1939+
`[xlings.envs]` was materialized into `.xlings.json` and read by nothing: a
1940+
program's environment is declared by its own package, and an environment's by
1941+
that environment. The key is now an error naming both. Nothing in the index
1942+
used it.
19261943

19271944
### 2.14 Host tools from a dependency (mcpp 2026.8.5.1+)
19281945

docs/17-the-project-environment.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,26 @@ Declaring it is what puts it there.
6363
the declared environment the default answer; everything else stays reachable
6464
behind it.
6565

66+
### 2.1 Which version pins apply (2026.9.3+)
67+
68+
Naming an environment also changes where a tool's version comes from. A
69+
project's own `[xlings.workspace]` entries always win; what differs is what
70+
they are laid over:
71+
72+
| The project declares | The version of a tool it did not name comes from |
73+
|---|---|
74+
| `[xlings.workspace]`, no `subos` | the machine's environment |
75+
| `[xlings.workspace]` and `subos = "<name>"` | that environment's own workspace; the machine's does not apply |
76+
77+
The second row is what isolation means. A named environment has its own
78+
installed set, and carrying the machine's versions into it would name versions
79+
that are not there — so a project that relied on the machine's tools has to
80+
declare them once it names an environment.
81+
82+
An `xlings use` performed inside the project outranks both, until mcpp rewrites
83+
the environment: it is the layer merged last, and an action a person took
84+
should beat a file.
85+
6686
## 3. What the declaration does not decide
6787

6888
`[xlings] deps` names packages to be present in the environment, and each one's

0 commit comments

Comments
 (0)