Skip to content

Commit bcf02f8

Browse files
xlings: [xlings.workspace] is the one table, deps superseded, envs removed, and the install-time edge published (2026.9.3.1) (#548)
* design: [xlings] converges on one table, and deps is retired deps and workspace state the same thing about a project and differ only in what mcpp does with the statement. The document proposes that workspace become the one table, records the two facts that make the split indefensible — the general form installs nothing while its own documented shorthand installs, and nothing compares the two when both name one package — and carries the deprecation path, the packaging map that does not exist, and the loss the per-platform resolution introduced for it. Design only. Five questions in section 11 are xlings' to answer and are stated rather than assumed. * design: settle what is written, and state the section it is being changed against Section 4 is decided rather than open: the correspondence is one to one in name and in meaning, so the merged table materialises as the file's own workspace object and no deps array is derived. The decision carries a consequence for W2 — mcpp appends the target's C library to that channel itself, as a namespaced reference with a version, so a key that cannot hold a namespace would leave mcpp unable to express its own entry. Section 12 records the section as a whole: field correspondence, the three places mcpp is not a pure mirror, the ownership rule and where the file is written, subos in full, and the two environment channels that are easy to confuse. * design: what the four fields do, read in the xlings source Section 13 answers section 4 and Q1 from the implementation rather than from inference, and corrects two claims this document and docs/05 both made. xlings does not provision from workspace: deps is the only key any install path reads, so the merge belongs in mcpp.toml and the file keeps both fields — one statement materialised into the two places xlings reads its two halves. envs has no reader on either side. Every consumer in the xlings source is either xvm's per-program VData::envs or a SubOS's subos_info provider sections, and neither is the flat object mcpp writes; mcpp's own run environment comes from the runtime binding. The key is documented as having an effect it does not have, and the section proposes deciding its fate before this proposal ships. Also recorded: a named subos drops the global workspace layer entirely, which is a property of subos that nothing in mcpp states today. * design: state the inheritance rule in full, not only the case that surprises The previous wording named the layer a declared subos drops and not the two rows where inheritance is what an author would expect. A project that pins a few tools and declares no subos starts from the global workspace, which is the common shape; a named subos inherits its own environment's workspace instead, because carrying the host's pins into a different installed set would name versions that are not there; and a fork copies its base's map once at creation. Also records that nothing falls back silently: with no layer naming a tool the shim diagnoses rather than choosing, which is the line the 2026.9.2.1 sandbox verification measured. * design: the documentation the rule needs, and a self-review that changes the proposal Section 14 assigns the inheritance rule to docs/05 §2.13 and docs/17 with their Chinese twins, as a table plus the reason the middle row is not an omission, and gives it a criterion so the paragraph is not the only record. Section 15 is the second read against the code. Its first finding changes the proposal: a project-file workspace entry is merged verbatim and the shim looks up a program's own name, so naming a package root pins a node nothing invokes while its programs resolve from the layer underneath. Group expansion happens in cmd_use, which a manifest never goes through. Three ways out are stated; expanding at read time is recommended and is xlings' to accept. Two smaller corrections: 'retired' has always meant the manifest key and never the file field, and section 12.6's claim to touch neither subos nor envs is withdrawn. * design: four questions answered from the source, and 15.1 withdrawn 15.1 claimed a manifest workspace entry cannot pin a package's programs. Both its premises hold and the conclusion does not: the expansion happens in the action mcpp triggers, not in the merge. Installing calls cmd_use, cmd_use resolves the release and writes one entry per member, and with a project config loaded those land in the project's own SubOS layer. What the read did find is one flag. Activation after install happens only when nothing is active yet, so on a machine already holding another version the declared one is installed and not run. The capability already takes useAfterInstall; D8 sends it. W1 and W2 are rewritten against the file format rather than inferred: stored values are always concrete, latest and a version prefix are the input spellings, and every key in three real subos files is a bare target name with no namespace. envs is decided — retired, with the documentation correction shippable on its own. * design: the key space measured, the namespace put where the file puts it, and the migration collapsed to one release Three corrections, all from data rather than inference. A workspace key is an xvm target of any kind. The default SubOS holds binutils beside ar, as and ld at one version, and gcc beside g++ and cc at another: package roots, programs and file assets share one namespace, and a root and its programs agree because cmd_use wrote them as one release. Calling the keys program names was wrong, and writing a package in a manifest is a legitimate entry whose programs receive the same version. The namespace rides the value, not the key: parse_ns_version splits the first colon of a version string and a real entry reads xim:2026.8.30.2. So the form is picolibc-riscv = "xim:1.8.12", which needs no rule mcpp has to invent. The migration is one release. Three manifests declare the key and mcpp declares none, so the deprecation window buys nothing; what does not bend is that deps is refused with a message rather than dropped in silence. * design: the key form checked against the data, and ns:name rejected with its reason W2 asserted where the namespace goes; it now measures. 1635 version-database targets and 546 workspace entries on the development host, and not one key contains a colon. Two reasons ns:name cannot be the key, and neither is convention. A workspace key is looked up by the name a program is invoked as, and nothing is invoked as xim:picolibc-riscv, so the key would be read by nobody. And the namespace qualifies where a version came from rather than what the tool is: mcpp itself holds eight versions under one target, some scoped xim: and some not, which moving the namespace onto the key would split into two tools with one name. A colon in a key is therefore a hard error naming the correct form, not a second accepted spelling. The section 3 example is corrected in the same pass: the invented "*" becomes latest. * design: the authored project file is the reference, not a SubOS state file Two artifacts share the name and this document measured one for the other. The authored file maps a name to a version string or a platform object; d2mcpp/.xlings.json is the published example, and it settles two of the three open items. Version unconstrained is the empty string. "code": "" is already in use, the resolver returns it unchanged, and an empty value reads downstream as claiming no version — which is what deps = ["cmake"] means today. No * and no latest is introduced. The namespace in the key position is undefined rather than forbidden: nothing derives an install address from a workspace entry, because nothing installs from workspace at all, so the question becomes real only when mcpp does. Both candidate forms are stated with what each costs, and the item is marked as a request rather than a finding. The native platform keys are recorded as linux, windows, macosx and default. * design: both namespace positions accepted, and mcpp normalises to the file's own W2 was a request; it is now a decision. An author may put the namespace on the version or on the key, because the two are interconvertible and neither is more natural than the other — one matches how deps spells a package, the other matches what the file already contains. Four rules keep it one fact: mcpp materialises the file's own form only, the install address is assembled from whichever half carried the namespace, stating it twice and differently is a hard error, and the key form needs quotes. The quoting is measured rather than asserted. TOML bare keys are [A-Za-z0-9_-] and mcpp's lexer matches; on the 2026.9.2.1 binary the unquoted key fails at the colon with a parser message that mentions nothing about namespaces, so the documented example carries the quotes. The previous rule making a colon in a key an error is withdrawn. * design: D8 worked through — activation must be obtained, not requested Three facts shape it. xlings auto-activates only when nothing is active for that name in the merged view, so a version active globally makes a project's declaration lose. Activation writes into the project's own SubOS layer, which config.cpp places under the project directory, so forcing it disturbs no other checkout and not the machine. And the two value spellings ask for different things: a version wants activation, an empty value wants presence. The deciding fact is the fourth. When a forced install fails to switch, the installer logs a warning and the call still exits zero, so useAfterInstall gives mcpp a request whose outcome it cannot read — #531 from the other side. use_version returns cmd_use's exit code instead. Hence the shape: one unforced install batch, then one use_version per versioned entry with its exit code checked, nothing extra for unconstrained entries, and the existing stamp covering both. Six criteria, one of which must be seen failing first. * design: D8 restated — the flag was the wrong question, the scope is the right one Correction first: provisioning does not write into the project's layer. It calls with make_xlings_env, which carries no projectDir, so the child runs in global scope against mcpp's registry home. The earlier claim that forcing was safely contained is withdrawn, and three measured facts follow from the real scope, including why the runner needed a payload lookup at all. xlings already implements both halves of the requirement: an existing payload is mapped rather than reinstalled, and install activates whenever nothing is active for that name. Whether mcpp receives that behaviour depends on the environment it asks in, not on a flag. Design P provisions in project scope, where the project's own SubOS is the layer written; with a named SubOS the global layer is not merged, so the declaration is what activates and no forcing is needed. Design G keeps the global scope and forces, which lets two checkouts fight and cannot be verified. P is recommended, and it carries one question that is not mcpp's alone: whether a project environment inherits the machine's. Both branches are stated with what they cost. * design: D8 withdrawn — the merge already does it, and the defect is the provisioning scope merge_workspace_into_ assigns rather than inserts, so the later layer wins: in Anonymous mode the project's declaration beats the machine's, and in Named mode the machine's layer is not merged at all. A project declaring a version therefore resolves to it without anything forcing a switch, and the developer picks the strength by naming a SubOS or not. What is wrong today is one argument. Provisioning calls with make_xlings_env, which carries no projectDir, so the child runs in global scope: the install's records land in the registry's shared workspace while programs invoked from the project resolve through the project's layers. That disagreement is the 'not installed in this subos' line the 2026.9.2.1 verification measured and the reason the runner needed a payload lookup. So the change is make_project_xlings_env, and useAfterInstall is not passed and no use_version call is added. Two consequences are written down rather than left to be discovered: the project SubOS layer outranks the manifest, and an uninstalled declared version already fails with wording that names the project. * design: rewritten as one document, and the scope fix's precondition checked The document had accumulated seven contradictions across the review rounds: the summary described a deprecation path the migration section had replaced with a single release, the open-questions section still named a decision a later section had withdrawn, and the axes referred to phases that no longer exist. Rewritten rather than patched, 851 lines to 493, with section 15 keeping every abandoned position and why it was abandoned. The framing is the one the review arrived at: [xlings] is mcpp's manifest surface for xlings' local project mechanism, and each decision is an answer to what that mechanism already does. Added a precondition the scope fix depends on and that nothing else would have caught: xlings refuses to treat a directory holding a bare subos/ as a project, and a project's SubOS lives one level deeper, so .mcpp qualifies today — measured — but a future bare subos/ under it would make mcpp's own project file invisible everywhere at once. C13 asserts it on an effect, because a skip degrades to today's behaviour and would look like success. * design: the migration corrected against the consumer, and an implementation plan Step 1 of the migration refused deps everywhere. That counts the wrong denominator: the three manifests that declare the key are consumed, and a consumer pins an exact version, so a refusal reaching a dependency's manifest makes riscv-virt-rt@0.6.0 unbuildable for everyone who pinned it while no republished version reaches them. The refusal applies to the root manifest, where the author can act on it; a dependency's is honoured and reported once. Section 16 splits the work into eight tasks with their dependency order, and states what each review axis demands of the implementation rather than of the design. * 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. * manifest: one address parser, and the conflict compared on the whole pin The deps compatibility path derived (namespace, target, version) from an address twice — once to compare against a workspace pin and once to phrase the advisory — which is the shape where two copies of one rule drift. parse_address is the inverse of XlingsEntry::address, and both sites read it. The comparison also widened. It tested versions alone, so 'other:foo@1.0' beside 'foo = "xim:1.0"' passed as agreement: two packages sharing a name at one version. Comparing the pin covers both halves. * design: state where the advisory does not reach It rides schemaWarnings, which prepare prints for the root manifest and escalates under --strict. A dependency's are attached and not printed, which is a pre-existing gap: surfacing them would also surface every unrelated warning those manifests carry, and that is a change of its own. So the package's author is told the moment they build it, and a consumer of an unmigrated package is not — stated here rather than left to be discovered. * design: the ecosystem migration waits, and the reason is a silent regression Republishing the three packages with the new table before their consumers move would stop provisioning on every older engine, silently: an older mcpp parses [xlings.workspace] and provisions nothing from it. That is worse than the advisory it would silence. This cycle verifies the three packages on the new engine instead; the republish waits for a floor that makes the older engine refuse rather than degrade. * docs(13): the board-support example uses the one table The chapter taught [xlings] deps as the way a board package declares its emulator. The declaration is the same statement; only the table changed, and the older spelling still works and says so. * docs+publisher: the subos example was nested under the wrong table, and deps emits no edge by design The 2.13 example put `subos = "dev"` after `[xlings.workspace]`, which in TOML makes it `xlings.workspace.subos` — a key nothing reads. Two blocks, each under the table it belongs to. The emitter gains the reason it skips a package still on `deps`: that key is host-resolved at load, so the per-platform declaration a descriptor needs is already gone, and writing the host's answer into all three blocks would be the machine-dependent descriptor the function exists to avoid. * tests: the fixtures that used the removed key, and the one assertion only macOS could fail Three CI-only failures, all from this change and each in a fixture rather than in the engine. e2e 88 and 205 declared [xlings.envs], which is now refused. 205 drops it; 88 asserted the key was materialized and now asserts it is not, and gains two criteria the merge introduced: the same statement in both tables collapses to one entry, and the superseded key is reported with the line to write. The manifest parser gained that collapse. deps and workspace agreeing about one package is not an error and is not two entries — appending it twice would ask xlings to install one package twice. Manifest.XlingsWorkspaceAcceptsPerPlatformValues compared host_platform_key() against "macos", and this change made that key xlings' own spelling, "macosx". The assertion was written in terms of the function precisely so it would run on every host, and it was the one line in the file that could only fail on a macOS runner. It did. * examples: the project-subos example writes the one table It declared its emulator with [xlings] deps, which now reports itself. The address the new form assembles is byte-identical — measured with auto-install off, which prints what the pass was going to ask for: 'xlings install xim:qemu-riscv@9.2.4-1' either way. * design: what implementation found that the design did not Four items, three visible only once the code ran: a nested std::map in an exported module truncating the BMI and reporting it in an unrelated file; the removed key's first casualties being this repository's own fixtures; one assertion that could only fail on macOS doing so; and the same statement in both tables collapsing rather than installing one package twice. * platform: one macos/macosx spelling rule, and the manifest asks it Two names for one platform: mcpp's triple vocabulary says macos, an xpkg descriptor and xlings' project file say macosx. The manifest parser had a second copy of that fold and a second copy of the host key, both hand-rolled beside a rule modules/platform already had. mcpp.platform.axis gains xpkg_platform_key_for, which is now the only place that knows, and TargetPlatform::for_os is written in terms of it rather than repeating the table. The manifest reads it for both directions — canonicalising a written key and folding a requested platform — and takes the host from xpkg_platform instead of its own #if. Both spellings are therefore accepted wherever a platform is named, and a new unit test asserts that the two write the same declaration on every host by resolving explicitly rather than against the one it runs on. * design: the macos alias is one rule, and mcpp already had it W3 said mcpp accepts the alias; it did not say where the fold lives. The implementation lifted it into mcpp::platform::xpkg_platform_key_for, which TargetPlatform::for_os and the manifest parser both call, and takes the host key from xpkg_platform rather than a second #if — an earlier draft hand-rolled both, which would have been a third copy of a two-line table. --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 4d99864 commit bcf02f8

22 files changed

Lines changed: 1469 additions & 192 deletions

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

Lines changed: 614 additions & 0 deletions
Large diffs are not rendered by default.

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: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1866,63 +1866,85 @@ 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
1870+
1871+
```toml
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" }
1878+
```
18701879

18711880
```toml
18721881
[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
1882+
subos = "dev" # a named, isolated environment
1883+
```
18751884

1876-
[xlings.workspace] # pin tool versions (general form of [toolchain])
1877-
clang = "20.1.7"
1885+
`[xlings]` is mcpp's surface for **xlings' local project mechanism**: the
1886+
project `.xlings.json` that gives a directory its own environment. The
1887+
subsection names and their meanings are that file's, and mcpp materializes them
1888+
into `<project>/.mcpp/.xlings.json` with no translation layer.
18781889

1879-
[xlings.envs] # env vars applied to the tool environment
1880-
OPENBLAS_NUM_THREADS = "1"
1881-
```
1890+
**`[xlings.workspace]` is the one table.** An entry names a package and the
1891+
version this project uses it at. mcpp provisions it — installing it when the
1892+
machine does not have it, mapping it when it does — and materializes it as a
1893+
resolution pin, so the version the project named is the one its tools resolve
1894+
to.
1895+
1896+
#### Writing an entry
1897+
1898+
| Form | Means |
1899+
|---|---|
1900+
| `cmake = "3.28"` | that version |
1901+
| `llvm = "22"` | the highest installed `22.*`; a version prefix resolves |
1902+
| `code = ""` | present, version unconstrained |
1903+
| `picolibc-riscv = "xim:1.8.12"` | from the `xim` index |
1904+
| `"xim:picolibc-riscv" = "1.8.12"` | the same entry, namespace written on the key |
1905+
| `llvm = { macosx = "20", default = "22" }` | per host platform |
1906+
1907+
The namespace may be written on either half. Writing it on the key requires
1908+
**quotes**, because a TOML bare key cannot contain a colon. Writing it on both
1909+
halves with different values is an error, and so is naming one package twice
1910+
under two spellings.
1911+
1912+
Platform keys are xlings' own — `linux`, `macosx`, `windows` — plus `default`.
1913+
`macos` and `macosx` are the same platform written in two vocabularies (mcpp's
1914+
triples say one, descriptors and xlings' project file say the other) and both
1915+
are accepted wherever a platform is named. A table with no key for this host
1916+
and no `default` declares nothing here.
1917+
1918+
#### Which version a tool the project did not name resolves to
1919+
1920+
| The project declares | The version comes from |
1921+
|---|---|
1922+
| `[xlings.workspace]`, no `subos` | the machine's environment, with the project's own entries laid over it |
1923+
| `[xlings.workspace]` and `subos = "<name>"` | that environment's own workspace; the machine's does not apply |
1924+
| neither | the machine's environment |
18821925

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.
1926+
The middle row is not an omission. A named environment has its own installed
1927+
set, and carrying the machine's versions into it would name versions that are
1928+
not there. Naming one is how a project asks for isolation; leaving it out is
1929+
how it asks for the machine's environment with its own entries on top.
18911930

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`:
1931+
An `xlings use` performed inside the project outranks this table until mcpp
1932+
rewrites the environment, because it is the layer merged last.
18951933

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.
1934+
#### `deps`, superseded
1935+
1936+
`deps = ["xim:qemu-riscv@9.2.4-1"]` is the pre-2026.9.3 spelling of the same
1937+
statement. It is still honoured and is reported once, with the
1938+
`[xlings.workspace]` line to write instead. It is not refused, because a
1939+
refusal would reach a *dependency's* manifest, which a project that pinned an
1940+
exact version of that package cannot edit.
1941+
1942+
#### `envs`, removed
1943+
1944+
`[xlings.envs]` was materialized into `.xlings.json` and read by nothing: a
1945+
program's environment is declared by its own package, and an environment's by
1946+
that environment. The key is now an error naming both. Nothing in the index
1947+
used it.
19261948

19271949
### 2.14 Host tools from a dependency (mcpp 2026.8.5.1+)
19281950

docs/13-baremetal.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -560,11 +560,12 @@ for hosted targets, the `--no-runner` escape and the not-run reporting of
560560
## Writing a board-support package
561561

562562
A board-support package is an ordinary mcpp package. It declares the emulator
563-
it needs under `[xlings] deps`, exports one C++ module for consumers, and emits
564-
its board facts from `build.mcpp`.
563+
it needs under `[xlings.workspace]`, exports one C++ module for consumers, and
564+
emits its board facts from `build.mcpp`.
565565

566-
**A declaration under `[xlings] deps` provisions the package on the first
567-
build** (since 2026.8.29). It is also what lets `mcpp::xpkg_dir` answer *"where
566+
**A declaration there provisions the package on the first build** (since
567+
2026.8.29; the table is `[xlings.workspace]` since 2026.9.3.1, and the older
568+
`[xlings] deps` still works and says so). It is also what lets `mcpp::xpkg_dir` answer *"where
568569
did that package land"*. Both halves matter: the same declaration installs the
569570
emulator and tells the build program where it went.
570571

@@ -669,8 +670,8 @@ int main() {
669670
The package's manifest declares the emulator and nothing else:
670671

671672
```toml
672-
[xlings]
673-
deps = ["xim:qemu-riscv@9.2.4-1"]
673+
[xlings.workspace]
674+
qemu-riscv = "xim:9.2.4-1"
674675
```
675676

676677
Linking `clang_rt.builtins` is not optional on this board. picolibc formats

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

docs/zh/05-mcpp-toml.md

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,54 +1591,74 @@ platforms = ["linux", "macos", "windows"]
15911591
两者都只是 warning,绝不报错:覆盖度属于发布纪律,而能作判断的人看的是发布,
15921592
不是这一次构建。
15931593

1594-
### 2.13 `[xlings]` — 构建环境
1594+
### 2.13 `[xlings]` — 工程的环境
1595+
1596+
```toml
1597+
[xlings.workspace] # 这个工程的环境里有什么
1598+
cmake = "3.28"
1599+
picolibc-riscv = "xim:1.8.12" # 命名空间写在版本上
1600+
"xim:qemu-user-aarch64" = "7.2.0" # 或写在键上 —— 必须带引号
1601+
code = "" # 存在即可,版本不限
1602+
llvm = { macosx = "20", default = "22" }
1603+
```
15951604

15961605
```toml
15971606
[xlings]
1598-
deps = ["make@4.4", "cmake@3.28", "python@3.13"] # 要供给的 host 构建工具
1599-
subos = "dev" # 命名的项目级沙箱
1607+
subos = "dev" # 指名的隔离环境
1608+
```
16001609

1601-
[xlings.workspace] # 固定工具版本([toolchain] 的通用形式)
1602-
clang = "20.1.7"
1610+
`[xlings]` 是 mcpp 对 **xlings local project 机制**的书写面:让一个目录拥有自己
1611+
环境的那份项目 `.xlings.json`。子段名与含义都是那份文件的,mcpp 原样物化进
1612+
`<project>/.mcpp/.xlings.json`,没有翻译层。
16031613

1604-
[xlings.envs] # 应用到工具环境的环境变量
1605-
OPENBLAS_NUM_THREADS = "1"
1606-
```
1614+
**`[xlings.workspace]` 是唯一的表。** 一条条目写出工程用哪个包、用哪个版本。
1615+
mcpp 既供给它——机器上没有就装,有就映射——也把它物化成解析用的钉,于是工程写下
1616+
的版本就是它的工具解析到的版本。
16071617

1608-
声明项目的**构建环境**,经 xlings(mcpp 的底座)供给。子段名与 xlings 自身的
1609-
`.xlings.json` schema **1:1** 对齐,因此 mcpp 把它们**原样**物化进
1610-
`<项目>/.mcpp/.xlings.json`(无翻译层):`deps`(host 构建工具)、`[xlings.workspace]`
1611-
(工具→版本固定)、`subos`(命名沙箱)、`[xlings.envs]`(环境变量)。用它声明构建所需的
1612-
host 工具(`make`/`cmake`/`protoc`…)、按项目固定工具版本、或设构建期环境变量——无需手改
1613-
`.xlings.json``[toolchain]`(§2.7)仍是编译器的便捷简写;`[xlings.workspace]` 是其通用形式。
1618+
#### 条目的形式
16141619

1615-
**按宿主平台取值(2026.9.2.1+)。** `deps` 的一个条目与 `[xlings.workspace]` 的一个值可以是
1616-
按平台为键的表,即 xlings 自身 `.xlings.json``workspace` 接受的形式:
1620+
| 形式 | 含义 |
1621+
|---|---|
1622+
| `cmake = "3.28"` | 该版本 |
1623+
| `llvm = "22"` | 已装的最高 `22.*`;版本前缀会被解析 |
1624+
| `code = ""` | 存在即可,版本不限 |
1625+
| `picolibc-riscv = "xim:1.8.12"` | 来自 `xim` 索引 |
1626+
| `"xim:picolibc-riscv" = "1.8.12"` | 同一条,命名空间写在键上 |
1627+
| `llvm = { macosx = "20", default = "22" }` | 按宿主平台 |
16171628

1618-
```toml
1619-
[xlings]
1620-
deps = ["xim:ninja", { linux = "qemu-user-aarch64" }, { windows = "nasm", default = "yasm" }]
1629+
命名空间写在哪一半都可以。写在键上**必须带引号**,因为 TOML 的裸键不能含冒号。
1630+
两半都写且不一致是错误;同一个包用两种拼法出现两次也是错误。
16211631

1622-
[xlings.workspace]
1623-
gcc = { linux = "15.1.0" }
1624-
llvm = { macos = "20", default = "22" }
1625-
```
1632+
平台键是 xlings 自己的 —— `linux``macosx``windows`,外加 `default``macos`
1633+
`macosx` 是同一个平台的两套词汇(mcpp 的三元组说前者,描述符与 xlings 的项目
1634+
文件说后者),**凡是点名平台的地方两者都接受**。表里既没有本机这一项也没有
1635+
`default`,就表示在这里什么都不声明。
1636+
1637+
#### 工程没点名的工具,其版本的来源
1638+
1639+
| 工程声明了 | 版本来自 |
1640+
|---|---|
1641+
| `[xlings.workspace]`,无 `subos` | 机器的环境,工程自己的条目叠在上面 |
1642+
| `[xlings.workspace]``subos = "<名>"` | 那个环境自己的 workspace;机器的不适用 |
1643+
| 两者都没有 | 机器的环境 |
1644+
1645+
中间那行不是遗漏。指名的环境有自己的已安装集合,把机器的版本带进去会指向那里不
1646+
存在的版本。**写 subos 就是要隔离,不写就是要机器的环境加上自己的条目。**
1647+
1648+
在工程内执行的 `xlings use` 压过这张表,直到 mcpp 重写环境为止——它是最后合并的
1649+
那一层。
1650+
1651+
#### `deps`,已被取代
16261652

1627-
键为 `linux``macos``windows``default`;`macosx` 作为 xlings 对 `macos` 的拼写也被
1628-
接受。mcpp 在加载清单时按运行它的宿主解析这张表:宿主对应的键优先,`default` 兜底,两者
1629-
都没有时该条目在本宿主上不作声明 —— 是缺席,不是空值。未知的键是错误,不是被丢弃的条目。
1630-
这条轴只到宿主操作系统:一个包存在于该 OS 但不存在于该架构时,在那台宿主上仍是
1631-
provisioning 错误。
1653+
`deps = ["xim:qemu-riscv@9.2.4-1"]` 是同一句话在 2026.9.3 之前的拼法。它仍然生效,
1654+
并且会被报告一次,同时给出该写的 `[xlings.workspace]` 那一行。**不拒绝**——拒绝会
1655+
落到**依赖**的 manifest 上,而钉了那个包精确版本的工程改不了它。
16321656

1633-
`subos` 选择根项目用于 build/run 的**本地开发 OS 环境**。未声明该键时固定使用 mcpp 已初始化、
1634-
经 release 验证的 `McppDefault`;`subos = "default"` 则仍是显式的
1635-
`NamedSubos("default")`。没有 CLI/环境变量 override,也不会隐式跟随 xlings active/current。
1657+
#### `envs`,已移除
16361658

1637-
在 Linux 上,所选环境同时固定 loader/libc contract,所以 `el8``trixie` 可在同一机器共存,
1638-
并进入不同构建指纹。workspace 整体构建时由 workspace root 覆盖 member 声明;member/依赖中的
1639-
SubOS 不传递——库只有作为独立 root 开发时才使用自己的声明,作为别人的源码依赖时使用消费者
1640-
root 的环境。指定的命名 SubOS 不存在、缺少或使用不兼容 runtime contract 都会直接报错,不会
1641-
回退 default/active/编译器烙入状态。参见 docs/08-toolchain-internals.md §2.1。
1659+
`[xlings.envs]` 曾被物化进 `.xlings.json`,而没有任何东西读它:程序的环境由它自己
1660+
的包声明,环境的环境由那个环境声明。现在这个键是错误,并同时点名这两者。索引里没有
1661+
任何包用过它。
16421662

16431663
### 2.14 依赖产出的 host 工具(mcpp 2026.8.5.1+)
16441664

0 commit comments

Comments
 (0)