From 595f192de5ddf5bb7c9d3dc99d6693258fe87201 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:51:09 +0800 Subject: [PATCH 01/36] docs: publish the three-tree architecture at the entry, and close the citation leaks The division of labour between user documentation, specifications and design records existed as a table in docs/specs/README.md -- two levels down, in the tree with the narrowest audience. docs/README.md, where a reader arrives, did not mention it. Nothing enforced the division because nothing published it. docs/README.md and docs/zh/README.md now state what each tree holds, index the chapters by part rather than as one numbered run, and carry a role index that maps an intention to chapters, examples and templates. Parts VI and VII separate the chapters written for tool authors and for maintainers from the chapters written for users; the numbers do not move, because docs/NN- is cited from 103 files including source and CI. Two of the four specifications were absent from the front page. All four are listed now. Nine user chapters sent a reader to `.agents/docs/...`. A design record describes a moment and carries no stability promise, so a chapter that ends in one has delegated a question it should have answered. Each pointer is removed; docs/20's "Not implemented" section becomes "Current limitations", a list of facts rather than the argument for each gap, and gains the two device extensions that no published rule package claims. Six code comments named chapters that do not exist -- survivors of an earlier numbering. `docs/35-pack-design.md`, `docs/04-schema-xpkg-extension.md`, `docs/34-release-readiness.md`, `docs/11-gcc15-cookbook.md`, `docs/14-data-layout.md`, `docs/21-toolchain-and-tools.md` and `docs/27-p1689-dyndep.md` are repointed or dropped. Every `docs/NN-*.md` path named outside `.agents/` now resolves. The style skill states the architecture this follows: which tree a document belongs to, that docs/ is a usage manual for what is implemented rather than a design account, the citation direction, the gradient, the coverage a surface owes, and the review criteria. --- ...-documentation-architecture-three-trees.md | 456 +++++++++++++++ ...mples-curriculum-and-documentation-plan.md | 520 ++++++++++++++++++ .agents/skills/mcpp-docs-style/SKILL.md | 269 +++++++-- CHANGELOG.md | 2 +- docs/05-mcpp-toml.md | 2 - docs/08-toolchain-internals.md | 5 +- docs/11-machine-output.md | 3 - docs/20-heterogeneous-builds.md | 34 +- docs/README.md | 84 ++- docs/zh/05-mcpp-toml.md | 2 - docs/zh/08-toolchain-internals.md | 2 - docs/zh/11-machine-output.md | 2 - docs/zh/20-heterogeneous-builds.md | 22 +- docs/zh/README.md | 75 ++- modules/manifest/src/toml.cppm | 2 +- modules/manifest/src/types.cppm | 6 +- src/config.cppm | 2 +- src/modgraph/p1689.cppm | 2 +- src/pack/pack.cppm | 2 +- src/pm/publisher.cppm | 2 +- src/publish/pipeline.cppm | 2 +- src/toolchain/stdmod.cppm | 2 +- 22 files changed, 1387 insertions(+), 111 deletions(-) create mode 100644 .agents/docs/2026-09-08-documentation-architecture-three-trees.md create mode 100644 .agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md diff --git a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md new file mode 100644 index 00000000..5889f608 --- /dev/null +++ b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md @@ -0,0 +1,456 @@ +# Three documentation trees, three audiences, and the rule for citing between them + +This restructures mcpp's documentation as a whole: the user documentation +(`docs/`), the specifications (`docs/specs/`), and the design records +(`.agents/docs/`). Each serves a different reader, so each gets its own +admission criterion, register, language policy, stability promise and +lifecycle — and the relation between them becomes a rule that a check can +enforce rather than a habit. + +It supersedes the documentation half of +`.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md`. That +plan's example curriculum stands and is referenced here rather than restated; +its chapter-level proposals (splitting `05`, adding chapters 22 and 23) are +carried in unchanged and placed inside the architecture this document defines. + +Measured 2026-09-08 against `origin/main` at `6e1c65c6`. + +--- + +## 1. The measurement + +### 1.1 The three trees + +| tree | files | lines | index | +|---|---|---|---| +| `docs/` chapters (English) | 23 | 11,313 | `docs/README.md`, 35 lines | +| `docs/zh/` | 23 | — | `docs/zh/README.md` | +| `docs/specs/` | 5 | 1,156 | `docs/specs/README.md`, 42 lines | +| `.agents/docs/` | **268** | **109,146** | `.agents/docs/README.md`, **one heading, no body** | +| `.agents/skills/` | 4 | 997 | none | + +The design records are **8.8 times** the entire user-facing documentation +(11,313 + 1,156 lines), and their whole index is the single line +`# 开发/方案文档目录`. + +### 1.2 Seven defects, each independently verifiable + +1. **The architecture is stated exactly once, in a leaf.** The three-way + division of labour — user docs / specs / design docs, with an audience named + for each — exists as a table in `docs/specs/README.md`. `docs/README.md`, + which is where a reader arrives, does not mention `.agents/docs` at all. + +2. **The specifications are written in Chinese, inside the English tree, with no + `docs/zh/` counterpart.** Every chapter under `docs/` has a `docs/zh/` mirror + whose heading structure CI compares. `docs/specs/` has neither: it is + Chinese-primary and unmirrored. + +3. **The style checker's scope is a glob, and the glob is why.** + `.github/tools/check_docs_style.sh` iterates `docs/*.md docs/zh/*.md`. That + pattern does not descend, so `docs/specs/` is exempt from the register rules + and from the parity loop — by accident rather than by decision. Defect 2 is + the visible consequence. + +4. **`docs/README.md` lists two of four specifications.** SPEC-002 + (`target-side.md`) and SPEC-003 (`exit-codes.md`) exist, are indexed in + `docs/specs/README.md`, and are absent from the tree's front page. + +5. **User documentation cites design records.** Five chapters send a reader to + `.agents/docs/…` (`05`, `08` twice, `11`, `20`), and four `docs/zh/` chapters + do the same. A design record carries no stability promise and describes a + moment; a user chapter that ends in one has delegated a question it should + have answered. + +6. **Six code comments cite chapters that do not exist.** + `docs/35-pack-design.md`, `docs/04-schema-xpkg-extension.md` and + `docs/34-release-readiness.md` are named from `modules/manifest/src/`, + `src/pack/`, `src/pm/` and `src/publish/` — survivors of an earlier numbering. + Nothing checks that a cited document exists. + +7. **The design tree has no taxonomy and no status.** Filenames carry an + implicit one — 92 contain `design`, 60 `plan`, 20 `analysis`, 7 `review`, + and **65 contain none of the sixteen classifier words in use** — and nothing + records whether a record is being executed, has shipped, was superseded, or + was abandoned. `2026-09-05-heterogeneous-build-ecosystem-design-v2.md` and + `2026-09-06-ecosystem-plan-v3.md` encode that in the filename, which works + for the two documents whose authors thought of it. + +### 1.3 The renumbering cost, which overturns yesterday's recommendation + +| cited as | files citing | where | +|---|---|---| +| `docs/NN-…` | **103** | `.agents/`, `docs/`, `src/`, `modules/` | +| `examples/NN-…` | **59** | `.agents/` 17, `docs/` 18, `examples/` 9, `tests/` 7, `.github/` 4, `src/` 3 | + +`docs/05-mcpp-toml` alone is cited 90 times. + +Yesterday's plan proposed renumbering five example directories, estimating the +cost as "external links". The measurement says otherwise: the example numbers +are cited from **seven test files, four CI workflows and three source files**, +where a stale path is a broken job rather than a broken link. Both trees are +therefore treated the same way in §6: **numbers are stable; grouping happens in +the index.** + +--- + +## 2. Diagnosis + +### 2.1 The division of labour is right and is unreachable + +`docs/specs/README.md` already says what the three trees are for, and its table +is correct. The defect is placement: it sits two levels down, in the tree whose +audience is the narrowest of the three, in a language the enclosing tree does +not use. A contributor who wants to know where a document belongs will not find +it, so documents land where the last similar one landed. + +That is the whole mechanism behind defects 1, 5 and 7. Nothing enforced the +division because nothing published it. + +### 2.2 `docs/` already contains three audiences + +The numbered sequence reads as one audience and is not: + +| chapters | audience | +|---|---| +| 00, 01, 02, 03, 05, 06, 07, 10, 12–18, 20, 21 | people using mcpp | +| 08 (toolchain internals), 11 (machine output) | people writing against a mechanism | +| 04 (build from source), 09 (releasing mcpp) | people changing mcpp | + +`09-release.md` documents how a maintainer cuts a release. It is chapter nine of +a sequence whose first three chapters are hello-world, dependencies and +packaging. Nothing marks the transition. + +### 2.3 Citation direction is a habit + +Four directions are in use and only one of them is examined: + +- spec → design record, in a metadata row: **correct**, and it is the + convention `docs/specs/README.md` already prescribes. +- user chapter → spec: **correct** and underused. +- user chapter → design record: **wrong**, and present five times. +- code comment → chapter: **unchecked**, and wrong six times. + +--- + +## 3. The design: three classes + +Each class is defined by its reader. Everything else — register, language, +stability, lifecycle — follows from that and is stated so the answer to "where +does this belong" is mechanical. + +| | **用户文档** `docs/` | **规范** `docs/specs/` | **设计记录** `.agents/docs/` | +|---|---|---|---| +| **reader** | someone with a task in hand | someone implementing against the mechanism: index authors, downstream tools, contributors | whoever works on that change, and whoever later asks why it is like this | +| **question it answers** | how do I do X | what exactly is guaranteed, and is it implemented yet | why is it this way, and what was refuted | +| **emphasis** | completing the task; the shortest correct path | precision; every rule tagged with implementation status | the reasoning and the measurements, including the ones that overturned the plan | +| **register** | declarative reference; tutorials may address the reader | RFC 2119 (必须 / 应当 / 可以) | narrative permitted; "why" is the content | +| **language** | English + `docs/zh/` parity, CI-checked | **decision in §6.2** | the language of the round; new writing in English academic register | +| **stability** | additive; a spelling is kept as an alias | numbered, versioned, state machine | **immutable once the change lands** | +| **lifecycle** | kept current with the implementation | Draft → Review → Accepted → Superseded | active → landed → superseded / abandoned | +| **history** | none — a chapter describes today | a change record at the end | it *is* history | +| **admission criterion** | a reader with this task cannot finish without it | two independent implementations could disagree without it | a decision was made whose reasoning would otherwise be lost | +| **who reviews** | anyone who has done the task | whoever owns the mechanism | whoever did the work | + +Three consequences worth naming because they are the ones that get violated: + +**A user chapter never records history.** "This was a bug until 2026.8.16" is a +design record's sentence. A chapter states what is true and, where a version +matters, states the floor: "2026.9.6.5+". + +**A design record is never edited after its change lands** — except to add a +status line or a correction block that says what later measurement overturned. +The alternative is a document that silently becomes a claim about the present, +and the repository has already met the failure that produces: a decision +written a second time without reading the first. + +**A specification is the only tree with a normative voice.** If a user chapter +finds itself writing 必须, the content belongs in a spec and the chapter should +cite it. + +### 3.1 The fourth surface, named so it stops being invisible + +`.agents/skills/` holds four procedure documents for agents: +`mcpp-usage`, `mcpp-contributing`, `mcpp-release`, `mcpp-docs-style`. They are +not a fourth class of documentation; they are **executable procedure** — +ordered steps with criteria, addressed to an agent rather than a reader. + +The rule that keeps them from becoming a fourth copy: **a skill states the +procedure and cites the chapter for the explanation; it does not restate the +explanation.** `mcpp-release` and `docs/09-release.md` are the pair to watch — +the skill is the checklist, the chapter is the reasoning, and the version-number +rules must exist in exactly one of them. + +--- + +## 4. The citation rule + +Six edges; four allowed, two forbidden. + +``` + docs/ ────────────────▶ docs/specs/ allowed (cite for exact semantics) + docs/ ─ ─ ─ ─ ─ ─ ─ ▶ .agents/docs/ FORBIDDEN +docs/specs/ ──────────────▶ .agents/docs/ allowed, metadata row only (provenance) +docs/specs/ ──────────────▶ docs/ allowed (point at the how-to) +.agents/docs/ ────────────▶ anything allowed + code ──────────────▶ docs/ or docs/specs/ allowed, and the target must exist +``` + +**Why the forbidden edge is forbidden.** A design record describes a moment and +carries no stability promise. Sending a user into one means either the chapter +is incomplete, or the record holds something that has become normative. Both +have a fix, and neither is a link: + +> When a user chapter wants to cite a design record, the content is **promoted** +> — into the chapter if it is how-to, into a spec if it is a guarantee. The +> design record is then cited by the spec's metadata row, where provenance +> belongs. + +The five existing leak sites are the promotion worklist, and each names what it +would promote: schema ownership (→ SPEC-004), toolchain naming and the hermetic +link model (→ `08` or a spec), the machine-output protocol's design (→ `11`, +which already has SPEC-003 beside it), the heterogeneous design v2 (→ `20`). + +**Code comments cite documents, and the document must exist.** Six do not +today. The check is one line and is listed in §7. + +--- + +## 5. What changes in `docs/` + +### 5.1 Numbers stay; the index groups + +Renumbering is refused on the measurement in §1.3. `docs/README.md` becomes a +grouped index over the numbers that exist, and gains the two new chapters at the +next free numbers. + +| part | chapters | reader | +|---|---|---| +| **I — Using mcpp** | 00, 01, 05, 06, 07, 21, **22 (new)** | someone building something | +| **II — Shipping what you built** | 02, 10, 12 | someone publishing | +| **III — Toolchains and targets** | 03, 16, 14, 15, 13, 17 | someone whose target is not the host | +| **IV — Devices and accelerators** | 18, 20 | someone with a GPU or a board | +| **V — Extending mcpp from outside** | **23 (new)** | rule-package and index authors | +| **VI — Machine interfaces and compatibility** | 11, 19, SPEC-003 | tool authors, release engineers | +| **VII — Contributing to mcpp itself** | 04, 08, 09 | maintainers | + +Part VII is the change that fixes §2.2: `04`, `08` and `09` keep their numbers +and stop appearing to be step four, step eight and step nine of a user's path. + +### 5.2 The role index sits above the parts + +Before the parts, one table mapping an intention to a path across all three +teaching surfaces — chapters, examples and templates. This is the table +yesterday's plan specified; it is unchanged and belongs here because it is the +entry point for the whole documentation set, not for the examples alone. + +### 5.3 Splitting `05`, and the two new chapters + +Carried unchanged from the earlier plan, restated here as a table only: + +| moved out of `05` | lines | to | +|---|---|---| +| features and capabilities (§2.8–2.8.2) | 394 | **22 — Features and capabilities** (new) | +| `[xlings]`, the project environment (§2.13) | 317 | **17** | +| the C++ runtime contract (§2.3 `cxx_runtime`) | 242 | **03** | +| `[target.*]` conditioning (§2.7.1) | 138 | **14** | +| host tools from a dependency (§2.14) | 274 | **07** | +| `[hooks]` (§2.16) | 192 | **21** | + +`05` lands at about 1,570 lines. **23 — Authoring a rule package** is assembled +from `docs/07` §"Writing a rule package", the `mcpp::action` roles, the island +ladder's four rungs, `device_extensions` / `rule_module`, and the probe and +advisory channels. + +--- + +## 6. What changes in `docs/specs/` + +### 6.1 Four specs, and what is missing from the set + +SPEC-001 identity, SPEC-002 target side, SPEC-003 exit codes, SPEC-004 manifest +semantics. Two are missing from `docs/README.md` (defect 4) — a one-line fix. + +Two candidates for SPEC-005 and SPEC-006 emerge from §4's promotion worklist and +from the accelerator work: + +- **The artifact compatibility tag**, including the `accel` field's grammar, + the coverage relation (family targets, portable-form floors) and the matching + algorithm. It is normative, it has a second implementer today (`mcpp-index` + descriptors are written by hand against it), and it currently lives in + `docs/20` §"What a prebuilt artifact states" — a user chapter writing rules. +- **The `build.mcpp` directive protocol**, whose version number already + functions as a normative contract (`kProtocolVersion`; an engine refuses a + program declaring a higher one) and whose only description is a section of + `docs/07`. + +Both are promotions of existing text rather than new writing, and both are +listed as staged work rather than decided here. + +### 6.2 The language decision + +Today: Chinese, unmirrored, unchecked. Three options. + +| | cost | consequence | +|---|---|---| +| **(a) English primary + `docs/zh/specs/` mirror** | translate 1,156 lines once, then parity forever | uniform with the enclosing tree; reachable by the downstream tool authors the specs name as their audience | +| (b) move to a top-level `specs/`, keep Chinese | rewrite paths in ~15 citation sites | admits that specs are not user docs, and abandons the audience that cannot read them | +| (c) English only, no mirror | translate once | breaks the tree's own parity policy in the other direction | + +**Recommendation: (a).** The audience the specs themselves name — index authors +and downstream tooling — is the least likely of the three audiences to be +Chinese-reading, and the specs are the documents where a misreading is most +expensive. + +### 6.3 The metadata contract becomes checkable + +`docs/specs/README.md` already requires a metadata table and a change record in +every spec. Nothing checks it. §7 adds that, and the same check verifies that +every spec in the directory appears in both indexes. + +--- + +## 7. What changes in `.agents/docs/` + +268 files and 110,143 lines are **not rewritten**. Three additions, all of which +apply to new documents and are backfilled only where a reader needs them. + +### 7.1 Front matter, required on new records + +```yaml +--- +subject: heterogeneous # one of a short controlled list +status: landed # active | landed | superseded | abandoned +superseded_by: 2026-09-07-module-first-heterogeneous-surface.md +implements: [docs/20-heterogeneous-builds.md, docs/specs/SPEC-005] +--- +``` + +`status` is what the tree lacks most. A reader opening +`2026-09-05-accelerator-support-design.md` today cannot tell from the document +that `…-v2` and then `ecosystem-plan-v3` moved past it; the filename carries it +for the two authors who thought of the convention. + +### 7.2 A generated index + +`.agents/docs/README.md` is generated: grouped by `subject`, ordered newest +first within each, showing status and title. For the 268 existing records the +title comes from the first heading and the status is `landed` unless the +document says otherwise — a mechanical default that is right for almost all of +them, since they describe changes that shipped. + +The generator is a script, and the index is checked in so that reading the tree +on GitHub works. + +### 7.3 The subject list, and why it is short + +Derived from the filenames rather than invented: `toolchain`, `packaging`, +`resolution`, `target`, `freestanding`, `heterogeneous`, `ecosystem`, +`performance`, `platform`, `docs`, `process`. Eleven. A twelfth is added when a +document does not fit, which is a decision someone makes rather than a field +someone fills in freely — a free-text subject would reproduce the +sixty-five-uncategorised state in a new column. + +### 7.4 Two housekeeping items + +Four records carry no date prefix (`fix-xlings-package-home-detection.md`, +`llvm-install-failure-analysis.md`, `platform-abstraction-plan.md`, +`platform-remaining-ifdefs-report.md`) and `todos/` holds four more under a +directory the convention does not mention. Both are absorbed by the front matter +and the generated index without moving a file. + +--- + +## 8. The checks + +`.github/tools/check_docs_style.sh` keeps its three rules and gains a scope; a +new `check_docs_structure.sh` carries the rules that are about the architecture +rather than the prose. + +| # | rule | reads | +|---|---|---| +| 1 | the existing three: heading register, second person in reference docs, bilingual heading parity | text | +| 2 | **scope extended to `docs/specs/`** | the glob, corrected to descend | +| 3 | no `docs/**` file cites `.agents/` | the forbidden edge in §4 | +| 4 | a spec cites `.agents/` only inside its metadata table | line position | +| 5 | every `docs/…md` path named anywhere in the repository resolves | catches defect 6, in code as well as prose | +| 6 | every spec in `docs/specs/` appears in `docs/README.md` and in `docs/specs/README.md` | catches defect 4 | +| 7 | every spec has a metadata table and a change record | the contract `docs/specs/README.md` already states | +| 8 | a new `.agents/docs/*.md` has front matter with a known `subject` and `status` | new files only, by comparing against the merge base | +| 9 | `.agents/docs/README.md` matches what the generator would produce | the index cannot drift | + +Rule 5 is the one with reach beyond documentation: a comment in `src/` naming a +chapter is a citation, and today six of them are stale. Rule 8 is scoped to new +files on purpose — a rule that demanded front matter on 268 existing records +would be satisfied by a mechanical pass that adds a field nobody chose. + +**What none of them check.** Whether a chapter is *right*, whether a spec's +rules are complete, or whether a design record's reasoning holds. The checks +establish that each document is in the tree its content belongs to and is +reachable from that tree's index; a reader establishes the rest. + +--- + +## 9. Staging + +| stage | content | independent? | +|---|---|---| +| **1** | publish the architecture: the three-way table moves to `docs/README.md`; role index; parts I–VII; the two missing specs listed | yes — index only, no content moves | +| **2** | close defects 4, 5 and 6: promote the five leak sites, fix the six code citations | yes | +| **3** | checks 2–7 (scope, citation direction, resolvable paths, spec index and metadata) | after stage 2, so the tree is already clean when the check turns on | +| **4** | translate the specs, mirror to `docs/zh/specs/`, put them under the parity loop | yes; the largest single translation cost | +| **5** | front matter, subject list, generated index, checks 8–9 | yes | +| **6** | split `05`; add chapters 22 and 23 | after stage 1, which is where their entries in the index go | +| **7** | SPEC-005 (compatibility tag) and SPEC-006 (directive protocol), each a promotion | last; each is its own review | + +Stages 1–3 are a week's worth of work and close five of the seven defects. +Stage 4 is the largest and is separable. Stage 6 is the chapter work carried +from the earlier plan and is where its example work rejoins. + +--- + +## 10. What this deliberately does not do + +- **It does not renumber anything.** §1.3 measured the cost in test and CI + files, not link rot, and this reverses a recommendation made yesterday on an + estimate. +- **It does not rewrite the 268 design records.** Their value is that they were + written when the reasoning was fresh; a pass to normalise them would edit + documents whose defining property is that they are not edited. +- **It does not merge `.agents/skills/` into any tree.** They are procedure, and + §3.1 states the rule that keeps them from duplicating explanation. +- **It does not introduce a documentation site generator.** Every tree stays + readable as Markdown on GitHub, which is where its readers are; a generated + site is a separate decision with its own hosting and staleness questions. +- **It does not change what is *in* a chapter beyond the moves listed.** The + register work landed in #452 and the content is largely correct; this is an + architecture change, not a rewrite. + +--- + +## 11. Open questions for review + +1. **The specs' language (§6.2).** Recommendation is English primary with a + `docs/zh/specs/` mirror, at 1,156 lines of translation. The alternative that + is cheapest — leave them Chinese and exempt them explicitly rather than by + glob accident — is defensible if the specs' real audience is this ecosystem's + own contributors rather than the downstream tool authors they name. + +2. **Whether `08-toolchain-internals` belongs in part VII.** It documents + mechanism for people writing against it as much as for people changing mcpp. + Placing it under "Contributing" may hide it from the first group. + +3. **SPEC-005's scope.** The compatibility tag alone, or the tag plus the + prebuilt-artifact selection algorithm? The algorithm is implemented once, in + `src/pack/prebuilt.cppm`, and has no second implementer today — which is the + admission criterion in §3 arguing against including it. + +4. **Whether `status: landed` is the right default for the backfill.** It is + right for almost all of the 268 and wrong for the handful that were written + and never executed. The alternative is `unknown`, which is honest and makes + the generated index less useful on its first day. + +5. **Whether design records should carry a correction block.** §3 says a record + is immutable except for a status line. Several records in the tree have been + corrected in place by later measurement, which is how their own value was + preserved. Making that a named, dated block at the end — rather than an edit + in the body — would keep both properties. diff --git a/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md b/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md new file mode 100644 index 00000000..22bd8f6c --- /dev/null +++ b/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md @@ -0,0 +1,520 @@ +# A curriculum for the examples, a reference for the documentation, and a check with a denominator + +This plans the `examples/` tree and the `docs/` chapters together, because the +request they answer is one request: that everything about **using** mcpp and +about **developing for** it be reachable by a reader who does not already know +where to look. + +It starts from a measurement rather than from an opinion about what is missing, +and it states a criterion for what earns an example so that the plan has a size +rather than an ambition. + +Measured 2026-09-08 against `origin/main` at `6e1c65c6` (engine 2026.9.8.1), +`mcpp:plugins` 0.4.0, `mcpp-index` and `xim-pkgindex` at their `origin/main`. + +--- + +## 1. The measurement this starts from + +Three surfaces, each with a denominator taken from the tree rather than from a +document. + +### 1.1 The manifest surface + +Denominator: the sections and keys `docs/05-mcpp-toml.md` documents (34 of them +with a distinct meaning). Numerator: whether any of the 21 example projects +contains that key in its own `mcpp.toml`. + +| covered by an example | 21 | +|---|---| +| **zero examples** | **13** | + +The thirteen: `[dev-dependencies]`, `[features]`, `[features.]`, +`[feature-deps.]`, `[feature-xlings.]`, `[scan_overrides]`, +`[profile.]`, `[runtime]`, `[package] platforms`, `[resources]`, +`[hooks]`, `cxx_runtime`, `module_extensions` (with `bmi_schedule`, +`build_program_timeout`, `device_extensions` and `rule_module` beside it). + +`[features]` is the one worth naming on its own. Every heterogeneous example +**consumes** a feature — `features = ["rules-cuda"]` on a dependency edge — and +**no example in the tree declares one**. The mechanism the entire accelerator +design rests on is visible only from the consuming side. + +### 1.2 The `build.mcpp` API + +Denominator: the names `docs/07-build-mcpp.md` documents (38). Numerator: the +names any example's `build.mcpp` calls. + +| used by an example | 10 | +|---|---| +| **zero examples** | **28** | + +Among the twenty-eight: `mcpp::action` (the primitive the whole rule layer is +built on, and the subject of a 136-line section in 07), `mcpp::runner`, +`mcpp::fact` / `mcpp::floor` (the probe channel), `mcpp::warning`, +`mcpp::has_feature`, `mcpp::xpkg_dir`, `mcpp::link_script`, `mcpp::target_os` +and the rest of the target-interrogation family. + +`mcpp::action` is reached indirectly — the rule packages call it, and the +examples call the rules — so a reader sees its effect and never its shape. That +is a defensible outcome for a consumer example and not for a repository that +also asks people to write rule packages. + +### 1.3 The command surface + +Denominator: the commands `print_usage()` in `src/cli.cppm` prints (21 +user-facing; the `dyndep` / `bmi-*` / `stage` family is excluded because, as the +source says, "nobody types it, ninja does"). + +| named in an example's README | 4 (`build`, `run`, `test`, `pack`) | +|---|---| +| **named in no example** | **17** | + +Including the entire library-author path: `new`, `add`, `update`, `search`, +`publish`, `emit xpkg`, `xpkg parse`. And the entire diagnosis path: `why`, +`self doctor`, `self env`, `self explain`, `clean --stale`, `cache`. + +### 1.4 The documentation's own shape + +| | lines | +|---|---| +| `docs/` total (English) | 12,469 | +| `docs/05-mcpp-toml.md` | **3,129 (25%)** | +| `docs/01-examples.md` | 64 | + +The chapter that indexes the curriculum is 64 lines and lists directories. The +chapter that documents the manifest is 3,129 lines and is simultaneously a field +reference, four conceptual essays and a compatibility record. + +### 1.5 What these numbers do not say + +A capability with no example is not automatically a defect. `bmi_schedule` is a +tuning key; an example directory for it would teach nothing a paragraph does not. +The numbers locate the question; §3.2 answers it. + +Two denominators are themselves imperfect and are stated so. The manifest count +is taken from what `docs/05` documents rather than from what the parser accepts, +so a key the parser accepts and the document omits is invisible to it — closing +that is part of §7. The command count excludes internal verbs by judgement, and +the judgement is recorded in the check's own table rather than left in this +paragraph. + +--- + +## 2. The defect the numbers describe + +### 2.1 Two structures, each grown along its own axis + +The examples are indexed by **build shape**: a program, a program with a +dependency, a static package, a workspace, a library, a cross build, a project +environment, a rule package, a device, a rendering pipeline. That ordering was +right when each new capability was a new shape. It broke at 09, where one number +acquired six sub-examples and the number line stopped being a line. + +The documentation is indexed by **mechanism**: the manifest, the build program, +toolchains, the target triple, the target side, devices, heterogeneous builds. +That is the right index for a reference and it is the wrong one for a first read. + +Neither is indexed by **the reader**. Someone who wants to publish a library +must know to read 10, then 02, then 12, then the `emit xpkg` paragraphs of 21, +and must discover that no example covers any of it. + +### 2.2 Three readers, traced + +**The library author.** Wants to publish. There is no example that goes from a +package to a descriptor to a consumer's `mcpp add`. `05-lib-distribution` stops +at the produced artifact; the descriptor half exists only in `docs/10` (160 +lines) and in the index repository's own conventions. + +**The rule-package author.** Wants a new device language, or a new generator. +The material exists: `docs/07` §"Writing a rule package" (115 lines inside a +944-line chapter), `examples/08-build-rules`, and — for anything about device +languages — `mcpp-plugins/README.md`, in a different repository. There is no +chapter, and `device_extensions` and `rule_module`, the two keys that make a new +device language cost no engine release, appear in no example. + +**The reader who wants features.** `docs/05` §2.8 is 130 lines and correct. No +example declares `[features]`, so the reader cannot see one work. + +### 2.3 A third teaching surface that nothing names + +`docs/13-baremetal.md` is 800 lines and its whole first lesson is: + +```bash +mcpp new blinky --template riscv-virt-rt +``` + +The lesson is a **template**, shipped by a package, not a directory under +`examples/`. `--template ocornut.imgui` is another. Any package may ship +`templates//`, and `src/scaffold/template.cppm` enumerates them. + +So the curriculum is not `examples/` alone, and an index listing only +directories is structurally unable to be complete. This is a decision to state, +not a gap to close: a bare-metal lesson belongs to the board package that +supplies the target world, and duplicating it into this repository would make +two copies that drift. What is missing is that **nothing tells a reader the +template surface exists**. + +--- + +## 3. Three decisions + +### 3.1 The examples are a curriculum; the documentation is a reference + +Neither should try to be the other. An example is ordered, runnable, and teaches +exactly one new thing relative to its predecessor. A chapter is complete, +indexed by mechanism, and assumes the reader arrives knowing what they want. + +The consequence for `docs/05`: the conceptual essays inside it are chapters, not +sections of a field reference (§5.2). + +The consequence for `docs/01`: it becomes the curriculum — tracks, order, what +each example is the **first** to teach, and the mapping from an intention to a +path through all three surfaces. + +### 3.2 What earns an example + +> A capability earns an example when it changes the **shape of a project** — the +> files it contains, the manifest it declares, or the commands its author types. +> A capability that is one line inside an existing project earns a code block in +> its chapter. A capability reached only through a command earns a scenario entry +> in `docs/21`. + +Applied to §1's thirteen uncovered manifest keys: + +| key | verdict | +|---|---| +| `[features]`, `[features.]`, `[feature-deps]`, `[dev-dependencies]`, `[profile.]` | **example** — they change what a project declares and what its author builds | +| `device_extensions`, `rule_module` | **example** — they are what a rule package IS | +| `[resources]`, `[runtime]` | **example**, folded into the publishing track rather than given their own | +| `[hooks]`, `scan_overrides`, `cxx_runtime`, `module_extensions`, `bmi_schedule`, `build_program_timeout`, `[package] platforms`, `[feature-xlings]` | **code block** — each is one line in a manifest that otherwise looks like an existing example's | + +Applied to §1's twenty-eight uncovered APIs: `mcpp::action`, `mcpp::runner`, +`mcpp::fact`/`floor`, `mcpp::warning` and `mcpp::has_feature` earn an example +because a rule package is a project shape. The target-interrogation family +(`target_os`, `target_arch`, `toolchain_dir`, `sysroot_dir`, …) earns code +blocks: each is one call inside a program the examples already contain. + +Applied to the seventeen uncovered commands: `new`, `add`, `search`, `publish`, +`emit xpkg` and `xpkg parse` earn an example because they are a **round trip** a +reader must see completed. `why`, `clean`, `cache`, `toolchain`, `self *` earn +scenario entries in `docs/21`, which is the chapter that exists for exactly this +and is 211 lines. + +This criterion is what keeps the plan at **four new examples** rather than +thirteen. + +### 3.3 One index over three surfaces + +Examples, templates and chapters are indexed from one place — the rewritten +`docs/01` — and the coverage check (§7) reads that index. A capability is +covered when the index names where it is taught, and the check verifies the +named place actually teaches it. + +--- + +## 4. The example tree + +### 4.1 Tracks + +The number line is replaced by six tracks. Numbering is retained inside a track +so existing links and muscle memory survive where they can. + +| track | question it answers | +|---|---| +| **A — The shape of a project** | I am writing a program or a library | +| **B — Publishing** | I want other people to use it | +| **C — The environment** | my build needs tools that are not the compiler | +| **D — Targets** | it does not run on the machine that builds it | +| **E — Devices** | part of it runs on a GPU or an accelerator | +| **F — Authoring for the ecosystem** | I am extending mcpp itself, from outside it | + +### 4.2 The tree + +| id | directory | status | the first to teach | +|---|---|---|---| +| A1 | `01-hello` | keep | a package, `import std`, `mcpp build` / `run` | +| A2 | `02-with-deps` | keep | `[dependencies]`, the lock file, `mcpp add` | +| A3 | `04-workspace` | keep | `[workspace]`, path dependencies | +| **A4** | **`03-features`** | **new** | `[features]` **declared**, `[feature-deps]`, `[dev-dependencies]`, `[profile.]`, `mcpp test` | +| B1 | `05-pack-static` | move from `03` | `mcpp pack --mode static`, `[resources]` folded in | +| B2 | `06-lib-distribution` | move from `05` | a library's interface and binaries, `[runtime]` folded in | +| **B3** | **`07-to-the-index`** | **new** | `mcpp emit xpkg` → `mcpp xpkg parse` → a descriptor → a consumer's `mcpp add`; the round trip completed | +| C1 | `08-project-subos` | move from `07` | `[xlings]`, `[xlings.workspace]`, a build program's `PATH` | +| D1 | `09-openkal-cross` | move from `06` | `--target`, one source for four machines | +| D2 | *(template)* | index only | bare metal, via `mcpp new … --template riscv-virt-rt`; owned by the board package | +| **E0** | **`10-heterogeneous/boundary`** | **new** | the island ladder: a consumer importing the **generated** module with no hand-written seam (§4.4) | +| E1 | `10-heterogeneous/cuda` | keep | the seam, a generated boundary, the driver as a fact and a floor | +| E2 | `10-heterogeneous/vulkan` | keep | a shader payload reached as a module | +| E3 | `10-heterogeneous/sycl` | keep | a second compiler with its own standard library | +| E4 | `10-heterogeneous/hip` | keep | the hand-written boundary, as the contrast to E1 | +| E5 | `10-heterogeneous/cann` | keep | a vendor outside the NVIDIA and Khronos lineages | +| E6 | `10-heterogeneous/multi-backend` | keep | several backends in one artifact, chosen at run time | +| E7 | `11-graphics/offscreen` | keep | a rendering pipeline whose result is pixels | +| F1 | `12-build-rules` | move from `08` | `host-module = true`, `mcpp::action` with `role = "check"` | +| **F2** | **`13-a-new-device-language`** | **new** | `device_extensions` + `rule_module`: a third-party rule package teaching mcpp a language the engine has never heard of | + +Four new directories; five renumbered; nothing deleted. + +### 4.3 What each new example is for, and its criterion + +**A4 `03-features`.** A library with an optional backend. Declares +`[features]` with a default set, `[feature-deps]` bringing a dependency in +behind one, `[dev-dependencies]` for its tests, and a `[profile.release]` +override. Its `build.mcpp` calls `mcpp::has_feature`. +*Criterion:* `mcpp build` with no features resolves a graph that names no +package belonging to the optional backend — the criterion `docs/20` already +states for the framework tier, applied to the smallest project that has it. + +**B3 `07-to-the-index`.** Two directories: a library, and a consumer. The +README walks `mcpp emit xpkg` to produce a descriptor, `mcpp xpkg parse +--json` to validate it, a local index registration, and the consumer's `mcpp +add`. *Criterion:* the consumer builds against the descriptor rather than +against a path dependency. This is the one example whose subject is the package +manager rather than the build system, and its absence is why seven commands have +no example. + +**E0 `10-heterogeneous/boundary`.** The ladder's bottom rung. `MCPP_EXPORT_C` +entry points, `mcpp.tools.island` generating the header and the module, and a +`main.cpp` that writes `import app.kernels;` — **no hand-written seam anywhere in +the tree**. Its README states what that costs: a C-shaped interface, and no place +for `cfg(accelerator = …)` to apply. E1 then reads as "and here is why you +usually add one". +*Criterion:* the project contains no `.cppm` other than generated ones, and the +program prints the right numbers. The plugins README claims this arrangement was +measured with GCC 16.1; nothing in either tree runs it, so this example is also +that claim's fixture. + +**F2 `13-a-new-device-language`.** A rule package in the example tree that +declares `device_extensions = [".toy"]` and `rule_module`, and a consumer whose +`[build] sources` names a `.toy` file. The "compiler" is a shell script that +transforms text, exactly as `tests/e2e/607` uses `cat` as a device linker: the +subject is the graph, not a vendor. +*Criterion:* the `.toy` file is compiled and its output joins the link, on an +engine release that has never heard of `.toy`. That is the property `docs/20` +credits the design with, and today its only instance is `rules-slang` in another +repository. + +### 4.4 The island ladder, which this round's discussion surfaced + +`mcpp.tools.island` generates two artefacts — the `extern "C"` header the device +compiler reads, and a module over it whose whole content is +`export using ::name;`. Four rungs exist in the mechanism today: + +| rung | who writes what | the consumer sees | +|---|---|---| +| L0 | nothing but marked entry points | `import app.kernels` — C-shaped | +| L1 | `scan()` generates; the project writes a seam | `import app.saxpy` — designed | +| L2 | `emit()` takes an explicit list; the project writes a seam | as L1 | +| L3 | the project writes header and module by hand | as L1 | + +Three of the four are documented in one paragraph of +`mcpp-plugins/README.md`; `tools-island` appears **zero times** in `docs/`. All +seven current examples are L1 or L3, so a reader cannot see L0 exist. + +The plan therefore does two things and not a third: it adds E0 and it documents +the ladder in the new chapter 23. It does **not** propose generating the seam's +C++ shape — `tools/island.cppm:29-31` states that "which functions, which types, +what happens on failure — is a design decision no generator makes well", and +overturning that is a design question of its own, listed in §10. + +--- + +## 5. The documentation chapters + +### 5.1 An entry layer indexed by the reader + +`docs/README.md` becomes a role index before it is a chapter list. Seven rows, +each naming an ordered path through all three surfaces: + +| I want to | read | run | +|---|---|---| +| write a program | 00, 05 §1 | A1, A2 | +| write a library others import | 10, 05 §2.4 | A3, A4 | +| publish it | 02, 10, 12 | B1, B2, B3 | +| build for another machine | 16, 15, 13 | D1, template `riscv-virt-rt` | +| use a GPU or an accelerator | 20, 18 | E0 → E1 → the rest | +| add a rule, a language or a generator | **23 (new)**, 07 | F1, F2 | +| add a package to the index | 10, `specs/package-identity` | B3 | +| change mcpp itself | 04, 09, 19 | — | + +### 5.2 Splitting `docs/05` + +`05` keeps the field reference and loses the essays, each of which goes to a +chapter that exists and is short: + +| section | lines | destination | +|---|---|---| +| §2.8, §2.8.1, §2.8.2 — features and capabilities | 394 | **new 22 — Features and capabilities** | +| §2.13 `[xlings]` — the project's environment | 317 | **17** (currently 199 lines) | +| §2.3 `cxx_runtime` — the C++ runtime contract | 242 | **03 — Toolchains** | +| §2.7.1 `[target.*]` conditioning | 138 | **14 — The target side** | +| §2.14 host tools from a dependency | 274 | **07 — build.mcpp** (which already has a section) | +| §2.16 `[hooks]` | 192 | **21 — Commands by scenario** | + +`05` lands at about 1,570 lines and becomes readable as what it is. + +### 5.3 Two chapters that must exist + +**22 — Features and capabilities.** Assembled from `05` §2.8; gains A4 as its +worked example. Nothing new is written except the example's walkthrough. + +**23 — Authoring a rule package.** New, and the chapter whose absence §2.2 +traces. It assembles: `docs/07` §"Writing a rule package"; `mcpp::action` roles +and the chained-action shape; `device_extensions` and `rule_module`; the island +ladder (§4.4) with its four rungs; the probe channel (`fact` / `floor`); the +advisory channel (`warning`); and the payload-declaration discipline +(`[feature-xlings]` under a `cfg(accelerator = …)` selector — two gates). It +points at `mcpp-plugins` for the shipped collection rather than restating it, +and it names F1 and F2 as its examples. + +### 5.4 The chapter table afterwards + +00–21 keep their numbers and meanings. 22 and 23 are added. `01` is rewritten as +the curriculum. `README.md` gains the role index. No chapter is removed. + +--- + +## 6. Defects to fix in the same batch + +Found while measuring, each independently verifiable: + +1. `examples/09-heterogeneous/README.md` never mentions that the boundary is + generated in two of its six sub-examples, while `docs/01`'s table does. +2. The same README's prose says "all four" three times and "The four beside it" + once, over a table listing six. +3. All seven heterogeneous and graphics examples pin + `plugins = { version = "0.3.0" }`. 0.4.0 is released and is the version in + which all six rules pass a depfile; a project copied from an example today + does not rebuild when a shader's `#include` changes. +4. `examples/09-heterogeneous/cuda/app/build.mcpp:20` states that "no source in + this project names a generated file", and line 51 of the same file states the + opposite, correctly — `src/cpu/saxpy.cpp:18` includes it. +5. `tools-island` appears zero times in `docs/`; `MCPP_EXPORT_C` appears twice, + both inside one table row. Closed by chapter 23. +6. `.cl` and `.metal` are in the engine's built-in device-extension table and no + published rule package claims either, so both produce a refusal. Either a rule + ships or the refusal says that no package claims the extension. This one is + engine or ecosystem work rather than documentation, and is recorded here + because the documentation currently implies support that does not exist. + +--- + +## 7. The check that keeps this true + +A capability added after this batch must be classified, or the build fails. + +**What it reads.** Three denominators, from the tree: + +- manifest keys, from the parse sites in `modules/manifest/src/` — not from + `docs/05`, which is what makes the check able to catch a key the document + omits; +- the `build.mcpp` API, from the exported names in `modules/buildmcpp/src/`; +- commands, from the `print_usage()` body in `src/cli.cppm`. + +**What it compares them against.** One checked-in table, `docs/coverage.toml`, +with exactly one row per capability and exactly one of three verdicts: + +```toml +[manifest."[features]"] +example = "examples/03-features" + +[manifest.bmi_schedule] +doc = "docs/05-mcpp-toml.md#build-concurrency" +reason = "one key in a manifest that otherwise looks like 01-hello" + +[api."mcpp::action"] +example = "examples/12-build-rules" +``` + +**What it refuses.** + +1. A capability in the tree with no row — a new key cannot ship unclassified. +2. A row naming an `example` whose project does not actually contain the + capability. For a manifest key the check parses that example's `mcpp.toml` + and asserts the key is present; for an API name it asserts the call appears + in that example's `build.mcpp`; for a command it asserts the README's command + block contains it. +3. A row naming a `doc` anchor that does not resolve. + +**Why the second refusal is written that way.** The obvious check — grep the +README for the key's name — passes on a document that merely mentions the key, +and fails the day someone rewords a heading. This repository has paid for that +shape before: a criterion that greps prose when it means to ask about state. The +check therefore reads the example's **manifest and sources**, and the prose is +not its subject. + +**What it cannot decide.** That an example teaches its subject *well*. The check +establishes that the capability is present where the index says it is; review +establishes the rest. + +--- + +## 8. Staging + +Each stage is independently mergeable and leaves the tree better than it found +it. + +| stage | content | why here | +|---|---|---| +| **1** | §6 defects 1–5; bump the seven examples to `plugins` 0.4.0 | pure corrections, no restructuring, and 3 is a live defect in copied projects | +| **2** | rewrite `docs/01` as the curriculum; add the role index to `docs/README.md`; name the template surface | the index must exist before things are moved into it | +| **3** | E0 and chapter 23 | the two halves of the gap this round's discussion found; E0 is also the fixture for a measured claim that nothing runs | +| **4** | A4 and chapter 22; split `[features]` out of `05` | the largest single uncovered mechanism | +| **5** | F2; finish the `05` split | rule authoring, which chapter 23 has by then described | +| **6** | B3 | the round trip; largest new example, and it needs a local index fixture | +| **7** | `docs/coverage.toml` and the check | last, because it is only enforceable once the rows it would demand exist | + +Renumbering (B1, B2, C1, D1, F1) happens in stage 2 with redirects left in +`docs/01`, so no stage both moves directories and adds content. + +--- + +## 9. What this deliberately does not do + +- **It does not delete an example.** Every current example teaches something no + other one does; the defect is the index, not the set. +- **It does not add an example per uncovered capability.** §3.2 is what keeps + four from becoming thirteen, and the criterion is stated so a future addition + is argued rather than assumed. +- **It does not duplicate the bare-metal lesson into `examples/`.** It belongs to + the board package that supplies the target world; what changes is that the + index names the template surface. +- **It does not restate `mcpp-plugins` in `docs/`.** Chapter 23 describes the + authoring surface and points at the collection; a second copy of a rule + collection's reference would drift on its own schedule. +- **It does not touch the Chinese translations' content policy.** Every chapter + added or split needs its `docs/zh/` counterpart in the same change, which CI + already enforces; the cost is counted in each stage. + +--- + +## 10. Open questions for review + +1. **Renumbering.** Stage 2 moves five directories. The alternative is to keep + today's numbers and let the tracks be a documentation-only grouping. Moving + makes the tree self-describing and breaks external links; not moving keeps + `03-pack-static` sitting between two basics. *Recommendation: move, with a + redirect table in `docs/01`.* + +2. **Whether the seam's C++ shape should ever be generated.** §4.4 leaves this + open deliberately. Generating it requires the generator to read intent it + cannot see today — that a pointer and a count are a span, that a returned + `int` is an error code — which is an IDL by another name. The question worth + deciding first is not "can we" but "how much convention before the generator + becomes a thing the design says it is not". + +3. **B3's index fixture.** The round trip needs an index to register against. A + local file-backed index in the example directory is self-contained and is not + what a real publisher does; using `mcpp-index` makes the example unrunnable + for a reader without publish rights. *Recommendation: local index, with the + README stating exactly which step differs in the real one.* + +4. **Whether `docs/21` should absorb the diagnosis commands or gain a sibling.** + Seventeen commands land there under §3.2. At 211 lines it can take them; at + double that it becomes the chapter nobody reads. *Recommendation: absorb now, + split when it passes 400 lines.* + +5. **Defect 6 (`.cl` and `.metal`).** Ship a rule package for one of them, or + change the refusal to state that no package claims the extension? The second + is small and honest; the first is ecosystem work with its own schedule. diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index ec667d26..74a62227 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -1,27 +1,103 @@ --- name: mcpp-docs-style -description: Use when writing or editing anything under docs/ (English or 简体中文), README files, or long-form design records — states the register mcpp documentation is written in (declarative, precise, professional), the constructions that are not admitted (question headings, conversational asides, internet slang, figurative jargon), and the bilingual parity rules. +description: Use when writing or editing anything under docs/ (English or 简体中文), docs/specs/, README files, or the design records under .agents/docs/ — states which tree a document belongs to, that docs/ is a usage manual for what mcpp has already implemented rather than a design account, the register it is written in (academic, declarative, precise, no emoji, no internet slang), the requirement that a document match the current implementation, the gradient a topic is documented along, the coverage a surface owes, and the bilingual parity rules. --- -# mcpp 文档风格规范 +# mcpp 文档规范 -## 适用范围 +本规范回答五个问题:**这份文档的归属**(第一节)、**怎么写**(第二至六节)、 +**它必须对得上什么**(第七节)、**它欠多少覆盖**(第九节)、 +**怎么评审它**(第十三节)。 -`docs/**`(含 `docs/zh/**`)、`README.md`、`.agents/docs/**` 的对外部分。 +最核心的一条在第一节:**用户文档是已实现功能的使用手册**,不是设计说明。 代码注释与 commit message **不受本规范约束** —— 它们的读者、篇幅与目的都不同, -那里允许并鼓励叙述「为什么」以及实测过程。本规范约束的是**面向用户的文档**。 +那里允许并鼓励叙述「为什么」以及实测过程。 -## 一、总原则 +## 一、文档的归属:三棵树与各自的准入判据 + +一份文档属于哪棵树,由**读者**决定,不由篇幅或主题决定。 + +| 树 | 读者 | 准入判据(问自己这一句) | 稳定性 | +|---|---|---|---| +| `docs/**` | 手上有任务的人 | 手上有这个任务的人,没有它做不完 | 增量;旧拼法留作别名 | +| `docs/specs/**` | 对着机制做实现的人:索引作者、下游工具、贡献者 | **没有它,两个独立实现会不一致** | 编号 + 版本 + 状态机 | +| `.agents/docs/**` | 做这次改动的人,以及以后问「为什么是这样」的人 | 做了一个决定,理由否则会丢 | **落地即不可变** | +| `.agents/skills/**` | 照着做的人或 agent | 这是**步骤**,不是解释 | 随流程变 | + +### 用户文档是**已实现功能的使用手册** + +这是本规范最核心的一条。 + +> `docs/**` 服务的是**要把事情做成的用户**。它写 mcpp **已经实现**的东西怎么用, +> 不写这些东西**为什么被设计成这样**,也不写**什么设计了、什么还没设计**。 + +不写进用户文档的四类内容,它们全部属于 `.agents/docs/`: + +1. **设计理由与取舍** ——「为什么是一个机制而不是两个」「这个边界由模型的性质 + 决定而不是本文档的雄心」。 +2. **被否掉的替代** ——「三种替代方案都不能去掉它」「某原语写出来又撤回了」。 +3. **路线图与设计状态** ——「计划中」「将来会支持」「已设计未实现」「下一步是」。 + 用户文档里一个能力只有两种状态:**能用**(带版本下界)与**不支持**(一句话)。 +4. **实现内幕** —— 除非用户不知道它就会用错。 + +**判据(逐段问一遍):把这一段删掉,读者还能不能正确地用?** + +- 能 → 删掉,或移进设计记录。 +- 不能 → 它不是设计论证,是**使用信息**;改写成事实陈述,去掉论证语气。 + +**边界写成事实,不写成论证。** 「当前边界」一节(第九节要求它必须存在)是一份 +清单,不是一段说理: + +| 不采用 | 采用 | +|---|---| +| OpenMP offload 与 stdpar 没有可分的岛,因此落在这套机制的论域之外 —— 这是模型的性质,而不是本文档雄心的缺口。 | 未支持:OpenMP offload、stdpar、Metal、HIP 的 AMD 平台。 | +| 之所以不发出 `accel` 字段,是因为该字段的含义是「实测所得」,而 mcpp 目前无从测量,把声明写进一个语义为实测的字段会让身份说谎。 | `mcpp pack` 不产出 `accel` 字段;需要它的发布方在描述符里手写。 | + +理由**就是不给**。要理由的读者是另一类读者,他去读设计记录 —— 而用户文档 +不链接设计记录(见上面的引用方向)。 + +**已测状态**(2026-09-08):九个用户章节含设计论证短语,`05` 21 处、`13` 8 处、 +`20` 6 处,`20` 另有两个设计型标题。清理按 +`.agents/docs/2026-09-08-documentation-architecture-three-trees.md` 的分阶段进行。 + +### 另外三条最容易被违反的推论 + +1. **用户章节不记历史。** 「过去是 X,2026.8.16 起改成 Y」是设计记录的句子。 + 章节陈述今天是什么;版本相关写**下界**(`2026.9.6.5+`)。 +2. **设计记录落地后不再编辑**,除了追加状态行或一个具名带日期的更正块。 + 就地改写会让一份「某时刻的记录」悄悄变成「对现在的断言」。 +3. **用户章节里出现「必须 / 禁止」,说明内容属于规范。** 把它移进 + `docs/specs/`,章节引用它。只有规范有规范性语气。 + +### 引用方向是规则,不是习惯 + +``` +docs/ ──▶ docs/specs/ 允许:引用精确语义 +docs/ ─╳─ .agents/docs/ 禁止 +docs/specs/ ──▶ .agents/docs/ 允许,仅限元数据表里的溯源行 +docs/specs/ ──▶ docs/ 允许 +.agents/docs/ ──▶ 任何 允许 +代码注释 ──▶ docs/ 或 specs/ 允许,且被引用的文件必须存在 +``` + +**为什么那条边被禁止**:设计记录描述一个时刻,不带稳定性承诺。用户章节想引用 +它,只说明两件事之一 —— 章节不完整,或那份记录里的内容已经变成规范性的。两者 +的修法都不是加链接,而是**把内容提升上来**(是操作就进章节,是保证就进规范)。 + +## 二、总原则 文档是**参考资料**,不是博客,也不是聊天记录。判据只有一条: -> 一位不认识作者、只想解决自己问题的工程师,能不能在最短时间内 -> 拿到准确的事实,并且不会误以为某个说法比实际更随意或更绝对。 +> 一位不认识作者、只想解决自己问题的工程师,能不能在最短时间内拿到准确的事实, +> 并且不会误以为某个说法比实际更随意或更绝对。 -由此得到三条可执行的规则:陈述、精确、克制。 +由此得到四条可执行的规则:**学术、陈述、精确、克制**。 -## 二、标题 +「学术」在这里是具体的三件事,不是气质:每个断言的强度与它的证据相符(第六节)、 +每个可粘贴的东西都可复现(第七节)、每个枚举都有分母(第九节)。 + +## 三、标题 **标题一律是名词短语或陈述句,不使用疑问句、不使用口语片段。** @@ -46,18 +122,25 @@ description: Use when writing or editing anything under docs/ (English or 简体 「…的原因」「…的依据」「…的范围」是把 why 型标题转成名词短语的常用形。 **保留 why 本身,去掉疑问语气。** -## 三、词汇 +## 四、词汇 ### 不采用的类别 -1. **网络用语与口语**:搞定、干活、坑、真香、翻车、打脸、一把梭、白给、 +1. **emoji 与装饰性符号**:✅ ❌ ⚠️ ⭐ 🎉 🚀 💡 🔥 以及同类。 + 状态用词表达:**已实现 / 部分实现 / 未实现**、**是 / 否**、 + **已验证 / 未验证**。一个符号要靠图例才能读,而词不用。 + - `docs/**`、`docs/specs/**`、`README*` 今天是**零 emoji**,保持。 + - `.agents/docs/**` 的既有记录里有四千余处(⚠ / ✅ / ⭐ / ❌)。 + **新记录不使用;既有记录不回改** —— 设计记录落地即不可变, + 为统一符号去改写历史记录,改的是它唯一的价值。 +2. **网络用语与口语**:搞定、干活、坑、真香、翻车、打脸、一把梭、白给、 凉了、炸了、神器、黑科技、敲黑板、划重点。 -2. **拟人与比喻性行话**:姊妹篇、腿(fat package 的一份产物)、travel(源码 +3. **拟人与比喻性行话**:姊妹篇、腿(fat package 的一份产物)、travel(源码 「旅行」)、picky、happy path 的中文直译。技术术语本身可以是比喻 (rpath、sysroot),但**不要新造比喻**。 -3. **填充语**:其实、说白了、简单来说、众所周知、显然、当然、值得一提的是。 +4. **填充语**:其实、说白了、简单来说、众所周知、显然、当然、值得一提的是。 如果一件事显然,就不必说;如果不显然,「显然」会让读者怀疑自己。 -4. **含糊的程度词**:很快、非常、极其、基本上、差不多。用数字或范围替代 —— +5. **含糊的程度词**:很快、非常、极其、基本上、差不多。用数字或范围替代 —— 「2.42×」「64.77s」「四个平台中的三个」。 ### 人称 @@ -75,7 +158,7 @@ description: Use when writing or editing anything under docs/ (English or 简体 `your toolchain : …` 是程序打印的原文,**逐字复现是要求,不是文风问题**。 检查脚本因此会先剔除行内代码段再判定。 -## 四、句式 +## 五、句式 - **陈述句优先。** 命令式仅用于操作步骤(「运行 `mcpp build`」)。 - **一句话一个事实。** 从句套从句的长句拆开。 @@ -84,7 +167,7 @@ description: Use when writing or editing anything under docs/ (English or 简体 - **破折号克制使用**:插入语用逗号或括号;破折号留给「随后是对前半句的 重述或收束」这一种用法。 -## 五、断言的强度必须与证据相符 +## 六、断言的强度必须与证据相符 这是本规范里最实质的一条,也是最容易违反的一条。 @@ -103,7 +186,83 @@ description: Use when writing or editing anything under docs/ (English or 简体 写「已在 Linux / macOS / Windows 验证」比写「全平台可用」更有价值, 因为前者可被检验。 -## 六、双语对照 +**「支持」有三档,分开写。** 同一个「是」可能意味着三件不同的事,合并写就是 +把最弱的一档说成最强的: + +| 档 | 含义 | +|---|---| +| 已端到端运行 | CI 或本机跑过,产物达成了断言 | +| 已安装并编译 | 组件装得上、代码编得过,没有跑过 | +| 已声明 | 描述符里有,没有装过 | + +## 七、文档必须对应当前实现 + +一份与实现脱节的文档比没有文档更坏:读者按它写出来的东西编不过,而错的是 +文档,他不知道。 + +1. **写作与核对一律读 `origin/main`,不读工作树。** 工作分支可能落后若干个 + 发布;「现在的实现是什么」只有 `origin/main` 能回答。 +2. **每一个可粘贴的东西都必须可复现**:命令、输出、报错原文、路径、版本号。 + 判据是「在当前发布版上跑一遍能不能得到这一行」。做不到就删掉,或标注版本 + 下界。**诊断信息里那行可粘贴的版本号也是承诺** —— 它会被读者原样敲进去。 +3. **改实现的 PR 同时改被它作废的文档。** 判据:这次改动触到的每一处 + `docs/` 断言都重新读一遍,而不是等下一次文档 PR。 +4. **用户章节不引源码行号。** 读者手上没有那一版源码树。引文件与符号 + (`src/pack/prebuilt.cppm` 的 `tag_check`)。规范可以引文件与符号; + 设计记录可以引行号,因为它记录的是一个时刻。 +5. **一份文档「对齐到哪个版本」必须可判定。** 规范由元数据表的「对应实现」 + 回答;章节由它写出的版本下界回答。都没有,就说明没人能判断它是不是过期的。 +6. **过期的判据不要用子串搜索。** 「grep 到这个词就算讲过了」会在有人改一次 + 措辞时静默变空转。要判断一份文档是否覆盖某个能力,读**结构化的东西** —— + 示例的 `mcpp.toml`、源码里的键表、`print_usage()` 的正文。 + +## 八、梯度:一个主题的五级台阶,以及只链接相邻级 + +文档目录要有梯度,读者才能按自己的深度进入。同一个主题从浅到深有五级: + +| 级 | 形态 | 语气 | +|---|---|---| +| 0 入口 | 角色索引:「我想做 X」→ 读哪几章、跑哪个示例、用哪个模板 | 指路 | +| 1 教程 | 最小可跑的一份工程,从头跟到尾 | 可用第二人称 | +| 2 参考 | 按机制索引,字段完整 | 陈述,不用第二人称 | +| 3 规范 | 语义、约束、匹配规则,每条带实现状态 | RFC 2119 | +| 4 记录 | 为什么是这样,以及什么被推翻了 | 允许叙述 | + +**规则:每份文档在开头点明它是哪一级,并且只链接相邻级。** 参考文档向上链到 +教程、向下链到规范;它**不直接把读者丢进设计记录**,那是跨两级 —— 也正是第一节 +那条被禁止的边。 + +一个能力的文档化按这个顺序推进,不跳级:先有一个能跑的最小形态,再有字段参考, +再在「两个实现会不一致」时抽出规范。**倒过来做会得到一份没有人验证过的规范。** + +## 九、覆盖度 + +「写了」不等于「覆盖了」。覆盖度要有分母,而**分母取自代码树,不取自文档**—— +用文档自己的列表当分母,只能证明这份文档自洽。 + +- manifest 键:取自解析点(`modules/manifest/src/`) +- 构建程序 API:取自导出名(`modules/buildmcpp/src/`) +- 命令:取自 `print_usage()` 的正文 +- 设备扩展名:取自 `modules/source-kind/src/` 的表 + +每个能力有且只有三种归宿,新增一个能力时**在同一个 PR 里回答它归哪一类**: + +| 归宿 | 判据 | +|---|---| +| **一个示例** | 它改变**工程的形状** —— 文件、manifest、或作者敲的命令 | +| **一个代码块** | 它是既有工程里的一行 | +| **一条场景条目** | 它只经由命令到达(`docs/21`) | + +两条配套要求: + +- **每份参考章节必须有「当前边界 / Current limitations」一节。** 没写边界的 + 文档等于声称自己完整。这一节不是可选的,而且**是一份事实清单,不是说理** + (写法见第一节)。 +- **缺口要写出来,不要留白。** 「这一项尚无示例」是一条信息;什么都不说, + 读者只能靠踩到才知道。缺口写在**它所属的那棵树**里:用户文档写「不支持 + X」,设计记录写为什么以及打算怎么办。 + +## 十、双语对照 `docs/X.md` 与 `docs/zh/X.md` 是**同一份文档的两个版本**,不是两篇文章。 @@ -114,41 +273,83 @@ description: Use when writing or editing anything under docs/ (English or 简体 - 改动一侧时**同时改另一侧**。只改一侧会让两份文档随时间分叉, 而读者无从知道哪一份是新的。 -## 七、结构 +## 十一、结构 -- 顶部一段引言说明**这份文档回答什么问题**,以及相关文档的链接 - (用「相关文档:」,不用「姊妹篇」)。 +- 顶部一段引言说明**这份文档回答什么问题**、它处在第八节的哪一级,以及相关 + 文档的链接(用「相关文档:」,不用「姊妹篇」)。 - 表格用于枚举与对照,散文用于因果。**不要用散文列举**。 -- 「当前边界 / Current limitations」一节是必要的,不是可选的: - 没有写出边界的文档,读者只能靠踩到才知道。 +- 规范另有两项硬性要求:开头一张元数据表(编号、标题、状态、版本、最后修改、 + 对应实现、相关设计文档),结尾一份变更记录。 -## 八、机器检查 +## 十二、机器检查 -规则里可判定的那一半由 `.github/tools/check_docs_style.sh` 执行: +规则里可判定的那一部分由 `.github/tools/check_docs_style.sh` 执行: ``` bash .github/tools/check_docs_style.sh ``` -它检查三条:标题不是疑问句/口语片段;参考文档不使用第二人称; -`docs/X.md` 与 `docs/zh/X.md` 的标题结构一致(按层级序列比对, -并剔除代码块内的 `#` 注释 —— 第一版脚本把 ```sh 块里的 `# GET, never HEAD` -数成了标题,报出一个并不存在的结构分歧)。 +它今天检查三条:标题不是疑问句/口语片段;参考文档不使用第二人称; +`docs/X.md` 与 `docs/zh/X.md` 的标题结构一致(按层级序列比对,并剔除代码块内的 +`#` 注释 —— 第一版脚本把 ```sh 块里的 `# GET, never HEAD` 数成了标题,报出一个 +并不存在的结构分歧)。 -**它不检查第五节** —— 断言强度与证据是否相符需要读者判断,而那是本规范里 -最重要的一条。脚本能做的事不等于规范的全部。 +**注意它的作用域是 `docs/*.md docs/zh/*.md`,不递归**,所以 `docs/specs/` 今天 +不在检查范围内。这是通配符的后果,不是决定;扩作用域与新增下列检查已列入 +`.agents/docs/2026-09-08-documentation-architecture-three-trees.md`:emoji、 +禁止边、被引用的 `docs/…md` 路径必须解析得到、规范双索引完整、规范元数据表与 +变更记录存在。 -## 九、自检清单 +**它不检查第六、七、九节** —— 断言强度与证据是否相符、文档是否对得上当前实现、 +覆盖是否有分母,都需要读者判断,而那三条是本规范里最重要的。 +**脚本能做的事不等于规范的全部。** + +## 十三、评审判据 + +文档改动**至少评审一次**,而且**不由写它的那一遍来评审** —— 刚写完就自审,读到的 +是自己的意图而不是文本。判据:评审时只读渲染后的成文,不读 diff。 + +八个维度,每个都有一条可执行的判据,不是感觉: + +| 维度 | 判据 | +|---|---| +| **面向人群** | 一句话说出这份文档的读者是谁。说不出,就是没定位。二次判据:从入口的角色索引能不能指到它 | +| **梯度** | 它是第八节五级里的哪一级?它链接的是不是相邻级?跨级链接一律是缺陷 | +| **渐进性** | 一个从零开始的读者,能不能不跳级地到达这里 —— 前置的最小可跑形态存在吗 | +| **直观** | 只读前 15 行,能不能答出「这章讲什么、我要不要读」 | +| **覆盖度** | 分母是什么(第九节)?「当前边界」一节在不在,且是事实清单不是说理 | +| **陈述方式** | 陈述句;无第二人称(教程除外);无 emoji;每条断言的强度与证据相符;「支持」分三档 | +| **信息密度** | 随机抽三段,逐段问「删掉它读者少知道什么」。答不上来就是低密度 —— 典型是复述上一段、为强调而重复、以及把一个事实拆成三句 | +| **易读** | 表格用于枚举、散文用于因果;一句一个事实;段落不超过约六行;代码块前有一句说明它演示什么 | + +**用户文档额外一条,优先级高于以上八条**:逐段问「删掉它读者还能不能正确地用」 +(第一节)。设计理由、被否掉的替代、路线图,一律不在用户文档里。 + +评审的产出是**一份逐条的结论**,不是「看起来不错」。每个维度给出:通过 / +不通过 + 具体位置。 + +## 十四、自检清单 提交文档改动前: ``` +[ ] 这份文档属于哪棵树,判据答得上来 +[ ] 用户文档:逐段问过「删掉它读者还能不能正确地用」,设计理由/被否替代/ + 路线图都不在里面 +[ ] 用户文档:一个能力只写「能用(带版本下界)」或「不支持(一句话)」 +[ ] 「当前边界」是事实清单,不是说理 +[ ] 没有 docs/** → .agents/** 的引用 +[ ] 开头点明了它在梯度里的哪一级,且只链接相邻级 [ ] 标题没有疑问句、没有口语片段 -[ ] 没有网络用语、没有新造比喻 +[ ] 没有 emoji、没有网络用语、没有新造比喻 [ ] 没有第二人称(教程体除外) [ ] 每条「实测」都有数字、路径或报错原文 +[ ] 「支持」按三档分开写,没有把「已声明」写成「已运行」 [ ] 没有未经验证的全称断言 -[ ] 中英两版结构对应,代码块逐字一致 +[ ] 每个可粘贴的命令与输出都在当前发布版上复现过,或标了版本下界 +[ ] 本次实现改动作废的文档已在同一个 PR 里改掉 +[ ] 新增的能力已归入示例 / 代码块 / 场景条目三者之一 [ ] 有「当前边界」一节 +[ ] 中英两版结构对应,代码块逐字一致 [ ] `bash .github/tools/check_docs_style.sh` 通过 ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 86676deb..dc026bd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -957,7 +957,7 @@ generic target 取得同样的覆盖,所以空的下界不放宽任何东西。 设计与调研:`.agents/docs/2026-09-05-accelerator-support-design.md`、 `.agents/docs/2026-09-04-ai-accelerator-toolchain-ecosystem-survey.md`。 -新增手册章节 `docs/20-accelerators.md`(中英双份)。 +新增手册章节 `docs/20-heterogeneous-builds.md`(中英双份)。 ## [2026.9.4.3] — 2026-09-04 diff --git a/docs/05-mcpp-toml.md b/docs/05-mcpp-toml.md index f789ff7a..eda42ada 100644 --- a/docs/05-mcpp-toml.md +++ b/docs/05-mcpp-toml.md @@ -2974,8 +2974,6 @@ it will still say so once the key has more than one accepted value. should DO) belongs on `[[runtime.artifacts]]` — see §2.11. `provenance` beginning with `mcpp-pack` is what marks a directory as one, and mcpp refuses to `build` inside it. -- See `.agents/docs/2026-06-04-manifest-schema-ownership.md` for the full field-ownership - table and the finalized decisions. ## 3. Worked Examples diff --git a/docs/08-toolchain-internals.md b/docs/08-toolchain-internals.md index 27621d57..71e4961f 100644 --- a/docs/08-toolchain-internals.md +++ b/docs/08-toolchain-internals.md @@ -60,8 +60,7 @@ frontend candidates — this is where host-split distribution names like `mingw-cross-gcc` (Linux host) vs `mingw-gcc` (Windows host) live; they are current distribution-layer identity, not user-facing spellings. The payload is resolved/auto-installed via the xlings backend into the sandbox -(`$MCPP_HOME/registry/data/xpkgs/xim-x-//`). See -`.agents/docs/2026-07-15-toolchain-target-naming-unification-design.md`. +(`$MCPP_HOME/registry/data/xpkgs/xim-x-//`). `detect`/`probe` (`src/toolchain/detect.cppm`, `probe.cppm`) then derive: @@ -647,5 +646,3 @@ answer them. | build.mcpp host flags | `src/build/build_program.cppm` | | hermetic link check | `src/build/hermetic.cppm` | | regression fences | `tests/e2e/86_llvm_hermetic_link.sh`, unit `test_linkmodel.cpp`, `test_post_install.cpp`; the no-host-toolchain CI job in `ci-linux-e2e.yml` | - -Design history: `.agents/docs/2026-07-07-hermetic-toolchain-link-model-design.md`. diff --git a/docs/11-machine-output.md b/docs/11-machine-output.md index 6116c789..68b87916 100644 --- a/docs/11-machine-output.md +++ b/docs/11-machine-output.md @@ -4,9 +4,6 @@ mcpp writes for two audiences. This chapter is the contract for the second one: programs. Editor extensions, CI scripts, and anything else that parses mcpp's output may rely on what is stated here. -Design and the measurements behind it: -`.agents/docs/2026-08-08-machine-readable-output-protocol-design.md`. - ## 1. Primary rule > **Detect the protocol by parsing stdout. Never by exit code, and never by diff --git a/docs/20-heterogeneous-builds.md b/docs/20-heterogeneous-builds.md index 20419087..b8ab0a0c 100644 --- a/docs/20-heterogeneous-builds.md +++ b/docs/20-heterogeneous-builds.md @@ -719,23 +719,17 @@ past it is the framework's own selector rather than a change to the packaging. `ggml-org:llamacpp` carries this as its `backend-vulkan` feature. -## Not implemented - -Device targets and the device linking they imply for the island shape, OpenMP -offload and stdpar, the AMD platform of HIP, and Metal. - -Two further gaps are per-platform rather than per-model, and both are -publishing work rather than engine work. **HIP on Windows** needs Windows -sections for the NVIDIA-platform header package and for `cuda-profiler-api`, -plus a Windows form for the header-declaration step that today writes into a -Linux SubOS view. **The 13.x CUDA line on Windows** needs a Windows form for -the back-end reunification: on that line upstream splits `nvvm/` and `crt/` out -of `cuda_nvcc` into four separately published components, which the index -reunites with symlinks, and `ln` is not a command on that host. The 12.x line -keeps its back end inside the component and needs none of it, so that is the -line Windows carries, declared as a deliberate divergence in each recipe rather -than left to be read off the file. - -See -`.agents/docs/2026-09-05-heterogeneous-build-ecosystem-design-v2.md` for the -design these follow from and the reason each is open. +## Current limitations + +- Device targets, and the device linking (RDC) they imply for the island shape. +- OpenMP `target` offload, and stdpar. +- The AMD platform of HIP. `rules-hip` reaches the NVIDIA platform only. +- Metal (`.metal`) and OpenCL C (`.cl`). Both extensions are classified as + device sources and no published rule package claims either, so a build that + names one is refused naming the file. +- The 13.x CUDA line on Windows. Windows carries the 12.x line. +- `mcpp pack` does not emit the `accel` field. A publisher writes it into the + descriptor. + +Per-platform limits for each lane are in the table under *Which platforms each +lane reaches*. diff --git a/docs/README.md b/docs/README.md index 90bdfab8..533b35e4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,34 +2,98 @@ **English** | [简体中文](zh/README.md) +This tree is the **usage manual for what mcpp implements**. Each chapter states +what a capability does, how it is written, and what its current limits are. The +reasoning behind a design, the alternatives that were rejected, and work that is +planned rather than shipped are deliberately absent — they belong to the design +records, which are not user documentation. + +## Where a document lives + +| tree | reader | what it holds | +|---|---|---| +| `docs/**` | someone with a task in hand | how to use what mcpp implements | +| [`docs/specs/**`](specs/README.md) | someone implementing against a mechanism: index authors, downstream tools, contributors | semantics, constraints and matching rules, each tagged with its implementation status | +| `.agents/docs/**` | whoever made a change, and whoever later asks why it is that way | the reasoning, the measurements, and what was refuted | +| `.agents/skills/**` | a contributor or agent following a procedure | ordered steps with criteria | + +A chapter cites a specification for exact semantics. It does not cite a design +record: a record describes a moment and carries no stability promise, so +anything a reader needs is written here or in a specification instead. + +## Start here + +| To | Read | Run | +|---|---|---| +| write a program | [00](00-getting-started.md), [05](05-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | +| write a library others import | [10](10-publishing-a-library.md), [05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`03-features`](../examples/03-features/) | +| publish it | [02](02-pack-and-release.md), [10](10-publishing-a-library.md), [12](12-binary-distribution.md) | [`05-lib-distribution`](../examples/05-lib-distribution/), [`07-to-the-index`](../examples/07-to-the-index/) | +| build for another machine | [16](16-the-target-triple.md), [15](15-openkal-cross.md), [13](13-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | +| use a GPU or an accelerator | [20](20-heterogeneous-builds.md), [18](18-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at `boundary/` | +| add a rule, a language or a generator | [23](23-authoring-a-rule-package.md), [07](07-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`13-a-new-device-language`](../examples/13-a-new-device-language/) | +| add a package to the index | [10](10-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [`07-to-the-index`](../examples/07-to-the-index/) | +| change mcpp itself | [04](04-build-from-source.md), [09](09-release.md), [19](19-supported-versions.md) | — | + +Lessons also arrive as project templates, which a package ships and `mcpp new +--template` instantiates. `riscv-virt-rt` (bare metal) and `ocornut.imgui` +(a graphical application) are the two documented today; the chapter that uses +one names it. + +## Chapters + +### Part I — Using mcpp + - [00 - Getting Started](00-getting-started.md) - [01 - Examples](01-examples.md) -- [02 - Packaging & Release](02-pack-and-release.md) -- [03 - Toolchain Management](03-toolchains.md) -- [04 - Building from Source & Contributing](04-build-from-source.md) - [05 - mcpp.toml Manifest Guide](05-mcpp-toml.md) - [06 - Workspaces](06-workspace.md) - [07 - build.mcpp Build Program](07-build-mcpp.md) -- [08 - Toolchain Internals](08-toolchain-internals.md) -- [09 - Releasing mcpp](09-release.md) +- [21 - Commands by Scenario](21-commands-by-scenario.md) +- [22 - Features and Capabilities](22-features-and-capabilities.md) + +### Part II — Shipping what was built + +- [02 - Packaging & Release](02-pack-and-release.md) - [10 - Publishing a Library to mcpp-index](10-publishing-a-library.md) -- [11 - Machine-Readable Output](11-machine-output.md) - [12 - Distributing a Prebuilt Library](12-binary-distribution.md) + +### Part III — Toolchains and targets + +- [03 - Toolchain Management](03-toolchains.md) - [13 - Bare-Metal and Freestanding Targets](13-baremetal.md) - [14 - The Target Side](14-target-side.md) - [15 - Cross-Compilation Over openkal](15-openkal-cross.md) - [16 - The Target Triple](16-the-target-triple.md) - [17 - The Project Environment](17-the-project-environment.md) + +### Part IV — Devices and accelerators + - [18 - Reaching a Device](18-devices.md) -- [19 - Supported Versions and Compatibility](19-supported-versions.md) - [20 - Heterogeneous Builds](20-heterogeneous-builds.md) -- [21 - Commands by Scenario](21-commands-by-scenario.md) + +### Part V — Extending mcpp from outside + +- [23 - Authoring a Rule Package](23-authoring-a-rule-package.md) + +### Part VI — Machine interfaces and compatibility + +- [11 - Machine-Readable Output](11-machine-output.md) +- [19 - Supported Versions and Compatibility](19-supported-versions.md) + +### Part VII — Contributing to mcpp itself + +- [04 - Building from Source & Contributing](04-build-from-source.md) +- [08 - Toolchain Internals](08-toolchain-internals.md) +- [09 - Releasing mcpp](09-release.md) ## Specifications -Normative documents — semantics, constraints and matching rules, each rule tagged -with its implementation status. For index authors, contributors and downstream tooling. +Normative documents — semantics, constraints and matching rules, each rule +tagged with its implementation status. For index authors, contributors and +downstream tooling. - [specs/](specs/README.md) — index of all specs - [SPEC-001 — Package identity, `[dependencies]` selectors and matching](specs/package-identity.md) + - [SPEC-002 — The target side: reserved namespace, five layers, three rules](specs/target-side.md) + - [SPEC-003 — The exit-code contract](specs/exit-codes.md) - [SPEC-004 — `mcpp.toml` planes, conditioning shape, resolution axes and naming](specs/manifest-semantics.md) diff --git a/docs/zh/05-mcpp-toml.md b/docs/zh/05-mcpp-toml.md index bdff7b08..1f6f0b4b 100644 --- a/docs/zh/05-mcpp-toml.md +++ b/docs/zh/05-mcpp-toml.md @@ -2517,8 +2517,6 @@ deps = ["xim:mcpp-hooks-audioplayer@0.0.1"] 失去静态可解析性(lockfile/LSP/审计的前提)。包的扩展点 = 固定机制内的开放值域。 - 包级旋钮统一收敛进 features;糖键(如 `backend=`)进入核心语法须满足: ① 领域中立(跨生态通用模式)② 1:1 脱糖、零新增解析语义。 -- 字段归属总表与定型决策见 - `.agents/docs/2026-06-04-manifest-schema-ownership.md`。 ## 3. 实战示例 diff --git a/docs/zh/08-toolchain-internals.md b/docs/zh/08-toolchain-internals.md index 1bd02391..4444be51 100644 --- a/docs/zh/08-toolchain-internals.md +++ b/docs/zh/08-toolchain-internals.md @@ -507,5 +507,3 @@ mcpp 把运行时 DLL 部署到产物 exe 旁,这正是该平台对 §3–§4 | build.mcpp 宿主 flags | `src/build/build_program.cppm` | | hermetic 链接校验 | `src/build/hermetic.cppm` | | 回归fence | `tests/e2e/86_llvm_hermetic_link.sh`、单测 `test_linkmodel.cpp`、`test_post_install.cpp`;`ci-linux-e2e.yml` 的无宿主工具链 CI job | - -设计沿革:`.agents/docs/2026-07-07-hermetic-toolchain-link-model-design.md`。 diff --git a/docs/zh/11-machine-output.md b/docs/zh/11-machine-output.md index 00920ab8..45b6a4ba 100644 --- a/docs/zh/11-machine-output.md +++ b/docs/zh/11-machine-output.md @@ -3,8 +3,6 @@ mcpp 面向两类读者。本章是对第二类 —— **程序** —— 的契约。编辑器扩展、CI 脚本,以及任何解析 mcpp 输出的程序, 可依赖的部分在此列出。 -设计与背后的实测:`.agents/docs/2026-08-08-machine-readable-output-protocol-design.md`。 - ## 1. 首要规则 > **靠解析 stdout 来识别协议。不要靠退出码,也不要靠「命令没失败」。** diff --git a/docs/zh/20-heterogeneous-builds.md b/docs/zh/20-heterogeneous-builds.md index f5654d77..0073fa55 100644 --- a/docs/zh/20-heterogeneous-builds.md +++ b/docs/zh/20-heterogeneous-builds.md @@ -588,18 +588,14 @@ Mesa 的 lavapipe 仅因类型就被排除 —— 尽管它声明了后端要求 `ggml-org:llamacpp` 以 `backend-vulkan` feature 承载这一整套。 -## 尚未实现 +## 当前边界 -岛形态的 device target 及其隐含的 device link、OpenMP offload 与 stdpar、HIP 的 AMD -平台,以及 Metal。 +- 岛形态的 device target,及其隐含的 device link(RDC)。 +- OpenMP `target` offload,以及 stdpar。 +- HIP 的 AMD 平台。`rules-hip` 只到达 NVIDIA 平台。 +- Metal(`.metal`)与 OpenCL C(`.cl`)。两个扩展名都被归类为设备源,而没有任何 + 已发布的规则包认领它们,因此声明了其中之一的构建会被拒绝,并点名该文件。 +- CUDA 的 13.x 线在 Windows 上。Windows 承载的是 12.x 线。 +- `mcpp pack` 不产出 `accel` 字段。需要它的发布方写进描述符。 -另有两处缺口是按平台而不是按编程模型分的,而且两处都是打包工作不是引擎工作。 -**HIP 上 Windows** 需要给 NVIDIA 平台的头文件包与 `cuda-profiler-api` 补 Windows 段, -以及给那一步「把头文件声明进去」补一个 Windows 形态 —— 它今天写进的是一个 Linux 的 -SubOS 视图。**CUDA 的 13.x 线上 Windows** 需要给后端的重新拼合补一个 Windows 形态: -那条线上,上游把 `nvvm/` 与 `crt/` 从 `cuda_nvcc` 里拆成四个单独发布的组件,索引用符号 -链接把它们拼回去,而 `ln` 不是那个宿主上的命令。12.x 线自带后端,一样都不需要,所以 -Windows 承载的是那条线,并在每个配方里显式声明为有意的分歧,而不是留给人从文件里读。 - -这些所依据的设计,以及每一项仍然开着的理由,见 -`.agents/docs/2026-09-05-heterogeneous-build-ecosystem-design-v2.md`。 +每条 lane 的按平台边界见*每条 lane 到达哪些平台*一节的表格。 diff --git a/docs/zh/README.md b/docs/zh/README.md index 67e71c89..69c92820 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -2,28 +2,85 @@ [English](../README.md) | **简体中文** +这棵树是 **mcpp 已实现功能的使用手册**。每一章陈述一个能力做什么、怎么写、 +当前边界在哪里。设计理由、被否掉的替代方案、以及尚未发布的规划,**不写在这里** +—— 它们属于设计记录,而设计记录不是用户文档。 + +## 文档的归属 + +| 树 | 读者 | 内容 | +|---|---|---| +| `docs/**` | 手上有任务的人 | mcpp 已实现的东西怎么用 | +| [`docs/specs/**`](../specs/README.md) | 对着机制做实现的人:索引作者、下游工具、贡献者 | 语义、约束与匹配规则,每条标注实现状态 | +| `.agents/docs/**` | 做过某次改动的人,以及以后问「为什么是这样」的人 | 推理、实测,以及什么被推翻了 | +| `.agents/skills/**` | 照流程执行的贡献者或 agent | 带判据的步骤 | + +章节引用规范以取得精确语义,但**不引用设计记录**:记录描述的是一个时刻,不带 +稳定性承诺,因此读者需要的东西一律写在本树或规范里。 + +## 从这里开始 + +| 目标 | 阅读 | 运行 | +|---|---|---| +| 写一个程序 | [00](00-getting-started.md)、[05](05-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | +| 写一个供他人 import 的库 | [10](10-publishing-a-library.md)、[05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`03-features`](../../examples/03-features/) | +| 发布它 | [02](02-pack-and-release.md)、[10](10-publishing-a-library.md)、[12](12-binary-distribution.md) | [`05-lib-distribution`](../../examples/05-lib-distribution/)、[`07-to-the-index`](../../examples/07-to-the-index/) | +| 为另一台机器构建 | [16](16-the-target-triple.md)、[15](15-openkal-cross.md)、[13](13-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | +| 使用 GPU 或加速器 | [20](20-heterogeneous-builds.md)、[18](18-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 `boundary/` 开始 | +| 增加一条规则、一种语言或一个生成器 | [23](23-authoring-a-rule-package.md)、[07](07-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`13-a-new-device-language`](../../examples/13-a-new-device-language/) | +| 向索引添加一个包 | [10](10-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [`07-to-the-index`](../../examples/07-to-the-index/) | +| 修改 mcpp 本身 | [04](04-build-from-source.md)、[09](09-release.md)、[19](19-supported-versions.md) | — | + +课程也可以以**项目模板**的形式到达:模板由包提供,`mcpp new --template` 实例化 +它。今天有文档的两个是 `riscv-virt-rt`(裸机)与 `ocornut.imgui`(图形应用), +用到它的章节会点名。 + +## 章节 + +### 第一部分 —— 使用 mcpp + - [00 - 快速开始](00-getting-started.md) - [01 - 示例项目](01-examples.md) -- [02 - 发布打包](02-pack-and-release.md) -- [03 - 工具链管理](03-toolchains.md) -- [04 - 从源码构建 & 参与贡献](04-build-from-source.md) - [05 - mcpp.toml 工程文件指南](05-mcpp-toml.md) - [06 - 工作空间](06-workspace.md) - [07 - build.mcpp 构建程序](07-build-mcpp.md) -- [08 - 工具链机制内幕](08-toolchain-internals.md) -- [09 - 发布 mcpp](09-release.md) +- [21 - 按场景选命令](21-commands-by-scenario.md) +- [22 - Feature 与能力](22-features-and-capabilities.md) + +### 第二部分 —— 发布构建产物 + +- [02 - 发布打包](02-pack-and-release.md) - [10 - 发布一个库到 mcpp-index](10-publishing-a-library.md) -- [11 - 机器可读输出](11-machine-output.md) - [12 - 分发预编译库](12-binary-distribution.md) + +### 第三部分 —— 工具链与目标 + +- [03 - 工具链管理](03-toolchains.md) - [13 - 裸机与 freestanding 目标](13-baremetal.md) - [14 - 目标侧](14-target-side.md) - [15 - 基于 openkal 的交叉构建](15-openkal-cross.md) - [16 - 目标三元组](16-the-target-triple.md) - [17 - 项目环境](17-the-project-environment.md) + +### 第四部分 —— 设备与加速器 + - [18 - 抵达一台设备](18-devices.md) -- [19 - 受支持的版本与兼容性](19-supported-versions.md) - [20 - 异构硬件构建](20-heterogeneous-builds.md) -- [21 - 按场景选命令](21-commands-by-scenario.md) + +### 第五部分 —— 从外部扩展 mcpp + +- [23 - 编写规则包](23-authoring-a-rule-package.md) + +### 第六部分 —— 机器接口与兼容性 + +- [11 - 机器可读输出](11-machine-output.md) +- [19 - 受支持的版本与兼容性](19-supported-versions.md) + +### 第七部分 —— 为 mcpp 本身做贡献 + +- [04 - 从源码构建 & 参与贡献](04-build-from-source.md) +- [08 - 工具链机制内幕](08-toolchain-internals.md) +- [09 - 发布 mcpp](09-release.md) ## 规范文档 @@ -32,4 +89,6 @@ - [specs/](../specs/README.md) —— 全部规范的索引 - [SPEC-001 —— 包身份、`[dependencies]` 选择器与匹配](../specs/package-identity.md) + - [SPEC-002 —— 目标侧模型:保留命名空间、五层、三条规则](../specs/target-side.md) + - [SPEC-003 —— 退出码契约](../specs/exit-codes.md) - [SPEC-004 —— `mcpp.toml` 的平面划分、条件化形状、解析轴与命名规约](../specs/manifest-semantics.md) diff --git a/modules/manifest/src/toml.cppm b/modules/manifest/src/toml.cppm index 43c08cd5..3085dae4 100644 --- a/modules/manifest/src/toml.cppm +++ b/modules/manifest/src/toml.cppm @@ -2376,7 +2376,7 @@ std::expected parse_string(std::string_view content, m.lib.path = *v; } - // [pack] — `mcpp pack` configuration. See docs/35-pack-design.md. + // [pack] — `mcpp pack` configuration. See docs/02-pack-and-release.md. if (auto v = doc->get_string("pack.default_mode")) { const auto& s = *v; if (s != "static" && s != "bundle-project" && s != "bundle-all") { diff --git a/modules/manifest/src/types.cppm b/modules/manifest/src/types.cppm index f4dd4498..5f570e09 100644 --- a/modules/manifest/src/types.cppm +++ b/modules/manifest/src/types.cppm @@ -154,7 +154,7 @@ struct Target { // Aliases at the top of this file keep `mcpp::manifest::DependencySpec` // resolvable for unchanged call sites. -// `[toolchain]` section per docs/21-toolchain-and-tools.md +// `[toolchain]` section per docs/03-toolchains.md // linux = "gcc@15.1.0" // macos = "llvm@20" // windows = "msvc@system" @@ -1149,7 +1149,7 @@ struct LibConfig { std::filesystem::path path; // explicit override; empty = use convention }; -// `[pack]` — `mcpp pack` configuration. See docs/35-pack-design.md. +// `[pack]` — `mcpp pack` configuration. See docs/02-pack-and-release.md. // // `default_mode` picks the bundling strategy when the user runs bare // `mcpp pack` (no `--mode` flag): @@ -1577,7 +1577,7 @@ struct Manifest { // [target.] tables — empty if user didn't declare any. std::map targetOverrides; - // [pack] — `mcpp pack` config (see docs/35-pack-design.md). + // [pack] — `mcpp pack` config (see docs/02-pack-and-release.md). PackConfig packConfig; // [lib] — library root interface convention (M5.x+). diff --git a/src/config.cppm b/src/config.cppm index d26ab064..75d4b1ae 100644 --- a/src/config.cppm +++ b/src/config.cppm @@ -1,6 +1,6 @@ // mcpp.config — global config + paths + xlings binary acquisition. // -// Layout (per docs/14-data-layout.md): +// Layout: // $MCPP_HOME/ default ~/.mcpp/ // bin/mcpp mcpp binary (self-contained mode) // registry/ XLINGS_HOME for mcpp's xlings diff --git a/src/modgraph/p1689.cppm b/src/modgraph/p1689.cppm index a591c641..7d7aa5af 100644 --- a/src/modgraph/p1689.cppm +++ b/src/modgraph/p1689.cppm @@ -14,7 +14,7 @@ // We hand-parse the .ddi (no nlohmann::json dep — same justification // as fetcher.cppm). // -// Spec: docs/27-p1689-dyndep.md. +// Spec: P1689R5, the dependency format ninja consumes. module; diff --git a/src/pack/pack.cppm b/src/pack/pack.cppm index de0dd2c2..4f81c19f 100644 --- a/src/pack/pack.cppm +++ b/src/pack/pack.cppm @@ -15,7 +15,7 @@ // mcpp.pack.binfmt reads the import table instead, so a Linux box packaging a // Windows build is simply what happens when nothing has to be executed. // -// See docs/35-pack-design.md for the full design. Three modes: +// See docs/02-pack-and-release.md for the full design. Three modes: // Static full musl static, no PT_INTERP / RUNPATH // BundleProject bundle only the project's third-party .so (default) // BundleAll bundle every dynamic dep incl. libc / libstdc++ / ld diff --git a/src/pm/publisher.cppm b/src/pm/publisher.cppm index c0411416..303d4fa9 100644 --- a/src/pm/publisher.cppm +++ b/src/pm/publisher.cppm @@ -1,6 +1,6 @@ // mcpp.pm.publisher — generate xpkg Lua entry from mcpp.toml + scanner. // -// See docs/04-schema-xpkg-extension.md for the produced layout. +// See docs/10-publishing-a-library.md for the produced layout. module; diff --git a/src/publish/pipeline.cppm b/src/publish/pipeline.cppm index e08cd70f..b30d48d2 100644 --- a/src/publish/pipeline.cppm +++ b/src/publish/pipeline.cppm @@ -180,7 +180,7 @@ export int publish_package(bool dry_run, bool allow_dirty) { // `mcpp publish --auto` to: // - gh release create v // - fork mcpp-index, add pkg lua, gh pr create - // See docs/34-release-readiness.md §3. + // See docs/10-publishing-a-library.md. std::println("Tip: future versions of mcpp may automate steps 2-3 via the gh CLI."); return 0; } diff --git a/src/toolchain/stdmod.cppm b/src/toolchain/stdmod.cppm index 243dfdb2..ff2224e1 100644 --- a/src/toolchain/stdmod.cppm +++ b/src/toolchain/stdmod.cppm @@ -3,7 +3,7 @@ module; // mcpp.toolchain.stdmod — pre-build the `import std` BMI and cache it. // -// GCC 15 flow (from docs/11-gcc15-cookbook.md §2): +// GCC 15 flow (see docs/08-toolchain-internals.md): // g++ -std=c++23 -fmodules -Og -c -o std.o // ⇒ produces gcm.cache/std.gcm + std.o // From 3c08faa6ebb8175bb565905e364e8991cfb8ecb4 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:53:17 +0800 Subject: [PATCH 02/36] ci: check where a document lives, not only how it reads check_docs_style.sh covers the register. check_docs_structure.sh covers the architecture the style skill states: a user chapter may not cite a design record, a specification may cite one only in its metadata table, every docs/NN-*.md path named outside .agents/ must resolve, every specification must appear in all three indexes and carry the four metadata rows that make its currency decidable, and docs/ carries no emoji. Each of the six rules was falsified before it was committed -- the fix removed, the check run, the failure read. The metadata rule was strengthened after the first attempt passed a spec whose implementation-version row had been deleted: the original accepted any of three markers, so it measured the title rather than the table. .agents/docs/ is deliberately out of scope for the emoji rule. Its records carry four thousand of them and are immutable once their change lands. --- .github/tools/check_docs_structure.sh | 116 ++++++++++++++++++++++++++ .github/workflows/ci-linux.yml | 7 ++ 2 files changed, 123 insertions(+) create mode 100755 .github/tools/check_docs_structure.sh diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh new file mode 100755 index 00000000..bcc4e2f0 --- /dev/null +++ b/.github/tools/check_docs_structure.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# check_docs_structure.sh — the rules of .agents/skills/mcpp-docs-style that are +# about WHERE a document lives rather than how its prose reads. +# +# check_docs_style.sh covers the register. This covers the architecture: +# +# 1. no user chapter cites a design record +# 2. a specification cites one only in its metadata table +# 3. every docs/*.md path named outside .agents/ resolves +# 4. every specification is listed in all three indexes +# 5. every specification has a metadata table and a change record +# 6. no emoji under docs/ or in a top-level README +# +# What it deliberately does NOT check: whether a chapter documents what is +# implemented, whether an assertion's strength matches its evidence, or whether +# a surface's coverage has a denominator. Those need a reader, and they are the +# three most important rules in the skill. +# +# Usage: bash .github/tools/check_docs_structure.sh +set -uo pipefail +cd "$(dirname "$0")/../.." || exit 1 + +fail=0 +bad() { echo "FAIL: $*"; fail=1; } + +# ── 1. no user chapter cites a design record ────────────────────────────── +# +# The two READMEs are exempt BY DECISION, not by accident: they are where the +# three-tree architecture is published, so they name `.agents/docs/` in order to +# say what it holds and that a chapter must not send a reader there. Every other +# file under docs/ that names it has delegated a question it should have +# answered -- see "引用方向是规则" in the skill. +ARCHITECTURE_PAGES="docs/README.md docs/zh/README.md" +for f in docs/*.md docs/zh/*.md; do + case " $ARCHITECTURE_PAGES " in *" $f "*) continue ;; esac + if grep -n '\.agents/' "$f" >/dev/null 2>&1; then + while IFS= read -r hit; do + bad "$f: a user chapter cites a design record: ${hit}" + done < <(grep -n '\.agents/' "$f") + fi +done + +# ── 2. a specification cites a design record only in its metadata table ─── +# +# Provenance belongs in the metadata row. A citation in the body is the same +# delegation rule 1 refuses, one tree over. +for f in docs/specs/*.md; do + [ "$(basename "$f")" = "README.md" ] && continue + # The metadata table is the leading block: everything before the first `##`. + body_start=$(grep -n '^## ' "$f" | head -1 | cut -d: -f1) + [ -z "$body_start" ] && body_start=1 + if awk -v s="$body_start" 'NR >= s && /\.agents\//' "$f" | grep -q .; then + bad "$f: cites a design record outside its metadata table" + fi +done + +# ── 3. every docs/*.md path named outside .agents/ resolves ─────────────── +# +# A comment in src/ naming a chapter is a citation. Six of them named chapters +# that had not existed since an earlier numbering, and nothing reported it. +while IFS= read -r p; do + [ -f "$p" ] || bad "a document names \`$p\`, which does not exist" +done < <(git grep -ohE 'docs/[0-9]{2}-[a-z0-9-]+\.md' -- ':!.agents' | sort -u) + +# ── 4. every specification is listed in all three indexes ──────────────── +for f in docs/specs/*.md; do + base="$(basename "$f")" + [ "$base" = "README.md" ] && continue + grep -q "$base" docs/README.md || bad "docs/README.md does not list docs/specs/$base" + grep -q "$base" docs/zh/README.md || bad "docs/zh/README.md does not list docs/specs/$base" + grep -q "$base" docs/specs/README.md || bad "docs/specs/README.md does not list $base" +done + +# ── 5. every specification has a metadata table and a change record ────── +# +# docs/specs/README.md has required both since the directory existed. Nothing +# checked, so the requirement held only for specs whose author read the README. +# +# Four rows are required rather than "a table", and the fourth is why: a +# specification whose implementation version is not stated cannot be judged +# stale by anyone. Two spellings are in use for it and both are accepted. +for f in docs/specs/*.md; do + base="$(basename "$f")" + [ "$base" = "README.md" ] && continue + meta="$(head -30 "$f")" + for row in '规范编号' '状态' '最后修改'; do + printf '%s' "$meta" | grep -q "$row" \ + || bad "$f: metadata table has no \`$row\` row" + done + printf '%s' "$meta" | grep -qE '对应实现|最低实现版本' \ + || bad "$f: metadata table states no implementation version, so nothing can decide whether it is stale" + grep -qE '^#{2,3} .*(变更记录|Change record|Change log)' "$f" \ + || bad "$f: no change record" +done + +# ── 6. no emoji under docs/ or in a top-level README ───────────────────── +# +# Status is a word: 已实现 / 未实现, yes / no, verified / not verified. A symbol +# needs a legend and a word does not. `.agents/docs/` is NOT checked: its +# records carry four thousand of them and are immutable once their change +# lands, so normalising them would edit documents whose value is that they are +# not edited. New records follow the rule by review. +EMOJI='[✅❌⚠⭐🎉🚀💡🔥👍✨📦🔧]' +for f in docs/*.md docs/zh/*.md docs/specs/*.md README.md README.zh-CN.md; do + [ -f "$f" ] || continue + if grep -nP "$EMOJI" "$f" >/dev/null 2>&1; then + while IFS= read -r hit; do + bad "$f: emoji — state the status as a word: ${hit%%:*}: $(echo "$hit" | cut -d: -f2- | cut -c1-60)" + done < <(grep -nP "$EMOJI" "$f") + fi +done + +if [[ "$fail" -eq 0 ]]; then + echo "OK: docs structure checks pass" +fi +exit "$fail" diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index c87d5c45..eb8d5d29 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -101,6 +101,13 @@ jobs: - name: Check documentation style and bilingual parity run: bash .github/tools/check_docs_style.sh + # The architecture half: which tree a document lives in, whether every + # path it names resolves, and whether each specification is reachable + # from all three indexes. Same placement and the same reason -- it needs + # no toolchain, and what it catches is invisible to every other job. + - name: Check documentation structure + run: bash .github/tools/check_docs_structure.sh + - uses: ./.github/actions/bootstrap-mcpp - name: Configure mirror + Build mcpp from source (self-host) From 63348574e2832dce7b32caf09726ba5ade377aad Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:03:20 +0800 Subject: [PATCH 03/36] examples: the boundary on its own, a package that declares features, and a device language the engine does not know Three examples, each the first in the tree to demonstrate what it demonstrates. `09-heterogeneous/boundary` is the island boundary with nothing on top: the consumer writes `import boundary.kernels` and the project contains no seam, no header and no hand-written `.cppm`. Every example and every fixture in this repository and in mcpp:plugins kept a seam over the generated module, so the arrangement the plugins README records as measured -- a consumer importing the generated module and linking against an implementation compiled by another driver -- was running nowhere. Its README states the four rungs and what each one costs; the island is an ordinary C file so the example runs with no device. `11-features` declares features rather than consuming them. Every heterogeneous example passes `features = [...]` on a dependency edge and none had ever declared `[features]`, `[feature-deps]` or `[dev-dependencies]`. The criterion is not that the default build works -- it is that the default build's resolution does not name the optional package: `counters` appears 0 times without the feature and 2 times with it. `13-a-new-device-language` is a rule package that declares `device_extensions = [".toy"]` and `rule_module`, and a project that compiles a `.toy`. That a new device language costs no engine release is the property the accelerator design is built on, and its only instance was `rules-slang` in another repository. Three defects the third example found while it was being written, each now in its README because a rule author will meet all three: an action's command does not run from the package root, so a rule passes absolute paths; a pipeline's exit status is its last command's, so a summator that lost its input still exited 0 and produced a program that printed the wrong number; and the compiler itself must be a declared input, or editing it leaves every edge clean and the artifact keeps the previous compiler's bytes. Also in this commit: the 09 README's prose said "all four" over a table of six and never mentioned that two of its sub-examples generate their boundary; the seven examples pinned mcpp:plugins 0.3.0, which is the release before all six rules passed a depfile; and cuda/app/build.mcpp asserted that no source in the project names a generated file while line 51 of the same file correctly said the host half includes it. --- docs/README.md | 8 +- docs/zh/README.md | 8 +- examples/09-heterogeneous/README.md | 41 ++++--- examples/09-heterogeneous/boundary/README.md | 84 +++++++++++++++ examples/09-heterogeneous/boundary/build.mcpp | 41 +++++++ examples/09-heterogeneous/boundary/mcpp.toml | 14 +++ .../boundary/src/kernels/saxpy.c | 17 +++ .../09-heterogeneous/boundary/src/main.cpp | 24 +++++ examples/09-heterogeneous/cann/app/mcpp.toml | 2 +- examples/09-heterogeneous/cuda/README.md | 2 +- examples/09-heterogeneous/cuda/app/build.mcpp | 7 +- examples/09-heterogeneous/cuda/app/mcpp.toml | 2 +- examples/09-heterogeneous/hip/app/mcpp.toml | 2 +- .../09-heterogeneous/multi-backend/README.md | 2 +- .../09-heterogeneous/multi-backend/mcpp.toml | 2 +- examples/09-heterogeneous/sycl/app/mcpp.toml | 2 +- .../09-heterogeneous/vulkan/app/mcpp.toml | 2 +- examples/10-graphics/README.md | 2 +- examples/10-graphics/offscreen/mcpp.toml | 2 +- examples/11-features/README.md | 77 ++++++++++++++ examples/11-features/counters/mcpp.toml | 6 ++ .../11-features/counters/src/counters.cppm | 10 ++ examples/11-features/greeter/build.mcpp | 11 ++ examples/11-features/greeter/mcpp.toml | 38 +++++++ examples/11-features/greeter/src/greeter.cppm | 19 ++++ examples/11-features/greeter/src/main.cpp | 17 +++ examples/11-features/greeter/src/metrics.cpp | 6 ++ .../11-features/greeter/tests/test_greet.cpp | 15 +++ examples/13-a-new-device-language/README.md | 95 +++++++++++++++++ .../13-a-new-device-language/app/build.mcpp | 11 ++ .../13-a-new-device-language/app/mcpp.toml | 17 +++ .../app/src/kernels/answer.toy | 3 + .../13-a-new-device-language/app/src/main.cpp | 12 +++ .../rules-toy/mcpp.toml | 26 +++++ .../rules-toy/src/rules-toy.cppm | 100 ++++++++++++++++++ .../rules-toy/tools/toyc.sh | 20 ++++ 36 files changed, 713 insertions(+), 34 deletions(-) create mode 100644 examples/09-heterogeneous/boundary/README.md create mode 100644 examples/09-heterogeneous/boundary/build.mcpp create mode 100644 examples/09-heterogeneous/boundary/mcpp.toml create mode 100644 examples/09-heterogeneous/boundary/src/kernels/saxpy.c create mode 100644 examples/09-heterogeneous/boundary/src/main.cpp create mode 100644 examples/11-features/README.md create mode 100644 examples/11-features/counters/mcpp.toml create mode 100644 examples/11-features/counters/src/counters.cppm create mode 100644 examples/11-features/greeter/build.mcpp create mode 100644 examples/11-features/greeter/mcpp.toml create mode 100644 examples/11-features/greeter/src/greeter.cppm create mode 100644 examples/11-features/greeter/src/main.cpp create mode 100644 examples/11-features/greeter/src/metrics.cpp create mode 100644 examples/11-features/greeter/tests/test_greet.cpp create mode 100644 examples/13-a-new-device-language/README.md create mode 100644 examples/13-a-new-device-language/app/build.mcpp create mode 100644 examples/13-a-new-device-language/app/mcpp.toml create mode 100644 examples/13-a-new-device-language/app/src/kernels/answer.toy create mode 100644 examples/13-a-new-device-language/app/src/main.cpp create mode 100644 examples/13-a-new-device-language/rules-toy/mcpp.toml create mode 100644 examples/13-a-new-device-language/rules-toy/src/rules-toy.cppm create mode 100755 examples/13-a-new-device-language/rules-toy/tools/toyc.sh diff --git a/docs/README.md b/docs/README.md index 533b35e4..d43d4341 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,12 +26,12 @@ anything a reader needs is written here or in a specification instead. | To | Read | Run | |---|---|---| | write a program | [00](00-getting-started.md), [05](05-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | -| write a library others import | [10](10-publishing-a-library.md), [05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`03-features`](../examples/03-features/) | -| publish it | [02](02-pack-and-release.md), [10](10-publishing-a-library.md), [12](12-binary-distribution.md) | [`05-lib-distribution`](../examples/05-lib-distribution/), [`07-to-the-index`](../examples/07-to-the-index/) | +| write a library others import | [10](10-publishing-a-library.md), [05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | +| publish it | [02](02-pack-and-release.md), [10](10-publishing-a-library.md), [12](12-binary-distribution.md) | [`03-pack-static`](../examples/03-pack-static/), [`05-lib-distribution`](../examples/05-lib-distribution/) | | build for another machine | [16](16-the-target-triple.md), [15](15-openkal-cross.md), [13](13-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | -| use a GPU or an accelerator | [20](20-heterogeneous-builds.md), [18](18-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at `boundary/` | +| use a GPU or an accelerator | [20](20-heterogeneous-builds.md), [18](18-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | | add a rule, a language or a generator | [23](23-authoring-a-rule-package.md), [07](07-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`13-a-new-device-language`](../examples/13-a-new-device-language/) | -| add a package to the index | [10](10-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [`07-to-the-index`](../examples/07-to-the-index/) | +| add a package to the index | [10](10-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [21](21-commands-by-scenario.md) — the publishing scenarios | | change mcpp itself | [04](04-build-from-source.md), [09](09-release.md), [19](19-supported-versions.md) | — | Lessons also arrive as project templates, which a package ships and `mcpp new diff --git a/docs/zh/README.md b/docs/zh/README.md index 69c92820..bcdaeb86 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -23,12 +23,12 @@ | 目标 | 阅读 | 运行 | |---|---|---| | 写一个程序 | [00](00-getting-started.md)、[05](05-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | -| 写一个供他人 import 的库 | [10](10-publishing-a-library.md)、[05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`03-features`](../../examples/03-features/) | -| 发布它 | [02](02-pack-and-release.md)、[10](10-publishing-a-library.md)、[12](12-binary-distribution.md) | [`05-lib-distribution`](../../examples/05-lib-distribution/)、[`07-to-the-index`](../../examples/07-to-the-index/) | +| 写一个供他人 import 的库 | [10](10-publishing-a-library.md)、[05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | +| 发布它 | [02](02-pack-and-release.md)、[10](10-publishing-a-library.md)、[12](12-binary-distribution.md) | [`03-pack-static`](../../examples/03-pack-static/)、[`05-lib-distribution`](../../examples/05-lib-distribution/) | | 为另一台机器构建 | [16](16-the-target-triple.md)、[15](15-openkal-cross.md)、[13](13-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | -| 使用 GPU 或加速器 | [20](20-heterogeneous-builds.md)、[18](18-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 `boundary/` 开始 | +| 使用 GPU 或加速器 | [20](20-heterogeneous-builds.md)、[18](18-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | | 增加一条规则、一种语言或一个生成器 | [23](23-authoring-a-rule-package.md)、[07](07-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`13-a-new-device-language`](../../examples/13-a-new-device-language/) | -| 向索引添加一个包 | [10](10-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [`07-to-the-index`](../../examples/07-to-the-index/) | +| 向索引添加一个包 | [10](10-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [21](21-commands-by-scenario.md) —— 发布相关场景 | | 修改 mcpp 本身 | [04](04-build-from-source.md)、[09](09-release.md)、[19](19-supported-versions.md) | — | 课程也可以以**项目模板**的形式到达:模板由包提供,`mcpp new --template` 实例化 diff --git a/examples/09-heterogeneous/README.md b/examples/09-heterogeneous/README.md index 88b9609f..3b6e0896 100644 --- a/examples/09-heterogeneous/README.md +++ b/examples/09-heterogeneous/README.md @@ -8,6 +8,7 @@ belong to the structure and which to a vendor. | directory | model | device compiler | what it adds to the structure | |---|---|---|---| +| [`boundary/`](boundary/) | none — a C island | none | the boundary alone: a generated module the consumer imports, with no seam and no header anywhere in the project | | [`cuda/`](cuda/) | CUDA | the project's own clang (`-x cuda`), or nvcc | the driver relation stated as a fact and a floor, and two pairings nvcc cannot have | | [`vulkan/`](vulkan/) | Vulkan compute | glslang or shaderc, to SPIR-V | a device output that is a header rather than an object, and one artifact that runs on three devices | | [`sycl/`](sycl/) | SYCL | the dpcpp payload's clang | a second compiler with its own standard library, and a chained action for the device link | @@ -15,14 +16,16 @@ belong to the structure and which to a vendor. | [`cann/`](cann/) | Ascend C | the toolkit's BiSheng (`-x asc`) | a device object for hardware nobody in this repository has, and a host half that declines cleanly | | [`multi-backend/`](multi-backend/) | CUDA **and** Vulkan | both of the above | the other shape: backends that are additive rather than a seam, chosen at run time | -Start with `cuda/`. The four beside it assume it, and `multi-backend/` assumes -two of them. +Start with `boundary/`, which needs no device and isolates the interface +between an island and the C++ side. Then `cuda/`, which adds the device compiler +and a seam over that boundary. The four beside `cuda/` assume it, and +`multi-backend/` assumes two of them. -The first five are one shape — a **seam**: exactly one implementation exists in -the artifact and the choice was made at build time. `multi-backend/` is the -other — several implementations in one artifact, chosen when the program runs. -A program can take either; a library that is compiled once and consumed by -people whose machines differ can only take the second. +`boundary/` and the five model directories are one shape — a **seam**: exactly +one implementation exists in the artifact and the choice was made at build time. +`multi-backend/` is the other — several implementations in one artifact, chosen +when the program runs. A program can take either; a library that is compiled +once and consumed by people whose machines differ can only take the second. ## The structure @@ -34,14 +37,16 @@ is which compiler consumes the file, not which dialect the file is written in. **A seam.** The island's interface is `extern "C"` and free of standard-library types, because the two sides do not share a C++ ABI. A module — `app.saxpy` in -all four — turns that C interface back into a C++ one. The seam is the single +each of the five model directories — turns that C interface back into a C++ one. +`boundary/` is the one that stops before this step, so the two can be read side +by side. The seam is the single place where the implementation underneath becomes a different model or a CPU loop, and the single place a `cfg(accelerator = ...)` section has to apply. Without it, every importer would be backend-specific. The seam carries one more entry point, `saxpy_device_name()`, for a reason -worth stating: all four islands and all four CPU fallbacks produce the same -four numbers. Without a name in the output, a run that silently fell back to +worth stating: every island and every CPU fallback here produces the same four +numbers. Without a name in the output, a run that silently fell back to the CPU is indistinguishable from a run on a device -- in a set of examples whose whole subject is which device ran the computation. Each backend fills it in with the device it used, the CPU file fills it in with `cpu`, and both do so @@ -71,6 +76,16 @@ anything is compiled. The two variants land in different artifact directories because the device axis is part of the build's identity, so alternating between them does not rebuild from scratch. +**A boundary, generated or written.** The `extern "C"` declarations under the +seam are each entry point's signature stated a second time, at the one place a +disagreement is invisible: C language linkage does not mangle, and an island and +its host fallback are never in one link. `mcpp.tools.island` reads the marked +declarations out of both implementations and writes that header and a module +over it, so the signatures exist once. `boundary/`, `cuda/` and `sycl/` take +that route; `hip/`, `vulkan/` and `cann/` keep the header written by hand, so +the two can be read side by side. [`boundary/`](boundary/) states what each +rung costs. + **A rule package, which brings its own environment.** Every vendor spelling — `--cuda-gpu-arch`, `-gencode`, `--target-env`, `-fsycl-targets`, `-fsycl-link`, `--cce-aicore-arch` — lives in `mcpp:plugins`, a package the project depends on @@ -84,7 +99,7 @@ selects it and the accelerator it serves, so a project writes one edge and no ```toml [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-cuda", "tools-island"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-cuda", "tools-island"], host-module = true } ``` `multi-backend/` is the one example here that also pins a version, and it does @@ -94,8 +109,8 @@ way — see *One package, one version* in `docs/05-mcpp-toml.md`. ## The layers underneath -A device build reaches hardware through four layers, and each of the four -examples uses all of them. Confusing two of them is the most common way a +A device build reaches hardware through four layers, and each of the model +directories uses all of them. Confusing two of them is the most common way a working build stops working on another machine. | layer | owns | example | diff --git a/examples/09-heterogeneous/boundary/README.md b/examples/09-heterogeneous/boundary/README.md new file mode 100644 index 00000000..d8079da6 --- /dev/null +++ b/examples/09-heterogeneous/boundary/README.md @@ -0,0 +1,84 @@ +# The boundary, on its own — the ladder's lowest rung + +The consumer imports a module that this build generated, and the project +contains no seam, no header, and no hand-written `.cppm`. + +``` +cd examples/09-heterogeneous/boundary +mcpp run # 12 24 36 48 +``` + +Read this before [`../cuda`](../cuda/). It isolates the boundary between an +island and the C++ side; `cuda/` then adds the device compiler and the seam over +it, and the difference between the two is what each rung buys. + +## The whole project + +``` +mcpp.toml one dependency edge +build.mcpp scan the island, emit the boundary +src/kernels/saxpy.c the island +src/main.cpp import boundary.kernels; +``` + +`mcpp.tools.island` reads the entry points marked `MCPP_EXPORT_C` and writes two +files into the build directory: the `extern "C"` header the island reads, and a +module over it whose whole content is a re-export. + +```cpp +// generated +module; +#include "boundary.kernels.h" +export module boundary.kernels; + +export using ::saxpy_device; +export using ::saxpy_device_name; +``` + +Re-exporting names rather than restating signatures is what lets the generator +work without a C parser: it needs only the identifier before the `(`. It is also +why no second copy of a signature exists — at a C-linkage boundary that is the +copy that can disagree without anything noticing. + +## The island is C here, and that is the only simplification + +An ordinary `.c` file, so this example runs on any machine with no device +toolkit. What makes it an island is the property the boundary exists for: it is +compiled separately, it cannot import a module, and its interface is +`extern "C"`. Substituting a `.cu` and a device rule is what `../cuda` shows, +and nothing about the boundary changes when that happens. + +## Four rungs, and what each one costs + +| rung | written by hand | the consumer writes | the consumer gets | +|---|---|---|---| +| **L0 — this example** | nothing but the marked entry points | `import boundary.kernels` | the island's own interface: pointers and a count | +| L1 — `../cuda`, `../sycl` | a seam module over the generated one | `import app.saxpy` | the interface the project designed | +| L2 | a seam, and the entry list passed to `emit` directly | `import app.saxpy` | the same, for entry points a scan cannot see | +| L3 — `../hip`, `../vulkan`, `../cann` | the header and the module | `import app.saxpy` | the same, with the signature written twice | + +**What L0 does not have.** The interface is C-shaped: `saxpy_device(2.0f, x, y, +out, 4)` rather than a span. And there is no place for a +`cfg(accelerator = ...)` section to apply, because a seam is the single point at +which one implementation is exchanged for another. A project with one island and +one backend can stop here; a project that will swap backends needs L1. + +**What the generator will not do.** Which functions, which types, and what +happens on failure are design decisions, so the C++ interface stays +hand-written. The generator produces what is mechanical around the entry +points — the include guard, the `extern "C"` block, the `__cplusplus` dance and +the module wrapper. + +## Criteria + +| criterion | measured | +|---|---| +| the consumer imports the generated module and names no header | `grep -rn '#include' src/` is empty | +| the project has no hand-written `.cppm` | the file list above | +| the result is the family's | `12 24 36 48` | + +The first two are why this example exists. `mcpp:plugins` records that a +consumer importing the generated module links against an implementation +compiled by a different driver, measured with GCC 16.1; until this example +nothing in either repository ran that arrangement — every fixture and every +example kept a seam on top. diff --git a/examples/09-heterogeneous/boundary/build.mcpp b/examples/09-heterogeneous/boundary/build.mcpp new file mode 100644 index 00000000..bddbf356 --- /dev/null +++ b/examples/09-heterogeneous/boundary/build.mcpp @@ -0,0 +1,41 @@ +import std; +import mcpp; +import mcpp.tools.island; + +// The lowest rung: the generated boundary module IS the interface, and this +// project contains no seam and no header. +// +// Both halves are handed to `scan` unconditionally. They are the only place the +// entry points' signatures are written, and the generator is the only point at +// which both texts exist at once -- C language linkage does not mangle, so two +// halves that declared one name differently would link cleanly and read the +// arguments by whichever signature each was compiled with. +int main() { + const std::string root = mcpp::manifest_dir(); + const std::vector halves{ + root + "/src/kernels/saxpy.c", + }; + for (auto const& f : halves) mcpp::rerun_if_changed(f.c_str()); + + mcpp::tools::island::options opt; + opt.module_name = "boundary.kernels"; + opt.out_dir = std::string(mcpp::out_dir()) + "/island"; + opt.produced_by = "the boundary example"; + + const auto entries = mcpp::tools::island::scan(halves, opt); + if (!entries) return 1; + const auto out = mcpp::tools::island::emit(*entries, opt); + if (!out) return 1; + + // The island reads the generated header through a forced-include flag, so + // no source in this project names a generated file. + // + // `cflag` and not `cxxflag`: the island is C, and forcing a header into + // every C++ translation unit would put declarations ahead of the + // `export module` line of a module interface, which is ill-formed. + for (auto const& f : mcpp::tools::island::force_include_flags( + out->header_file, mcpp::compiler())) + mcpp::cflag(f.c_str()); + mcpp::generated(out->interface_file.c_str()); + return 0; +} diff --git a/examples/09-heterogeneous/boundary/mcpp.toml b/examples/09-heterogeneous/boundary/mcpp.toml new file mode 100644 index 00000000..5decbd75 --- /dev/null +++ b/examples/09-heterogeneous/boundary/mcpp.toml @@ -0,0 +1,14 @@ +[package] +name = "boundary" +version = "0.1.0" + +[language] +standard = "c++23" + +# `tools-island` is not a device rule: it claims no extension and names no rule +# module, so the edge states `host-module = true` itself. +[build-dependencies.mcpp] +plugins = { version = "0.4.0", features = ["tools-island"], host-module = true } + +[build] +sources = ["src/*.cpp", "src/kernels/*.c"] diff --git a/examples/09-heterogeneous/boundary/src/kernels/saxpy.c b/examples/09-heterogeneous/boundary/src/kernels/saxpy.c new file mode 100644 index 00000000..491ab608 --- /dev/null +++ b/examples/09-heterogeneous/boundary/src/kernels/saxpy.c @@ -0,0 +1,17 @@ +/* The island. An ordinary C file here, so this example runs on any machine; + substituting a `.cu` and a device rule is what `../cuda` shows. What makes + this an island is the property the boundary exists for: it is compiled + separately, it cannot import a module, and its interface is `extern "C"`. */ + +static const char* g_ran_on = ""; + +MCPP_EXPORT_C +int saxpy_device(float a, const float* x, const float* y, + float* out, unsigned n) { + for (unsigned i = 0; i < n; ++i) out[i] = a * x[i] + y[i]; + g_ran_on = "cpu (this example's island is an ordinary C file)"; + return 0; +} + +MCPP_EXPORT_C +const char* saxpy_device_name(void) { return g_ran_on; } diff --git a/examples/09-heterogeneous/boundary/src/main.cpp b/examples/09-heterogeneous/boundary/src/main.cpp new file mode 100644 index 00000000..dd4e03fc --- /dev/null +++ b/examples/09-heterogeneous/boundary/src/main.cpp @@ -0,0 +1,24 @@ +import std; + +// THE CONSUMER IMPORTS THE GENERATED MODULE. There is no seam in this project +// and no header in its source tree: `boundary.kernels` is written into the +// build directory by `mcpp.tools.island` during this build. +// +// What arrives is the island's own interface -- pointers and a count, an +// `extern "C"` signature. `../cuda` adds a hand-written seam over exactly this +// module and turns it into spans; the README states what each rung costs. +import boundary.kernels; + +int main() { + const float x[]{ 1, 2, 3, 4 }; + const float y[]{ 10, 20, 30, 40 }; + float out[4]{}; + + if (saxpy_device(2.0f, x, y, out, 4) != 0) { + std::println(std::cerr, "saxpy_device failed"); + return 1; + } + for (int i = 0; i < 4; ++i) std::print("{}{}", out[i], i == 3 ? "\n" : " "); + std::println("ran on: {}", saxpy_device_name()); + return 0; +} diff --git a/examples/09-heterogeneous/cann/app/mcpp.toml b/examples/09-heterogeneous/cann/app/mcpp.toml index 37375fcc..27625bc0 100644 --- a/examples/09-heterogeneous/cann/app/mcpp.toml +++ b/examples/09-heterogeneous/cann/app/mcpp.toml @@ -14,7 +14,7 @@ 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.3.0", features = ["rules-ascendc"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-ascendc"], host-module = true } # NO [xlings.workspace]. `mcpp.rules.ascendc` declares `xim:cann-toolkit` # itself, gated on this accelerator and on the feature that selects the rule -- diff --git a/examples/09-heterogeneous/cuda/README.md b/examples/09-heterogeneous/cuda/README.md index b8af616f..68e5b0cc 100644 --- a/examples/09-heterogeneous/cuda/README.md +++ b/examples/09-heterogeneous/cuda/README.md @@ -91,7 +91,7 @@ The rule names it. This project writes one edge and no payload list at all: ```toml [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-cuda", "tools-island"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-cuda", "tools-island"], host-module = true } ``` `mcpp.rules.cuda` declares nvcc, cudart, cuRAND's headers, CCCL and the driver diff --git a/examples/09-heterogeneous/cuda/app/build.mcpp b/examples/09-heterogeneous/cuda/app/build.mcpp index 1ff04deb..8ce24a18 100644 --- a/examples/09-heterogeneous/cuda/app/build.mcpp +++ b/examples/09-heterogeneous/cuda/app/build.mcpp @@ -16,9 +16,10 @@ import mcpp.rules.cuda; // arguments by whichever signature it was compiled with. `scan` is the only // point at which both texts exist at once, and it refuses there. // -// The island reads the generated header through the compiler's forced-include -// flag, so no source in this project names a generated file and the project has -// no header in its source tree at all. +// The ISLAND reads the generated header through the compiler's forced-include +// flag, so it names no generated file. The host half does include it by name -- +// `src/cpu/saxpy.cpp` says so at its first line and explains why the two sides +// differ. What this project has none of is a header in its SOURCE TREE. // WHERE THE FLAGS GO, AND WHY IT IS NOT `mcpp::cflag`. // // `force_include_flags` makes a compiler read the generated boundary header diff --git a/examples/09-heterogeneous/cuda/app/mcpp.toml b/examples/09-heterogeneous/cuda/app/mcpp.toml index 16279404..7823492b 100644 --- a/examples/09-heterogeneous/cuda/app/mcpp.toml +++ b/examples/09-heterogeneous/cuda/app/mcpp.toml @@ -23,7 +23,7 @@ default = "llvm@22.1.8" # `extern "C"` boundary the island's compiler reads and the module the seam # imports, so neither signature is stated twice. [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-cuda", "tools-island"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-cuda", "tools-island"], 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. diff --git a/examples/09-heterogeneous/hip/app/mcpp.toml b/examples/09-heterogeneous/hip/app/mcpp.toml index e982626e..7cba9239 100644 --- a/examples/09-heterogeneous/hip/app/mcpp.toml +++ b/examples/09-heterogeneous/hip/app/mcpp.toml @@ -20,7 +20,7 @@ import_std = true default = "llvm@22.1.8" [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-hip"], host-module = true } +plugins = { version = "0.4.0", 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 diff --git a/examples/09-heterogeneous/multi-backend/README.md b/examples/09-heterogeneous/multi-backend/README.md index 34ea8bc1..bd61e58d 100644 --- a/examples/09-heterogeneous/multi-backend/README.md +++ b/examples/09-heterogeneous/multi-backend/README.md @@ -93,7 +93,7 @@ saying it after the vocabulary grows. ```toml [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-cuda", "rules-spirv"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-cuda", "rules-spirv"], host-module = true } ``` Two rules, in one build program, which is what an additive-backend package diff --git a/examples/09-heterogeneous/multi-backend/mcpp.toml b/examples/09-heterogeneous/multi-backend/mcpp.toml index 401da1c3..42cd5ae1 100644 --- a/examples/09-heterogeneous/multi-backend/mcpp.toml +++ b/examples/09-heterogeneous/multi-backend/mcpp.toml @@ -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.3.0", features = ["rules-cuda", "rules-spirv"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-cuda", "rules-spirv"], host-module = true } # ── the payloads, gated on the device they are for ────────────────────────── # diff --git a/examples/09-heterogeneous/sycl/app/mcpp.toml b/examples/09-heterogeneous/sycl/app/mcpp.toml index b876124c..a997f2ff 100644 --- a/examples/09-heterogeneous/sycl/app/mcpp.toml +++ b/examples/09-heterogeneous/sycl/app/mcpp.toml @@ -20,7 +20,7 @@ import_std = true default = "llvm@22.1.8" [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-sycl", "tools-island"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-sycl", "tools-island"], host-module = true } # The SYCL runtime, on the artifact's runtime search path. mcpp's private # loader does not consult /usr/lib, so `libsycl.so.9` -- which the rule diff --git a/examples/09-heterogeneous/vulkan/app/mcpp.toml b/examples/09-heterogeneous/vulkan/app/mcpp.toml index 820df29b..6fdd6a11 100644 --- a/examples/09-heterogeneous/vulkan/app/mcpp.toml +++ b/examples/09-heterogeneous/vulkan/app/mcpp.toml @@ -13,7 +13,7 @@ import_std = true # The rule that compiles the shaders lives in the official plugin collection, # selected by its feature; `build.mcpp` imports it as `mcpp.rules.spirv`. [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-spirv"], host-module = true } # The Khronos loader, built by the index rather than taken from the host, and # the adapter that makes the host's own ICDs reachable from a binary running diff --git a/examples/10-graphics/README.md b/examples/10-graphics/README.md index f3536f67..ed9500e5 100644 --- a/examples/10-graphics/README.md +++ b/examples/10-graphics/README.md @@ -53,7 +53,7 @@ tells the two apart, which is why it is printed. ```toml [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-spirv"], host-module = true } [build] accel = "vulkan1.2" diff --git a/examples/10-graphics/offscreen/mcpp.toml b/examples/10-graphics/offscreen/mcpp.toml index e8849d5b..f3ce5431 100644 --- a/examples/10-graphics/offscreen/mcpp.toml +++ b/examples/10-graphics/offscreen/mcpp.toml @@ -13,7 +13,7 @@ import_std = true # One edge. `mcpp.rules.spirv` declares the shader compiler it drives, so this # project names no payload for it. [build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } +plugins = { version = "0.4.0", features = ["rules-spirv"], host-module = true } # The Khronos loader. Unconditional, and the reason is a rule of the engine # rather than a preference: A DEPENDENCY CANNOT BE CONDITIONED ON A LAYER. diff --git a/examples/11-features/README.md b/examples/11-features/README.md new file mode 100644 index 00000000..da8323dd --- /dev/null +++ b/examples/11-features/README.md @@ -0,0 +1,77 @@ +# 11 — Features: what a package declares, and what a consumer pays for + +Every heterogeneous example in this repository *consumes* a feature +(`features = ["rules-cuda"]` on a dependency edge). This one **declares** them. + +``` +cd examples/11-features/greeter +mcpp run # HELLO, WORLD! metrics: off +mcpp run --features metrics # HELLO, WORLD! metrics: on +mcpp test # the dev-dependency reaches the test +``` + +## Three shapes of feature, in one manifest + +```toml +[features] +default = ["shout"] + +shout = {} # a compile macro and nothing else + +[features.metrics] # ...and a source +sources = ["src/metrics.cpp"] + +[feature-deps.metrics] # ...and a dependency +counters = { path = "../counters" } +``` + +| shape | what it adds | reached in the source as | +|---|---|---| +| `shout = {}` | `MCPP_FEATURE_SHOUT` | `#ifdef MCPP_FEATURE_SHOUT` | +| `[features.metrics] sources` | one file in the source set | the file is compiled or it is not | +| `[feature-deps.metrics]` | a package in the graph | `import counters;` inside that file | + +Two more keys are here because a library has them and no other example does: + +```toml +[dev-dependencies] # reaches tests, never the artifact +counters = { path = "../counters" } + +[profile.release] +opt_level = 3 +``` + +## The criterion + +Not "the default build still works" — that passes when the optional dependency +is resolved and simply unused. The criterion is that **the default build's +resolution does not name the optional package**: + +``` +mcpp build counters appears 0 times in the build output +mcpp build --features metrics counters appears 2 times +``` + +Measured on 2026.9.8.1. That is the property `[feature-deps]` exists for: a +consumer that does not ask for the backend does not download it, does not +compile it, and does not link it. + +## A build program sees the resolved feature set + +```cpp +if (mcpp::has_feature("metrics")) + mcpp::warning("the metrics feature is active; `counters` is in the graph"); +``` + +A rule or a generator acts on the feature set without the project stating the +decision a second time. This is the mechanism the accelerator lanes use: a rule +package declares its toolkit under the feature that selects it, so a build that +does not name the feature installs nothing. + +## Current limitations + +**A default feature is turned off in the manifest, not on the command line.** +There is no `--no-default-features`. `mcpp run --features metrics` yields +`default ∪ {metrics}`; to build without `shout`, `[features] default` is +edited. With `default = []` this example prints `Hello, world.` instead of +`HELLO, WORLD!`. diff --git a/examples/11-features/counters/mcpp.toml b/examples/11-features/counters/mcpp.toml new file mode 100644 index 00000000..bc282786 --- /dev/null +++ b/examples/11-features/counters/mcpp.toml @@ -0,0 +1,6 @@ +[package] +name = "counters" +version = "0.1.0" + +[language] +standard = "c++23" diff --git a/examples/11-features/counters/src/counters.cppm b/examples/11-features/counters/src/counters.cppm new file mode 100644 index 00000000..fd85133b --- /dev/null +++ b/examples/11-features/counters/src/counters.cppm @@ -0,0 +1,10 @@ +// The optional backend. A consumer that does not name the `metrics` feature +// never resolves this package, which is the criterion the README states. +export module counters; +import std; + +export namespace counters { +inline int calls = 0; +inline void record() { ++calls; } +inline std::string report() { return std::format("{} call(s)", calls); } +} diff --git a/examples/11-features/greeter/build.mcpp b/examples/11-features/greeter/build.mcpp new file mode 100644 index 00000000..77c7b1bd --- /dev/null +++ b/examples/11-features/greeter/build.mcpp @@ -0,0 +1,11 @@ +import std; +import mcpp; + +// A build program sees the feature set that was resolved, so a rule or a +// generator can act on it without the project repeating the decision in a +// second place. +int main() { + if (mcpp::has_feature("metrics")) + mcpp::warning("the metrics feature is active; `counters` is in the graph"); + return 0; +} diff --git a/examples/11-features/greeter/mcpp.toml b/examples/11-features/greeter/mcpp.toml new file mode 100644 index 00000000..feae21de --- /dev/null +++ b/examples/11-features/greeter/mcpp.toml @@ -0,0 +1,38 @@ +[package] +name = "greeter" +version = "0.1.0" + +[language] +standard = "c++23" + +[targets.greet] +kind = "bin" +main = "src/main.cpp" + +# FEATURES ARE ADDITIVE AND DECLARED HERE. `default` is what a consumer that +# says nothing gets; every other feature is opted into by name. +[features] +default = ["shout"] + +# A feature that only turns a compile macro on. Every active feature defines +# `MCPP_FEATURE_`, so a source can branch without the manifest naming a +# file. +shout = {} + +# A feature that ADDS A SOURCE. The file is compiled only when the feature is +# active, so a consumer that does not ask for it does not pay for it. +[features.metrics] +sources = ["src/metrics.cpp"] + +# ...AND PULLS A DEPENDENCY. `[feature-deps.]` is what makes an optional +# backend optional: a build that does not name `metrics` resolves a graph in +# which `counters` does not appear at all. +[feature-deps.metrics] +counters = { path = "../counters" } + +# Used by `mcpp test` and by nothing the artifact contains. +[dev-dependencies] +counters = { path = "../counters" } + +[profile.release] +opt_level = 3 diff --git a/examples/11-features/greeter/src/greeter.cppm b/examples/11-features/greeter/src/greeter.cppm new file mode 100644 index 00000000..18a72a6d --- /dev/null +++ b/examples/11-features/greeter/src/greeter.cppm @@ -0,0 +1,19 @@ +export module greeter; +import std; + +export namespace greeter { + +// `MCPP_FEATURE_SHOUT` is defined because `shout` is in `[features] default`. +// A consumer that builds with `--no-default-features` gets the other branch, +// and neither branch is named in the manifest. +inline std::string greet(std::string_view who) { +#ifdef MCPP_FEATURE_SHOUT + std::string s = std::format("HELLO, {}", who); + for (auto& c : s) c = static_cast(std::toupper(static_cast(c))); + return s + "!"; +#else + return std::format("Hello, {}.", who); +#endif +} + +} // namespace greeter diff --git a/examples/11-features/greeter/src/main.cpp b/examples/11-features/greeter/src/main.cpp new file mode 100644 index 00000000..758e64f9 --- /dev/null +++ b/examples/11-features/greeter/src/main.cpp @@ -0,0 +1,17 @@ +import std; +import greeter; + +#ifdef MCPP_FEATURE_METRICS +extern "C" void greeter_record_call(); +#endif + +int main() { + std::println("{}", greeter::greet("world")); +#ifdef MCPP_FEATURE_METRICS + greeter_record_call(); + std::println("metrics: on"); +#else + std::println("metrics: off"); +#endif + return 0; +} diff --git a/examples/11-features/greeter/src/metrics.cpp b/examples/11-features/greeter/src/metrics.cpp new file mode 100644 index 00000000..510139bf --- /dev/null +++ b/examples/11-features/greeter/src/metrics.cpp @@ -0,0 +1,6 @@ +// Compiled only when the `metrics` feature is active: `[features.metrics]` +// names this file. Under any other feature set it is not in the source set, +// so `counters` is not linked and not resolved. +import counters; + +extern "C" void greeter_record_call() { counters::record(); } diff --git a/examples/11-features/greeter/tests/test_greet.cpp b/examples/11-features/greeter/tests/test_greet.cpp new file mode 100644 index 00000000..9b1d864f --- /dev/null +++ b/examples/11-features/greeter/tests/test_greet.cpp @@ -0,0 +1,15 @@ +// `mcpp test` compiles every `tests/**/*.cpp` as its own program. This one uses +// `counters`, which reaches it through `[dev-dependencies]` -- a dependency the +// artifact never links. +import std; +import greeter; +import counters; + +int main() { + const auto s = greeter::greet("x"); + if (s.empty()) { std::println(std::cerr, "greet returned nothing"); return 1; } + counters::record(); + if (counters::calls != 1) { std::println(std::cerr, "counters did not record"); return 1; } + std::println("ok: {} / {}", s, counters::report()); + return 0; +} diff --git a/examples/13-a-new-device-language/README.md b/examples/13-a-new-device-language/README.md new file mode 100644 index 00000000..54135d84 --- /dev/null +++ b/examples/13-a-new-device-language/README.md @@ -0,0 +1,95 @@ +# 13 — A device language the engine has never heard of + +A rule package teaches mcpp to compile `.toy`, and a project uses it. No mcpp +release is involved: `.toy` is not in the engine's built-in device-extension +table and will never be. + +``` +cd examples/13-a-new-device-language/app +mcpp run # toy_answer() = 42 +``` + +## The two keys + +`rules-toy/mcpp.toml` declares them on the feature that selects the rule: + +```toml +[features.rules-toy] +sources = ["src/rules-toy.cppm"] +rule_module = "example.rules.toy" +device_extensions = [".toy"] +``` + +| key | effect | +|---|---| +| `device_extensions` | a consumer that activates the feature gets `.toy` classified as a **device source**: never scanned for imports, never producing a BMI, and refused if no action claims it | +| `rule_module` | the module the consumer's build program imports. It implies `host-module = true`, so the consumer writes the feature and nothing else | + +The consumer's whole declaration: + +```toml +[dependencies] +rules-toy = { path = "../rules-toy", features = ["rules-toy"] } + +[build] +sources = ["src/*.cpp", "src/kernels/*.toy"] +``` + +Device extensions are not in the default source glob. A `.toy` is compiled +because the manifest names it. + +## The compiler + +`rules-toy/tools/toyc.sh` is the entire toolchain for `.toy`: a file is a list +of integers and the entry point returns their sum. A shell script rather than a +vendor toolkit, because the subject here is the build graph — how a language the +engine does not know reaches the link — and a real device compiler would only +add a download to it. + +The generated C++ declares the entry point `extern "C"`, for the same reason a +device island's boundary is: the two sides are produced by different compilers +and do not share a C++ ABI. + +## What this example is the first to demonstrate + +Every other rule in this repository is built into `mcpp:plugins` and compiles an +extension the engine already knows. This one adds a language from outside, which +is the property the accelerator design is built on and which nothing in the tree +exercised. + +## Criteria + +Three, each measured while this example was written, and each corresponding to +a defect the first version had: + +| criterion | measured | +|---|---| +| a `.toy` is compiled and its output joins the link, on an engine that does not know the extension | `toy_answer() = 42` | +| editing the `.toy` reaches the artifact | adding `1` to the file: `42` → `43` | +| **editing the compiler reaches the artifact** | changing `toyc.sh` to add 100: `42` → `142` | + +The third is the one that fails silently. An action whose only declared input is +its source leaves every edge clean when the compiler changes, so the artifact +keeps the bytes the previous compiler produced. `rules-toy` declares the script +as an input alongside the source. + +## Three things the first version got wrong + +Recorded because each is a mistake a rule author will make once. + +**An action's command does not run from the package root.** +`mcpp::device_sources()` answers with package-root-relative paths, and the +command runs from the build directory. The relative path reached `toyc` +unchanged and the read failed there. The rule passes an absolute path. + +**A pipeline's exit status is its last command's.** The first `toyc` summed with +`sed … | grep … | paste -sd+ - | bc`. When an earlier stage produced nothing, +`bc` still exited 0, so `set -e` never fired and the script wrote a program that +compiled, linked, ran, and printed `0`. It sums with one `awk` now. + +**A rule finds its own files through `mcpp::dep_dir`, under the name the +consumer declared.** `dep_dir("rules-toy")` answers; `dep_dir("example.rules-toy")` +and `dep_dir("example:rules-toy")` return empty, because the key is the spelling +in the consumer's `[dependencies]`. The rule exposes `options::rule_dir` so a +consumer that declares the edge under another key can say so, and refuses with a +message naming what it looked for rather than running `sh /tools/toyc.sh`. diff --git a/examples/13-a-new-device-language/app/build.mcpp b/examples/13-a-new-device-language/app/build.mcpp new file mode 100644 index 00000000..c5ecc72b --- /dev/null +++ b/examples/13-a-new-device-language/app/build.mcpp @@ -0,0 +1,11 @@ +import std; +import mcpp; +import example.rules.toy; + +int main() { + // The rule reads the device set from the graph, so this program names no + // file. What it must track is the SET of paths: a `.toy` appearing or + // disappearing changes what the graph should be. + mcpp::rerun_if_changed_glob("src/kernels/**/*.toy"); + return example::rules::toy::compile() ? 0 : 1; +} diff --git a/examples/13-a-new-device-language/app/mcpp.toml b/examples/13-a-new-device-language/app/mcpp.toml new file mode 100644 index 00000000..322f351b --- /dev/null +++ b/examples/13-a-new-device-language/app/mcpp.toml @@ -0,0 +1,17 @@ +[package] +name = "toyapp" +version = "0.1.0" + +[language] +standard = "c++23" + +# ONE EDGE. `rule_module` on the feature implies `host-module = true`, so the +# consumer names the feature and nothing else. +[dependencies] +rules-toy = { path = "../rules-toy", features = ["rules-toy"] } + +# `.toy` IS NOT IN THE DEFAULT SOURCE GLOB. Device sources are opted into by +# naming them -- a package that vendors one it builds elsewhere must not start +# compiling it on an mcpp upgrade. +[build] +sources = ["src/*.cpp", "src/kernels/*.toy"] diff --git a/examples/13-a-new-device-language/app/src/kernels/answer.toy b/examples/13-a-new-device-language/app/src/kernels/answer.toy new file mode 100644 index 00000000..76a6b520 --- /dev/null +++ b/examples/13-a-new-device-language/app/src/kernels/answer.toy @@ -0,0 +1,3 @@ +# A .toy file is a list of integers. The entry point returns their sum. +40 +2 diff --git a/examples/13-a-new-device-language/app/src/main.cpp b/examples/13-a-new-device-language/app/src/main.cpp new file mode 100644 index 00000000..a49debf5 --- /dev/null +++ b/examples/13-a-new-device-language/app/src/main.cpp @@ -0,0 +1,12 @@ +import std; + +// Defined by C++ that `toyc` wrote from `src/kernels/answer.toy`. The +// declaration is `extern "C"` for the same reason a device island's is: the two +// sides are produced by different compilers and do not share a C++ ABI. +extern "C" int toy_answer(); + +int main() { + const int v = toy_answer(); + std::println("toy_answer() = {}", v); + return v == 42 ? 0 : 1; +} diff --git a/examples/13-a-new-device-language/rules-toy/mcpp.toml b/examples/13-a-new-device-language/rules-toy/mcpp.toml new file mode 100644 index 00000000..1b5fd874 --- /dev/null +++ b/examples/13-a-new-device-language/rules-toy/mcpp.toml @@ -0,0 +1,26 @@ +[package] +name = "rules-toy" +namespace = "example" +version = "0.1.0" +description = "A rule package that teaches mcpp a device language the engine has never heard of" +license = "Apache-2.0" + +# THE TWO KEYS THAT MAKE A NEW DEVICE LANGUAGE COST NO ENGINE RELEASE. +# +# `device_extensions` says which sources this rule compiles. A consumer that +# activates the feature gets `.toy` classified as a DEVICE source: never scanned +# for imports, never producing a BMI, and refused if no action claims it. +# +# `rule_module` says which module the consumer's build program imports to reach +# the rule. It also implies `host-module = true`, so a consumer writes the +# feature and nothing else. +# +# `.toy` is not in the engine's built-in table and never will be. The engine +# knows what a device source IS; it does not know that `.toy` is anything. +[features] +default = [] + +[features.rules-toy] +sources = ["src/rules-toy.cppm"] +rule_module = "example.rules.toy" +device_extensions = [".toy"] diff --git a/examples/13-a-new-device-language/rules-toy/src/rules-toy.cppm b/examples/13-a-new-device-language/rules-toy/src/rules-toy.cppm new file mode 100644 index 00000000..242cbf58 --- /dev/null +++ b/examples/13-a-new-device-language/rules-toy/src/rules-toy.cppm @@ -0,0 +1,100 @@ +// A rule for `.toy`, a language whose entire definition is "one integer per +// line, and the file's name is the entry point". +// +// The compiler is a shell script rather than a vendor toolkit, for the same +// reason `tests/e2e/607` uses `cat` as a device linker: the subject is the +// GRAPH -- how a language that the engine does not know reaches the link -- +// and a real device compiler would only add a download to it. +export module example.rules.toy; + +import std; +import mcpp; + +export namespace example::rules::toy { + +struct options { + std::string out_dir = std::string(mcpp::out_dir()); + + // Where this rule's own files are. `mcpp::dep_dir` answers under the name + // the CONSUMER declared in `[dependencies]`, which is why the default is a + // string this package cannot verify -- a consumer that declares the edge + // under another key passes its own spelling here. + std::string rule_dir = std::string(mcpp::dep_dir("rules-toy")); +}; + +// Compile every `.toy` the consumer listed. `mcpp::device_sources()` returns +// the device sources of the package being built, so the rule reads the source +// set from the graph rather than asking the project to repeat it. +inline bool compile(options opt = {}) { + const std::string root = mcpp::manifest_dir(); + if (root.empty()) { + std::println(std::cerr, + "example.rules.toy: no mcpp build context -- this runs from build.mcpp"); + return false; + } + + if (opt.rule_dir.empty()) { + std::println(std::cerr, + "example.rules.toy: cannot locate this rule package. `mcpp::dep_dir` " + "answers under the name the consumer declared; this rule looked for " + "`rules-toy`. Pass `options::rule_dir` if the edge is declared " + "under another key."); + return false; + } + const std::string script = opt.rule_dir + "/tools/toyc.sh"; + + // `mcpp::device_sources()` is one string, one path per line, and it is the + // package's WHOLE device set rather than this rule's share of it. A rule + // selects by the extension it claims and leaves the rest: a project with a + // second backend puts that backend's sources in the same list, and handing + // them to `toyc` would fail on a file `toyc` never claimed. + std::vector mine; + std::string_view all(mcpp::device_sources()); + for (std::size_t i = 0; i <= all.size();) { + const auto sep = all.find('\n', i); + const auto one = all.substr(i, sep == std::string_view::npos ? all.size() - i : sep - i); + i = sep == std::string_view::npos ? all.size() + 1 : sep + 1; + if (one.ends_with(".toy")) mine.emplace_back(one); + } + + bool any = false; + for (auto const& src : mine) { + const std::string stem = std::filesystem::path(src).stem().string(); + const std::string gen = opt.out_dir + "/toy_" + stem + ".cpp"; + + // The strings outlive the action. `a.id = ("toy:" + stem).c_str()` + // would hand it a pointer into a temporary that is gone by `submit`. + const std::string id = "toy:" + stem; + const std::string desc = "compile " + src; + // ABSOLUTE. `mcpp::device_sources()` answers package-root-relative, and + // an action's command does not run from the package root -- it runs + // from the build directory. Measured: the relative path reached `toyc` + // unchanged and the read failed there. + const std::string abs = root + "/" + src; + + mcpp::action a; + a.id = id.c_str(); + // `source`: what this produces is C++ that mcpp then compiles. A rule + // whose compiler emitted an object directly would use `object`. + a.role = "source"; + a.description = desc.c_str(); + a.arg("sh"); a.arg(script.c_str()); a.arg(abs.c_str()); a.arg(gen.c_str()); + a.arg(stem.c_str()); + a.input(abs.c_str()); + // THE COMPILER IS AN INPUT TOO. Without this line, editing `toyc.sh` + // leaves every edge clean and the artifact keeps the bytes the previous + // compiler produced -- a green build over a stale result, which is the + // failure that is hardest to notice. Measured while writing this + // example: a fix to the summator did not reach the program. + a.input(script.c_str()); + a.output(gen.c_str()); + a.submit(); + any = true; + } + + if (!any) + std::println(std::cerr, "example.rules.toy: no `.toy` source in this package"); + return any; +} + +} // namespace example::rules::toy diff --git a/examples/13-a-new-device-language/rules-toy/tools/toyc.sh b/examples/13-a-new-device-language/rules-toy/tools/toyc.sh new file mode 100755 index 00000000..add47b04 --- /dev/null +++ b/examples/13-a-new-device-language/rules-toy/tools/toyc.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# toyc -- the whole compiler for `.toy`. +# +# toyc.sh +# +# A `.toy` file is a list of integers, one per line, with `#` comments. The +# entry point returns their sum. This is not a language; it is the smallest +# thing that is compiled by something mcpp does not drive. +# +# awk rather than `paste | bc`: bc is not installed on every runner this +# repository's CI uses, and a missing summator produced a program that built, +# ran, and printed the wrong number. +set -eu +in=$1; out=$2; name=$3 +sum=$(awk '{ sub(/#.*/, ""); gsub(/[ \t]/, "") } /^-?[0-9]+$/ { s += $0 } END { print s + 0 }' "$in") +mkdir -p "$(dirname "$out")" +cat > "$out" < Date: Tue, 8 Sep 2026 15:07:52 +0800 Subject: [PATCH 04/36] docs: chapter 22 and 23, and 01 becomes the curriculum `[features]` was 394 lines inside a 3,129-line field reference, and no example declared one. It is chapter 22 now, with `provides`/`requires` and `[feature-deps]`, and `examples/11-features` is its worked example. docs/05 keeps a pointer and loses the essay; it is about 1,570 lines lighter. Chapter 23 is new and is the one this tree did not have: authoring a rule package. The material existed as a 115-line section inside a 944-line chapter, plus a README in another repository, and the two keys that make a new device language cost no engine release -- `device_extensions` and `rule_module` -- were documented nowhere in docs/. It assembles the manifest keys, the four action roles, declared inputs (including the compiler among them), depfiles, chained actions, the payload declaration with its two gates, the island boundary's four rungs, the reporting channels, and how a rule finds its own files. docs/01 was 64 lines listing directories. It is the curriculum now: six tracks, what each example is the FIRST to teach, and the criterion for when a new capability earns an example rather than a code block or a scenario entry. It also names the third teaching surface -- project templates, which is where the bare-metal lesson actually lives and which nothing told a reader about. The new device-language example is renumbered 12 so the sequence has no hole. --- ...mples-curriculum-and-documentation-plan.md | 4 +- docs/01-examples.md | 151 ++++--- docs/05-mcpp-toml.md | 395 +---------------- docs/22-features-and-capabilities.md | 405 ++++++++++++++++++ docs/23-authoring-a-rule-package.md | 249 +++++++++++ docs/README.md | 2 +- docs/zh/01-examples.md | 137 +++--- docs/zh/05-mcpp-toml.md | 326 +------------- docs/zh/22-features-and-capabilities.md | 334 +++++++++++++++ docs/zh/23-authoring-a-rule-package.md | 228 ++++++++++ docs/zh/README.md | 2 +- .../README.md | 4 +- .../app/build.mcpp | 0 .../app/mcpp.toml | 0 .../app/src/kernels/answer.toy | 0 .../app/src/main.cpp | 0 .../rules-toy/mcpp.toml | 0 .../rules-toy/src/rules-toy.cppm | 0 .../rules-toy/tools/toyc.sh | 0 19 files changed, 1414 insertions(+), 823 deletions(-) create mode 100644 docs/22-features-and-capabilities.md create mode 100644 docs/23-authoring-a-rule-package.md create mode 100644 docs/zh/22-features-and-capabilities.md create mode 100644 docs/zh/23-authoring-a-rule-package.md rename examples/{13-a-new-device-language => 12-a-new-device-language}/README.md (97%) rename examples/{13-a-new-device-language => 12-a-new-device-language}/app/build.mcpp (100%) rename examples/{13-a-new-device-language => 12-a-new-device-language}/app/mcpp.toml (100%) rename examples/{13-a-new-device-language => 12-a-new-device-language}/app/src/kernels/answer.toy (100%) rename examples/{13-a-new-device-language => 12-a-new-device-language}/app/src/main.cpp (100%) rename examples/{13-a-new-device-language => 12-a-new-device-language}/rules-toy/mcpp.toml (100%) rename examples/{13-a-new-device-language => 12-a-new-device-language}/rules-toy/src/rules-toy.cppm (100%) rename examples/{13-a-new-device-language => 12-a-new-device-language}/rules-toy/tools/toyc.sh (100%) diff --git a/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md b/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md index 22bd8f6c..a90bfe0e 100644 --- a/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md +++ b/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md @@ -252,7 +252,7 @@ so existing links and muscle memory survive where they can. | E6 | `10-heterogeneous/multi-backend` | keep | several backends in one artifact, chosen at run time | | E7 | `11-graphics/offscreen` | keep | a rendering pipeline whose result is pixels | | F1 | `12-build-rules` | move from `08` | `host-module = true`, `mcpp::action` with `role = "check"` | -| **F2** | **`13-a-new-device-language`** | **new** | `device_extensions` + `rule_module`: a third-party rule package teaching mcpp a language the engine has never heard of | +| **F2** | **`12-a-new-device-language`** | **new** | `device_extensions` + `rule_module`: a third-party rule package teaching mcpp a language the engine has never heard of | Four new directories; five renumbered; nothing deleted. @@ -285,7 +285,7 @@ program prints the right numbers. The plugins README claims this arrangement was measured with GCC 16.1; nothing in either tree runs it, so this example is also that claim's fixture. -**F2 `13-a-new-device-language`.** A rule package in the example tree that +**F2 `12-a-new-device-language`.** A rule package in the example tree that declares `device_extensions = [".toy"]` and `rule_module`, and a consumer whose `[build] sources` names a `.toy` file. The "compiler" is a shell script that transforms text, exactly as `tests/e2e/607` uses `cat` as a device linker: the diff --git a/docs/01-examples.md b/docs/01-examples.md index 33edb693..7af18cc6 100644 --- a/docs/01-examples.md +++ b/docs/01-examples.md @@ -1,11 +1,11 @@ # 01 — Examples -> The repository's [`examples/`](../examples) directory provides a set of -> progressively more advanced minimal projects, covering common scenarios from -> a single-file `import std` to a fully static release package. Each example can -> be entered on its own and built with `mcpp build`. +The [`examples/`](../examples) directory is a curriculum. Each project is +runnable on its own, and each one teaches **one thing no earlier example +teaches**. This chapter says what that thing is, so you can enter at the level +you need rather than reading from the start. -## How to Run +## Running an example ```bash git clone https://github.com/mcpp-community/mcpp @@ -13,52 +13,95 @@ cd mcpp/examples/01-hello mcpp build && mcpp run ``` -Each example ships with its own README that only explains the new concepts it -introduces relative to the previous one. Common material such as installation -steps and toolchain initialization lives in -[00 — Getting Started](00-getting-started.md) and is not repeated within the -examples. - -## Example List - -| # | Path | Description | Key Concepts | -|---|---|---|---| -| 01 | [`examples/01-hello`](../examples/01-hello/) | Minimal single-file project with `import std` | The minimal package shape (`mcpp new` also emits `tests/test_smoke.cpp`) | -| 02 | [`examples/02-with-deps`](../examples/02-with-deps/) | Adds the `mcpplibs.cmdline` dependency to parse command-line arguments | `[dependencies]`, SemVer, `mcpp.lock` | -| 03 | [`examples/03-pack-static`](../examples/03-pack-static/) | Produces a fully static release package via `mcpp pack --mode static` | `[target.]` and `[pack]` configuration | -| 04 | [`examples/04-workspace`](../examples/04-workspace/) | A multi-package workspace: two libraries and an application sharing one namespace | `[workspace]`, path dependencies, `mcpp build --workspace` | -| 05 | [`examples/05-lib-distribution`](../examples/05-lib-distribution/) | A prebuilt library and its consumer, interesting only together | `mcpp pack` for a library, a C header and a C++ module from one source, a distribution package | -| 06 | [`examples/06-openkal-cross`](../examples/06-openkal-cross/) | One program asking each machine what it is, built for four targets from any host | `--target`, openkal, cross-compilation without editing the source | -| 07 | [`examples/07-project-subos`](../examples/07-project-subos/) | A build program that finds its tools in the environment the project declared | `[xlings] subos`, `[xlings.workspace]`, a build program whose `PATH` is the environment the project named | -| 08 | [`examples/08-build-rules`](../examples/08-build-rules/) | Two rule packages and a project that uses both | `host-module = true`, `[build-dependencies]`, `mcpp::action` with `role = "check"` | -| 09 | [`examples/09-heterogeneous`](../examples/09-heterogeneous/) | One computation on a device, in several programming models, with a CPU fallback in each; plus one artifact carrying several backends at once | `accel`, constrained source globs, the seam module, rule packages from `mcpp:plugins`, `cfg(accelerator = …)` | -| 09a | [`…/cuda`](../examples/09-heterogeneous/cuda/) | A CUDA kernel behind a seam module, with the `extern "C"` boundary GENERATED | `mcpp.rules.cuda`, `mcpp.tools.island`, `mcpp::action` with `role = "object"`, the driver stated as a fact and a floor | -| 09b | [`…/vulkan`](../examples/09-heterogeneous/vulkan/) | The same computation as a Vulkan compute shader, with the SPIR-V payload reached as a MODULE | `mcpp.rules.spirv`, the module surface, `mcpp::action` with `role = "source"`, a software driver as a payload | -| 09c | [`…/sycl`](../examples/09-heterogeneous/sycl/) | The same computation as a SYCL kernel, compiled by a second compiler | `mcpp.rules.sycl`, `mcpp.tools.island`, the `.sycl` device extension, a chained `mcpp::action` for the device link, `compat:sycl-runtime` | -| 09d | [`…/hip`](../examples/09-heterogeneous/hip/) | The same computation in HIP, with the boundary WRITTEN BY HAND — the contrast against 09a | `mcpp.rules.hip`, HIP as a header layer over the CUDA runtime, a two-chunk `accel` | -| 09e | [`…/multi-backend`](../examples/09-heterogeneous/multi-backend/) | Several backends in ONE artifact, chosen at run time — the library shape, not the program shape | `accel` as a set, `cfg(accelerator = "none")` and its negation, a dispatch chain, a module seam over a C island boundary | -| 09f | [`…/cann`](../examples/09-heterogeneous/cann/) | An Ascend C kernel behind the same seam. **Does not build yet** — its README names the two missing pieces | the `.asc` device extension, `op_kernel`/`op_host` as an island CANN already has, `accelerator = "none"` for the fallback | -| 10 | [`examples/10-graphics`](../examples/10-graphics/) | Graphics rather than compute: a rendering pipeline whose result is pixels | `mcpp.rules.spirv` for the vertex and fragment stages, offscreen rendering as the assertable form | -| 10a | [`…/offscreen`](../examples/10-graphics/offscreen/) | A triangle rasterised by Vulkan into a buffer, and the same triangle by a software rasteriser behind the same seam | two shader stages from one glob, a render pass with no window or swapchain, a pixel as the criterion | - -## Suggested Reading Order - -We recommend reading them in numerical order: - -1. **`01-hello`** shows the minimal package skeleton (`mcpp.toml` and - `src/main.cpp`) and demonstrates the basic usage of `import std`. The current - `mcpp new` scaffold also emits `tests/test_smoke.cpp`. -2. **`02-with-deps`** builds on the previous example by introducing an external - dependency, covering the lock-file mechanism and how the modular package - index works. -3. **`03-pack-static`** demonstrates how to package build artifacts into a - standalone, independently distributable single-file binary; for packaging - details, see [02 — Packaging and Release](02-pack-and-release.md). - -## Adding a New Example - -Example projects follow a consistent directory structure: `mcpp.toml` + `src/` + -`README.md`. To add a new example, create a numbered directory under -`examples/` (e.g. `04-xxx/`), briefly describe the concept it demonstrates in -its README, and then open a PR. For contribution guidelines, see -[04 — Build from Source & Contributing](04-build-from-source.md). +Every example ships a README that explains only what it adds. Installation and +toolchain setup live in [00 — Getting Started](00-getting-started.md) and are +not repeated. + +## The curriculum + +### A — The shape of a project + +| example | first to teach | +|---|---| +| [`01-hello`](../examples/01-hello/) | a package, `import std`, `mcpp build` and `mcpp run` | +| [`02-with-deps`](../examples/02-with-deps/) | `[dependencies]`, the lock file, `mcpp add` | +| [`04-workspace`](../examples/04-workspace/) | `[workspace]`, path dependencies, `mcpp build --workspace` | +| [`11-features`](../examples/11-features/) | `[features]` **declared** rather than consumed, `[feature-deps]`, `[dev-dependencies]`, `[profile.]`, `mcpp::has_feature` | + +### B — Publishing + +| example | first to teach | +|---|---| +| [`03-pack-static`](../examples/03-pack-static/) | `mcpp pack --mode static`, `[target.]`, `[pack]` | +| [`05-lib-distribution`](../examples/05-lib-distribution/) | a library's interface and its prebuilt binaries; a C header and a C++ module from one source | + +### C — The environment + +| example | first to teach | +|---|---| +| [`07-project-subos`](../examples/07-project-subos/) | `[xlings]`, `[xlings.workspace]`, a build program whose `PATH` is the environment the project declared | + +### D — Targets + +| example | first to teach | +|---|---| +| [`06-openkal-cross`](../examples/06-openkal-cross/) | `--target`, one source built for four machines from any host | + +Bare metal is taught by a **template** rather than by a directory here — see +*Lessons that arrive as templates* below. + +### E — Devices and graphics + +Read [`09-heterogeneous`](../examples/09-heterogeneous/) in order. Its README is +the map; the table below is what each sub-example adds. + +| example | first to teach | +|---|---| +| [`…/boundary`](../examples/09-heterogeneous/boundary/) | the island boundary alone: a generated module the consumer imports, with no seam and no header in the project. Needs no device | +| [`…/cuda`](../examples/09-heterogeneous/cuda/) | a device compiler, a seam over the generated boundary, the driver stated as a fact and a floor | +| [`…/vulkan`](../examples/09-heterogeneous/vulkan/) | a compute shader whose SPIR-V payload arrives as a module | +| [`…/sycl`](../examples/09-heterogeneous/sycl/) | a second compiler with its own standard library | +| [`…/hip`](../examples/09-heterogeneous/hip/) | the boundary written by hand — the contrast against `boundary/` and `cuda/` | +| [`…/cann`](../examples/09-heterogeneous/cann/) | a vendor outside the NVIDIA and Khronos lineages | +| [`…/multi-backend`](../examples/09-heterogeneous/multi-backend/) | several backends in one artifact, chosen when the program runs | +| [`10-graphics/offscreen`](../examples/10-graphics/offscreen/) | a rendering pipeline whose result is pixels, asserted against a software rasteriser | + +### F — Authoring for the ecosystem + +| example | first to teach | +|---|---| +| [`08-build-rules`](../examples/08-build-rules/) | two rule packages and a project using both; `host-module = true`, `mcpp::action` with `role = "check"` | +| [`12-a-new-device-language`](../examples/12-a-new-device-language/) | `device_extensions` and `rule_module`: a rule package teaching mcpp a language the engine has never heard of | + +[23 — Authoring a Rule Package](23-authoring-a-rule-package.md) is the reference +these two illustrate. + +## Lessons that arrive as templates + +A package may ship `templates//`, which `mcpp new --template` instantiates. +That is a third teaching surface beside this directory and the chapters, and it +is where a lesson belongs when the thing being taught is owned by a package +rather than by mcpp. + +| template | lesson | chapter | +|---|---|---| +| `riscv-virt-rt` | a bare-metal project, its board support and its runner | [13](13-baremetal.md) | +| `riscv-virt-rt:nolibc` | the same with no C library | [13](13-baremetal.md) | +| `ocornut.imgui` | a graphical application with its window and rendering stack | [03](03-toolchains.md) | + +```bash +mcpp new blinky --template riscv-virt-rt +``` + +## Adding an example + +An example directory is `mcpp.toml` + `src/` + `README.md`, numbered after the +last one. A new example is warranted when a capability **changes the shape of a +project** — the files it contains, the manifest it declares, or the commands its +author types. A capability that is one line inside a project an example already +contains belongs in that chapter as a code block; one reached only through a +command belongs in [21 — Commands by Scenario](21-commands-by-scenario.md). + +The README states what the example is the first to teach and the criterion by +which it is judged to work. For contribution mechanics see +[04 — Building from Source & Contributing](04-build-from-source.md). diff --git a/docs/05-mcpp-toml.md b/docs/05-mcpp-toml.md index eda42ada..05388e7b 100644 --- a/docs/05-mcpp-toml.md +++ b/docs/05-mcpp-toml.md @@ -1432,400 +1432,11 @@ Exit code 1 keeps its meaning — a test ran and failed — and 0 means every te ran and passed. `--message-format json` carries `"status":"not_run"` and a `reason` on each record, and `not_run` / `not_run_reason` on the summary record (see [11 — Machine-Readable Output](11-machine-output.md)). +### 2.8 `[features]` — Features -### 2.8 `[features]` — Features (Cargo-style, additive) +Moved to [22 — Features and Capabilities](22-features-and-capabilities.md), +with `provides` / `requires` and `[feature-deps.]`. -```toml -[features] -default = ["base"] # Default activation set -base = [] -docking = ["extra"] # Activating docking implies activating extra (transitive closure) -extra = [] -``` - -- Activation sources: the package's own `default` set ∪ explicit requests (the root - package via `mcpp build --features a,b`; dependencies via the long-form dep spec's - `features = [...]` / `backend = "..."` sugar). -- Each activated feature gets the macro `-DMCPP_FEATURE_` during that package's - compilation (the name is uppercased and non-alphanumerics become `_`, e.g. - `backend-a` → `MCPP_FEATURE_BACKEND_A`). -- **strict validation**: when the target package declares a `[features]` table, - requesting an undeclared feature produces a warning; an error under `--strict`. A - package that does not declare `[features]` accepts any request (pure macro usage). - -#### Table form — a feature that contributes more than implied features - -A `[features]` entry may be written as a **table** instead of an array, letting the -feature carry package-owned preprocessor `defines`, feature-gated source globs -(`sources`, mcpp 0.0.95+ — the globs leave the default build and compile only when -the feature is active, exactly like an index descriptor's `features..sources`; -the highest-frequency shape for vendored libraries: *feature = a source set + a -define*), feature-gated per-glob compile flags (`flags`, mcpp 0.0.101+), and/or -capability `requires` / `provides` (see §2.8.1) alongside its implied features: - -```toml -[features] -default = [] -# Array shorthand: just implied features. -docking = ["extra"] -extra = [] -# Table form: contribute a package-owned define when active. -mpl2only = { defines = ["EIGEN_MPL2_ONLY"] } -# Table form: a define + an implied feature. -fast_math = { defines = ["APP_FAST=1"], implies = ["extra"] } -# Table form: feature-gated sources + per-glob flags that co-locate with them. -simd = { sources = ["src/simd/**"], flags = [ - { glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"] } ] } -``` - -- **The table form accepts exactly** `implies`, `forward`, `defines`, `sources`, - `flags`, `requires`, `provides`. Anything else is reported as a schema warning - and ignored (mcpp 2026.9.1.1+); `deps` is reported separately as reserved and - points at `[feature-deps.]`. Before that release `[features]` was - the one structured section with no schema check at all, so a misplaced - `include_dirs` inside a feature built successfully with no diagnostic while - the identical mistake in `[build]` was reported. -- `defines` are **bare** macro names (no `-D`); each desugars to `-D` on the - package's own compile when the feature is active — exactly like `[targets.*] - defines`. They are restricted by convention to the package's **own** namespaced - macros: a feature does **not** inject free-form package-wide `cflags`/`ldflags`, - which would break the additive feature-union model. Link flags come from a - provider dependency (§2.8.1), not from a feature. -- The automatic `-DMCPP_FEATURE_` is still defined for every active feature, - so `defines` are additive to it. -- `flags` (mcpp 0.0.101+) is the same ordered array-of-inline-tables grammar as - `[build].flags` (§2.3: `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ - `defines`; the `[[features..flags]]` array-of-tables spelling is accepted - too, like `[[build.flags]]`). When the feature is active the entries - are appended **after** the base `[build].flags`, features in name order, so a - feature rule wins over a broader base rule via "last flag wins"; when it is - inactive the entries do not exist (no dead-glob warning). This is how a - feature's group-specific flags co-locate with its `sources` instead of living - as base rules whose globs go dead on feature-off builds. Unlike `defines`, - feature `flags` are **private per-TU build flags** — they never propagate to - consumers (same contract as `[build].flags`), so they stay inside the additive - model: scoped by glob, deterministic order, no cross-package effect. - - -#### A feature that is a build rule (mcpp 2026.9.7.1+) - -Two keys turn a feature into a build rule other packages can use. They are what -lets a consumer write one dependency edge and no build program. - -```toml -[features.rules-spirv] -sources = ["rules/spirv.cppm"] -rule_module = "mcpp.rules.spirv" -device_extensions = [".comp", ".vert", ".frag", ".glsl"] -``` - -`device_extensions` states which **device source** extensions this rule -compiles. A consumer that activates the feature gets them classified as device -sources -- never scanned for imports, never a BMI, compiled by something mcpp -does not drive. This is the same shape as `[build] module_extensions`: mcpp -knows what a device source *is* and does not know that `.cu` is CUDA, so a NEW -device language costs no engine change. It is what makes -[20 — Heterogeneous Builds](20-heterogeneous-builds.md)' claim that "a sixth -backend is a package rather than an engine change" true rather than -aspirational; `.slang` was removed from mcpp's built-in table and now arrives -this way. - -`rule_module` names the module a consumer's build program imports to reach the -rule, and whose `compile()` it calls. Declared rather than scanned out of the -source, because the program has to be **written** before anything is compiled -and a build that scanned a dependency to decide what to write would order the -two the wrong way round. - -Two things follow, and neither puts a package name inside mcpp: - -- **`host-module = true` is implied.** A feature naming a rule module has - already said that is the only way to use it, so the edge does not repeat it. -- **A package with no `build.mcpp` gets one.** mcpp writes the program those - rules describe into the build directory and compiles that. A package with its - own program keeps it: the synthesis fills an absence and never overrides, and - the generated file is the program a project would have written, so taking it - over is a copy and an edit. - -The feature is still requested **by name**: - -```toml -[build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-spirv"] } -``` - -An earlier design derived the set from the extensions a project's sources -carried. It was withdrawn because two packages may claim one extension -- a -third-party CUDA rule is a thing someone will write -- and because a manifest's -job is to describe the build, which a derived feature set no longer does. - -Both keys must appear together. One without the other is a declaration nothing -can act on, and it is refused at parse time rather than in a consumer's build. - - -### 2.8.1 `provides` / `requires` — Capabilities (backend selection) - -A **capability** is a shared abstract name (e.g. `blas`). A package can *provide* -one; a feature can *require* one instead of naming a concrete package, and the -resolver binds exactly one provider from the dependency graph. This is how a build selects -one of several interchangeable backends (OpenBLAS / MKL / …) without baking a choice -into the library. - -```toml -# A provider package satisfies a capability for any dependent that requires it. -[package] -name = "compat.openblas" -version = "0.3.0" -provides = ["blas", "lapack"] -``` - -```toml -# A consumer requires the abstract capability via one of its features. -[features] -use_blas = { defines = ["EIGEN_USE_BLAS"], requires = ["blas"] } - -# When >1 provider is in the graph, pick one (else the build errors and lists them). -[capabilities] -blas = "compat.openblas" # equivalently: mcpp build --cap blas=compat.openblas - -[dependencies] -compat.openblas = "0.3.0" # the provider must be a real dependency in the graph -``` - -The reserved prefix `mcpp:` names the target-side layers this engine resolves, -and those names are validated against a closed set. A package-level `requires` -array carries the symmetric statement — what a target-side layer must resolve to -for this package to be usable. - -```toml -[package] -name = "acme.llvm-runtime" -version = "0.1.0" -provides = ["mcpp:compiler-runtime=compiler-rt", "mcpp:c++-abi=libc++"] -requires = ["mcpp:compiler=llvm"] -``` - -A package that is a standard library states its `std` module source under -`[build]`, where the flags it needs become conditional like any other build -input. - -```toml -[build] -std-module = "llvm-generated/std.cppm" -std-compat-module = "llvm-generated/std.compat.cppm" -std-module-flags = ["--no-default-config", "-nostdinc++"] - -[target.'cfg(c-abi = "musl")'.build] -std-module-flags = ["-D_GNU_SOURCE"] -``` - -See [14 - The Target Side](14-target-side.md) for the five layers, the rules -that govern them, and the diagnostics. - -Binding is **deterministic**: - -| Providers of a required capability in the graph | Result | -|---|---| -| exactly one | bound automatically (no config needed) | -| a `[capabilities]` pin / `--cap` names one | the pin wins | -| zero | **error**: no package provides `` | -| two or more, unpinned | **error**, listing the candidates — never a silent guess | - -The bound provider's link/include flags reach the consumer through normal -dependency mechanics; the capability layer is the *selection-and-validation* step -that turns a silently-wrong or missing backend into a loud configure-time error. - -**Binding selects a provider; it does not prune the link line.** A dependency -package contributes its object files to the consumer's link regardless of whether -its capability was the one bound. Measured with two packages that both provide -one capability and both define `cap_probe`: unpinned, resolution fails as the -table says; after pinning one with `[capabilities]`, the build reaches the linker -and fails there instead — - -``` -ld: obj/mcpplibs_pa/src/impl.o: in function `cap_probe': - multiple definition of `cap_probe'; obj/mcpplibs_pb/src/impl.o: first defined here -``` - -This matters for a capability whose providers define the **same symbols** — a -whole-program singleton such as `operator new`, or a C API with one fixed name -set. For those, two providers in the graph is a defect to fix rather than an -ambiguity to pin: pinning replaces an error that names both candidates with one -that names a mangled symbol. Interchangeable *libraries* (BLAS implementations, -which export distinct symbol sets and are selected per link) are unaffected. - -#### `exclusive` — a package declaring it is the only provider - -The paragraph above describes a defect the engine cannot detect. Seeing that two -providers define the same symbols requires their object files, which do not -exist when capabilities are bound; and refusing every duplicate provider as a -rule would break the BLAS case in the same paragraph, which is legitimate. - -So the package says it: - -```toml -[package] -name = "compat.cublas" -provides = ["gpu-blas"] -exclusive = ["gpu-blas"] -``` - -Two packages that both provide `gpu-blas`, where at least one declares it -exclusive, are refused when capabilities are bound — before anything is -compiled, naming the capability and both providers: - -``` -error: capability 'gpu-blas' is provided by more than one package, and they - declare it EXCLUSIVE. - providers: [compat.cublas, compat.rocblas] - exclusive: [compat.cublas, compat.rocblas] - Two implementations of one interface define the same symbols, so the - link would resolve every call to whichever archive it reached first. - Keep one of them — a `[capabilities]` pin selects a provider for a - REQUIREMENT and cannot make two definitions of one symbol safe. -``` - -The refusal reports `exclusive-capability` in `--format json` (chapter 11). - -#### `version-floor` — needing more of the machine than it has - -Some facts about a machine bound what may be built for it, and the failure when -they are ignored arrives late: a program built against a runtime newer than the -driver it will meet links cleanly and fails at first use, naming neither side. - -A package states what it needs: - -```toml -[[runtime.requirements]] -kind = "version-floor" -value = "cuda.driver >= 12.0" -``` - -and a package that established a fact about this machine — at install time, -which is where probing belongs — states it: - -```toml -[runtime] -provides = ["cuda.driver=12.4"] -``` - -mcpp compares them when capabilities are bound and refuses before anything is -compiled, reporting `version-floor-unmet`: - -``` -error: `toolkitnew` requires cuda.driver >= 13.0, and this machine has 12.4. - stated by: driverfact -``` - -**No vendor vocabulary reaches the engine.** It reads a name, a relation and a -version; `cuda.driver` is data passing through, and a backend mcpp has never -heard of compares the same way. - -**A floor nobody answered is silent.** A machine that never declared what it -has is not a machine that fails the floor — it is one nobody asked. Turning -"we do not know" into "no" is the failure mode this exists to avoid, and it is -asserted directly: `tests/e2e/603_version_floor.sh` builds a project whose floor -names something no package provides. - -**It is a claim about this package's own symbols**, so an entry that names a -capability the package does not provide is reported as a schema warning: there -is nothing to be exclusive about. And a capability nobody declares exclusive -behaves exactly as before — two BLAS implementations still coexist, and the -existing "two or more, unpinned" error still applies only when something -*requires* the capability. - -### 2.8.2 `[feature-deps.]` — dependencies a feature pulls in - -A dependency declared under `[feature-deps.]` is **optional**: it is -resolved only when that feature is active (root `--features`, or a dependency -spec's `features = [...]`). A dependency in `[dependencies]` is always resolved; -optionality is expressed by *where* the entry is declared, not by a flag. - -```toml -[features] -use_blas = { defines = ["EIGEN_USE_BLAS"], requires = ["blas"] } -backend-openblas = { implies = ["use_blas"] } - -# Pulled ONLY when `backend-openblas` is active. Each entry is a full dependency -# spec (version/path/git + its own features). -[feature-deps.backend-openblas] -compat.openblas = "0.3" -``` - -**`"^0.3.0"` and not `"0.3.x"` or `"0.3"`.** Measured against a package the -index certainly carries, with a **build** as the criterion: - -| Written | Result | -|---|---| -| `cmdline = "0.0.1"` | builds | -| `cmdline = "^0.0.1"` | builds | -| `cmdline = "0.0"` | resolves, then `install path missing after fetch` | -| `cmdline = "0.0.x"` | `E_NOT_FOUND`, naming the package — which exists | - -The three outcomes are worth distinguishing, because two weaker criteria each -admit a form that does not work: "no `E_NOT_FOUND`" admits the two-segment -prefix, and "resolves" admits it as well. Only building against the real index -settles it. - -This matters more here than in `[dependencies]`. A feature whose -implementation cannot be fetched is a feature that does not exist, and a project -using a **path** dependency during development never consults the index — so the -failure appears only after publication, to somebody else. - -This composes with capabilities (§2.8.1): a single `backend-openblas` feature -both **pulls** the provider (`compat.openblas`, which `provides = ["blas"]`) and -**turns on** the consumer switch (`implies = ["use_blas"]`, which -`requires = ["blas"]`). With one provider in the graph the capability binds -automatically — `features = ["backend-openblas"]` is all the consumer writes. - -In an index package's Lua descriptor the same is written inline: - -```lua -features = { - use_blas = { defines = { "EIGEN_USE_BLAS" }, requires = { "blas" } }, - ["backend-openblas"] = { - implies = { "use_blas" }, - deps = { ["compat.openblas"] = "0.3.x" }, - }, -} -``` - -#### A default implementation that stays replaceable - -The same three pieces cover the case where a library wants to *offer* an -implementation without *imposing* one — a whole-program singleton such as -`operator new`, a logging sink, or a panic handler: - -```toml -[features] -default = [] -# The consumer-side switch: "I use the part of this library that needs an allocator". -alloc = { requires = ["freestanding-allocator"] } -# The built-in default: activating this one is enough. -alloc-kal = { implies = ["alloc"] } - -# Resolved only when `alloc-kal` is active, so the library itself carries no -# dependency on the implementation. -[feature-deps.alloc-kal] -std-freestanding-alloc-kal = "0.1.x" -``` - -Three usages, one line each: - -| Consumer needs | What the manifest says | -|---|---| -| none of the allocating parts | `std-freestanding = "0.2.0"` — no allocator enters the graph | -| the default | `features = ["alloc-kal"]` — the implementation arrives with it, and its package name never has to be known | -| its own or a third party's | `features = ["alloc"]` plus a package that `provides = ["freestanding-allocator"]` | - -Two properties make this preferable to shipping the implementation -unconditionally. A library that ships one takes a decision belonging to the -program, and it cannot be undone: features are **additive**, so there is no way -for a consumer to switch a default *off*. And because a dependency package's -objects link unconditionally (§2.8.1), a shipped default plus a program-supplied -one is a duplicate definition rather than a replacement — the archive semantics -that let a C++ standard library offer a replaceable `operator new` do not apply -to a package dependency. Keeping the implementation behind a switch means the -two never coexist. ### 2.8.3 `[scan_overrides.""]` — Author-Asserted Scan Results diff --git a/docs/22-features-and-capabilities.md b/docs/22-features-and-capabilities.md new file mode 100644 index 00000000..62d57697 --- /dev/null +++ b/docs/22-features-and-capabilities.md @@ -0,0 +1,405 @@ +# 22 — Features and Capabilities + +Features are how a package offers something optional: a compile macro, an extra +source, an extra dependency, or a choice between backends. This chapter is the +reference for declaring them and for consuming them. + +Related documents: [05 — mcpp.toml](05-mcpp-toml.md) is the field reference for +the rest of the manifest; [`examples/11-features`](../examples/11-features/) is +a package that declares all three shapes and a test that uses a +dev-dependency; [20 — Heterogeneous Builds](20-heterogeneous-builds.md) is the +largest consumer of the mechanism, because every accelerator lane is a feature. + +## `[features]` — Features (Cargo-style, additive) + +```toml +[features] +default = ["base"] # Default activation set +base = [] +docking = ["extra"] # Activating docking implies activating extra (transitive closure) +extra = [] +``` + +- Activation sources: the package's own `default` set ∪ explicit requests (the root + package via `mcpp build --features a,b`; dependencies via the long-form dep spec's + `features = [...]` / `backend = "..."` sugar). +- Each activated feature gets the macro `-DMCPP_FEATURE_` during that package's + compilation (the name is uppercased and non-alphanumerics become `_`, e.g. + `backend-a` → `MCPP_FEATURE_BACKEND_A`). +- **strict validation**: when the target package declares a `[features]` table, + requesting an undeclared feature produces a warning; an error under `--strict`. A + package that does not declare `[features]` accepts any request (pure macro usage). + +### Table form — a feature that contributes more than implied features + +A `[features]` entry may be written as a **table** instead of an array, letting the +feature carry package-owned preprocessor `defines`, feature-gated source globs +(`sources`, mcpp 0.0.95+ — the globs leave the default build and compile only when +the feature is active, exactly like an index descriptor's `features..sources`; +the highest-frequency shape for vendored libraries: *feature = a source set + a +define*), feature-gated per-glob compile flags (`flags`, mcpp 0.0.101+), and/or +capability `requires` / `provides` (see §2.8.1) alongside its implied features: + +```toml +[features] +default = [] +# Array shorthand: just implied features. +docking = ["extra"] +extra = [] +# Table form: contribute a package-owned define when active. +mpl2only = { defines = ["EIGEN_MPL2_ONLY"] } +# Table form: a define + an implied feature. +fast_math = { defines = ["APP_FAST=1"], implies = ["extra"] } +# Table form: feature-gated sources + per-glob flags that co-locate with them. +simd = { sources = ["src/simd/**"], flags = [ + { glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"] } ] } +``` + +- **The table form accepts exactly** `implies`, `forward`, `defines`, `sources`, + `flags`, `requires`, `provides`. Anything else is reported as a schema warning + and ignored (mcpp 2026.9.1.1+); `deps` is reported separately as reserved and + points at `[feature-deps.]`. Before that release `[features]` was + the one structured section with no schema check at all, so a misplaced + `include_dirs` inside a feature built successfully with no diagnostic while + the identical mistake in `[build]` was reported. +- `defines` are **bare** macro names (no `-D`); each desugars to `-D` on the + package's own compile when the feature is active — exactly like `[targets.*] + defines`. They are restricted by convention to the package's **own** namespaced + macros: a feature does **not** inject free-form package-wide `cflags`/`ldflags`, + which would break the additive feature-union model. Link flags come from a + provider dependency (§2.8.1), not from a feature. +- The automatic `-DMCPP_FEATURE_` is still defined for every active feature, + so `defines` are additive to it. +- `flags` (mcpp 0.0.101+) is the same ordered array-of-inline-tables grammar as + `[build].flags` (§2.3: `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ + `defines`; the `[[features..flags]]` array-of-tables spelling is accepted + too, like `[[build.flags]]`). When the feature is active the entries + are appended **after** the base `[build].flags`, features in name order, so a + feature rule wins over a broader base rule via "last flag wins"; when it is + inactive the entries do not exist (no dead-glob warning). This is how a + feature's group-specific flags co-locate with its `sources` instead of living + as base rules whose globs go dead on feature-off builds. Unlike `defines`, + feature `flags` are **private per-TU build flags** — they never propagate to + consumers (same contract as `[build].flags`), so they stay inside the additive + model: scoped by glob, deterministic order, no cross-package effect. + + +### A feature that is a build rule (mcpp 2026.9.7.1+) + +Two keys turn a feature into a build rule other packages can use. They are what +lets a consumer write one dependency edge and no build program. + +```toml +[features.rules-spirv] +sources = ["rules/spirv.cppm"] +rule_module = "mcpp.rules.spirv" +device_extensions = [".comp", ".vert", ".frag", ".glsl"] +``` + +`device_extensions` states which **device source** extensions this rule +compiles. A consumer that activates the feature gets them classified as device +sources -- never scanned for imports, never a BMI, compiled by something mcpp +does not drive. This is the same shape as `[build] module_extensions`: mcpp +knows what a device source *is* and does not know that `.cu` is CUDA, so a NEW +device language costs no engine change. It is what makes +[20 — Heterogeneous Builds](20-heterogeneous-builds.md)' claim that "a sixth +backend is a package rather than an engine change" true rather than +aspirational; `.slang` was removed from mcpp's built-in table and now arrives +this way. + +`rule_module` names the module a consumer's build program imports to reach the +rule, and whose `compile()` it calls. Declared rather than scanned out of the +source, because the program has to be **written** before anything is compiled +and a build that scanned a dependency to decide what to write would order the +two the wrong way round. + +Two things follow, and neither puts a package name inside mcpp: + +- **`host-module = true` is implied.** A feature naming a rule module has + already said that is the only way to use it, so the edge does not repeat it. +- **A package with no `build.mcpp` gets one.** mcpp writes the program those + rules describe into the build directory and compiles that. A package with its + own program keeps it: the synthesis fills an absence and never overrides, and + the generated file is the program a project would have written, so taking it + over is a copy and an edit. + +The feature is still requested **by name**: + +```toml +[build-dependencies.mcpp] +plugins = { version = "0.3.0", features = ["rules-spirv"] } +``` + +An earlier design derived the set from the extensions a project's sources +carried. It was withdrawn because two packages may claim one extension -- a +third-party CUDA rule is a thing someone will write -- and because a manifest's +job is to describe the build, which a derived feature set no longer does. + +Both keys must appear together. One without the other is a declaration nothing +can act on, and it is refused at parse time rather than in a consumer's build. + + +## `provides` / `requires` — Capabilities (backend selection) + +A **capability** is a shared abstract name (e.g. `blas`). A package can *provide* +one; a feature can *require* one instead of naming a concrete package, and the +resolver binds exactly one provider from the dependency graph. This is how a build selects +one of several interchangeable backends (OpenBLAS / MKL / …) without baking a choice +into the library. + +```toml +# A provider package satisfies a capability for any dependent that requires it. +[package] +name = "compat.openblas" +version = "0.3.0" +provides = ["blas", "lapack"] +``` + +```toml +# A consumer requires the abstract capability via one of its features. +[features] +use_blas = { defines = ["EIGEN_USE_BLAS"], requires = ["blas"] } + +# When >1 provider is in the graph, pick one (else the build errors and lists them). +[capabilities] +blas = "compat.openblas" # equivalently: mcpp build --cap blas=compat.openblas + +[dependencies] +compat.openblas = "0.3.0" # the provider must be a real dependency in the graph +``` + +The reserved prefix `mcpp:` names the target-side layers this engine resolves, +and those names are validated against a closed set. A package-level `requires` +array carries the symmetric statement — what a target-side layer must resolve to +for this package to be usable. + +```toml +[package] +name = "acme.llvm-runtime" +version = "0.1.0" +provides = ["mcpp:compiler-runtime=compiler-rt", "mcpp:c++-abi=libc++"] +requires = ["mcpp:compiler=llvm"] +``` + +A package that is a standard library states its `std` module source under +`[build]`, where the flags it needs become conditional like any other build +input. + +```toml +[build] +std-module = "llvm-generated/std.cppm" +std-compat-module = "llvm-generated/std.compat.cppm" +std-module-flags = ["--no-default-config", "-nostdinc++"] + +[target.'cfg(c-abi = "musl")'.build] +std-module-flags = ["-D_GNU_SOURCE"] +``` + +See [14 - The Target Side](14-target-side.md) for the five layers, the rules +that govern them, and the diagnostics. + +Binding is **deterministic**: + +| Providers of a required capability in the graph | Result | +|---|---| +| exactly one | bound automatically (no config needed) | +| a `[capabilities]` pin / `--cap` names one | the pin wins | +| zero | **error**: no package provides `` | +| two or more, unpinned | **error**, listing the candidates — never a silent guess | + +The bound provider's link/include flags reach the consumer through normal +dependency mechanics; the capability layer is the *selection-and-validation* step +that turns a silently-wrong or missing backend into a loud configure-time error. + +**Binding selects a provider; it does not prune the link line.** A dependency +package contributes its object files to the consumer's link regardless of whether +its capability was the one bound. Measured with two packages that both provide +one capability and both define `cap_probe`: unpinned, resolution fails as the +table says; after pinning one with `[capabilities]`, the build reaches the linker +and fails there instead — + +``` +ld: obj/mcpplibs_pa/src/impl.o: in function `cap_probe': + multiple definition of `cap_probe'; obj/mcpplibs_pb/src/impl.o: first defined here +``` + +This matters for a capability whose providers define the **same symbols** — a +whole-program singleton such as `operator new`, or a C API with one fixed name +set. For those, two providers in the graph is a defect to fix rather than an +ambiguity to pin: pinning replaces an error that names both candidates with one +that names a mangled symbol. Interchangeable *libraries* (BLAS implementations, +which export distinct symbol sets and are selected per link) are unaffected. + +### `exclusive` — a package declaring it is the only provider + +The paragraph above describes a defect the engine cannot detect. Seeing that two +providers define the same symbols requires their object files, which do not +exist when capabilities are bound; and refusing every duplicate provider as a +rule would break the BLAS case in the same paragraph, which is legitimate. + +So the package says it: + +```toml +[package] +name = "compat.cublas" +provides = ["gpu-blas"] +exclusive = ["gpu-blas"] +``` + +Two packages that both provide `gpu-blas`, where at least one declares it +exclusive, are refused when capabilities are bound — before anything is +compiled, naming the capability and both providers: + +``` +error: capability 'gpu-blas' is provided by more than one package, and they + declare it EXCLUSIVE. + providers: [compat.cublas, compat.rocblas] + exclusive: [compat.cublas, compat.rocblas] + Two implementations of one interface define the same symbols, so the + link would resolve every call to whichever archive it reached first. + Keep one of them — a `[capabilities]` pin selects a provider for a + REQUIREMENT and cannot make two definitions of one symbol safe. +``` + +The refusal reports `exclusive-capability` in `--format json` (chapter 11). + +### `version-floor` — needing more of the machine than it has + +Some facts about a machine bound what may be built for it, and the failure when +they are ignored arrives late: a program built against a runtime newer than the +driver it will meet links cleanly and fails at first use, naming neither side. + +A package states what it needs: + +```toml +[[runtime.requirements]] +kind = "version-floor" +value = "cuda.driver >= 12.0" +``` + +and a package that established a fact about this machine — at install time, +which is where probing belongs — states it: + +```toml +[runtime] +provides = ["cuda.driver=12.4"] +``` + +mcpp compares them when capabilities are bound and refuses before anything is +compiled, reporting `version-floor-unmet`: + +``` +error: `toolkitnew` requires cuda.driver >= 13.0, and this machine has 12.4. + stated by: driverfact +``` + +**No vendor vocabulary reaches the engine.** It reads a name, a relation and a +version; `cuda.driver` is data passing through, and a backend mcpp has never +heard of compares the same way. + +**A floor nobody answered is silent.** A machine that never declared what it +has is not a machine that fails the floor — it is one nobody asked. Turning +"we do not know" into "no" is the failure mode this exists to avoid, and it is +asserted directly: `tests/e2e/603_version_floor.sh` builds a project whose floor +names something no package provides. + +**It is a claim about this package's own symbols**, so an entry that names a +capability the package does not provide is reported as a schema warning: there +is nothing to be exclusive about. And a capability nobody declares exclusive +behaves exactly as before — two BLAS implementations still coexist, and the +existing "two or more, unpinned" error still applies only when something +*requires* the capability. + +## `[feature-deps.]` — dependencies a feature pulls in + +A dependency declared under `[feature-deps.]` is **optional**: it is +resolved only when that feature is active (root `--features`, or a dependency +spec's `features = [...]`). A dependency in `[dependencies]` is always resolved; +optionality is expressed by *where* the entry is declared, not by a flag. + +```toml +[features] +use_blas = { defines = ["EIGEN_USE_BLAS"], requires = ["blas"] } +backend-openblas = { implies = ["use_blas"] } + +# Pulled ONLY when `backend-openblas` is active. Each entry is a full dependency +# spec (version/path/git + its own features). +[feature-deps.backend-openblas] +compat.openblas = "0.3" +``` + +**`"^0.3.0"` and not `"0.3.x"` or `"0.3"`.** Measured against a package the +index certainly carries, with a **build** as the criterion: + +| Written | Result | +|---|---| +| `cmdline = "0.0.1"` | builds | +| `cmdline = "^0.0.1"` | builds | +| `cmdline = "0.0"` | resolves, then `install path missing after fetch` | +| `cmdline = "0.0.x"` | `E_NOT_FOUND`, naming the package — which exists | + +The three outcomes are worth distinguishing, because two weaker criteria each +admit a form that does not work: "no `E_NOT_FOUND`" admits the two-segment +prefix, and "resolves" admits it as well. Only building against the real index +settles it. + +This matters more here than in `[dependencies]`. A feature whose +implementation cannot be fetched is a feature that does not exist, and a project +using a **path** dependency during development never consults the index — so the +failure appears only after publication, to somebody else. + +This composes with capabilities (§2.8.1): a single `backend-openblas` feature +both **pulls** the provider (`compat.openblas`, which `provides = ["blas"]`) and +**turns on** the consumer switch (`implies = ["use_blas"]`, which +`requires = ["blas"]`). With one provider in the graph the capability binds +automatically — `features = ["backend-openblas"]` is all the consumer writes. + +In an index package's Lua descriptor the same is written inline: + +```lua +features = { + use_blas = { defines = { "EIGEN_USE_BLAS" }, requires = { "blas" } }, + ["backend-openblas"] = { + implies = { "use_blas" }, + deps = { ["compat.openblas"] = "0.3.x" }, + }, +} +``` + +### A default implementation that stays replaceable + +The same three pieces cover the case where a library wants to *offer* an +implementation without *imposing* one — a whole-program singleton such as +`operator new`, a logging sink, or a panic handler: + +```toml +[features] +default = [] +# The consumer-side switch: "I use the part of this library that needs an allocator". +alloc = { requires = ["freestanding-allocator"] } +# The built-in default: activating this one is enough. +alloc-kal = { implies = ["alloc"] } + +# Resolved only when `alloc-kal` is active, so the library itself carries no +# dependency on the implementation. +[feature-deps.alloc-kal] +std-freestanding-alloc-kal = "0.1.x" +``` + +Three usages, one line each: + +| Consumer needs | What the manifest says | +|---|---| +| none of the allocating parts | `std-freestanding = "0.2.0"` — no allocator enters the graph | +| the default | `features = ["alloc-kal"]` — the implementation arrives with it, and its package name never has to be known | +| its own or a third party's | `features = ["alloc"]` plus a package that `provides = ["freestanding-allocator"]` | + +Two properties make this preferable to shipping the implementation +unconditionally. A library that ships one takes a decision belonging to the +program, and it cannot be undone: features are **additive**, so there is no way +for a consumer to switch a default *off*. And because a dependency package's +objects link unconditionally (§2.8.1), a shipped default plus a program-supplied +one is a duplicate definition rather than a replacement — the archive semantics +that let a C++ standard library offer a replaceable `operator new` do not apply +to a package dependency. Keeping the implementation behind a switch means the +two never coexist. diff --git a/docs/23-authoring-a-rule-package.md b/docs/23-authoring-a-rule-package.md new file mode 100644 index 00000000..79b94404 --- /dev/null +++ b/docs/23-authoring-a-rule-package.md @@ -0,0 +1,249 @@ +# 23 — Authoring a Rule Package + +A rule package teaches mcpp to build something the engine has no rule for: a +device language, a shader, a generated interface, a check. It is an ordinary +package whose module a consumer's build program imports. + +Related documents: [07 — build.mcpp](07-build-mcpp.md) is the reference for the +directive protocol and the typed API this chapter uses; [22 — Features and +Capabilities](22-features-and-capabilities.md) covers the feature keys; +[20 — Heterogeneous Builds](20-heterogeneous-builds.md) is where the shipped +rules are listed. Two examples: +[`08-build-rules`](../examples/08-build-rules/) for a rule that checks and +embeds, [`12-a-new-device-language`](../examples/12-a-new-device-language/) for +one that adds a language. + +## What a rule package is + +Three parts, and none of them is special to mcpp: + +| part | what it is | +|---|---| +| a package | an ordinary `mcpp.toml` with a version and a licence | +| a module | a `.cppm` exporting `options` and a function that submits build edges | +| a feature | the switch that selects it, and the place its own dependencies hang | + +The engine holds no rule names and no vendor names. A consumer names the +package, activates a feature, and the build program imports the module. + +## The manifest: three keys + +```toml +[features] +default = [] + +[features.rules-toy] +sources = ["src/rules-toy.cppm"] +rule_module = "example.rules.toy" +device_extensions = [".toy"] +``` + +| key | effect on a consumer | +|---|---| +| `sources` | the module is compiled only when the feature is active | +| `rule_module` *(2026.9.7.1+)* | the module the consumer's build program imports. It implies `host-module = true` | +| `device_extensions` *(2026.9.7.1+)* | those extensions are classified as **device sources**: never scanned for imports, never producing a BMI, and refused if no action claims them | + +A rule that generates or checks rather than compiling a language declares no +`device_extensions`; `08-build-rules` is that shape and its consumer writes +`host-module = true` on the dependency edge itself. + +Device extensions are not in the default source glob. A consumer opts in by +naming the files: + +```toml +[build] +sources = ["src/*.cpp", "src/kernels/*.toy"] +``` + +## The module a consumer imports + +The convention every shipped rule follows is an `options` struct with defaults, +and a `compile` (or `generate`) function that submits the edges: + +```cpp +export module example.rules.toy; +import std; +import mcpp; + +export namespace example::rules::toy { + +struct options { + std::string out_dir = std::string(mcpp::out_dir()); + std::string rule_dir = std::string(mcpp::dep_dir("rules-toy")); +}; + +bool compile(options opt = {}); + +} +``` + +A rule that plans its edges in one function and submits them in another gives a +consumer a way past its last knob without hand-writing the action: +`08-build-rules`'s `plan` / `submit` pair is that shape. + +**A rule takes the extensions it claims and leaves the rest.** +`mcpp::device_sources()` is one string, one package-root-relative path per line, +and it holds the package's **whole** device set. A build with two backends puts +both backends' sources in that one list, and every rule in the build program +reads the same value. + +## Declaring work: `mcpp::action` + +```cpp +mcpp::action a; +a.id = id.c_str(); // stable, unique within the package +a.role = "source"; +a.description = desc.c_str(); +a.arg("sh").arg(script.c_str()).arg(input.c_str()).arg(output.c_str()); +a.input(input.c_str()); +a.input(script.c_str()); +a.output(output.c_str()); +a.submit(); +``` + +The strings must outlive the action. `a.id = ("toy:" + stem).c_str()` hands it a +pointer into a temporary that is gone by `submit()`. + +### The four roles + +`role` decides where the edge's outputs go and when the edge runs. + +| `role` | outputs | ordering | +|---|---|---| +| `source` | compilable ones join the compile set | every compile edge of the declaring package waits for them | +| `check` | a stamp file | runs alongside compilation; `blocking = true` makes compiles wait | +| `object` | join the **link** set | the link edge consumes them | +| `artifact` | a new file | its inputs are link outputs, so it runs after the link | + +### Declared inputs, and the compiler among them + +An action re-runs when a declared input changes. **The tool the command invokes +is an input.** Without it, editing the rule's own compiler leaves every edge +clean and the artifact keeps the bytes the previous compiler produced — a green +build over a stale result. + +An action's command runs from the **build directory**, not from the package +root. `mcpp::device_sources()` answers package-root-relative, so a rule joins +`mcpp::manifest_dir()` to each path before putting it on a command line. + +### A depfile, when the command discovers its own inputs + +A shader or a kernel that includes another file has inputs the rule cannot +enumerate. Every compiler involved emits a depfile — +`glslangValidator --depfile`, `glslc -MD -MF`, `slangc -depfile`, and +`-MD -MF` for the clang-family drivers: + +```cpp +a.depfile = dep.c_str(); // a path the command writes +a.arg("--depfile").arg(dep.c_str()); +``` + +The depfile must not also be declared as an `output()`. + +### Chaining actions + +One action may consume what another produced. The engine orders and fingerprints +them, and that is the whole engine-side content of a device link: N `artifact` +actions whose outputs stay out of the link, and one `object` action that reads +them and produces the object that joins it. + +## The environment a rule brings with it + +A rule owns the list of packages it drives, because it is the code that runs the +compiler and puts the library directory on the link line. + +```toml +[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, and the `cfg(accelerator = ...)` selector says +whether this build compiles for the device. A build with no accelerator opens +neither. + +A bare version is a **choice** a project may override; `>=` is a +**requirement** a project may not go below. See *One package, one version* in +[05 — mcpp.toml](05-mcpp-toml.md). + +## Generating an island's boundary + +A device translation unit cannot import a module, so the boundary between it and +the C++ side is an `extern "C"` header. `mcpp.tools.island` from `mcpp:plugins` +reads the entry points marked `MCPP_EXPORT_C` out of both implementations and +writes that header and a module over it, so each signature exists once. + +```cpp +mcpp::tools::island::options opt; +opt.module_name = "myapp.kernels"; +opt.out_dir = std::string(mcpp::out_dir()) + "/island"; + +const auto entries = mcpp::tools::island::scan(halves, opt); +const auto out = mcpp::tools::island::emit(*entries, opt); +mcpp::generated(out->interface_file.c_str()); +``` + +Four rungs are available, and each overrides the one above: + +| rung | written by hand | the consumer writes | +|---|---|---| +| L0 | nothing but the marked entry points | `import myapp.kernels` — the island's own C-shaped interface | +| L1 | a seam module over the generated one | `import myapp.saxpy` — the interface the project designed | +| L2 | a seam, plus the entry list passed to `emit` directly | the same, for entry points a scan cannot see | +| L3 | the header and the module | the same, with the signature written twice | + +The generated header reaches the island through +`mcpp::tools::island::force_include_flags`, whose flags go to the **rule** that +drives the device compiler rather than through `mcpp::cxxflag` — forcing a +header into every C++ translation unit puts declarations ahead of a module +interface's `export module` line, which is ill-formed. + +[`examples/09-heterogeneous/boundary`](../examples/09-heterogeneous/boundary/) +is L0 and states what each rung costs. + +## Reporting what the build should know + +### `warning` — succeeding and still being heard + +`mcpp::warning` is the channel for a rule that finished its job and found +something worth saying: a host compiler it had to choose, a payload it fell back +to. A build program's output is otherwise printed only on a non-zero exit. + +### `fact` / `floor` — the probe channel + +A rule measures, and the engine compares before anything is compiled. + +```cpp +mcpp::fact("cuda.driver", driver_version); +mcpp::floor("cuda.driver", runtime_needs); +``` + +The engine reads a name, a relation and a version; the name is data flowing +through it. A probe that reaches no answer states none. + +## Finding the rule's own files + +| what | how | +|---|---| +| the rule package's own tree | `mcpp::dep_dir("")` — under the name the **consumer** declared in `[dependencies]` | +| a payload declared under `[xlings.workspace]` | `mcpp::xpkg_dir("")` | +| a host tool built from a dependency | `mcpp::dep_bin("", "")` | + +`dep_dir` answers under the spelling in the consumer's manifest. A rule that +ships files beside its module exposes the directory as an option so a consumer +that declares the edge under another key can supply it, and refuses with a +message naming what it looked for rather than running a command with an empty +path. + +## Current limitations + +- A rule feature that is in the package's own `[features] default` does not + imply `host-module`. `mcpp:plugins` declares `default = []`; a rule package + whose rule is on by default is refused with a message naming the module and + the key to add. +- `mcpp emit xpkg` produces a descriptor that `mcpp xpkg parse` rejects for a + package that keeps its own `mcpp.toml`: the emitted `mcpp` segment carries + `manifest = "mcpp.toml"` and no `sources` list, and the validator requires + one. Adding `sources = { … }` to the emitted descriptor makes it validate. diff --git a/docs/README.md b/docs/README.md index d43d4341..81d7ab4e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -30,7 +30,7 @@ anything a reader needs is written here or in a specification instead. | publish it | [02](02-pack-and-release.md), [10](10-publishing-a-library.md), [12](12-binary-distribution.md) | [`03-pack-static`](../examples/03-pack-static/), [`05-lib-distribution`](../examples/05-lib-distribution/) | | build for another machine | [16](16-the-target-triple.md), [15](15-openkal-cross.md), [13](13-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | | use a GPU or an accelerator | [20](20-heterogeneous-builds.md), [18](18-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | -| add a rule, a language or a generator | [23](23-authoring-a-rule-package.md), [07](07-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`13-a-new-device-language`](../examples/13-a-new-device-language/) | +| add a rule, a language or a generator | [23](23-authoring-a-rule-package.md), [07](07-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`12-a-new-device-language`](../examples/12-a-new-device-language/) | | add a package to the index | [10](10-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [21](21-commands-by-scenario.md) — the publishing scenarios | | change mcpp itself | [04](04-build-from-source.md), [09](09-release.md), [19](19-supported-versions.md) | — | diff --git a/docs/zh/01-examples.md b/docs/zh/01-examples.md index ae817974..df2a1d22 100644 --- a/docs/zh/01-examples.md +++ b/docs/zh/01-examples.md @@ -1,8 +1,8 @@ -# 01 — 示例项目 +# 01 —— 示例项目 -> 仓库的 [`examples/`](../../examples) 目录下提供了一组循序渐进的最小工程, -> 覆盖从单文件 `import std` 到全静态发布包的常见场景。每个示例都可以 -> 独立进入并通过 `mcpp build` 完成构建。 +[`examples/`](../../examples) 目录是一套课程。每个工程都可以单独跑起来,而且每个 +工程都教**一件更早的示例没有教过的事**。本章说明那件事是什么,于是你可以按自己 +需要的深度进入,而不必从头读起。 ## 运行方式 @@ -12,47 +12,88 @@ cd mcpp/examples/01-hello mcpp build && mcpp run ``` -每个示例附带独立的 README,仅说明该示例相对前一个引入的新概念。 -安装步骤、工具链初始化等通用内容统一放在 -[00 — 快速开始](00-getting-started.md) 中,不再在示例内重复。 - -## 示例列表 - -| # | 路径 | 说明 | 涉及的关键概念 | -|---|---|---|---| -| 01 | [`examples/01-hello`](../../examples/01-hello/) | 单文件 + `import std` 的最小工程 | 最小工程形态(`mcpp new` 还会生成 `tests/test_smoke.cpp`) | -| 02 | [`examples/02-with-deps`](../../examples/02-with-deps/) | 引入依赖 `mcpplibs.cmdline` 解析命令行参数 | `[dependencies]`、SemVer、`mcpp.lock` | -| 03 | [`examples/03-pack-static`](../../examples/03-pack-static/) | 通过 `mcpp pack --mode static` 生成全静态发布包 | `[target.]` 与 `[pack]` 配置 | -| 04 | [`examples/04-workspace`](../../examples/04-workspace/) | 多包工作空间:两个库 + 一个应用,共享一个命名空间 | `[workspace]`、path 依赖、`mcpp build --workspace` | -| 05 | [`examples/05-lib-distribution`](../../examples/05-lib-distribution/) | 一个预建库和它的消费者,只有放在一起才有意义 | 对库做 `mcpp pack`、同一份源码同时给出 C 头文件与 C++ 模块、distribution 包 | -| 06 | [`examples/06-openkal-cross`](../../examples/06-openkal-cross/) | 同一个程序问每台机器它是什么,从任意宿主构建到四个目标 | `--target`、openkal、不改源码的交叉编译 | -| 07 | [`examples/07-project-subos`](../../examples/07-project-subos/) | 构建程序在工程声明的环境里找工具,而不是问机器上恰好有什么 | `[xlings] subos`、`[xlings.workspace]`、构建程序的 `PATH` 来自工程声明的那个环境 | -| 08 | [`examples/08-build-rules`](../../examples/08-build-rules/) | 两个规则包,以及同时用到它们的工程 | `host-module = true`、`[build-dependencies]`、`role = "check"` 的 `mcpp::action` | -| 09 | [`examples/09-heterogeneous`](../../examples/09-heterogeneous/) | 同一个计算在设备上跑,写成多种编程模型,每种都带 CPU 回退;外加一个同时携带多个后端的产物 | `accel`、带约束的 source glob、接缝模块、来自 `mcpp:plugins` 的规则包、`cfg(accelerator = …)` | -| 09a | [`…/cuda`](../../examples/09-heterogeneous/cuda/) | 接缝模块背后的 CUDA kernel,`extern "C"` 边界是**生成**的 | `mcpp.rules.cuda`、`mcpp.tools.island`、`role = "object"` 的 `mcpp::action`、把驱动陈述为 fact 与 floor | -| 09b | [`…/vulkan`](../../examples/09-heterogeneous/vulkan/) | 同一个计算写成 Vulkan compute shader,SPIR-V 载荷以**模块**到达 | `mcpp.rules.spirv`、模块表面、`role = "source"` 的 `mcpp::action`、作为载荷的软件驱动 | -| 09c | [`…/sycl`](../../examples/09-heterogeneous/sycl/) | 同一个计算写成 SYCL kernel,由第二个编译器编译 | `mcpp.rules.sycl`、`mcpp.tools.island`、`.sycl` 设备扩展名、为 device link 串起来的 `mcpp::action`、`compat:sycl-runtime` | -| 09d | [`…/hip`](../../examples/09-heterogeneous/hip/) | 同一个计算写成 HIP,边界是**手写**的 —— 与 09a 的对照 | `mcpp.rules.hip`、HIP 作为 CUDA 运行时之上的一层头文件、两段式的 `accel` | -| 09e | [`…/multi-backend`](../../examples/09-heterogeneous/multi-backend/) | 多个后端进**同一个产物**,运行期选择 —— 这是库的形态,不是程序的形态 | `accel` 作为集合、`cfg(accelerator = "none")` 及其否定、分发链、C 岛边界之上的模块接缝 | -| 09f | [`…/cann`](../../examples/09-heterogeneous/cann/) | 同一道接缝背后的 Ascend C kernel。**目前还构建不了** —— README 里点明了缺的两块 | `.asc` 设备扩展名、CANN 本来就有的 `op_kernel`/`op_host` 岛、回退用 `accelerator = "none"` | -| 10 | [`examples/10-graphics`](../../examples/10-graphics/) | 图形而不是计算:一条渲染管线,它的结果是像素 | `mcpp.rules.spirv` 编译顶点与片段两个阶段、离屏渲染作为可断言的形态 | -| 10a | [`…/offscreen`](../../examples/10-graphics/offscreen/) | Vulkan 把一个三角形光栅化进缓冲区,同一道接缝背后是同一个三角形的软件光栅器 | 一个 glob 带出两个着色器阶段、无窗口无交换链的 render pass、判据是一个像素 | - -## 推荐阅读顺序 - -建议按编号依次阅读: - -1. **`01-hello`** 展示 mcpp 工程的最小骨架(`mcpp.toml` 与 `src/main.cpp`), - 并演示 `import std` 的基本用法。当前 `mcpp new` 脚手架还会生成 - `tests/test_smoke.cpp`。 -2. **`02-with-deps`** 在前一示例基础上引入外部依赖,涵盖锁文件机制 - 与模块化包索引的工作方式。 -3. **`03-pack-static`** 演示如何将构建产物打包为可独立分发的单文件 - 二进制;打包细节可参考 [02 — 发布打包](02-pack-and-release.md)。 - -## 新增示例 - -示例工程遵循统一的目录结构:`mcpp.toml` + `src/` + `README.md`。 -新增示例时,在 `examples/` 下创建编号目录(如 `04-xxx/`),并在 -README 中简要说明该示例演示的概念,然后提交 PR。提交规范见 -[04 — 从源码构建 & 参与贡献](04-build-from-source.md)。 +每个示例自带 README,只解释它新增的部分。安装与工具链初始化在 +[00 —— 快速开始](00-getting-started.md),不在示例里重复。 + +## 课程 + +### A —— 工程的形状 + +| 示例 | 第一个教什么 | +|---|---| +| [`01-hello`](../../examples/01-hello/) | 一个包、`import std`、`mcpp build` 与 `mcpp run` | +| [`02-with-deps`](../../examples/02-with-deps/) | `[dependencies]`、锁文件、`mcpp add` | +| [`04-workspace`](../../examples/04-workspace/) | `[workspace]`、path 依赖、`mcpp build --workspace` | +| [`11-features`](../../examples/11-features/) | **声明** feature 而不是消费它,`[feature-deps]`、`[dev-dependencies]`、`[profile.]`、`mcpp::has_feature` | + +### B —— 发布 + +| 示例 | 第一个教什么 | +|---|---| +| [`03-pack-static`](../../examples/03-pack-static/) | `mcpp pack --mode static`、`[target.]`、`[pack]` | +| [`05-lib-distribution`](../../examples/05-lib-distribution/) | 一个库的接口与它的预编译二进制;从同一份源产出 C 头文件与 C++ 模块 | + +### C —— 环境 + +| 示例 | 第一个教什么 | +|---|---| +| [`07-project-subos`](../../examples/07-project-subos/) | `[xlings]`、`[xlings.workspace]`,以及 `PATH` 来自工程声明环境的构建程序 | + +### D —— 目标 + +| 示例 | 第一个教什么 | +|---|---| +| [`06-openkal-cross`](../../examples/06-openkal-cross/) | `--target`,同一份源在任意宿主上为四台机器构建 | + +裸机由**模板**而不是本目录里的一个工程来教 —— 见下面的*以模板形式到达的课程*。 + +### E —— 设备与图形 + +[`09-heterogeneous`](../../examples/09-heterogeneous/) 按顺序读。它的 README 是 +地图;下表是每个子示例新增的部分。 + +| 示例 | 第一个教什么 | +|---|---| +| [`…/boundary`](../../examples/09-heterogeneous/boundary/) | 单独的岛边界:消费者 import 一个生成的模块,工程里没有接缝也没有头文件。不需要设备 | +| [`…/cuda`](../../examples/09-heterogeneous/cuda/) | 设备编译器、生成边界之上的接缝、把驱动陈述为 fact 与 floor | +| [`…/vulkan`](../../examples/09-heterogeneous/vulkan/) | 一个 compute shader,其 SPIR-V 载荷以模块到达 | +| [`…/sycl`](../../examples/09-heterogeneous/sycl/) | 第二个编译器,自带它自己的标准库 | +| [`…/hip`](../../examples/09-heterogeneous/hip/) | 手写的边界 —— 与 `boundary/` 和 `cuda/` 的对照 | +| [`…/cann`](../../examples/09-heterogeneous/cann/) | NVIDIA 与 Khronos 谱系之外的厂商 | +| [`…/multi-backend`](../../examples/09-heterogeneous/multi-backend/) | 多个后端进同一个产物,运行期选择 | +| [`10-graphics/offscreen`](../../examples/10-graphics/offscreen/) | 结果是像素的渲染管线,并与软件光栅器逐像素比对 | + +### F —— 为生态编写扩展 + +| 示例 | 第一个教什么 | +|---|---| +| [`08-build-rules`](../../examples/08-build-rules/) | 两个规则包与同时使用它们的工程;`host-module = true`、`role = "check"` 的 `mcpp::action` | +| [`12-a-new-device-language`](../../examples/12-a-new-device-language/) | `device_extensions` 与 `rule_module`:规则包教会 mcpp 一门引擎从未听说过的语言 | + +[23 —— 编写规则包](23-authoring-a-rule-package.md) 是这两个示例所演示内容的参考。 + +## 以模板形式到达的课程 + +一个包可以提供 `templates//`,由 `mcpp new --template` 实例化。那是与本目录 +和章节并列的第三个教学面;当被教的东西属于某个包而不属于 mcpp 时,课程就落在那里。 + +| 模板 | 课程 | 章节 | +|---|---|---| +| `riscv-virt-rt` | 一个裸机工程、它的板级支持与它的 runner | [13](13-baremetal.md) | +| `riscv-virt-rt:nolibc` | 同上,但没有 C 库 | [13](13-baremetal.md) | +| `ocornut.imgui` | 一个带窗口与渲染栈的图形应用 | [03](03-toolchains.md) | + +```bash +mcpp new blinky --template riscv-virt-rt +``` + +## 新增一个示例 + +一个示例目录是 `mcpp.toml` + `src/` + `README.md`,编号接在最后一个之后。什么时候 +值得新增一个示例:当一个能力**改变工程的形状** —— 它包含的文件、它声明的 manifest、 +或者作者敲的命令。如果一个能力只是既有示例工程里的一行,它属于对应章节里的一个 +代码块;如果它只经由命令到达,它属于 +[21 —— 按场景选命令](21-commands-by-scenario.md)。 + +README 要写明这个示例第一个教什么,以及判断它是否成立的判据。贡献流程见 +[04 —— 从源码构建 & 参与贡献](04-build-from-source.md)。 diff --git a/docs/zh/05-mcpp-toml.md b/docs/zh/05-mcpp-toml.md index 1f6f0b4b..b5938edf 100644 --- a/docs/zh/05-mcpp-toml.md +++ b/docs/zh/05-mcpp-toml.md @@ -1243,331 +1243,11 @@ error: test result: NOT RUN. 0 passed; 0 failed; 1 not run (this host cannot exe 退出码 1 含义不变 —— 有测试运行并失败;0 表示每个测试都运行并通过。 `--message-format json` 在每条记录上带 `"status":"not_run"` 与 `reason`,在汇总记录上 带 `not_run` / `not_run_reason`(见 [11 —— 机器可读输出](11-machine-output.md))。 +### 2.8 `[features]` —— Feature -### 2.8 `[features]` —— Feature(Cargo 风格,可加性) +已移入 [22 —— Feature 与能力](22-features-and-capabilities.md), +连同 `provides` / `requires` 与 `[feature-deps.]`。 -#### 表形式 —— 让 feature 贡献的不止是隐含 feature - -`[features]` 的条目除了写成数组,还可写成**表**,从而让该 feature 在隐含 feature -之外,携带包自有的预处理 `defines`、feature 门控的源 glob(`sources`,mcpp -0.0.95+——列出的 glob 离开默认构建,仅当 feature 激活时才编译,与 index 描述符的 -`features..sources` 完全对等;这正是 vendored 大库最高频的形态:*feature = -一组源文件 + 一个 define*)、feature 门控的 per-glob 编译旗标(`flags`,mcpp -0.0.101+),以及 capability 的 `requires` / `provides`(见 §2.8.1): - -```toml -[features] -default = [] -# 数组简写:仅隐含 feature。 -docking = ["extra"] -extra = [] -# 表形式:激活时贡献一个包自有的宏。 -mpl2only = { defines = ["EIGEN_MPL2_ONLY"] } -# 表形式:宏 + 一个隐含 feature。 -fast_math = { defines = ["APP_FAST=1"], implies = ["extra"] } -# 表形式:feature 门控源 + 与其同居的 per-glob 旗标。 -simd = { sources = ["src/simd/**"], flags = [ - { glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"] } ] } -``` - -- **表形式恰好接受** `implies`、`forward`、`defines`、`sources`、`flags`、 - `requires`、`provides`。其余键会被报成一条 schema 警告并忽略(mcpp 2026.9.1.1+); - `deps` 单独报为「保留」(它是计划中的而不是写错的),并指向 `[feature-deps.]`。在该版本之前,`[features]` - 是唯一一个完全没有 schema 检查的结构化段落 —— 把 `include_dirs` 误写进 feature 里 - 会零诊断地构建成功,而同样的错误写在 `[build]` 里会被报出来。 -- `defines` 为**裸**宏名(不带 `-D`);feature 激活时每个脱糖为 `-D`,加到该包 - 自己的编译上——与 `[targets.*] defines` 完全一致。按约定仅限包**自有**的带命名 - 空间宏:feature **不**注入自由的包级 `cflags`/`ldflags`,否则会破坏加性的 feature - 并集模型。链接旗标来自 provider 依赖(§2.8.1),而非 feature。 -- 每个激活的 feature 仍会得到自动的 `-DMCPP_FEATURE_`,`defines` 与之叠加。 -- `flags`(mcpp 0.0.101+)与 `[build].flags`(§2.3)共用同一有序 inline-table 数组 - 文法(`glob` 必填,加 `cflags`/`cxxflags`/`asmflags`/`defines`;与 - `[[build.flags]]` 一样也接受 `[[features..flags]]` 拼写)。feature 激活时 - 条目追加在 base `[build].flags` **之后**(feature 按名 - 序),"last flag wins" 使 feature 规则可覆盖更宽的 base 规则;未激活时条目根本 - 不存在(不会有死 glob 告警)。这让 feature 的组内专属旗标与其 `sources` 同居, - 而不必写成 base 规则、在 feature-off 构建里留下必死的 glob。与 `defines` 不同, - feature `flags` 是**私有 per-TU 构建旗标**——永不传播给消费者(与 `[build].flags` - 同契约),因此不破坏加性模型:glob 限定作用面、顺序确定、无跨包效应。 - - -#### 作为构建规则的 feature(mcpp 2026.9.7.1+) - -两个键把一个 feature 变成其它包可以使用的构建规则。它们是消费者只写一条依赖边、 -不写构建程序的原因。 - -```toml -[features.rules-spirv] -sources = ["rules/spirv.cppm"] -rule_module = "mcpp.rules.spirv" -device_extensions = [".comp", ".vert", ".frag", ".glsl"] -``` - -`device_extensions` 陈述这条规则编译哪些**设备源**扩展名。激活了该 feature 的消费者 -会把它们分类为设备源 —— 不扫描 import、不产 BMI、由 mcpp 不驱动的编译器编译。这与 -`[build] module_extensions` 是同一个形状:mcpp 知道设备源*是什么*,不知道 `.cu` 是 -CUDA,所以**一门新设备语言不需要引擎改动**。 -[20 — 异构硬件构建](20-heterogeneous-builds.md) 里那句「第六个后端是一个包而不是一次 -引擎改动」由此才成立;`.slang` 已从 mcpp 的内置表中移除,现在正是经由这条路到达的。 - -`rule_module` 给出消费者的构建程序为够到这条规则而 import 的模块,以及它调用的 -`compile()` 所在。它是**声明**的而不是从源码扫描出来的,因为那个程序必须在任何东西 -被编译**之前**写出来,而一次为了决定写什么而去扫描依赖源码的构建会把两者的顺序颠倒。 - -由此得出两件事,而且都不把任何包名放进 mcpp: - -- **`host-module = true` 被推出来。** 一个点名了规则模块的 feature 已经说过那是使用 - 它的唯一方式,所以依赖边不必再说一遍。 -- **没有 `build.mcpp` 的包会得到一个。** mcpp 把这些规则描述的程序写进构建目录并编译 - 它。自带程序的包保留自己的:合成只填补缺席、绝不覆盖;而生成出来的那份就是这个工程 - 本来要手写的那份,所以接管它是一次复制加一次编辑。 - -feature 仍然**按名字**请求: - -```toml -[build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-spirv"] } -``` - -早先的一版设计从工程源码里出现的扩展名推导这个集合。它被撤销了,因为两个包可能认领 -同一个扩展名 —— 第三方写一条处理 `.cu` 的规则是会发生的事 —— 也因为 manifest 的职责 -是描述这次构建,而派生出来的 feature 集合让文件不再陈述它。 - -两个键必须成对出现。只写其一是一条没有任何东西能据以行动的声明,会在解析期被拒绝, -而不是留到消费者的构建里。 - - -### 2.8.1 `provides` / `requires` —— 能力(后端选择) - -**capability(能力)** 是一个共享的抽象名字(如 `blas`)。包可以 *provide*(提供) -一种能力;feature 可以 *require*(需要)一种能力而非点名某个具体包,解析器会从依赖 -图中绑定**恰好一个** provider。这样就能在多个可互换后端(OpenBLAS / MKL / …)中选其 -一,而不必把选择写死进库里。 - -```toml -# provider 包为任何 require 它的依赖方满足某能力。 -[package] -name = "compat.openblas" -version = "0.3.0" -provides = ["blas", "lapack"] -``` - -```toml -# 消费方经由自己的某个 feature 来 require 这个抽象能力。 -[features] -use_blas = { defines = ["EIGEN_USE_BLAS"], requires = ["blas"] } - -# 图中有 >1 个 provider 时,选其一(否则构建报错并列出候选)。 -[capabilities] -blas = "compat.openblas" # 等价于:mcpp build --cap blas=compat.openblas - -[dependencies] -compat.openblas = "0.3.0" # provider 必须是图中真实存在的依赖 -``` - -保留前缀 `mcpp:` 命名本引擎解析的目标侧层,这些名字对照一个闭集校验。 -包级 `requires` 数组承载对称的陈述 —— 某个目标侧层必须解析为什么, -本包才可用。 - -```toml -[package] -name = "acme.llvm-runtime" -version = "0.1.0" -provides = ["mcpp:compiler-runtime=compiler-rt", "mcpp:c++-abi=libc++"] -requires = ["mcpp:compiler=llvm"] -``` - -作为标准库的包在 `[build]` 下陈述它的 `std` 模块源, -其所需的 flag 在那里与任何其它构建输入一样可条件化。 - -```toml -[build] -std-module = "llvm-generated/std.cppm" -std-compat-module = "llvm-generated/std.compat.cppm" -std-module-flags = ["--no-default-config", "-nostdinc++"] - -[target.'cfg(c-abi = "musl")'.build] -std-module-flags = ["-D_GNU_SOURCE"] -``` - -五个层、约束它们的规则与相应诊断,见 [14 - 目标侧](14-target-side.md)。 - -绑定是**确定性**的: - -| 图中某被需要能力的 provider 数量 | 结果 | -|---|---| -| 恰好一个 | 自动绑定(无需配置) | -| `[capabilities]` pin / `--cap` 指定了一个 | 以 pin 为准 | -| 零个 | **报错**:没有包提供 `` | -| 两个及以上且未 pin | **报错**并列出候选——绝不静默猜测 | - -被绑定 provider 的链接/头文件旗标经由常规依赖机制流到消费方;capability 层是那道 -*选择与校验* 步骤,把"静默选错后端 / 缺后端"变成构建期的显式报错。 - -**绑定选中的是 provider,它不裁剪链接行。** 依赖包的目标文件一律进入消费方的链接, -与它的能力是否被绑定无关。实测:两个包都提供同一能力且都定义 `cap_probe`,未 pin 时 -解析按上表报错;按提示用 `[capabilities]` pin 其中一个之后,构建走到链接器才失败—— - -``` -ld: obj/mcpplibs_pa/src/impl.o: in function `cap_probe': - multiple definition of `cap_probe'; obj/mcpplibs_pb/src/impl.o: first defined here -``` - -这一点对**多个 provider 定义同一批符号**的能力有影响 —— 全程序单例(例如 -`operator new`),或名字集合固定的 C 接口。对这类能力,图中出现两个 provider 是**待修的 -缺陷**而非可 pin 的歧义:pin 会把一个点名两个候选的报错,换成一个点名 mangled 符号的报错。 -可互换的**库**(各 BLAS 实现导出不同的符号集合,按链接选其一)不受此影响。 - -#### `exclusive` —— 包声明自己是唯一提供者 - -上一段描述的是一个引擎**看不见**的缺陷:要看出两个 provider 定义了同一批符号, -需要它们的目标文件,而绑定 capability 时那些还不存在;而「一律拒绝重复 provider」 -又会打断同一段里那个合法的 BLAS 用例。 - -所以由包自己声明: - -```toml -[package] -name = "compat.cublas" -provides = ["gpu-blas"] -exclusive = ["gpu-blas"] -``` - -两个都提供 `gpu-blas` 的包,只要其中至少一个声明了独占,就在绑定 capability 时 -被拒绝 —— 在任何东西被编译之前,并点名该能力与双方: - -``` -error: capability 'gpu-blas' is provided by more than one package, and they - declare it EXCLUSIVE. - providers: [compat.cublas, compat.rocblas] - exclusive: [compat.cublas, compat.rocblas] -``` - -该拒绝在 `--format json` 里报 `exclusive-capability`(见第 11 章)。 - -#### `version-floor` —— 对机器的要求高于它所有 - -有些关于机器的事实约束着能为它构建什么,而忽略它们时的失败来得很晚: -一个针对比它将遇到的驱动更新的运行时构建出来的程序,**干净地链接**, -在第一次使用时失败,而消息里两侧都没有。 - -包声明它需要什么: - -```toml -[[runtime.requirements]] -kind = "version-floor" -value = "cuda.driver >= 12.0" -``` - -而某个在**安装期**(探测该发生的地方)确立了机器某项事实的包,声明它: - -```toml -[runtime] -provides = ["cuda.driver=12.4"] -``` - -mcpp 在绑定 capability 时比较二者,并在任何东西被编译之前拒绝, -报 `version-floor-unmet`: - -``` -error: `toolkitnew` requires cuda.driver >= 13.0, and this machine has 12.4. - stated by: driverfact -``` - -**没有任何厂商词汇抵达引擎。** 它读到的是一个名字、一个关系和一个版本; -`cuda.driver` 是流过的数据,一个 mcpp 从未听说过的后端比较方式完全相同。 - -**没人回答的下界是沉默的。** 一台从未声明自己有什么的机器,不是「未满足下界」的机器, -而是「没人问过」的机器。把「我们不知道」变成「不行」正是这个机制要避免的失败, -并且有直接判据:`tests/e2e/603_version_floor.sh` 会构建一个下界指向无人提供之物的工程。 - -**它是关于这个包自己的符号的声明**,所以一条指向本包并不提供的能力的条目会被报为 -schema 警告:那里没有可独占的东西。而无人声明独占的能力行为完全不变 —— 两个 BLAS -实现照常共存,既有的「两个或更多、未 pin」报错也仍然只在**有人 require** 该能力时出现。 - -### 2.8.2 `[feature-deps.]` —— 由 feature 拉取的依赖 - -在 `[feature-deps.]` 下声明的依赖是**可选的**:仅当该 feature 激活时(根 `--features`, -或某依赖 spec 的 `features = [...]`)才会被解析。`[dependencies]` 中的依赖始终被解析; -可选性由声明的*位置*表达,而非某个标志位。 - -```toml -[features] -use_blas = { defines = ["EIGEN_USE_BLAS"], requires = ["blas"] } -backend-openblas = { implies = ["use_blas"] } - -# 仅当 `backend-openblas` 激活时才拉取。每个条目都是完整的依赖 spec -#(version/path/git + 其自身的 features)。 -[feature-deps.backend-openblas] -compat.openblas = "0.3" -``` - -**写 `"^0.3.0"`,而不是 `"0.3.x"` 或 `"0.3"`。** 以索引中确定存在的包作对照, -判据取**构建成功**: - -| 写法 | 结果 | -|---|---| -| `cmdline = "0.0.1"` | 构建通过 | -| `cmdline = "^0.0.1"` | 构建通过 | -| `cmdline = "0.0"` | 解析通过,随后 `install path missing after fetch` | -| `cmdline = "0.0.x"` | `E_NOT_FOUND`,点名的是包 —— 而该包存在 | - -这三种结果值得分开,因为两个更弱的判据各自会放行一种不可用的写法: -「没有 `E_NOT_FOUND`」放行两段前缀,「解析通过」同样放行它。**只有对着真实索引构建 -一次**才能定论。 - -这一点在此处比在 `[dependencies]` 中更要紧:**实现取不回来的 feature 等于不存在的 -feature**,而开发期使用 **path** 依赖的工程根本不查索引 —— 该失败只在发布之后才出现, -而且是出现在别人身上。 - -该机制与能力(§2.8.1)组合:单个 `backend-openblas` feature 既**拉取** provider -(`compat.openblas`,其 `provides = ["blas"]`),又**开启**消费方开关 -(`implies = ["use_blas"]`,其 `requires = ["blas"]`)。当图中只有一个 provider 时, -能力自动绑定——消费方只需写 `features = ["backend-openblas"]`。 - -在索引包的 Lua 描述符中,等价写法为内联形式: - -```lua -features = { - use_blas = { defines = { "EIGEN_USE_BLAS" }, requires = { "blas" } }, - ["backend-openblas"] = { - implies = { "use_blas" }, - deps = { ["compat.openblas"] = "0.3.x" }, - }, -} -``` - -#### 保持可替换的默认实现 - -同样这三件东西,也覆盖"库希望**提供**一份实现但不**强加**一份"的情形 —— 全程序单例, -例如 `operator new`、日志 sink、panic handler: - -```toml -[features] -default = [] -# 消费方开关:"我用到了本库中需要分配器的那部分"。 -alloc = { requires = ["freestanding-allocator"] } -# 内置默认:激活它就够了。 -alloc-kal = { implies = ["alloc"] } - -# 仅在 `alloc-kal` 激活时解析,因此库本体不携带对该实现的依赖。 -[feature-deps.alloc-kal] -std-freestanding-alloc-kal = "0.1.x" -``` - -三种用法各一行: - -| 消费方需要 | 清单里怎么写 | -|---|---| -| 不用会分配的那部分 | `std-freestanding = "0.2.0"` —— 分配器不进图 | -| 默认实现 | `features = ["alloc-kal"]` —— 实现随之进图,**无需知道其包名** | -| 自己的或第三方的 | `features = ["alloc"]` 加一个 `provides = ["freestanding-allocator"]` 的包 | - -有两条性质使该形状优于无条件随包提供实现。随包提供实现的库替程序做了本属程序的决定, -而且**撤销不掉**:feature 是**加性**的,消费方没有把某个默认**关掉**的手段。以及,由于 -依赖包的目标文件无条件参与链接(§2.8.1),随包的默认加上程序自备的那份是**重复定义** -而非替换 —— 让 C++ 标准库能提供可替换 `operator new` 的那套归档语义,对包依赖并不适用。 -把实现放在开关之后,意味着两者**从不共存**。 ### 2.8.3 `[scan_overrides.""]` —— 作者断言的扫描结果 diff --git a/docs/zh/22-features-and-capabilities.md b/docs/zh/22-features-and-capabilities.md new file mode 100644 index 00000000..d2eb1d22 --- /dev/null +++ b/docs/zh/22-features-and-capabilities.md @@ -0,0 +1,334 @@ +# 22 —— Feature 与能力 + +Feature 是一个包提供可选内容的方式:一个编译宏、一份额外的源文件、一个额外的 +依赖,或者在多个后端之间做选择。本章是声明与消费 feature 的参考。 + +相关文档:[05 —— mcpp.toml](05-mcpp-toml.md) 是 manifest 其余部分的字段参考; +[`examples/11-features`](../../examples/11-features/) 是一个把三种形态都声明了 +一遍、并且用 dev-dependency 写测试的包;[20 —— 异构硬件构建](20-heterogeneous-builds.md) +是这套机制最大的消费者,因为每条加速器 lane 都是一个 feature。 + +## `[features]` —— Feature(Cargo 风格,可加性) + +### 表形式 —— 让 feature 贡献的不止是隐含 feature + +`[features]` 的条目除了写成数组,还可写成**表**,从而让该 feature 在隐含 feature +之外,携带包自有的预处理 `defines`、feature 门控的源 glob(`sources`,mcpp +0.0.95+——列出的 glob 离开默认构建,仅当 feature 激活时才编译,与 index 描述符的 +`features..sources` 完全对等;这正是 vendored 大库最高频的形态:*feature = +一组源文件 + 一个 define*)、feature 门控的 per-glob 编译旗标(`flags`,mcpp +0.0.101+),以及 capability 的 `requires` / `provides`(见 §2.8.1): + +```toml +[features] +default = [] +# 数组简写:仅隐含 feature。 +docking = ["extra"] +extra = [] +# 表形式:激活时贡献一个包自有的宏。 +mpl2only = { defines = ["EIGEN_MPL2_ONLY"] } +# 表形式:宏 + 一个隐含 feature。 +fast_math = { defines = ["APP_FAST=1"], implies = ["extra"] } +# 表形式:feature 门控源 + 与其同居的 per-glob 旗标。 +simd = { sources = ["src/simd/**"], flags = [ + { glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"] } ] } +``` + +- **表形式恰好接受** `implies`、`forward`、`defines`、`sources`、`flags`、 + `requires`、`provides`。其余键会被报成一条 schema 警告并忽略(mcpp 2026.9.1.1+); + `deps` 单独报为「保留」(它是计划中的而不是写错的),并指向 `[feature-deps.]`。在该版本之前,`[features]` + 是唯一一个完全没有 schema 检查的结构化段落 —— 把 `include_dirs` 误写进 feature 里 + 会零诊断地构建成功,而同样的错误写在 `[build]` 里会被报出来。 +- `defines` 为**裸**宏名(不带 `-D`);feature 激活时每个脱糖为 `-D`,加到该包 + 自己的编译上——与 `[targets.*] defines` 完全一致。按约定仅限包**自有**的带命名 + 空间宏:feature **不**注入自由的包级 `cflags`/`ldflags`,否则会破坏加性的 feature + 并集模型。链接旗标来自 provider 依赖(§2.8.1),而非 feature。 +- 每个激活的 feature 仍会得到自动的 `-DMCPP_FEATURE_`,`defines` 与之叠加。 +- `flags`(mcpp 0.0.101+)与 `[build].flags`(§2.3)共用同一有序 inline-table 数组 + 文法(`glob` 必填,加 `cflags`/`cxxflags`/`asmflags`/`defines`;与 + `[[build.flags]]` 一样也接受 `[[features..flags]]` 拼写)。feature 激活时 + 条目追加在 base `[build].flags` **之后**(feature 按名 + 序),"last flag wins" 使 feature 规则可覆盖更宽的 base 规则;未激活时条目根本 + 不存在(不会有死 glob 告警)。这让 feature 的组内专属旗标与其 `sources` 同居, + 而不必写成 base 规则、在 feature-off 构建里留下必死的 glob。与 `defines` 不同, + feature `flags` 是**私有 per-TU 构建旗标**——永不传播给消费者(与 `[build].flags` + 同契约),因此不破坏加性模型:glob 限定作用面、顺序确定、无跨包效应。 + + +### 作为构建规则的 feature(mcpp 2026.9.7.1+) + +两个键把一个 feature 变成其它包可以使用的构建规则。它们是消费者只写一条依赖边、 +不写构建程序的原因。 + +```toml +[features.rules-spirv] +sources = ["rules/spirv.cppm"] +rule_module = "mcpp.rules.spirv" +device_extensions = [".comp", ".vert", ".frag", ".glsl"] +``` + +`device_extensions` 陈述这条规则编译哪些**设备源**扩展名。激活了该 feature 的消费者 +会把它们分类为设备源 —— 不扫描 import、不产 BMI、由 mcpp 不驱动的编译器编译。这与 +`[build] module_extensions` 是同一个形状:mcpp 知道设备源*是什么*,不知道 `.cu` 是 +CUDA,所以**一门新设备语言不需要引擎改动**。 +[20 — 异构硬件构建](20-heterogeneous-builds.md) 里那句「第六个后端是一个包而不是一次 +引擎改动」由此才成立;`.slang` 已从 mcpp 的内置表中移除,现在正是经由这条路到达的。 + +`rule_module` 给出消费者的构建程序为够到这条规则而 import 的模块,以及它调用的 +`compile()` 所在。它是**声明**的而不是从源码扫描出来的,因为那个程序必须在任何东西 +被编译**之前**写出来,而一次为了决定写什么而去扫描依赖源码的构建会把两者的顺序颠倒。 + +由此得出两件事,而且都不把任何包名放进 mcpp: + +- **`host-module = true` 被推出来。** 一个点名了规则模块的 feature 已经说过那是使用 + 它的唯一方式,所以依赖边不必再说一遍。 +- **没有 `build.mcpp` 的包会得到一个。** mcpp 把这些规则描述的程序写进构建目录并编译 + 它。自带程序的包保留自己的:合成只填补缺席、绝不覆盖;而生成出来的那份就是这个工程 + 本来要手写的那份,所以接管它是一次复制加一次编辑。 + +feature 仍然**按名字**请求: + +```toml +[build-dependencies.mcpp] +plugins = { version = "0.3.0", features = ["rules-spirv"] } +``` + +早先的一版设计从工程源码里出现的扩展名推导这个集合。它被撤销了,因为两个包可能认领 +同一个扩展名 —— 第三方写一条处理 `.cu` 的规则是会发生的事 —— 也因为 manifest 的职责 +是描述这次构建,而派生出来的 feature 集合让文件不再陈述它。 + +两个键必须成对出现。只写其一是一条没有任何东西能据以行动的声明,会在解析期被拒绝, +而不是留到消费者的构建里。 + + +## `provides` / `requires` —— 能力(后端选择) + +**capability(能力)** 是一个共享的抽象名字(如 `blas`)。包可以 *provide*(提供) +一种能力;feature 可以 *require*(需要)一种能力而非点名某个具体包,解析器会从依赖 +图中绑定**恰好一个** provider。这样就能在多个可互换后端(OpenBLAS / MKL / …)中选其 +一,而不必把选择写死进库里。 + +```toml +# provider 包为任何 require 它的依赖方满足某能力。 +[package] +name = "compat.openblas" +version = "0.3.0" +provides = ["blas", "lapack"] +``` + +```toml +# 消费方经由自己的某个 feature 来 require 这个抽象能力。 +[features] +use_blas = { defines = ["EIGEN_USE_BLAS"], requires = ["blas"] } + +# 图中有 >1 个 provider 时,选其一(否则构建报错并列出候选)。 +[capabilities] +blas = "compat.openblas" # 等价于:mcpp build --cap blas=compat.openblas + +[dependencies] +compat.openblas = "0.3.0" # provider 必须是图中真实存在的依赖 +``` + +保留前缀 `mcpp:` 命名本引擎解析的目标侧层,这些名字对照一个闭集校验。 +包级 `requires` 数组承载对称的陈述 —— 某个目标侧层必须解析为什么, +本包才可用。 + +```toml +[package] +name = "acme.llvm-runtime" +version = "0.1.0" +provides = ["mcpp:compiler-runtime=compiler-rt", "mcpp:c++-abi=libc++"] +requires = ["mcpp:compiler=llvm"] +``` + +作为标准库的包在 `[build]` 下陈述它的 `std` 模块源, +其所需的 flag 在那里与任何其它构建输入一样可条件化。 + +```toml +[build] +std-module = "llvm-generated/std.cppm" +std-compat-module = "llvm-generated/std.compat.cppm" +std-module-flags = ["--no-default-config", "-nostdinc++"] + +[target.'cfg(c-abi = "musl")'.build] +std-module-flags = ["-D_GNU_SOURCE"] +``` + +五个层、约束它们的规则与相应诊断,见 [14 - 目标侧](14-target-side.md)。 + +绑定是**确定性**的: + +| 图中某被需要能力的 provider 数量 | 结果 | +|---|---| +| 恰好一个 | 自动绑定(无需配置) | +| `[capabilities]` pin / `--cap` 指定了一个 | 以 pin 为准 | +| 零个 | **报错**:没有包提供 `` | +| 两个及以上且未 pin | **报错**并列出候选——绝不静默猜测 | + +被绑定 provider 的链接/头文件旗标经由常规依赖机制流到消费方;capability 层是那道 +*选择与校验* 步骤,把"静默选错后端 / 缺后端"变成构建期的显式报错。 + +**绑定选中的是 provider,它不裁剪链接行。** 依赖包的目标文件一律进入消费方的链接, +与它的能力是否被绑定无关。实测:两个包都提供同一能力且都定义 `cap_probe`,未 pin 时 +解析按上表报错;按提示用 `[capabilities]` pin 其中一个之后,构建走到链接器才失败—— + +``` +ld: obj/mcpplibs_pa/src/impl.o: in function `cap_probe': + multiple definition of `cap_probe'; obj/mcpplibs_pb/src/impl.o: first defined here +``` + +这一点对**多个 provider 定义同一批符号**的能力有影响 —— 全程序单例(例如 +`operator new`),或名字集合固定的 C 接口。对这类能力,图中出现两个 provider 是**待修的 +缺陷**而非可 pin 的歧义:pin 会把一个点名两个候选的报错,换成一个点名 mangled 符号的报错。 +可互换的**库**(各 BLAS 实现导出不同的符号集合,按链接选其一)不受此影响。 + +### `exclusive` —— 包声明自己是唯一提供者 + +上一段描述的是一个引擎**看不见**的缺陷:要看出两个 provider 定义了同一批符号, +需要它们的目标文件,而绑定 capability 时那些还不存在;而「一律拒绝重复 provider」 +又会打断同一段里那个合法的 BLAS 用例。 + +所以由包自己声明: + +```toml +[package] +name = "compat.cublas" +provides = ["gpu-blas"] +exclusive = ["gpu-blas"] +``` + +两个都提供 `gpu-blas` 的包,只要其中至少一个声明了独占,就在绑定 capability 时 +被拒绝 —— 在任何东西被编译之前,并点名该能力与双方: + +``` +error: capability 'gpu-blas' is provided by more than one package, and they + declare it EXCLUSIVE. + providers: [compat.cublas, compat.rocblas] + exclusive: [compat.cublas, compat.rocblas] +``` + +该拒绝在 `--format json` 里报 `exclusive-capability`(见第 11 章)。 + +### `version-floor` —— 对机器的要求高于它所有 + +有些关于机器的事实约束着能为它构建什么,而忽略它们时的失败来得很晚: +一个针对比它将遇到的驱动更新的运行时构建出来的程序,**干净地链接**, +在第一次使用时失败,而消息里两侧都没有。 + +包声明它需要什么: + +```toml +[[runtime.requirements]] +kind = "version-floor" +value = "cuda.driver >= 12.0" +``` + +而某个在**安装期**(探测该发生的地方)确立了机器某项事实的包,声明它: + +```toml +[runtime] +provides = ["cuda.driver=12.4"] +``` + +mcpp 在绑定 capability 时比较二者,并在任何东西被编译之前拒绝, +报 `version-floor-unmet`: + +``` +error: `toolkitnew` requires cuda.driver >= 13.0, and this machine has 12.4. + stated by: driverfact +``` + +**没有任何厂商词汇抵达引擎。** 它读到的是一个名字、一个关系和一个版本; +`cuda.driver` 是流过的数据,一个 mcpp 从未听说过的后端比较方式完全相同。 + +**没人回答的下界是沉默的。** 一台从未声明自己有什么的机器,不是「未满足下界」的机器, +而是「没人问过」的机器。把「我们不知道」变成「不行」正是这个机制要避免的失败, +并且有直接判据:`tests/e2e/603_version_floor.sh` 会构建一个下界指向无人提供之物的工程。 + +**它是关于这个包自己的符号的声明**,所以一条指向本包并不提供的能力的条目会被报为 +schema 警告:那里没有可独占的东西。而无人声明独占的能力行为完全不变 —— 两个 BLAS +实现照常共存,既有的「两个或更多、未 pin」报错也仍然只在**有人 require** 该能力时出现。 + +## `[feature-deps.]` —— 由 feature 拉取的依赖 + +在 `[feature-deps.]` 下声明的依赖是**可选的**:仅当该 feature 激活时(根 `--features`, +或某依赖 spec 的 `features = [...]`)才会被解析。`[dependencies]` 中的依赖始终被解析; +可选性由声明的*位置*表达,而非某个标志位。 + +```toml +[features] +use_blas = { defines = ["EIGEN_USE_BLAS"], requires = ["blas"] } +backend-openblas = { implies = ["use_blas"] } + +# 仅当 `backend-openblas` 激活时才拉取。每个条目都是完整的依赖 spec +#(version/path/git + 其自身的 features)。 +[feature-deps.backend-openblas] +compat.openblas = "0.3" +``` + +**写 `"^0.3.0"`,而不是 `"0.3.x"` 或 `"0.3"`。** 以索引中确定存在的包作对照, +判据取**构建成功**: + +| 写法 | 结果 | +|---|---| +| `cmdline = "0.0.1"` | 构建通过 | +| `cmdline = "^0.0.1"` | 构建通过 | +| `cmdline = "0.0"` | 解析通过,随后 `install path missing after fetch` | +| `cmdline = "0.0.x"` | `E_NOT_FOUND`,点名的是包 —— 而该包存在 | + +这三种结果值得分开,因为两个更弱的判据各自会放行一种不可用的写法: +「没有 `E_NOT_FOUND`」放行两段前缀,「解析通过」同样放行它。**只有对着真实索引构建 +一次**才能定论。 + +这一点在此处比在 `[dependencies]` 中更要紧:**实现取不回来的 feature 等于不存在的 +feature**,而开发期使用 **path** 依赖的工程根本不查索引 —— 该失败只在发布之后才出现, +而且是出现在别人身上。 + +该机制与能力(§2.8.1)组合:单个 `backend-openblas` feature 既**拉取** provider +(`compat.openblas`,其 `provides = ["blas"]`),又**开启**消费方开关 +(`implies = ["use_blas"]`,其 `requires = ["blas"]`)。当图中只有一个 provider 时, +能力自动绑定——消费方只需写 `features = ["backend-openblas"]`。 + +在索引包的 Lua 描述符中,等价写法为内联形式: + +```lua +features = { + use_blas = { defines = { "EIGEN_USE_BLAS" }, requires = { "blas" } }, + ["backend-openblas"] = { + implies = { "use_blas" }, + deps = { ["compat.openblas"] = "0.3.x" }, + }, +} +``` + +### 保持可替换的默认实现 + +同样这三件东西,也覆盖"库希望**提供**一份实现但不**强加**一份"的情形 —— 全程序单例, +例如 `operator new`、日志 sink、panic handler: + +```toml +[features] +default = [] +# 消费方开关:"我用到了本库中需要分配器的那部分"。 +alloc = { requires = ["freestanding-allocator"] } +# 内置默认:激活它就够了。 +alloc-kal = { implies = ["alloc"] } + +# 仅在 `alloc-kal` 激活时解析,因此库本体不携带对该实现的依赖。 +[feature-deps.alloc-kal] +std-freestanding-alloc-kal = "0.1.x" +``` + +三种用法各一行: + +| 消费方需要 | 清单里怎么写 | +|---|---| +| 不用会分配的那部分 | `std-freestanding = "0.2.0"` —— 分配器不进图 | +| 默认实现 | `features = ["alloc-kal"]` —— 实现随之进图,**无需知道其包名** | +| 自己的或第三方的 | `features = ["alloc"]` 加一个 `provides = ["freestanding-allocator"]` 的包 | + +有两条性质使该形状优于无条件随包提供实现。随包提供实现的库替程序做了本属程序的决定, +而且**撤销不掉**:feature 是**加性**的,消费方没有把某个默认**关掉**的手段。以及,由于 +依赖包的目标文件无条件参与链接(§2.8.1),随包的默认加上程序自备的那份是**重复定义** +而非替换 —— 让 C++ 标准库能提供可替换 `operator new` 的那套归档语义,对包依赖并不适用。 +把实现放在开关之后,意味着两者**从不共存**。 diff --git a/docs/zh/23-authoring-a-rule-package.md b/docs/zh/23-authoring-a-rule-package.md new file mode 100644 index 00000000..2ae61a3e --- /dev/null +++ b/docs/zh/23-authoring-a-rule-package.md @@ -0,0 +1,228 @@ +# 23 —— 编写规则包 + +规则包教会 mcpp 构建引擎本身没有规则的东西:一种设备语言、一份着色器、一个生成的 +接口、一项检查。它是一个普通的包,它的模块被消费者的构建程序 import。 + +相关文档:[07 —— build.mcpp](07-build-mcpp.md) 是本章所用指令协议与类型化 API 的 +参考;[22 —— Feature 与能力](22-features-and-capabilities.md) 覆盖 feature 相关键; +[20 —— 异构硬件构建](20-heterogeneous-builds.md) 列出已发布的规则。两个示例: +[`08-build-rules`](../../examples/08-build-rules/) 是做检查与嵌入的规则, +[`12-a-new-device-language`](../../examples/12-a-new-device-language/) 是新增一门 +语言的规则。 + +## 规则包是什么 + +三部分,没有一部分是 mcpp 特有的: + +| 部分 | 是什么 | +|---|---| +| 一个包 | 普通的 `mcpp.toml`,有版本与许可证 | +| 一个模块 | 一个 `.cppm`,导出 `options` 与一个提交构建边的函数 | +| 一个 feature | 选中它的开关,也是它自己的依赖挂靠的地方 | + +引擎里没有规则名,也没有厂商名。消费者命名这个包、激活一个 feature,构建程序 +import 那个模块。 + +## manifest 的三个键 + +```toml +[features] +default = [] + +[features.rules-toy] +sources = ["src/rules-toy.cppm"] +rule_module = "example.rules.toy" +device_extensions = [".toy"] +``` + +| 键 | 对消费者的作用 | +|---|---| +| `sources` | 只有 feature 激活时才编译该模块 | +| `rule_module`(2026.9.7.1+) | 消费者构建程序 import 的模块。它隐含 `host-module = true` | +| `device_extensions`(2026.9.7.1+) | 这些扩展名被归类为**设备源**:不做 import 扫描、不产出 BMI,且没有 action 认领时被拒绝 | + +只做生成或检查、不编译某种语言的规则不声明 `device_extensions`; +`08-build-rules` 是这个形状,它的消费者在依赖边上自己写 `host-module = true`。 + +设备扩展名不在默认 source glob 里。消费者通过点名这些文件来选入: + +```toml +[build] +sources = ["src/*.cpp", "src/kernels/*.toy"] +``` + +## 消费者 import 的那个模块 + +每条已发布规则遵循的写法是:一个带默认值的 `options` 结构,加一个提交边的 +`compile`(或 `generate`)函数: + +```cpp +export module example.rules.toy; +import std; +import mcpp; + +export namespace example::rules::toy { + +struct options { + std::string out_dir = std::string(mcpp::out_dir()); + std::string rule_dir = std::string(mcpp::dep_dir("rules-toy")); +}; + +bool compile(options opt = {}); + +} +``` + +把「规划边」与「提交边」拆成两个函数的规则,给了消费者一条越过它最后一个旋钮 +而不必手写 action 的路;`08-build-rules` 的 `plan` / `submit` 就是这个形状。 + +**规则只取自己认领的扩展名,其余留给别人。** `mcpp::device_sources()` 是一个串、 +每行一个包根相对路径,而且它是这个包**全部**的设备集合。一个有两个后端的构建把 +两边的源都放进同一个列表,而构建程序里的每条规则读到的是同一个值。 + +## 声明工作:`mcpp::action` + +```cpp +mcpp::action a; +a.id = id.c_str(); // 包内稳定且唯一 +a.role = "source"; +a.description = desc.c_str(); +a.arg("sh").arg(script.c_str()).arg(input.c_str()).arg(output.c_str()); +a.input(input.c_str()); +a.input(script.c_str()); +a.output(output.c_str()); +a.submit(); +``` + +这些字符串必须比 action 活得久。`a.id = ("toy:" + stem).c_str()` 交给它的是一个 +指向临时对象的指针,而该临时对象在 `submit()` 之前就已经消失。 + +### 四种 role + +`role` 决定这条边的输出去哪里,以及这条边什么时候跑。 + +| `role` | 输出 | 次序 | +|---|---|---| +| `source` | 可编译的那些进入编译集合 | 声明它的包的每条编译边都等它 | +| `check` | 一个 stamp 文件 | 与编译并行;`blocking = true` 让编译等它 | +| `object` | 进入**链接**集合 | 链接边消费它们 | +| `artifact` | 一个新文件 | 它的输入是链接产物,所以它在链接之后跑 | + +### 声明的输入,以及其中的编译器 + +声明的输入变化时 action 重跑。**命令调用的那个工具也是输入。** 少了它,改动规则 +自己的编译器会让每条边都是干净的,产物保留上一个编译器产生的字节 —— 一次覆盖在 +陈旧结果之上的绿色构建。 + +action 的命令跑在**构建目录**里,不是包根。`mcpp::device_sources()` 答的是包根 +相对路径,所以规则要先用 `mcpp::manifest_dir()` 拼成绝对路径再放上命令行。 + +### 命令自己发现输入时,声明 depfile + +一份 include 了别的文件的着色器或 kernel,有规则枚举不出来的输入。涉及的每个 +编译器都能产出 depfile ——`glslangValidator --depfile`、`glslc -MD -MF`、 +`slangc -depfile`,以及 clang 家族驱动的 `-MD -MF`: + +```cpp +a.depfile = dep.c_str(); // 命令写出的一个路径 +a.arg("--depfile").arg(dep.c_str()); +``` + +depfile 不得同时被声明为 `output()`。 + +### 串接 action + +一个 action 可以消费另一个的产物。引擎只负责给它们定序与做指纹,而这就是一次 +device link 在引擎侧的全部内容:N 个 `artifact` action,其输出不进链接;再加一个 +`object` action 读取它们,产出真正进链接的对象。 + +## 规则自带的环境 + +规则拥有它所驱动的那份包清单,因为运行编译器、读头文件、把库目录放上链接行的 +正是它的代码。 + +```toml +[target.'cfg(accelerator = "cuda")'.feature-xlings.rules-cuda] +"xim:cuda-nvcc" = "12.9.86" +"xim:cuda-cudart" = "12.9.79" +``` + +两道闸,下载一个字节之前两道都要开:feature 说这条规则要不要, +`cfg(accelerator = ...)` 选择器说这次构建要不要为设备编译。不带加速器的构建两道 +都不开。 + +裸版本是项目可以覆盖的**选择**;`>=` 是项目不得低于的**要求**。见 +[05 —— mcpp.toml](05-mcpp-toml.md) 的*一个包,一个版本*。 + +## 生成岛的边界 + +设备翻译单元不能 import 模块,所以它与 C++ 侧之间的边界是一个 `extern "C"` 头。 +`mcpp:plugins` 的 `mcpp.tools.island` 从两个实现里读出标了 `MCPP_EXPORT_C` 的 +入口点,写出那个头以及它之上的模块,于是每个签名只存在一份。 + +```cpp +mcpp::tools::island::options opt; +opt.module_name = "myapp.kernels"; +opt.out_dir = std::string(mcpp::out_dir()) + "/island"; + +const auto entries = mcpp::tools::island::scan(halves, opt); +const auto out = mcpp::tools::island::emit(*entries, opt); +mcpp::generated(out->interface_file.c_str()); +``` + +可用的有四级,每一级覆盖上一级: + +| 级 | 手写的部分 | 消费者写 | +|---|---|---| +| L0 | 只有被标记的入口点 | `import myapp.kernels` —— 岛自己的 C 形状接口 | +| L1 | 生成模块之上的一个接缝模块 | `import myapp.saxpy` —— 项目设计的接口 | +| L2 | 接缝,外加直接传给 `emit` 的入口点列表 | 同上,用于 scan 看不见的入口点 | +| L3 | 头文件与模块都手写 | 同上,签名写了两遍 | + +生成的头经由 `mcpp::tools::island::force_include_flags` 到达岛,而这些旗标交给 +驱动设备编译器的那条**规则**,不走 `mcpp::cxxflag` —— 把一个头强制灌进每个 C++ +翻译单元,会让声明出现在模块接口的 `export module` 之前,那是非良构的。 + +[`examples/09-heterogeneous/boundary`](../../examples/09-heterogeneous/boundary/) +是 L0,并写明了每一级的代价。 + +## 报告构建应当知道的事 + +### `warning` —— 成功了,并且仍然被听见 + +`mcpp::warning` 是规则「干完了活、但有话要说」的通道:它不得不选择的宿主编译器、 +它回落到的载荷。除此之外,构建程序的输出只在非零退出时才被打印。 + +### `fact` / `floor` —— 探针通道 + +规则测量,引擎在编译任何东西之前比较。 + +```cpp +mcpp::fact("cuda.driver", driver_version); +mcpp::floor("cuda.driver", runtime_needs); +``` + +引擎读到的是一个名字、一个关系和一个版本;名字是穿过它的数据。探不到答案的探针 +不陈述任何答案。 + +## 找到规则自己的文件 + +| 要找什么 | 怎么找 | +|---|---| +| 规则包自己的目录树 | `mcpp::dep_dir("")` —— 按**消费者**在 `[dependencies]` 里写的那个名字 | +| `[xlings.workspace]` 声明的载荷 | `mcpp::xpkg_dir("")` | +| 由依赖构建出的宿主工具 | `mcpp::dep_bin("", "")` | + +`dep_dir` 按消费者 manifest 里的拼法作答。随模块一起发文件的规则要把该目录作为 +一个 option 暴露出来,好让用别的键声明这条边的消费者能提供它;并且在找不到时以 +一条点名「找的是什么」的消息拒绝,而不是拿一个空路径去执行命令。 + +## 当前边界 + +- 规则包自己 `[features] default` 里的规则 feature 不隐含 `host-module`。 + `mcpp:plugins` 声明 `default = []`;规则默认开启的规则包会被拒绝,消息点名 + 该模块与需要补的键。 +- 对一个自带 `mcpp.toml` 的包,`mcpp emit xpkg` 产出的描述符会被 + `mcpp xpkg parse` 拒绝:产出的 `mcpp` 段带 `manifest = "mcpp.toml"` 而没有 + `sources` 列表,而校验器要求有一个。给产出的描述符补上 `sources = { … }` + 之后即可通过校验。 diff --git a/docs/zh/README.md b/docs/zh/README.md index bcdaeb86..d140b421 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -27,7 +27,7 @@ | 发布它 | [02](02-pack-and-release.md)、[10](10-publishing-a-library.md)、[12](12-binary-distribution.md) | [`03-pack-static`](../../examples/03-pack-static/)、[`05-lib-distribution`](../../examples/05-lib-distribution/) | | 为另一台机器构建 | [16](16-the-target-triple.md)、[15](15-openkal-cross.md)、[13](13-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | | 使用 GPU 或加速器 | [20](20-heterogeneous-builds.md)、[18](18-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | -| 增加一条规则、一种语言或一个生成器 | [23](23-authoring-a-rule-package.md)、[07](07-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`13-a-new-device-language`](../../examples/13-a-new-device-language/) | +| 增加一条规则、一种语言或一个生成器 | [23](23-authoring-a-rule-package.md)、[07](07-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`12-a-new-device-language`](../../examples/12-a-new-device-language/) | | 向索引添加一个包 | [10](10-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [21](21-commands-by-scenario.md) —— 发布相关场景 | | 修改 mcpp 本身 | [04](04-build-from-source.md)、[09](09-release.md)、[19](19-supported-versions.md) | — | diff --git a/examples/13-a-new-device-language/README.md b/examples/12-a-new-device-language/README.md similarity index 97% rename from examples/13-a-new-device-language/README.md rename to examples/12-a-new-device-language/README.md index 54135d84..ef26401d 100644 --- a/examples/13-a-new-device-language/README.md +++ b/examples/12-a-new-device-language/README.md @@ -1,11 +1,11 @@ -# 13 — A device language the engine has never heard of +# 12 — A device language the engine has never heard of A rule package teaches mcpp to compile `.toy`, and a project uses it. No mcpp release is involved: `.toy` is not in the engine's built-in device-extension table and will never be. ``` -cd examples/13-a-new-device-language/app +cd examples/12-a-new-device-language/app mcpp run # toy_answer() = 42 ``` diff --git a/examples/13-a-new-device-language/app/build.mcpp b/examples/12-a-new-device-language/app/build.mcpp similarity index 100% rename from examples/13-a-new-device-language/app/build.mcpp rename to examples/12-a-new-device-language/app/build.mcpp diff --git a/examples/13-a-new-device-language/app/mcpp.toml b/examples/12-a-new-device-language/app/mcpp.toml similarity index 100% rename from examples/13-a-new-device-language/app/mcpp.toml rename to examples/12-a-new-device-language/app/mcpp.toml diff --git a/examples/13-a-new-device-language/app/src/kernels/answer.toy b/examples/12-a-new-device-language/app/src/kernels/answer.toy similarity index 100% rename from examples/13-a-new-device-language/app/src/kernels/answer.toy rename to examples/12-a-new-device-language/app/src/kernels/answer.toy diff --git a/examples/13-a-new-device-language/app/src/main.cpp b/examples/12-a-new-device-language/app/src/main.cpp similarity index 100% rename from examples/13-a-new-device-language/app/src/main.cpp rename to examples/12-a-new-device-language/app/src/main.cpp diff --git a/examples/13-a-new-device-language/rules-toy/mcpp.toml b/examples/12-a-new-device-language/rules-toy/mcpp.toml similarity index 100% rename from examples/13-a-new-device-language/rules-toy/mcpp.toml rename to examples/12-a-new-device-language/rules-toy/mcpp.toml diff --git a/examples/13-a-new-device-language/rules-toy/src/rules-toy.cppm b/examples/12-a-new-device-language/rules-toy/src/rules-toy.cppm similarity index 100% rename from examples/13-a-new-device-language/rules-toy/src/rules-toy.cppm rename to examples/12-a-new-device-language/rules-toy/src/rules-toy.cppm diff --git a/examples/13-a-new-device-language/rules-toy/tools/toyc.sh b/examples/12-a-new-device-language/rules-toy/tools/toyc.sh similarity index 100% rename from examples/13-a-new-device-language/rules-toy/tools/toyc.sh rename to examples/12-a-new-device-language/rules-toy/tools/toyc.sh From 3f3a117aeb371fa61bb8e50976c463ff7c9cfb5e Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:09:15 +0800 Subject: [PATCH 05/36] docs: 05 stops being four essays inside a field reference 3,129 lines, of which six sections were conceptual chapters that had landed in the manifest reference because that is where the keys are documented. Each moves to the chapter that already owns its subject: cxx_runtime, the C++ runtime contract 242 -> 03 [target.*] conditioning 138 -> 14 [xlings], the project environment 317 -> 17 host tools from a dependency 274 -> 07 [hooks] 192 -> 21 features and capabilities 394 -> 22 (previous commit) 05 is 1,626 lines and is a field reference again; no chapter is above 1,218. A pointer stays at each old section so its number keeps its place in the sequence and an existing link still lands somewhere useful. --- docs/03-toolchains.md | 242 ++++++ docs/05-mcpp-toml.md | 1126 +------------------------ docs/07-build-mcpp.md | 274 ++++++ docs/14-target-side.md | 138 +++ docs/17-the-project-environment.md | 317 +++++++ docs/21-commands-by-scenario.md | 161 ++++ docs/zh/03-toolchains.md | 215 +++++ docs/zh/05-mcpp-toml.md | 959 +-------------------- docs/zh/07-build-mcpp.md | 234 +++++ docs/zh/14-target-side.md | 113 +++ docs/zh/17-the-project-environment.md | 269 ++++++ docs/zh/21-commands-by-scenario.md | 132 +++ 12 files changed, 2110 insertions(+), 2070 deletions(-) diff --git a/docs/03-toolchains.md b/docs/03-toolchains.md index 43affda1..05a6f28f 100644 --- a/docs/03-toolchains.md +++ b/docs/03-toolchains.md @@ -650,3 +650,245 @@ Tracked as [mcpp#256](https://github.com/mcpp-community/mcpp/issues/256). `tests/e2e/150_clang_module_operator_template.sh` is a canary over the bundled LLVM toolchains, so a future Clang bump that fixes — or re-breaks — this becomes visible instead of silently changing what packages can express. + +## The C++ runtime contract (`cxx_runtime`) + +`cxx_runtime` states what the produced artifact promises about the machine that +runs it. It is a **distribution** property, not a build one — it describes the +runtime dependency set, and the flags that deliver it differ per platform. + +> **A target with no C++ in it has no C++ runtime contract to honour.** mcpp +> links such a target with the C driver and leaves the C++ runtime flags off the +> line entirely, so a pure-C shared library does not acquire a dependency on +> `libstdc++`/`libc++` it has no use for. One C++ translation unit anywhere in +> the target puts the whole target back on the C++ driver. This is derived from +> the sources — there is no key for it. + +```toml +[build] +cxx_runtime = "self-contained" # applies to every target (the default) + +# or, per role: +[build.cxx_runtime] +default = "self-contained" # executables +tests = "host-coupled" # test binaries never leave this machine +shared = "self-contained" # shared libraries (see below — the + # default differs by target format) + +# or, per target triple — beside `linkage`, which is the same axis: +[target.x86_64-linux-gnu] +cxx_runtime = "host-coupled" # e.g. this build is for a distro package +``` + +| value | the artifact needs, at run time | typical use | +|---|---|---| +| `self-contained` (default) | no C++ runtime outside itself | shipping a binary | +| `toolchain-coupled` | the C++ runtime of the toolchain mcpp installed | local iteration | +| `host-coupled` | whatever the driver resolves by default (the system runtime) | distro packaging, `dlopen` plugins that must share a runtime with their host | + +**Self-contained by default (portable by default)**: on macOS this statically +links LLVM's bundled libc++/libc++abi — the system libc++ would otherwise pin the +runnable version to the build machine's OS (older systems lack newer symbols, e.g. +the support symbols behind `std::print`), and only static linking can truly deliver +the `macos_deployment_target` floor. On Linux/MinGW it is `-static-libstdc++` (GCC) +or the whole-link `-static` (MinGW); on a Linux clang/libc++ toolchain it links +libc++.a/libc++abi.a/libunwind.a explicitly. A lower macOS floor (11–13) requires a +self-built libc++ archive (already verified to work, a data-level switch, available +on request). + +**Shared libraries are the one role whose default depends on the target format**, +because the hazard does. A `.so`/`.dylib`/`.dll` is not a small executable — it is +loaded *into* a process that already has a C++ runtime. + +| target | default for `kind = "shared"` | why | +|---|---|---| +| ELF (Linux, …) | `toolchain-coupled` | ELF has one global symbol namespace and the first definition loaded wins. A `.so` that statically embedded libstdc++ **exports** it, and the executable linking that library binds *its* `std::` references there — its own `self-contained` contract silently becomes a no-op, and its C++ runtime is whichever build of that library happens to load. | +| Mach-O | `self-contained` | the mechanism there is already `-load_hidden`, i.e. hidden visibility, so dyld never unifies those symbols; and toolchain-coupled is not available on macOS at all (see the note below). | +| PE (Windows) | `self-contained` | PE has no global symbol namespace — imports resolve per-DLL by name, so a DLL's private runtime cannot be picked up by anything else. | + +Setting `shared = "self-contained"` on ELF is supported and does exactly what it +says: the library embeds the runtime. mcpp additionally passes +`-Wl,--exclude-libs` for the standard-library archives, so the embedded copy stays +out of the library's dynamic symbol table and cannot be picked up by anything that +links it. Template instantiations emitted by the consuming code (weak/COMDAT `std::string` +symbols and the like) are still exported — that is the intended C++ ABI behaviour +and is not the leak this guards against. + +A project-wide `cxx_runtime = "…"` (or `static_stdlib = false`) applies to shared +libraries too: a human said what the whole project promises. The format-specific +default applies only when nobody said anything. + +`static_stdlib` is the older spelling and still works: `true` means +`self-contained`, `false` means `host-coupled`. An explicit `cxx_runtime` wins. + +**A contract that cannot be honored is reported, never silently downgraded.** If a +toolchain ships no `libc++.a`, or a contract has no mechanism on that platform, +the build prints what it fell back to instead of quietly producing a different +artifact than the manifest asked for. + +### On the MSVC runtime + +The CRT model is the mechanism here, and it is a **whole-project** switch: cl +bakes `_MSVC_MT`/`_MSVC_MD` into the one `std` module a project builds, so a +per-role contract that disagrees with the project's cannot be honoured and is +reported rather than ignored. + +| value | what it is on MSVC | +|---|---| +| `self-contained` | `/MT` — the static CRT. `linkage = "static"` selects the same thing from the libc axis. | +| `host-coupled` (default under `/MD`) | the target provides `vcruntime140.dll` / `msvcp140.dll` — i.e. Visual Studio or the redistributable is installed there. | +| `toolchain-coupled` | the toolset's **own** copy of those DLLs travels with the artifact. | + +`toolchain-coupled` is worth spelling out, because the obvious reading is +wrong. `ucrtbase.dll` *is* a Windows component (since Windows 10) and mcpp +never ships it. `vcruntime140.dll` and `msvcp140.dll` are **not**: every MSVC +toolset carries them under `VC\Redist\MSVC\\\`, exactly the +way a gcc payload carries `libstdc++.so`. Under this contract mcpp stages them +beside the artifact — which is what makes a default `/MD` build runnable on a +machine that has only the pinned toolset and no Visual Studio at all. + +The debug CRT (`vcruntime140d.dll` and friends, under `debug_nonredist\`) is +never staged: it may not be redistributed. + +> **Upgrading from 2026.8.15 or earlier?** This key used to be **inert** on the +> MSVC ABI — it reported `not implemented for the MSVC runtime yet` and every +> value fell back to `/MD`. Since 2026.8.16 it is honoured, so a manifest that +> carries `cxx_runtime = "self-contained"` from that era **changes CRT model on +> upgrade**, from `/MD` to `/MT`. It is not a stricter version of the same +> model, and the switch is silent because the value was always valid. A project +> that set it while the key did nothing should re-confirm the intended value. + +Combining it with `/MT` is a contradiction rather than a missing feature — a +static CRT leaves no DLL to couple to — so it is reported and resolved to +`self-contained`. `mcpp pack` enforces the other half: a mode that bundles +nothing (`--mode system`, `--mode static`) cannot deliver `toolchain-coupled` +and refuses. + +**Scope.** The contract governs the C++ runtime only. Static **libc** is a separate +axis (`linkage = "static"` / `--static`, e.g. a musl target), and the deployment +floor is a third (`macos_deployment_target`). Also, `host-coupled` means mcpp adds +nothing to embed a C++ runtime; it does not strip the toolchain rpath the link +carries for other reasons, so on ELF such an artifact may still find the +toolchain's libraries first. + +> **macOS + `self-contained` and static initialization order.** Mach-O has no +> priority-ordered initializer section and libc++'s `` carries no +> `ios_base::Init` guard of its own (unlike libstdc++ and the MSVC STL), so a +> stream initializer pulled out of `libc++.a` would otherwise run *after* the +> program's own global constructors — a global whose constructor touches +> `std::cout` would read an unconstructed stream and crash at process start. mcpp +> links a tiny generated object first to force the streams up; nothing is required +> of the calling code. See mcpp-community/mcpp#336. + +`defines` takes **bare** macro names (no `-D`) and desugars each entry to `-D` on +both the C and C++ compile channels. It reaches every TU in the package — module +interface units included — so it also reaches the compiler's own P1689 module scan. + +> **It does not make a macro-guarded `import` acceptable.** mcpp runs its own +> lexical pre-scan before the compiler ever sees the file, and that scanner +> rejects an `import` inside **any** `#if` / `#ifdef` block without evaluating the +> condition: +> +> ``` +> error: import statement inside conditional preprocessor block (forbidden in M1) +> ``` +> +> So a `#ifdef FOO` / `import bar;` pair fails even when `FOO` is in `defines`. +> Put the conditional around an `#include` in the global module fragment instead. +> Tracked as mcpp-community/mcpp#421. Assembly units pick it up too. It is a +build input like any other, so `[target.'cfg(...)'.build]` can carry it: + +```toml +[build] +defines = ["APP_NAME=\"demo\""] + +[target.'cfg(windows)'.build] +defines = ["USE_WIN32", "WINVER=0x0A00"] +``` + +Picking the right axis: + +| You want the macro on… | Use | +|---|---| +| every TU of this package | `[build].defines` (here) | +| one binary's own entry source only | `[targets.].defines` | +| a specific set of files | `[build].flags` with a `glob` + `defines` | +| every TU **and** every consumer's TUs | `[features.].defines` (an interface contribution) | + +`[build].defines` is private to the package: it does not propagate to consumers. + +Unsupported keys under `[build]` are reported as a warning (an error under +`--strict`) rather than silently ignored. + +Do not configure the C++ standard via `build.cxxflags = ["-std=..."]`. Instead use: + +```toml +[package] +standard = "c++26" +``` + +mcpp applies the same standard to ordinary C++ compilation, module scanning, +`compile_commands.json`, and the standard library BMI build for `import std`. + +**glob exclusion** (`!` prefix, mcpp 0.0.4+): + +```toml +[build] +sources = [ + "src/**/*.cpp", + "!src/**/*_test.cpp", # Exclude test files + "!src/**/*_fuzzer.cpp", # Exclude fuzzers +] +``` + +**Per-glob flags** (mcpp 0.0.95+): `[build] flags` is an ordered array of +inline tables attaching extra compile flags to exactly the sources a glob +matches — the tool for SIMD dispatch TUs and vendored-code warning isolation: + +```toml +[build] +flags = [ + { glob = "third_party/**", cflags = ["-w"], cxxflags = ["-w"] }, + { glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"], defines = ["HAVE_AVX2"] }, + { glob = "src/x86/**/*.asm", asmflags = ["-DPREFIX"] }, +] +``` + +Keys per entry: `glob` (package-root-relative, required) plus `cflags` / +`cxxflags` / `asmflags` / `defines` (no `ldflags` — linking has no per-TU +scope). Declaration order is application order: a later entry's flags land +later on the command line, so with GNU "last flag wins" a narrower glob +placed after a broader one overrides it. All matching entries apply. These +are private build flags — they never propagate to consumers. A glob that +matches no source file prints a warning (a typo'd glob must not silently do +nothing). + +**Generated files** (mcpp 0.0.95+): `[generated_files]` maps a relative path +to file contents (TOML multiline strings supported). Entries are written +into the project tree before source globs expand — the same mechanism index +descriptors use to synthesize module wrappers — and the content enters the +fingerprint, so editing it rebuilds: + +```toml +[generated_files] +"src/gen/wrap.cppm" = """ +module; +#include +export module wrap; +""" +``` + +Paths must stay inside the project root (`..` / absolute paths are parse +errors). + +**Assembly sources** (mcpp 0.0.95+): `.S`/`.s` (GAS — preprocessed by the C +driver, covers ARM and AT&T-syntax x86) and `.asm` (NASM — Intel-syntax x86) +are first-class sources: default-globbed, fingerprinted, built incrementally +in parallel, and linked like any other object. The NASM output format is +derived from the target triple (`elf64`/`win64`/`macho64`/... — cross builds +just work), and `nasm` itself is resolved lazily only when `.asm` units exist: +`PATH` first, then the mcpp sandbox, then `xlings install nasm`; if none +yields nasm ≥ 2.16 the build **fails hard** (assembly is never silently +skipped). Limits: `.asm` targets x86 only (hard error elsewhere — gate the +files off other targets), `.S` is unavailable on the MSVC toolchain, and +`.asm` means NASM syntax (MASM sources should be `!`-excluded). diff --git a/docs/05-mcpp-toml.md b/docs/05-mcpp-toml.md index 05388e7b..50e05315 100644 --- a/docs/05-mcpp-toml.md +++ b/docs/05-mcpp-toml.md @@ -576,248 +576,10 @@ timeout wants. The **compile** phase is not bounded, only the build *program*. See [07-build-mcpp.md](07-build-mcpp.md) for why that asymmetry is deliberate. - ### The C++ runtime contract (`cxx_runtime`) -`cxx_runtime` states what the produced artifact promises about the machine that -runs it. It is a **distribution** property, not a build one — it describes the -runtime dependency set, and the flags that deliver it differ per platform. - -> **A target with no C++ in it has no C++ runtime contract to honour.** mcpp -> links such a target with the C driver and leaves the C++ runtime flags off the -> line entirely, so a pure-C shared library does not acquire a dependency on -> `libstdc++`/`libc++` it has no use for. One C++ translation unit anywhere in -> the target puts the whole target back on the C++ driver. This is derived from -> the sources — there is no key for it. - -```toml -[build] -cxx_runtime = "self-contained" # applies to every target (the default) - -# or, per role: -[build.cxx_runtime] -default = "self-contained" # executables -tests = "host-coupled" # test binaries never leave this machine -shared = "self-contained" # shared libraries (see below — the - # default differs by target format) - -# or, per target triple — beside `linkage`, which is the same axis: -[target.x86_64-linux-gnu] -cxx_runtime = "host-coupled" # e.g. this build is for a distro package -``` - -| value | the artifact needs, at run time | typical use | -|---|---|---| -| `self-contained` (default) | no C++ runtime outside itself | shipping a binary | -| `toolchain-coupled` | the C++ runtime of the toolchain mcpp installed | local iteration | -| `host-coupled` | whatever the driver resolves by default (the system runtime) | distro packaging, `dlopen` plugins that must share a runtime with their host | - -**Self-contained by default (portable by default)**: on macOS this statically -links LLVM's bundled libc++/libc++abi — the system libc++ would otherwise pin the -runnable version to the build machine's OS (older systems lack newer symbols, e.g. -the support symbols behind `std::print`), and only static linking can truly deliver -the `macos_deployment_target` floor. On Linux/MinGW it is `-static-libstdc++` (GCC) -or the whole-link `-static` (MinGW); on a Linux clang/libc++ toolchain it links -libc++.a/libc++abi.a/libunwind.a explicitly. A lower macOS floor (11–13) requires a -self-built libc++ archive (already verified to work, a data-level switch, available -on request). - -**Shared libraries are the one role whose default depends on the target format**, -because the hazard does. A `.so`/`.dylib`/`.dll` is not a small executable — it is -loaded *into* a process that already has a C++ runtime. - -| target | default for `kind = "shared"` | why | -|---|---|---| -| ELF (Linux, …) | `toolchain-coupled` | ELF has one global symbol namespace and the first definition loaded wins. A `.so` that statically embedded libstdc++ **exports** it, and the executable linking that library binds *its* `std::` references there — its own `self-contained` contract silently becomes a no-op, and its C++ runtime is whichever build of that library happens to load. | -| Mach-O | `self-contained` | the mechanism there is already `-load_hidden`, i.e. hidden visibility, so dyld never unifies those symbols; and toolchain-coupled is not available on macOS at all (see the note below). | -| PE (Windows) | `self-contained` | PE has no global symbol namespace — imports resolve per-DLL by name, so a DLL's private runtime cannot be picked up by anything else. | - -Setting `shared = "self-contained"` on ELF is supported and does exactly what it -says: the library embeds the runtime. mcpp additionally passes -`-Wl,--exclude-libs` for the standard-library archives, so the embedded copy stays -out of the library's dynamic symbol table and cannot be picked up by anything that -links it. Template instantiations emitted by the consuming code (weak/COMDAT `std::string` -symbols and the like) are still exported — that is the intended C++ ABI behaviour -and is not the leak this guards against. - -A project-wide `cxx_runtime = "…"` (or `static_stdlib = false`) applies to shared -libraries too: a human said what the whole project promises. The format-specific -default applies only when nobody said anything. - -`static_stdlib` is the older spelling and still works: `true` means -`self-contained`, `false` means `host-coupled`. An explicit `cxx_runtime` wins. - -**A contract that cannot be honored is reported, never silently downgraded.** If a -toolchain ships no `libc++.a`, or a contract has no mechanism on that platform, -the build prints what it fell back to instead of quietly producing a different -artifact than the manifest asked for. - -#### On the MSVC runtime - -The CRT model is the mechanism here, and it is a **whole-project** switch: cl -bakes `_MSVC_MT`/`_MSVC_MD` into the one `std` module a project builds, so a -per-role contract that disagrees with the project's cannot be honoured and is -reported rather than ignored. - -| value | what it is on MSVC | -|---|---| -| `self-contained` | `/MT` — the static CRT. `linkage = "static"` selects the same thing from the libc axis. | -| `host-coupled` (default under `/MD`) | the target provides `vcruntime140.dll` / `msvcp140.dll` — i.e. Visual Studio or the redistributable is installed there. | -| `toolchain-coupled` | the toolset's **own** copy of those DLLs travels with the artifact. | - -`toolchain-coupled` is worth spelling out, because the obvious reading is -wrong. `ucrtbase.dll` *is* a Windows component (since Windows 10) and mcpp -never ships it. `vcruntime140.dll` and `msvcp140.dll` are **not**: every MSVC -toolset carries them under `VC\Redist\MSVC\\\`, exactly the -way a gcc payload carries `libstdc++.so`. Under this contract mcpp stages them -beside the artifact — which is what makes a default `/MD` build runnable on a -machine that has only the pinned toolset and no Visual Studio at all. - -The debug CRT (`vcruntime140d.dll` and friends, under `debug_nonredist\`) is -never staged: it may not be redistributed. - -> **Upgrading from 2026.8.15 or earlier?** This key used to be **inert** on the -> MSVC ABI — it reported `not implemented for the MSVC runtime yet` and every -> value fell back to `/MD`. Since 2026.8.16 it is honoured, so a manifest that -> carries `cxx_runtime = "self-contained"` from that era **changes CRT model on -> upgrade**, from `/MD` to `/MT`. It is not a stricter version of the same -> model, and the switch is silent because the value was always valid. A project -> that set it while the key did nothing should re-confirm the intended value. - -Combining it with `/MT` is a contradiction rather than a missing feature — a -static CRT leaves no DLL to couple to — so it is reported and resolved to -`self-contained`. `mcpp pack` enforces the other half: a mode that bundles -nothing (`--mode system`, `--mode static`) cannot deliver `toolchain-coupled` -and refuses. - -**Scope.** The contract governs the C++ runtime only. Static **libc** is a separate -axis (`linkage = "static"` / `--static`, e.g. a musl target), and the deployment -floor is a third (`macos_deployment_target`). Also, `host-coupled` means mcpp adds -nothing to embed a C++ runtime; it does not strip the toolchain rpath the link -carries for other reasons, so on ELF such an artifact may still find the -toolchain's libraries first. - -> **macOS + `self-contained` and static initialization order.** Mach-O has no -> priority-ordered initializer section and libc++'s `` carries no -> `ios_base::Init` guard of its own (unlike libstdc++ and the MSVC STL), so a -> stream initializer pulled out of `libc++.a` would otherwise run *after* the -> program's own global constructors — a global whose constructor touches -> `std::cout` would read an unconstructed stream and crash at process start. mcpp -> links a tiny generated object first to force the streams up; nothing is required -> of the calling code. See mcpp-community/mcpp#336. - -`defines` takes **bare** macro names (no `-D`) and desugars each entry to `-D` on -both the C and C++ compile channels. It reaches every TU in the package — module -interface units included — so it also reaches the compiler's own P1689 module scan. - -> **It does not make a macro-guarded `import` acceptable.** mcpp runs its own -> lexical pre-scan before the compiler ever sees the file, and that scanner -> rejects an `import` inside **any** `#if` / `#ifdef` block without evaluating the -> condition: -> -> ``` -> error: import statement inside conditional preprocessor block (forbidden in M1) -> ``` -> -> So a `#ifdef FOO` / `import bar;` pair fails even when `FOO` is in `defines`. -> Put the conditional around an `#include` in the global module fragment instead. -> Tracked as mcpp-community/mcpp#421. Assembly units pick it up too. It is a -build input like any other, so `[target.'cfg(...)'.build]` can carry it: - -```toml -[build] -defines = ["APP_NAME=\"demo\""] - -[target.'cfg(windows)'.build] -defines = ["USE_WIN32", "WINVER=0x0A00"] -``` - -Picking the right axis: - -| You want the macro on… | Use | -|---|---| -| every TU of this package | `[build].defines` (here) | -| one binary's own entry source only | `[targets.].defines` | -| a specific set of files | `[build].flags` with a `glob` + `defines` | -| every TU **and** every consumer's TUs | `[features.].defines` (an interface contribution) | - -`[build].defines` is private to the package: it does not propagate to consumers. - -Unsupported keys under `[build]` are reported as a warning (an error under -`--strict`) rather than silently ignored. - -Do not configure the C++ standard via `build.cxxflags = ["-std=..."]`. Instead use: - -```toml -[package] -standard = "c++26" -``` - -mcpp applies the same standard to ordinary C++ compilation, module scanning, -`compile_commands.json`, and the standard library BMI build for `import std`. - -**glob exclusion** (`!` prefix, mcpp 0.0.4+): - -```toml -[build] -sources = [ - "src/**/*.cpp", - "!src/**/*_test.cpp", # Exclude test files - "!src/**/*_fuzzer.cpp", # Exclude fuzzers -] -``` - -**Per-glob flags** (mcpp 0.0.95+): `[build] flags` is an ordered array of -inline tables attaching extra compile flags to exactly the sources a glob -matches — the tool for SIMD dispatch TUs and vendored-code warning isolation: - -```toml -[build] -flags = [ - { glob = "third_party/**", cflags = ["-w"], cxxflags = ["-w"] }, - { glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"], defines = ["HAVE_AVX2"] }, - { glob = "src/x86/**/*.asm", asmflags = ["-DPREFIX"] }, -] -``` - -Keys per entry: `glob` (package-root-relative, required) plus `cflags` / -`cxxflags` / `asmflags` / `defines` (no `ldflags` — linking has no per-TU -scope). Declaration order is application order: a later entry's flags land -later on the command line, so with GNU "last flag wins" a narrower glob -placed after a broader one overrides it. All matching entries apply. These -are private build flags — they never propagate to consumers. A glob that -matches no source file prints a warning (a typo'd glob must not silently do -nothing). +Moved to [03 — Toolchain Management](03-toolchains.md). -**Generated files** (mcpp 0.0.95+): `[generated_files]` maps a relative path -to file contents (TOML multiline strings supported). Entries are written -into the project tree before source globs expand — the same mechanism index -descriptors use to synthesize module wrappers — and the content enters the -fingerprint, so editing it rebuilds: - -```toml -[generated_files] -"src/gen/wrap.cppm" = """ -module; -#include -export module wrap; -""" -``` - -Paths must stay inside the project root (`..` / absolute paths are parse -errors). - -**Assembly sources** (mcpp 0.0.95+): `.S`/`.s` (GAS — preprocessed by the C -driver, covers ARM and AT&T-syntax x86) and `.asm` (NASM — Intel-syntax x86) -are first-class sources: default-globbed, fingerprinted, built incrementally -in parallel, and linked like any other object. The NASM output format is -derived from the target triple (`elf64`/`win64`/`macho64`/... — cross builds -just work), and `nasm` itself is resolved lazily only when `.asm` units exist: -`PATH` first, then the mcpp sandbox, then `xlings install nasm`; if none -yields nasm ≥ 2.16 the build **fails hard** (assembly is never silently -skipped). Limits: `.asm` targets x86 only (hard error elsewhere — gate the -files off other targets), `.S` is unavailable on the MSVC toolchain, and -`.asm` means NASM syntax (MASM sources should be `!`-excluded). ### File names outside the host code page @@ -1148,144 +910,10 @@ default = "gcc@16.1.0" toolchain = "gcc@16.1.0" linkage = "static" ``` - ### 2.7.1 `[target.*]` — Platform-Conditional Dependencies & Flags -Scope dependencies and build flags to a platform with a `[target.]` table. -The selector `` has three forms: - -| Selector | Meaning | Example | -|---|---|---| -| **bare OS alias** | a single OS / family — the concise, common form | `[target.windows]`, `[target.unix]` | -| **`cfg(...)` predicate** | a compound condition (arch / env / combinators) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see §2.7.3) | `[target.x86_64-linux-musl]` | - -A selector may carry platform-conditional **dependencies** and **build flags**: - -```toml -# Concise bare-alias form — pull OpenBLAS and link it only on Windows. -[target.windows.dependencies.compat] -openblas = "0.3.33" -[target.windows.build] -ldflags = ["-Llib", "-llibopenblas"] - -# cfg(...) for compound predicates (grammar: all/any/not over os/arch/family/env, -# plus the bare aliases windows/unix/linux/macos). -[target.'cfg(all(linux, not(arch = "aarch64")))'.build] -cxxflags = ["-march=x86-64-v2"] -``` - -`[target.windows]` is exactly equivalent to `[target.'cfg(windows)']` — the bare -aliases `windows` / `linux` / `macos` / `unix` are never valid target triples, so -there is no ambiguity. Use the bare form for a single OS/family; use `cfg(...)` -for arch/env conditions and combinators. - -- **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies` / - `feature-deps.` (mcpp 2026.8.6.2+ — see §2.14; the feature is - registered unconditionally, only its dependency set is scoped), and - `build` with `cflags` / `cxxflags` / `ldflags` / `sources` (mcpp 0.0.95+ — - conditional source globs, e.g. gating `src/x86/**/*.asm` behind - `cfg(arch = "x86_64")`; `!`-exclusion globs work here too), plus `flags` and - `include_dirs` / `include_dirs_after` (mcpp 0.0.102+), plus - `private_include_dirs` and `std-module-flags` (mcpp 2026.9.1.1+). -- **What `build` accepts is exactly the set of *additive build inputs*** — the - things that combine by appending and are consumed after the predicate is - evaluated, which is the member list of `BuildInputs`. `linkage`, `target`, - and the profile knobs are deliberately not among them: they are *inputs to* - target selection (conditioning `target` on a predicate evaluated against - `target` is circular), or they need override-rather-than-append semantics. - A key outside the set is reported and ignored; the message lists the set it - checked against, so it cannot drift from the check. -- **Evaluated against the resolved target** — the `--target` triple for a cross - build, otherwise the host. So a native Linux build never even *downloads* a - `[target.windows]` dependency. -- **Predicate keys**: `os`, `arch`, `family`, `env` — the triple's coordinates — - and, from mcpp 2026.9.1.1, the five target-side layer names `compiler`, - `compiler-runtime`, `kernel-abi`, `c-abi`, `c++-abi` - ([14 — The Target Side](14-target-side.md)). `accelerator` is a key here too - and is answered from this build's own `accel` — the backend names in - `--accel` or `[build] accel` — so it is a membership test over a set, and - `accelerator = "none"` is how a section says "this build named no backend" - without enumerating the ones it is not. Barewords `linux` / `macos` / - `windows` / `unix` are sugar for the matching `os` / `family` test. A key - outside this set is reported as a schema warning and the section does not - apply — it used to answer false in silence, which is indistinguishable from - a section that correctly did not match. -- **A resolved-layer predicate cannot select dependencies.** A layer is - resolved *from* the dependency graph, so a dependency chosen by one would - decide the answer it is asking for. `[target.'cfg(c-abi = "musl")'.dependencies]` - is reported and ignored; the `build` inputs under the same predicate do apply. - `accelerator` is not one of these (mcpp 2026.9.6.5): it is an input to the - build rather than an answer from the graph, so - `[target.'cfg(accelerator = "cuda")'.dependencies]` applies. -- **Precedence**: an exact-triple table wins over a `cfg`/alias table; multiple - matching predicate tables have their flags concatenated. Conditional entries - are appended **after** the unconditional `[build]` ones, so under GNU - "last flag wins" a conditional rule overrides a broader unconditional one. - That is what makes a per-OS **removal** expressible: - - ```toml - [build] - flags = [{ glob = "third_party/zlib/**", defines = ["HAVE_UNISTD_H=1"] }] - - # clang-MSVC has no : undo the base define, add the windows one. - [target.'cfg(windows)'.build] - flags = [{ glob = "third_party/zlib/**", - defines = ["NO_FSEEKO"], cflags = ["-UHAVE_UNISTD_H"] }] - ``` - -- **A conditional `flags` entry that does not match the current target does not - exist at all**, so it cannot produce a "glob matched no source file" warning. - One manifest can therefore carry all three OSes' flag tables without any of - them generating noise on the other two — the same way an inactive feature's - entries simply are not there. A zero-hit glob in the *unconditional* table - still warns, because there it is a real defect. -- **`toolchain` / `linkage` / `sysroot` are exact-triple only** — they describe - one specific cross target, so put them under `[target.]` (above), not - under a bare alias or `cfg(...)`. - -#### `sysroot` — the target's C library - -`sysroot` (mcpp 2026.8.20.2+) overrides the C library the target table binds to -a triple, on the same axis as `toolchain` overriding the compiler pin: one names -the compiler a target resolves, the other names its C library, and both were -engine-only until a project had a reason to disagree. +Moved to [14 — The Target Side](14-target-side.md). -```toml -[target.riscv64-none-elf] -sysroot = "xim:newlib-riscv@4.4" # a different C library -``` - -```toml -[target.riscv64-none-elf] -sysroot = "" # no C library at all -``` - -**An absent key and an empty one are different answers.** Absent inherits the -target table's C library. Present-and-empty is the **zero-libc tier**: no C -library is resolved, no include or library path is added, and the link carries -only what the project and its dependencies supply. `#include ` stops -resolving. A kernel or a bootloader wants exactly that, and collapsing the two -cases would silently hand such a project the target's C library back. - -The value is an xpkg reference or the empty string; a bare name is rejected when -the manifest is parsed, because accepting it would install nothing and then fail -much later naming a missing libc. - -A build program can ask which C library **payload** supplies the sysroot: -`mcpp::target_libc()` returns that package's name and -`mcpp::target_libc_profile()` the sub-directory for the target's ISA profile. -Both are empty on the zero-libc tier. See -[13 — Bare-Metal and Freestanding Targets](13-baremetal.md). - -**That is not the same question as "which C library did the target side -resolve to".** `target_libc()` names the payload mcpp installed, and that value -is an *input* to target-side resolution — a package in the dependency graph can -supply the C library instead, in which case the resolved `c-abi` is not what -this returns. To branch on the resolved layer, use a layer predicate: -`[target.'cfg(c-abi = "musl")'.build]` ([14 — The Target -Side](14-target-side.md)). This paragraph said "which C library was resolved" -until 2026.9.1.1, which was the wrong one of the two. ### 2.7.2 Bare metal (`os = none`) — freestanding targets @@ -1707,597 +1335,14 @@ Distinct from an artifact's `accel` field on purpose. A declaration is written by hand and may be aspirational; `accel` is measured from the build that produced a binary and is what a consumer is refused against. See [20 — Heterogeneous Builds](20-heterogeneous-builds.md). - ### 2.13 `[xlings]` — the project's environment -```toml -[xlings.workspace] # what this project's environment contains -cmake = "3.28" -"xim:picolibc-riscv" = "1.8.12" # a namespaced package - quotes required -code = "" # present; version unconstrained -llvm = { macosx = "20", default = "22" } -``` - -```toml -[xlings] -subos = "dev" # a named, isolated environment -``` - -`[xlings]` is mcpp's surface for **xlings' local project mechanism**: the -project `.xlings.json` that gives a directory its own environment. The -subsection names and their meanings are that file's, and mcpp materializes them -into `/.mcpp/.xlings.json` with no translation layer. - -**`[xlings.workspace]` is the one table.** An entry names a package and the -version this project uses it at. mcpp provisions it — installing it when the -machine does not have it, mapping it when it does — and materializes it as a -resolution pin, so the version the project named is the one its tools resolve -to. - -#### Writing an entry - -| Form | Means | -|---|---| -| `cmake = "3.28"` | that version | -| `llvm = "22"` | the highest installed `22.*`; a version prefix resolves | -| `code = ""` | present, version unconstrained | -| `"xim:picolibc-riscv" = "1.8.12"` | a package from the `xim` index | -| `llvm = { macosx = "20", default = "22" }` | per host platform | - -**A namespaced package is written `":" = ""`, and the -quotes are required** — a TOML bare key cannot contain a colon. That is the -recommended form and the one every official package uses: an entry names a -package and then says which version of it, so the namespace belongs to the -name. - -The namespace is also accepted on the version (`picolibc-riscv = "xim:1.8.12"`), -because that is what the materialised `.xlings.json` carries — a key there is an -xvm target and the scope qualifies the version. Two vocabularies, one entry. -Writing it on both halves with different values is an error, and so is naming -one package twice under two spellings. - -Platform keys are xlings' own — `linux`, `macosx`, `windows` — plus `default`. -`macos` and `macosx` are the same platform written in two vocabularies (mcpp's -triples say one, descriptors and xlings' project file say the other) and both -are accepted wherever a platform is named. A table with no key for this host -and no `default` declares nothing here. - -#### Two resolution axes — host and target (mcpp 2026.9.6.4+) - -A tool entry answers one of two different questions, and the table it is written -in decides which: - -| Written | Axis | Resolved against | -|---|---|---| -| `[xlings.workspace]`, platform keys in the value | host | the machine running the build | -| `[target..xlings.workspace]` | target | the resolved target (`--target`, else the host) | - -Both are correct spellings and neither supersedes the other. A tool that -executes on the build machine belongs on the host axis; a payload the produced -code is compiled or linked against belongs on the target axis. - -```toml -[xlings.workspace] -"xim:dpcpp" = "7.1.0" # a compiler, and it runs here - -[target.'cfg(os = "linux")'.xlings.workspace] -"xim:glibc" = "" # what the device units are compiled against -"xim:linux-headers" = "" -``` - -On a native build the two axes name the same platform, so a project that states -target facts on the host axis is right by accident and keeps working. It stops -being right the first time that project is cross-compiled. **For anything the -produced code is compiled or linked against, the target axis is the recommended -form.** - -`[target..feature-xlings.]` composes the condition with the -gate, exactly as `[target..feature-deps.]` does: the selector -says which targets, the feature says whether at all. - -```toml -[target.'cfg(os = "linux")'.feature-xlings.backend-vulkan] -"xim:shaderc" = "2026.3" -``` +Moved to [17 — The Project Environment](17-the-project-environment.md). -**A selector here must not name a RESOLVED layer.** `c-abi`, `c++-abi`, -`compiler`, `compiler-runtime` and `kernel-abi` are answered by dependency -resolution, which happens after tools are installed and after build programs -run. A tool conditioned on one would be declared and never installed — a build -that succeeds with the tool simply absent — so such a manifest is refused, -naming both the tool and the predicate. Condition it on the target, or gate it -on a feature: `[feature-xlings.]` is known before anything is -provisioned. +### 2.14 Host tools from a dependency -**`accelerator` is the exception, and is admitted** (mcpp 2026.9.6.5). It is -not resolved from anything: it is `--accel`, or `[build] accel`, read before the -first package is looked up. A payload predicated on it is merged in the same -pass as a triple predicate and installed like any other. - -```toml -[target.'cfg(accelerator = "cuda")'.xlings.workspace] -"xim:cuda-nvcc" = "12.9.86" -"xim:cuda-cudart" = "12.9.79" -``` - -This is the form a project with a device island should use. Without it the -vendor toolkit is declared unconditionally or not at all, so `mcpp build` with -no accelerator — the cheapest build, and the one CI usually runs — downloaded -gigabytes for a device it was not compiling for. - -The same rule governs dependencies: `[target.'cfg(accelerator = "cuda")'.dependencies]` -is honoured, while a dependency conditioned on a resolved layer is not, because -that one would decide the answer it is asking for. Nothing about the -accelerator is circular. - -The selector is the only place the condition is written. A value under a -selector that also carries platform keys states one fact twice, and is refused -naming both halves: - -``` -[target.cfg(os = "linux").xlings.workspace] xim:tool: the value carries platform -keys (linux, macosx), but [target.cfg(os = "linux")] already says which targets -this applies to. -``` - -`subos` is not conditional on a target: a project has one environment, so -`[target..xlings]` refuses the key rather than dropping it. - -**A published descriptor carries no edge for a target-axis entry**, and -`mcpp publish` says so. A descriptor has one block per platform, and a selector -is not a platform — `cfg(target_arch = "aarch64")` names no block that file has. -What a CONSUMER of the package gets installed comes from the top-level -`[xlings.workspace]`; the target axis stays correct for what the package's own -build compiles against. - -See [SPEC-004](specs/manifest-semantics.md) for the general rule these two axes -are an instance of. - -#### `when` — which verbs need this tool (mcpp 2026.9.4.2+) - -```toml -[xlings.workspace] -"xim:qemu-arm" = "9.2.4-1" # every build, as before -"xim:codegen" = { version = "1.0", when = "build" } -"xim:probe-rs" = { version = "0.24.0", when = "run" } -"xim:clang-tidy"= { version = "20", when = "dev" } -``` - -Package dependencies have had this axis since the beginning — -`[dependencies]`, `[build-dependencies]`, `[dev-dependencies]`. Tools had one -list, so a board-support package that named both an emulator and a debug probe -installed both for every consumer, including one that only wanted the library -to compile. - -| `when` | Installed by | Reaches a consumer | -|---|---|---| -| *(omitted)* | every verb that builds | yes | -| `build` | every verb that builds | yes | -| `run` | `mcpp run`, `mcpp test` | yes | -| `dev` | only the package that declared it, as the root | **no** | - -**Omitting `when` is the pre-2026.9.4.2 behaviour exactly**, so no manifest has -to change. Narrowing is optional; it is not a question an author has to answer. - -`dev` is the only tier that does not propagate. It means *"while the package -that declared this is itself being developed"*, so a dependency's `dev` entry is -never installed for a consumer. Every other tier does reach one, which is the -point of a board package knowing its own machine: it declares the emulator once -and every consumer gets it. - -The tier is written on the entry rather than as a second table, on the same -reasoning that makes `[dependencies]` accept both `dep = "1.0"` and -`dep = { version = "1.0", features = [...] }`. A scoped entry must name -`version` even to leave it empty (`version = ""` means *present, any version*), -because `{ when = "run" }` and a misspelt `version` key would otherwise be -indistinguishable. - -#### `[feature-xlings.]` — a tool a feature needs - -```toml -[features] -default = ["emulator"] -emulator = {} -hardware = {} - -[feature-xlings.hardware] -"xim:probe-rs" = "0.24.0" -``` +Moved to [07 — build.mcpp](07-build-mcpp.md). -The same table, gated on a feature, spelled the way `[feature-deps.]` -is. A consumer who never asks for `hardware` never downloads a probe driver. -Entries here accept `when` exactly as the unconditional ones do. - -A feature name no `[features]` table declares is reported as a schema warning: -it activates for nobody and installs nothing, and a tool whose absence is only -visible as *"the device is never reachable"* is the hardest kind to diagnose. - -#### A rule package brings its own environment (2026.9.6.6+) - -The table above is what a project writes when it has an opinion. Most projects -have none, and nothing is what they write: - -```toml -[build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-cuda"], host-module = true } -``` - -That one edge is the whole declaration. The rule package names the packages its -rule needs and the version it needs them from, under the feature that selects -it and the accelerator it is for: - -```toml -# in the rule package, not in your project -[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. The feature says *whether this rule is wanted*; -the selector says *which builds actually download it*. A CPU-only build of the -same project opens neither and installs nothing. - -Which package, and how old it may be, is the rule author's knowledge. Repeating -it in every project that uses the rule is a copy that goes stale silently — the -rule moves and the projects do not. - -#### One package, one version (2026.9.6.6+) - -A tool address is `[:][@]`, and its **identity is the -`(namespace, name)` pair**. The version is a constraint on that package, never -part of its name, so `xim:glibc`, `xim:glibc@2.40` and `xim:glibc@>=2.38` all -name one package. One version of it is installed per build. - -Which one is decided in two steps. - -**Adjudication — the declaration nearer the artifact wins.** Your project -outranks a package it depends on, so a pin overrides a rule's requirement: - -```toml -# the project, when it does have an opinion -[target.'cfg(accelerator = "cuda")'.xlings.workspace] -"xim:cuda-nvcc" = "13.3.33" -``` - -A declaration that names no version abstains: it says the package is wanted and -nothing about which version, so it cannot outrank a floor merely by being -nearer. When two declarations disagree and both name a version, mcpp reports -which was used — an override visible only as *"two versions were declared and -one directory exists"* is a fact the reader has to reconstruct from the -filesystem. - -**Validation — the winner must satisfy every requirement that lost.** `>=`, -`^`, `~` and comma-combined forms are requirements. A pin that fails one is -refused, naming both sides: - -``` -error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins - requires >=12.9.86. - One version of a package is installed, so the two cannot both hold. - fix: pin a version satisfying >=12.9.86, or drop the pin and let the - requirement decide. -``` - -A bare version is a *choice*, not a requirement: two exact pins that differ are -adjudicated and reported, not refused. Only a stated requirement can be -violated. - -**This is a comparison, not a search.** The version is chosen by adjudication -and then checked, so mcpp never has to ask the index which versions exist and -carries no constraint solver. The cost is stated rather than hidden: a -combination a solver could satisfy — a project's `>=8.0`, a rule's `8.5.0`, and -8.3 as the newest in the index — is refused instead, and the refusal says how to -proceed. - -Ranges are resolved in both directions. `>=2026.1` installs the highest -published version satisfying it, `>=2099.1` is refused as unsatisfiable, and -`mcpp::xpkg_dir` answers with the highest **installed** version satisfying the -range — a rule that declares a floor can find what the floor brought in. - -#### Which version a tool the project did not name resolves to - -| The project declares | The version comes from | -|---|---| -| `[xlings.workspace]`, no `subos` | the machine's environment, with the project's own entries laid over it | -| `[xlings.workspace]` and `subos = ""` | that environment's own workspace; the machine's does not apply | -| neither | the machine's environment | - -The middle row is not an omission. A named environment has its own installed -set, and carrying the machine's versions into it would name versions that are -not there. Naming one is how a project asks for isolation; leaving it out is -how it asks for the machine's environment with its own entries on top. - -An `xlings use` performed inside the project outranks this table until mcpp -rewrites the environment, because it is the layer merged last. - -#### `deps`, superseded - -`deps = ["xim:qemu-riscv@9.2.4-1"]` is the pre-2026.9.3 spelling of the same -statement. It is still honoured and is reported once, with the -`[xlings.workspace]` line to write instead. It is not refused, because a -refusal would reach a *dependency's* manifest, which a project that pinned an -exact version of that package cannot edit. - -#### `envs`, removed - -`[xlings.envs]` was materialized into `.xlings.json` and read by nothing: a -program's environment is declared by its own package, and an environment's by -that environment. The key is now an error naming both. Nothing in the index -used it. - -### 2.14 Host tools from a dependency (mcpp 2026.8.5.1+) - -A package can build a binary its consumers need *at build time* — `protoc`, a -`grpc_cpp_plugin`, `flatc`, `moc`, a transpiler. Ask for it on the dependency: - -```toml -[dependencies] -protobuf = { version = "35.1", tools = ["protoc"] } -grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } -``` - -Each name must be a `kind = "bin"` target of that package. mcpp builds it **for -the build machine** and hands `build.mcpp` its absolute path as -`MCPP_DEP__BIN_` — read it with `mcpp::dep_bin("protobuf", "protoc")` -(see [07 — build.mcpp](07-build-mcpp.md)). - -Four properties worth knowing: - -- **Always a host binary.** Under `mcpp build --target ` the tool is - still built for *this* machine, because a code generator has to run here. It - is a separate, host-targeted sub-build — the tool package's own `[toolchain]` - and its own dependency resolution apply, and none of it has to agree with - the consuming build. That is safe precisely because an executable has no ABI - contact with the consuming code. -- **One version axis.** The tool's version *is* the dependency's version, so - a `protoc` that does not match its runtime is not expressible. (This is the - problem with packaging the tool separately, and it is the failure mode that - bites at run time rather than compile time.) -- **Default off.** Nothing is built unless someone asks; the cost is the - consumer's to pay. A package gates the expensive part with - `[features]` + `required_features` (protobuf's `protoc` needs libprotoc's - ~157 extra TUs, which the runtime's users must not compile). -- **Cached globally**, keyed on package version × host toolchain × features × - its own dependency closure — built once per machine, not once per project. - -#### `[tools.overrides]` — use an existing binary - -```toml -[tools.overrides] -"compat.protobuf:protoc" = "/usr/bin/protoc" -``` - -or, without editing the manifest (CI, distro packaging): - -```bash -MCPP_TOOL_PROTOBUF_PROTOC=/usr/bin/protoc mcpp build -``` - -An override **skips the build entirely**. Every comparable system provides this -escape hatch (vcpkg's `VCPKG_HOST_TRIPLET`, CMake's `LLVM_NATIVE_TOOL_DIR`, -Qt's `QT_HOST_PATH`), and for the same reason: a tool that cannot be built from -source on this machine must not be a dead end. It is deliberately **not** part -of the cache key — an override is an escape hatch, not a reproducible input. - -#### `host-module = true` — reusable build rules as packages - -A rule (say "run protoc over these `.proto` files") should be written once, not -copy-pasted into every consumer's `build.mcpp`. Ship it as an ordinary mcpp -library package and import it: - -```toml -[dependencies] -protobufgen = { version = "0.1.0", host-module = true } -``` - -```cpp -// build.mcpp -import mcpp; -import protobufgen; -int main() { return protobufgen::generate({"schema"}) ? 0 : 1; } -``` - -mcpp compiles that package's lib-root module **for the host, in the same -command as `build.mcpp`** — which is what makes the BMI usable at all, since a -module interface is only importable by a compile that agrees with it on -standard, dialect and compiler identity. - -Rules are therefore versioned, testable and distributable through the package -manager already in use, written in **C++** — no second language, which is the -whole point of `build.mcpp` existing. - -**The module name is what the rule's source declares** (mcpp 2026.8.29.1+). -`export module acme.rules.protobuf;` is imported as `acme.rules.protobuf`, -whatever the package is called. Module names are authored API and do not mirror -package identity — the rule ordinary library packages have always followed. - -Until 2026.8.29.1 the host-module path registered the bare `package.name` -instead, which made a divergent name build under GCC and fail under Clang and -MSVC: GCC's BMIs are implicit under `gcm.cache` and keyed by the declared name, -while the other two are handed an explicit `=` mapping. Package names -carry no C++ naming constraint as a result, and `grpc-rules` is a legal package -name again. - -**Two rules may not declare one module name.** `import` addresses the module, -so two such packages are indistinguishable to the compiler, and their BMIs and -objects share a filename — the second overwrites the first and the surviving -object reaches the link twice. mcpp refuses this, naming both packages and both -interface paths. The check covers the rules one `build.mcpp` can see; it is not -an index-wide uniqueness rule, which `path` dependencies and private registries -would escape anyway. - -**`mcpp.` is reserved for rules maintained by the mcpp project.** A module name -under that prefix from a package outside the `mcpp` namespace produces a -warning naming both, and the build proceeds. It is a warning because the engine -cannot decide who is official: a `path` dependency, a private mirror and an -internal fork are all legitimate and indistinguishable from here. - -The lib root must be at `src/.cppm` (or wherever `[lib] path` points); a -missing one is reported as *"host module 'x': no interface unit at …"*. - -**A package may offer several rules, selected by features** (mcpp 2026.9.5.3+). -Every module interface unit among the package's resolved `[build] sources` — -including the sources a feature adds — is compiled as a host module under the -name it declares, the lib root first. A feature unit may import the lib root; -units are otherwise compiled alone, so they import `std`, `mcpp` and nothing -else. Only listed sources take part: the inferred `src/**` of a package that -declares no `sources` is not consulted, so a rule package published before this -release exposes exactly what it exposed then. - -```toml -# the collection's manifest -[build] -sources = ["src/plugins.cppm"] # export module mcpp.plugins; - -[features] -rules-cuda = { sources = ["rules/cuda.cppm"] } # export module mcpp.rules.cuda; -rules-spirv = { sources = ["rules/spirv.cppm"] } # export module mcpp.rules.spirv; -``` - -```toml -# a consumer -[build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } -``` - -**`[build-dependencies]`, not `[dependencies]`** — a rule package is the case -§2.6.1 describes exactly: its library must never reach the target while its -rule is still wanted. The two axes are separate, so `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" on the second axis, and the -section is where that is said. Written in `[dependencies]` it still works, and -that is precisely why the distinction has to be stated rather than enforced by -a failure. - -The module set is the feature set: a unit whose feature is not active is not -compiled, and importing it fails as an unknown module. `mcpp:plugins` is the -collection the mcpp project maintains (repository `mcpp-community/mcpp-plugins`); -its members are named `mcpp.rules.` for rule packages and `mcpp.tools.` -for build-time utilities. - -*Build-time only:* a `host-module = true` dependency is **not** compiled into -or linked with the target, and neither is anything it depends on. It exists to -run during `build.mcpp` and nowhere else. (Before 2026.8.5.2 it was also built -as an ordinary library, which made `import mcpp;` inside a rule fail: the -bundled module does not exist in that second compile. Until 2026.8.29.1 the -rule itself was excluded but its own `[dependencies]` were not, so they were -compiled and linked into the consumer's binary while the rule could not import -them.) - -#### A rule that depends on another rule (mcpp 2026.8.29.1+) - -A rule declares what it needs in its own `[build-dependencies]`, and may import -any entry there marked `host-module = true`: - -```toml -# inside the rule package's manifest -[build-dependencies] -globbing = { path = "../globbing", host-module = true } -``` - -```cpp -// the rule's own interface -export module tidyrule; -import std; -import mcpp; -import globbing; -``` - -mcpp compiles the inner rule first, in the same command and with the same -flags, so BMI agreement stays structural rather than checked. - -The consumer may **not** import `globbing`: build-time provisions cross one -further edge only on a `reexport = true` edge, and mcpp enforces that rather -than leaving it to the compiler, which on GCC would allow the import and then -fail on someone else's machine. - -*Limit:* one interface unit per host module. A library with implementation -units or several modules cannot yet be a rule's build dependency. - -#### `reexport = true` — a library standing up a toolchain for its user (2026.8.6.2+) - -Everything above is declared by whoever *uses* the tool. That is the wrong -place when the knowledge belongs to a library: gRPC's code generation needs -protobuf's `protoc`, and no user of a gRPC package should have to know that. - -`reexport = true` hands an edge's build-time provisions — its `tools`, its -`host-module`, and the dependency's directory — to **this package's own -consumers**: - -```toml -# inside the grpc package's manifest -[feature-deps.codegen] -"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true } -grpc-plugin = { version = "1.83.0", tools = ["grpc_cpp_plugin"], reexport = true } -grpcgen = { version = "1.83.0", host-module = true, reexport = true } -``` - -Its user then writes one line, and imports the rule: - -```toml -[dependencies] -grpc = { version = "1.83.0", features = ["codegen"] } -``` - -```cpp -// build.mcpp -import mcpp; -import grpcgen; -int main() { return grpcgen::generate_all() ? 0 : 1; } -``` - -- **Off by default, and deliberately not the edge's `visibility`.** `visibility` - already defaults to `"public"`, so riding it would let any dependency at any - depth put entries into the build program's tool namespace without saying so. - Handing something to consumers is a supply-chain statement; it has to be - written down. -- **One hop per declaration.** A re-exported provision reaches the consumers of - the package that declared it. For it to travel further, the next package must - re-export in turn — each package decides only what *it* hands on. -- **A feature may add a request to an already-declared dependency.** gRPC - depends on protobuf unconditionally and its `codegen` feature adds - `tools = ["protoc"], reexport = true` to that same edge. `tools` and - `features` union, `host-module` and `reexport` OR together; `version` / - `path` / `git` do not merge, so a feature still cannot silently override the - unconditional entry's identity. -- **Visibility, not execution.** `dep_bin()` returns a path; whether anything - runs is still the consumer's `build.mcpp`'s decision. Nothing changes about - who builds the tool or how the tool store is keyed. -- **Unqualified names are resolved by a ladder, not by luck.** Once two - libraries can re-export, both may offer the tail `protobuf`. The - fully-qualified `MCPP_DEP___BIN_` is always published; the - bare spelling is bound to `mcpplibs.`, else `compat.`, else an - unnamespaced ``, else the single remaining candidate — and when it is - contested mcpp says so instead of picking silently. - -##### Older mcpp reading a manifest that uses this - -An unrecognized dependency key is reported as a **degradation** and ignored -(mcpp 2026.8.6.2+), so a package written for a newer mcpp still loads and the -parts this reader understands still apply. Before that release it was a hard -load failure with a misleading message, which is why a published package could -not adopt a new key at all — the same property the index floor establishes: -data must not decide whether the program works. - -Consequently a package that *relies* on `reexport` for its ergonomics still -needs a client new enough to implement it; what changed is that everything else -about that package keeps working on an older one. - -##### Scoping a provision per platform - -A package may declare a `bin` target on some platforms only. Because the -*library* now decides what is requested, an unconditional request turns an -unsupported platform into an error its user cannot edit away. Scope it: - -```toml -[target.'cfg(not(windows))'.feature-deps.codegen] -"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true } -``` - -`[target..feature-deps.]` (2026.8.6.2+) follows the same rules as -the other conditional dependency tables (§2.7.1). The **feature itself is -registered on every platform** — only what it pulls in is conditional — so -requesting it where no predicate matches is not an unknown-feature error. ### 2.15 `[resources]` — Metadata and Assets Embedded in the Artifact (2026.8.7.1+) @@ -2397,167 +1442,10 @@ See [07 — build.mcpp](07-build-mcpp.md). Naming such a file in `[build].ldflags` also "works", but ldflags is a flat string in the link command: nothing tracks it, and editing the file produces `ninja: no work to do`. +### 2.16 `[hooks]` — Project Build Lifecycle Commands -### 2.16 `[hooks]` — Project Build Lifecycle Commands (experimental) - -> **Experimental.** A hook cannot currently decide whether a build succeeded. -> Every hook failure is reported as a **warning** and `mcpp build` keeps the -> result it earned on its own; `side_effect = true` is refused with an error -> rather than honoured. The key stays in the schema so that manifests written -> today do not have to change when the feature is promoted. Two further limits -> are permanent rather than provisional: only the root project's hooks run, and -> only `mcpp build` runs them. - -A hook is a command `mcpp build` **owns for an interval**, and the event names -the interval: - -```toml -[hooks] -build_start = "echo build started" -build_failed = "notify-send 'build failed'" -build_finished = "notify-send 'build finished'" - -# Optional; these are the defaults. -timeout_seconds = 10 -enabled = true -side_effect = false # `true` is refused while this is experimental -``` - -| Key | Type | Default | The interval it names | -|---|---|---:|---| -| `build_start` | command | — | Opens after project preparation, closes when the command exits | -| `build_finished` | command | — | Opens after a build that succeeded, closes when the command exits | -| `build_failed` | command | — | Opens after a build that failed, closes when the command exits | -| `during_build` | command | — | Opens before the build, closes after it | -| `timeout_seconds` | integer, 1–86400 | `10` | Bounds one run of a command | -| `enabled` | bool | `true` | Enables all commands in this table | -| `side_effect` | bool | `false` | Whether a hook failure makes the build fail. **Reserved** — only `false` is accepted while this is experimental | - -The first three intervals are **self-closing** — they end when the command -does. "Synchronous" is not a separate mode here; it is what a self-closing -interval looks like. `during_build` is the one interval closed by something -else, and the two keys that only make sense for one shape follow from that -rather than being exceptions. - -A command is a string, or a table when it needs options: - -| Table key | Applies to | Meaning | -|---|---|---| -| `cmd` | every event | The command. Required. | -| `timeout_seconds` | self-closing events | Overrides the table default for this event | -| `loop` | `during_build` | Restart the command if it exits before the build ends | - -`loop` on a self-closing event and `timeout_seconds` on `during_build` are both -**errors**, not ignored keys: a self-closing interval ends when its command -exits, so there is nothing to restart, and `during_build` is already bounded by -the build. A key that is accepted and does nothing reads as a broken feature. - -Commands run through the host shell (`/bin/sh` or `cmd.exe`), with the -**project root** as their working directory — not the directory `mcpp build` -was typed in, so a relative path in a hook means the same thing wherever the -build was started. A self-closing command keeps ordinary terminal -input/output. Missing event commands are skipped. - -The lifecycle is: - -```text -during_build opens -build_start - ├─ build succeeds → during_build closes → build_finished - └─ build fails → during_build closes → build_failed -``` - -`during_build` closes **before** the terminal hook, so the two commands never -overlap. - -`build_failed` and `build_finished` are mutually exclusive, and both are -reachable only after `build_start` has run. A project that cannot be *prepared* -— an invalid manifest, an unresolvable dependency, no usable toolchain — fires -nothing: it has not started building, and its hook program may be exactly what -preparation would have installed. - -A hook command that cannot start, returns non-zero, or exceeds its timeout is a -hook failure. For `during_build` there is one more: a looped command that -**fails to stay up** — five consecutive runs ending unsuccessfully within a -second — stops being restarted and is reported. (A command that finishes -quickly and *successfully* is doing exactly what `loop` was asked to repeat, -and is not a failure.) Every one of those is reported as a **warning**, and the -build keeps the result it earned on its own — while `[hooks]` is experimental -it does not get a vote. A hook's own failure does not trigger another hook. - -`side_effect = true` is what will change that, and asking for it today is an -error: - -```text -error: mcpp.toml: error: [hooks].side_effect = true is not available yet: -[hooks] is experimental and cannot decide whether a build succeeded. … -``` - -Refused rather than quietly downgraded, because both silent options are worse: -honouring it would give an experimental feature a veto over every build, and -ignoring it would leave a project believing its build is gated on a notifier -when nothing is. When the feature is promoted, `true` will mean "a hook failure -fails the build" — and a build that failed on its own will still keep its own -exit code, so `mcpp build` never reports a compile error as a notifier problem. - -Two things are worth knowing about a `during_build` command specifically: - -- **Its output is discarded**, because it writes concurrently with the build - and would otherwise land in the middle of a compiler diagnostic. Run - `mcpp build --verbose` to see it. -- **It is stopped as a process tree**, not as a process. `player & wait` makes - the player a grandchild of the command mcpp started, and stopping only the - latter would leave the audio device held after the build. mcpp puts the - command in its own process group (a job object on Windows) and stops that, - including when the build is interrupted with Ctrl-C. - -Scope, precisely: - -- Only `mcpp build` runs hooks. `mcpp run`, `mcpp test` and - `mcpp build --configure-only` build too, and deliberately do not. -- Hooks belong to the **package being built**. In a workspace fan-out that is - each member in turn — its own `[hooks]`, around its own build, in its own - root. A *virtual* workspace root (`[workspace]` with no `[package]`) builds - nothing, so a `[hooks]` table there never fires. -- A dependency's `[hooks]` is **skipped**, always. Only the root project's run. - Every manifest mcpp parses carries the section, a dependency's included, and - nothing reads it — which is what keeps `mcpp add` from meaning "run this - author's shell command on my next build". This is a property of the design, - not a default awaiting a switch. -- Declaring an active hook opts the project out of the no-op fast path, because - `build_start` is specified to run after preparation. Expect `mcpp build` on an - already-current hooked project to cost a preparation pass rather than - milliseconds. - -An unrecognised key in `[hooks]`, or inside one event's table, is a warning (an -error under `--strict`), so a manifest written for a newer mcpp still loads. An -unrecognised *value* — a missing or non-string `cmd`, a `timeout_seconds` -outside 1–86400, a key offered to the wrong interval — is a manifest error. - -> **A hook is code, and `mcpp.toml` is part of the repository.** Building a -> freshly cloned project runs whatever its `[hooks]` say, with the privileges -> of whoever invoked `mcpp build`. This is the same trust `build.mcpp` already -> asks for ([07 — build.mcpp](07-build-mcpp.md)); `[hooks]` widens its reach -> rather than introducing it. - -Hook programs can be installed as ordinary xlings dependencies. For example, -an audio notifier can keep its sound files inside its own executable rather -than adding media handling to mcpp: - -```toml -[hooks] -build_finished = "mcpp-hooks-audioplayer niulai-mm" -build_failed = "mcpp-hooks-audioplayer niulai-niulai" -side_effect = false - -[xlings.workspace] -"xim:mcpp-hooks-audioplayer" = "0.0.1" -``` +Moved to [21 — Commands by Scenario](21-commands-by-scenario.md). -A different sound for a successful or failed build. `side_effect = false` is -written out rather than left to the default: it is the value this manifest -wants on its own terms — a missing audio device should never fail a build — so -it will still say so once the key has more than one accepted value. ## Appendix A. Schema Ownership Principle (admission criteria for new fields) diff --git a/docs/07-build-mcpp.md b/docs/07-build-mcpp.md index bcbc508d..aab8682f 100644 --- a/docs/07-build-mcpp.md +++ b/docs/07-build-mcpp.md @@ -942,3 +942,277 @@ When nothing changed the output is `build.mcpp up to date (cached)`; otherwise > blocked on a prompt is harder to diagnose than one that failed; and a build > whose outcome depends on a keystroke is not reproducible. The configurable > bound plus an error that names the file to edit answers the same need. + +## Host tools from a dependency (mcpp 2026.8.5.1+) + +A package can build a binary its consumers need *at build time* — `protoc`, a +`grpc_cpp_plugin`, `flatc`, `moc`, a transpiler. Ask for it on the dependency: + +```toml +[dependencies] +protobuf = { version = "35.1", tools = ["protoc"] } +grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } +``` + +Each name must be a `kind = "bin"` target of that package. mcpp builds it **for +the build machine** and hands `build.mcpp` its absolute path as +`MCPP_DEP__BIN_` — read it with `mcpp::dep_bin("protobuf", "protoc")` +(see [07 — build.mcpp](07-build-mcpp.md)). + +Four properties worth knowing: + +- **Always a host binary.** Under `mcpp build --target ` the tool is + still built for *this* machine, because a code generator has to run here. It + is a separate, host-targeted sub-build — the tool package's own `[toolchain]` + and its own dependency resolution apply, and none of it has to agree with + the consuming build. That is safe precisely because an executable has no ABI + contact with the consuming code. +- **One version axis.** The tool's version *is* the dependency's version, so + a `protoc` that does not match its runtime is not expressible. (This is the + problem with packaging the tool separately, and it is the failure mode that + bites at run time rather than compile time.) +- **Default off.** Nothing is built unless someone asks; the cost is the + consumer's to pay. A package gates the expensive part with + `[features]` + `required_features` (protobuf's `protoc` needs libprotoc's + ~157 extra TUs, which the runtime's users must not compile). +- **Cached globally**, keyed on package version × host toolchain × features × + its own dependency closure — built once per machine, not once per project. + +### `[tools.overrides]` — use an existing binary + +```toml +[tools.overrides] +"compat.protobuf:protoc" = "/usr/bin/protoc" +``` + +or, without editing the manifest (CI, distro packaging): + +```bash +MCPP_TOOL_PROTOBUF_PROTOC=/usr/bin/protoc mcpp build +``` + +An override **skips the build entirely**. Every comparable system provides this +escape hatch (vcpkg's `VCPKG_HOST_TRIPLET`, CMake's `LLVM_NATIVE_TOOL_DIR`, +Qt's `QT_HOST_PATH`), and for the same reason: a tool that cannot be built from +source on this machine must not be a dead end. It is deliberately **not** part +of the cache key — an override is an escape hatch, not a reproducible input. + +### `host-module = true` — reusable build rules as packages + +A rule (say "run protoc over these `.proto` files") should be written once, not +copy-pasted into every consumer's `build.mcpp`. Ship it as an ordinary mcpp +library package and import it: + +```toml +[dependencies] +protobufgen = { version = "0.1.0", host-module = true } +``` + +```cpp +// build.mcpp +import mcpp; +import protobufgen; +int main() { return protobufgen::generate({"schema"}) ? 0 : 1; } +``` + +mcpp compiles that package's lib-root module **for the host, in the same +command as `build.mcpp`** — which is what makes the BMI usable at all, since a +module interface is only importable by a compile that agrees with it on +standard, dialect and compiler identity. + +Rules are therefore versioned, testable and distributable through the package +manager already in use, written in **C++** — no second language, which is the +whole point of `build.mcpp` existing. + +**The module name is what the rule's source declares** (mcpp 2026.8.29.1+). +`export module acme.rules.protobuf;` is imported as `acme.rules.protobuf`, +whatever the package is called. Module names are authored API and do not mirror +package identity — the rule ordinary library packages have always followed. + +Until 2026.8.29.1 the host-module path registered the bare `package.name` +instead, which made a divergent name build under GCC and fail under Clang and +MSVC: GCC's BMIs are implicit under `gcm.cache` and keyed by the declared name, +while the other two are handed an explicit `=` mapping. Package names +carry no C++ naming constraint as a result, and `grpc-rules` is a legal package +name again. + +**Two rules may not declare one module name.** `import` addresses the module, +so two such packages are indistinguishable to the compiler, and their BMIs and +objects share a filename — the second overwrites the first and the surviving +object reaches the link twice. mcpp refuses this, naming both packages and both +interface paths. The check covers the rules one `build.mcpp` can see; it is not +an index-wide uniqueness rule, which `path` dependencies and private registries +would escape anyway. + +**`mcpp.` is reserved for rules maintained by the mcpp project.** A module name +under that prefix from a package outside the `mcpp` namespace produces a +warning naming both, and the build proceeds. It is a warning because the engine +cannot decide who is official: a `path` dependency, a private mirror and an +internal fork are all legitimate and indistinguishable from here. + +The lib root must be at `src/.cppm` (or wherever `[lib] path` points); a +missing one is reported as *"host module 'x': no interface unit at …"*. + +**A package may offer several rules, selected by features** (mcpp 2026.9.5.3+). +Every module interface unit among the package's resolved `[build] sources` — +including the sources a feature adds — is compiled as a host module under the +name it declares, the lib root first. A feature unit may import the lib root; +units are otherwise compiled alone, so they import `std`, `mcpp` and nothing +else. Only listed sources take part: the inferred `src/**` of a package that +declares no `sources` is not consulted, so a rule package published before this +release exposes exactly what it exposed then. + +```toml +# the collection's manifest +[build] +sources = ["src/plugins.cppm"] # export module mcpp.plugins; + +[features] +rules-cuda = { sources = ["rules/cuda.cppm"] } # export module mcpp.rules.cuda; +rules-spirv = { sources = ["rules/spirv.cppm"] } # export module mcpp.rules.spirv; +``` + +```toml +# a consumer +[build-dependencies.mcpp] +plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } +``` + +**`[build-dependencies]`, not `[dependencies]`** — a rule package is the case +§2.6.1 describes exactly: its library must never reach the target while its +rule is still wanted. The two axes are separate, so `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" on the second axis, and the +section is where that is said. Written in `[dependencies]` it still works, and +that is precisely why the distinction has to be stated rather than enforced by +a failure. + +The module set is the feature set: a unit whose feature is not active is not +compiled, and importing it fails as an unknown module. `mcpp:plugins` is the +collection the mcpp project maintains (repository `mcpp-community/mcpp-plugins`); +its members are named `mcpp.rules.` for rule packages and `mcpp.tools.` +for build-time utilities. + +*Build-time only:* a `host-module = true` dependency is **not** compiled into +or linked with the target, and neither is anything it depends on. It exists to +run during `build.mcpp` and nowhere else. (Before 2026.8.5.2 it was also built +as an ordinary library, which made `import mcpp;` inside a rule fail: the +bundled module does not exist in that second compile. Until 2026.8.29.1 the +rule itself was excluded but its own `[dependencies]` were not, so they were +compiled and linked into the consumer's binary while the rule could not import +them.) + +### A rule that depends on another rule (mcpp 2026.8.29.1+) + +A rule declares what it needs in its own `[build-dependencies]`, and may import +any entry there marked `host-module = true`: + +```toml +# inside the rule package's manifest +[build-dependencies] +globbing = { path = "../globbing", host-module = true } +``` + +```cpp +// the rule's own interface +export module tidyrule; +import std; +import mcpp; +import globbing; +``` + +mcpp compiles the inner rule first, in the same command and with the same +flags, so BMI agreement stays structural rather than checked. + +The consumer may **not** import `globbing`: build-time provisions cross one +further edge only on a `reexport = true` edge, and mcpp enforces that rather +than leaving it to the compiler, which on GCC would allow the import and then +fail on someone else's machine. + +*Limit:* one interface unit per host module. A library with implementation +units or several modules cannot yet be a rule's build dependency. + +### `reexport = true` — a library standing up a toolchain for its user (2026.8.6.2+) + +Everything above is declared by whoever *uses* the tool. That is the wrong +place when the knowledge belongs to a library: gRPC's code generation needs +protobuf's `protoc`, and no user of a gRPC package should have to know that. + +`reexport = true` hands an edge's build-time provisions — its `tools`, its +`host-module`, and the dependency's directory — to **this package's own +consumers**: + +```toml +# inside the grpc package's manifest +[feature-deps.codegen] +"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true } +grpc-plugin = { version = "1.83.0", tools = ["grpc_cpp_plugin"], reexport = true } +grpcgen = { version = "1.83.0", host-module = true, reexport = true } +``` + +Its user then writes one line, and imports the rule: + +```toml +[dependencies] +grpc = { version = "1.83.0", features = ["codegen"] } +``` + +```cpp +// build.mcpp +import mcpp; +import grpcgen; +int main() { return grpcgen::generate_all() ? 0 : 1; } +``` + +- **Off by default, and deliberately not the edge's `visibility`.** `visibility` + already defaults to `"public"`, so riding it would let any dependency at any + depth put entries into the build program's tool namespace without saying so. + Handing something to consumers is a supply-chain statement; it has to be + written down. +- **One hop per declaration.** A re-exported provision reaches the consumers of + the package that declared it. For it to travel further, the next package must + re-export in turn — each package decides only what *it* hands on. +- **A feature may add a request to an already-declared dependency.** gRPC + depends on protobuf unconditionally and its `codegen` feature adds + `tools = ["protoc"], reexport = true` to that same edge. `tools` and + `features` union, `host-module` and `reexport` OR together; `version` / + `path` / `git` do not merge, so a feature still cannot silently override the + unconditional entry's identity. +- **Visibility, not execution.** `dep_bin()` returns a path; whether anything + runs is still the consumer's `build.mcpp`'s decision. Nothing changes about + who builds the tool or how the tool store is keyed. +- **Unqualified names are resolved by a ladder, not by luck.** Once two + libraries can re-export, both may offer the tail `protobuf`. The + fully-qualified `MCPP_DEP___BIN_` is always published; the + bare spelling is bound to `mcpplibs.`, else `compat.`, else an + unnamespaced ``, else the single remaining candidate — and when it is + contested mcpp says so instead of picking silently. + +#### Older mcpp reading a manifest that uses this + +An unrecognized dependency key is reported as a **degradation** and ignored +(mcpp 2026.8.6.2+), so a package written for a newer mcpp still loads and the +parts this reader understands still apply. Before that release it was a hard +load failure with a misleading message, which is why a published package could +not adopt a new key at all — the same property the index floor establishes: +data must not decide whether the program works. + +Consequently a package that *relies* on `reexport` for its ergonomics still +needs a client new enough to implement it; what changed is that everything else +about that package keeps working on an older one. + +#### Scoping a provision per platform + +A package may declare a `bin` target on some platforms only. Because the +*library* now decides what is requested, an unconditional request turns an +unsupported platform into an error its user cannot edit away. Scope it: + +```toml +[target.'cfg(not(windows))'.feature-deps.codegen] +"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true } +``` + +`[target..feature-deps.]` (2026.8.6.2+) follows the same rules as +the other conditional dependency tables (§2.7.1). The **feature itself is +registered on every platform** — only what it pulls in is conditional — so +requesting it where no predicate matches is not an unknown-feature error. diff --git a/docs/14-target-side.md b/docs/14-target-side.md index cc7c62a5..87f31e98 100644 --- a/docs/14-target-side.md +++ b/docs/14-target-side.md @@ -332,3 +332,141 @@ published package. An unknown key elsewhere in a manifest is ignored. That provision governs future engines only. A package declaring a layer name still requires its consumers to run an engine no older than the release that introduced the name. + +## `[target.*]` — Platform-Conditional Dependencies & Flags + +Scope dependencies and build flags to a platform with a `[target.]` table. +The selector `` has three forms: + +| Selector | Meaning | Example | +|---|---|---| +| **bare OS alias** | a single OS / family — the concise, common form | `[target.windows]`, `[target.unix]` | +| **`cfg(...)` predicate** | a compound condition (arch / env / combinators) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | +| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see §2.7.3) | `[target.x86_64-linux-musl]` | + +A selector may carry platform-conditional **dependencies** and **build flags**: + +```toml +# Concise bare-alias form — pull OpenBLAS and link it only on Windows. +[target.windows.dependencies.compat] +openblas = "0.3.33" +[target.windows.build] +ldflags = ["-Llib", "-llibopenblas"] + +# cfg(...) for compound predicates (grammar: all/any/not over os/arch/family/env, +# plus the bare aliases windows/unix/linux/macos). +[target.'cfg(all(linux, not(arch = "aarch64")))'.build] +cxxflags = ["-march=x86-64-v2"] +``` + +`[target.windows]` is exactly equivalent to `[target.'cfg(windows)']` — the bare +aliases `windows` / `linux` / `macos` / `unix` are never valid target triples, so +there is no ambiguity. Use the bare form for a single OS/family; use `cfg(...)` +for arch/env conditions and combinators. + +- **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies` / + `feature-deps.` (mcpp 2026.8.6.2+ — see §2.14; the feature is + registered unconditionally, only its dependency set is scoped), and + `build` with `cflags` / `cxxflags` / `ldflags` / `sources` (mcpp 0.0.95+ — + conditional source globs, e.g. gating `src/x86/**/*.asm` behind + `cfg(arch = "x86_64")`; `!`-exclusion globs work here too), plus `flags` and + `include_dirs` / `include_dirs_after` (mcpp 0.0.102+), plus + `private_include_dirs` and `std-module-flags` (mcpp 2026.9.1.1+). +- **What `build` accepts is exactly the set of *additive build inputs*** — the + things that combine by appending and are consumed after the predicate is + evaluated, which is the member list of `BuildInputs`. `linkage`, `target`, + and the profile knobs are deliberately not among them: they are *inputs to* + target selection (conditioning `target` on a predicate evaluated against + `target` is circular), or they need override-rather-than-append semantics. + A key outside the set is reported and ignored; the message lists the set it + checked against, so it cannot drift from the check. +- **Evaluated against the resolved target** — the `--target` triple for a cross + build, otherwise the host. So a native Linux build never even *downloads* a + `[target.windows]` dependency. +- **Predicate keys**: `os`, `arch`, `family`, `env` — the triple's coordinates — + and, from mcpp 2026.9.1.1, the five target-side layer names `compiler`, + `compiler-runtime`, `kernel-abi`, `c-abi`, `c++-abi` + ([14 — The Target Side](14-target-side.md)). `accelerator` is a key here too + and is answered from this build's own `accel` — the backend names in + `--accel` or `[build] accel` — so it is a membership test over a set, and + `accelerator = "none"` is how a section says "this build named no backend" + without enumerating the ones it is not. Barewords `linux` / `macos` / + `windows` / `unix` are sugar for the matching `os` / `family` test. A key + outside this set is reported as a schema warning and the section does not + apply — it used to answer false in silence, which is indistinguishable from + a section that correctly did not match. +- **A resolved-layer predicate cannot select dependencies.** A layer is + resolved *from* the dependency graph, so a dependency chosen by one would + decide the answer it is asking for. `[target.'cfg(c-abi = "musl")'.dependencies]` + is reported and ignored; the `build` inputs under the same predicate do apply. + `accelerator` is not one of these (mcpp 2026.9.6.5): it is an input to the + build rather than an answer from the graph, so + `[target.'cfg(accelerator = "cuda")'.dependencies]` applies. +- **Precedence**: an exact-triple table wins over a `cfg`/alias table; multiple + matching predicate tables have their flags concatenated. Conditional entries + are appended **after** the unconditional `[build]` ones, so under GNU + "last flag wins" a conditional rule overrides a broader unconditional one. + That is what makes a per-OS **removal** expressible: + + ```toml + [build] + flags = [{ glob = "third_party/zlib/**", defines = ["HAVE_UNISTD_H=1"] }] + + # clang-MSVC has no : undo the base define, add the windows one. + [target.'cfg(windows)'.build] + flags = [{ glob = "third_party/zlib/**", + defines = ["NO_FSEEKO"], cflags = ["-UHAVE_UNISTD_H"] }] + ``` + +- **A conditional `flags` entry that does not match the current target does not + exist at all**, so it cannot produce a "glob matched no source file" warning. + One manifest can therefore carry all three OSes' flag tables without any of + them generating noise on the other two — the same way an inactive feature's + entries simply are not there. A zero-hit glob in the *unconditional* table + still warns, because there it is a real defect. +- **`toolchain` / `linkage` / `sysroot` are exact-triple only** — they describe + one specific cross target, so put them under `[target.]` (above), not + under a bare alias or `cfg(...)`. + +### `sysroot` — the target's C library + +`sysroot` (mcpp 2026.8.20.2+) overrides the C library the target table binds to +a triple, on the same axis as `toolchain` overriding the compiler pin: one names +the compiler a target resolves, the other names its C library, and both were +engine-only until a project had a reason to disagree. + +```toml +[target.riscv64-none-elf] +sysroot = "xim:newlib-riscv@4.4" # a different C library +``` + +```toml +[target.riscv64-none-elf] +sysroot = "" # no C library at all +``` + +**An absent key and an empty one are different answers.** Absent inherits the +target table's C library. Present-and-empty is the **zero-libc tier**: no C +library is resolved, no include or library path is added, and the link carries +only what the project and its dependencies supply. `#include ` stops +resolving. A kernel or a bootloader wants exactly that, and collapsing the two +cases would silently hand such a project the target's C library back. + +The value is an xpkg reference or the empty string; a bare name is rejected when +the manifest is parsed, because accepting it would install nothing and then fail +much later naming a missing libc. + +A build program can ask which C library **payload** supplies the sysroot: +`mcpp::target_libc()` returns that package's name and +`mcpp::target_libc_profile()` the sub-directory for the target's ISA profile. +Both are empty on the zero-libc tier. See +[13 — Bare-Metal and Freestanding Targets](13-baremetal.md). + +**That is not the same question as "which C library did the target side +resolve to".** `target_libc()` names the payload mcpp installed, and that value +is an *input* to target-side resolution — a package in the dependency graph can +supply the C library instead, in which case the resolved `c-abi` is not what +this returns. To branch on the resolved layer, use a layer predicate: +`[target.'cfg(c-abi = "musl")'.build]` ([14 — The Target +Side](14-target-side.md)). This paragraph said "which C library was resolved" +until 2026.9.1.1, which was the wrong one of the two. diff --git a/docs/17-the-project-environment.md b/docs/17-the-project-environment.md index 414d8eda..bb5d4120 100644 --- a/docs/17-the-project-environment.md +++ b/docs/17-the-project-environment.md @@ -197,3 +197,320 @@ naming the payload's path. - [8 - Toolchain Internals](08-toolchain-internals.md) — runtime selection, the `RuntimeBinding` snapshot, and the degradation rules. - [5 - mcpp.toml](05-mcpp-toml.md) — every manifest key, including `[xlings]`. + +## `[xlings]` — the project's environment + +```toml +[xlings.workspace] # what this project's environment contains +cmake = "3.28" +"xim:picolibc-riscv" = "1.8.12" # a namespaced package - quotes required +code = "" # present; version unconstrained +llvm = { macosx = "20", default = "22" } +``` + +```toml +[xlings] +subos = "dev" # a named, isolated environment +``` + +`[xlings]` is mcpp's surface for **xlings' local project mechanism**: the +project `.xlings.json` that gives a directory its own environment. The +subsection names and their meanings are that file's, and mcpp materializes them +into `/.mcpp/.xlings.json` with no translation layer. + +**`[xlings.workspace]` is the one table.** An entry names a package and the +version this project uses it at. mcpp provisions it — installing it when the +machine does not have it, mapping it when it does — and materializes it as a +resolution pin, so the version the project named is the one its tools resolve +to. + +### Writing an entry + +| Form | Means | +|---|---| +| `cmake = "3.28"` | that version | +| `llvm = "22"` | the highest installed `22.*`; a version prefix resolves | +| `code = ""` | present, version unconstrained | +| `"xim:picolibc-riscv" = "1.8.12"` | a package from the `xim` index | +| `llvm = { macosx = "20", default = "22" }` | per host platform | + +**A namespaced package is written `":" = ""`, and the +quotes are required** — a TOML bare key cannot contain a colon. That is the +recommended form and the one every official package uses: an entry names a +package and then says which version of it, so the namespace belongs to the +name. + +The namespace is also accepted on the version (`picolibc-riscv = "xim:1.8.12"`), +because that is what the materialised `.xlings.json` carries — a key there is an +xvm target and the scope qualifies the version. Two vocabularies, one entry. +Writing it on both halves with different values is an error, and so is naming +one package twice under two spellings. + +Platform keys are xlings' own — `linux`, `macosx`, `windows` — plus `default`. +`macos` and `macosx` are the same platform written in two vocabularies (mcpp's +triples say one, descriptors and xlings' project file say the other) and both +are accepted wherever a platform is named. A table with no key for this host +and no `default` declares nothing here. + +### Two resolution axes — host and target (mcpp 2026.9.6.4+) + +A tool entry answers one of two different questions, and the table it is written +in decides which: + +| Written | Axis | Resolved against | +|---|---|---| +| `[xlings.workspace]`, platform keys in the value | host | the machine running the build | +| `[target..xlings.workspace]` | target | the resolved target (`--target`, else the host) | + +Both are correct spellings and neither supersedes the other. A tool that +executes on the build machine belongs on the host axis; a payload the produced +code is compiled or linked against belongs on the target axis. + +```toml +[xlings.workspace] +"xim:dpcpp" = "7.1.0" # a compiler, and it runs here + +[target.'cfg(os = "linux")'.xlings.workspace] +"xim:glibc" = "" # what the device units are compiled against +"xim:linux-headers" = "" +``` + +On a native build the two axes name the same platform, so a project that states +target facts on the host axis is right by accident and keeps working. It stops +being right the first time that project is cross-compiled. **For anything the +produced code is compiled or linked against, the target axis is the recommended +form.** + +`[target..feature-xlings.]` composes the condition with the +gate, exactly as `[target..feature-deps.]` does: the selector +says which targets, the feature says whether at all. + +```toml +[target.'cfg(os = "linux")'.feature-xlings.backend-vulkan] +"xim:shaderc" = "2026.3" +``` + +**A selector here must not name a RESOLVED layer.** `c-abi`, `c++-abi`, +`compiler`, `compiler-runtime` and `kernel-abi` are answered by dependency +resolution, which happens after tools are installed and after build programs +run. A tool conditioned on one would be declared and never installed — a build +that succeeds with the tool simply absent — so such a manifest is refused, +naming both the tool and the predicate. Condition it on the target, or gate it +on a feature: `[feature-xlings.]` is known before anything is +provisioned. + +**`accelerator` is the exception, and is admitted** (mcpp 2026.9.6.5). It is +not resolved from anything: it is `--accel`, or `[build] accel`, read before the +first package is looked up. A payload predicated on it is merged in the same +pass as a triple predicate and installed like any other. + +```toml +[target.'cfg(accelerator = "cuda")'.xlings.workspace] +"xim:cuda-nvcc" = "12.9.86" +"xim:cuda-cudart" = "12.9.79" +``` + +This is the form a project with a device island should use. Without it the +vendor toolkit is declared unconditionally or not at all, so `mcpp build` with +no accelerator — the cheapest build, and the one CI usually runs — downloaded +gigabytes for a device it was not compiling for. + +The same rule governs dependencies: `[target.'cfg(accelerator = "cuda")'.dependencies]` +is honoured, while a dependency conditioned on a resolved layer is not, because +that one would decide the answer it is asking for. Nothing about the +accelerator is circular. + +The selector is the only place the condition is written. A value under a +selector that also carries platform keys states one fact twice, and is refused +naming both halves: + +``` +[target.cfg(os = "linux").xlings.workspace] xim:tool: the value carries platform +keys (linux, macosx), but [target.cfg(os = "linux")] already says which targets +this applies to. +``` + +`subos` is not conditional on a target: a project has one environment, so +`[target..xlings]` refuses the key rather than dropping it. + +**A published descriptor carries no edge for a target-axis entry**, and +`mcpp publish` says so. A descriptor has one block per platform, and a selector +is not a platform — `cfg(target_arch = "aarch64")` names no block that file has. +What a CONSUMER of the package gets installed comes from the top-level +`[xlings.workspace]`; the target axis stays correct for what the package's own +build compiles against. + +See [SPEC-004](specs/manifest-semantics.md) for the general rule these two axes +are an instance of. + +### `when` — which verbs need this tool (mcpp 2026.9.4.2+) + +```toml +[xlings.workspace] +"xim:qemu-arm" = "9.2.4-1" # every build, as before +"xim:codegen" = { version = "1.0", when = "build" } +"xim:probe-rs" = { version = "0.24.0", when = "run" } +"xim:clang-tidy"= { version = "20", when = "dev" } +``` + +Package dependencies have had this axis since the beginning — +`[dependencies]`, `[build-dependencies]`, `[dev-dependencies]`. Tools had one +list, so a board-support package that named both an emulator and a debug probe +installed both for every consumer, including one that only wanted the library +to compile. + +| `when` | Installed by | Reaches a consumer | +|---|---|---| +| *(omitted)* | every verb that builds | yes | +| `build` | every verb that builds | yes | +| `run` | `mcpp run`, `mcpp test` | yes | +| `dev` | only the package that declared it, as the root | **no** | + +**Omitting `when` is the pre-2026.9.4.2 behaviour exactly**, so no manifest has +to change. Narrowing is optional; it is not a question an author has to answer. + +`dev` is the only tier that does not propagate. It means *"while the package +that declared this is itself being developed"*, so a dependency's `dev` entry is +never installed for a consumer. Every other tier does reach one, which is the +point of a board package knowing its own machine: it declares the emulator once +and every consumer gets it. + +The tier is written on the entry rather than as a second table, on the same +reasoning that makes `[dependencies]` accept both `dep = "1.0"` and +`dep = { version = "1.0", features = [...] }`. A scoped entry must name +`version` even to leave it empty (`version = ""` means *present, any version*), +because `{ when = "run" }` and a misspelt `version` key would otherwise be +indistinguishable. + +### `[feature-xlings.]` — a tool a feature needs + +```toml +[features] +default = ["emulator"] +emulator = {} +hardware = {} + +[feature-xlings.hardware] +"xim:probe-rs" = "0.24.0" +``` + +The same table, gated on a feature, spelled the way `[feature-deps.]` +is. A consumer who never asks for `hardware` never downloads a probe driver. +Entries here accept `when` exactly as the unconditional ones do. + +A feature name no `[features]` table declares is reported as a schema warning: +it activates for nobody and installs nothing, and a tool whose absence is only +visible as *"the device is never reachable"* is the hardest kind to diagnose. + +### A rule package brings its own environment (2026.9.6.6+) + +The table above is what a project writes when it has an opinion. Most projects +have none, and nothing is what they write: + +```toml +[build-dependencies.mcpp] +plugins = { version = "0.3.0", features = ["rules-cuda"], host-module = true } +``` + +That one edge is the whole declaration. The rule package names the packages its +rule needs and the version it needs them from, under the feature that selects +it and the accelerator it is for: + +```toml +# in the rule package, not in your project +[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. The feature says *whether this rule is wanted*; +the selector says *which builds actually download it*. A CPU-only build of the +same project opens neither and installs nothing. + +Which package, and how old it may be, is the rule author's knowledge. Repeating +it in every project that uses the rule is a copy that goes stale silently — the +rule moves and the projects do not. + +### One package, one version (2026.9.6.6+) + +A tool address is `[:][@]`, and its **identity is the +`(namespace, name)` pair**. The version is a constraint on that package, never +part of its name, so `xim:glibc`, `xim:glibc@2.40` and `xim:glibc@>=2.38` all +name one package. One version of it is installed per build. + +Which one is decided in two steps. + +**Adjudication — the declaration nearer the artifact wins.** Your project +outranks a package it depends on, so a pin overrides a rule's requirement: + +```toml +# the project, when it does have an opinion +[target.'cfg(accelerator = "cuda")'.xlings.workspace] +"xim:cuda-nvcc" = "13.3.33" +``` + +A declaration that names no version abstains: it says the package is wanted and +nothing about which version, so it cannot outrank a floor merely by being +nearer. When two declarations disagree and both name a version, mcpp reports +which was used — an override visible only as *"two versions were declared and +one directory exists"* is a fact the reader has to reconstruct from the +filesystem. + +**Validation — the winner must satisfy every requirement that lost.** `>=`, +`^`, `~` and comma-combined forms are requirements. A pin that fails one is +refused, naming both sides: + +``` +error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins + requires >=12.9.86. + One version of a package is installed, so the two cannot both hold. + fix: pin a version satisfying >=12.9.86, or drop the pin and let the + requirement decide. +``` + +A bare version is a *choice*, not a requirement: two exact pins that differ are +adjudicated and reported, not refused. Only a stated requirement can be +violated. + +**This is a comparison, not a search.** The version is chosen by adjudication +and then checked, so mcpp never has to ask the index which versions exist and +carries no constraint solver. The cost is stated rather than hidden: a +combination a solver could satisfy — a project's `>=8.0`, a rule's `8.5.0`, and +8.3 as the newest in the index — is refused instead, and the refusal says how to +proceed. + +Ranges are resolved in both directions. `>=2026.1` installs the highest +published version satisfying it, `>=2099.1` is refused as unsatisfiable, and +`mcpp::xpkg_dir` answers with the highest **installed** version satisfying the +range — a rule that declares a floor can find what the floor brought in. + +### Which version a tool the project did not name resolves to + +| The project declares | The version comes from | +|---|---| +| `[xlings.workspace]`, no `subos` | the machine's environment, with the project's own entries laid over it | +| `[xlings.workspace]` and `subos = ""` | that environment's own workspace; the machine's does not apply | +| neither | the machine's environment | + +The middle row is not an omission. A named environment has its own installed +set, and carrying the machine's versions into it would name versions that are +not there. Naming one is how a project asks for isolation; leaving it out is +how it asks for the machine's environment with its own entries on top. + +An `xlings use` performed inside the project outranks this table until mcpp +rewrites the environment, because it is the layer merged last. + +### `deps`, superseded + +`deps = ["xim:qemu-riscv@9.2.4-1"]` is the pre-2026.9.3 spelling of the same +statement. It is still honoured and is reported once, with the +`[xlings.workspace]` line to write instead. It is not refused, because a +refusal would reach a *dependency's* manifest, which a project that pinned an +exact version of that package cannot edit. + +### `envs`, removed + +`[xlings.envs]` was materialized into `.xlings.json` and read by nothing: a +program's environment is declared by its own package, and an environment's by +that environment. The key is now an error naming both. Nothing in the index +used it. diff --git a/docs/21-commands-by-scenario.md b/docs/21-commands-by-scenario.md index 2f385eb4..285fe1f8 100644 --- a/docs/21-commands-by-scenario.md +++ b/docs/21-commands-by-scenario.md @@ -209,3 +209,164 @@ select it for the other. - `mcpp cache gc --older-than 0` is rejected with `bad --older-than value '0' (expected {s,m,h,d})`, while `mcpp clean --stale --older-than 0` accepts it. The two options share a parser but not this case. + +## `[hooks]` — Project Build Lifecycle Commands (experimental) + +> **Experimental.** A hook cannot currently decide whether a build succeeded. +> Every hook failure is reported as a **warning** and `mcpp build` keeps the +> result it earned on its own; `side_effect = true` is refused with an error +> rather than honoured. The key stays in the schema so that manifests written +> today do not have to change when the feature is promoted. Two further limits +> are permanent rather than provisional: only the root project's hooks run, and +> only `mcpp build` runs them. + +A hook is a command `mcpp build` **owns for an interval**, and the event names +the interval: + +```toml +[hooks] +build_start = "echo build started" +build_failed = "notify-send 'build failed'" +build_finished = "notify-send 'build finished'" + +# Optional; these are the defaults. +timeout_seconds = 10 +enabled = true +side_effect = false # `true` is refused while this is experimental +``` + +| Key | Type | Default | The interval it names | +|---|---|---:|---| +| `build_start` | command | — | Opens after project preparation, closes when the command exits | +| `build_finished` | command | — | Opens after a build that succeeded, closes when the command exits | +| `build_failed` | command | — | Opens after a build that failed, closes when the command exits | +| `during_build` | command | — | Opens before the build, closes after it | +| `timeout_seconds` | integer, 1–86400 | `10` | Bounds one run of a command | +| `enabled` | bool | `true` | Enables all commands in this table | +| `side_effect` | bool | `false` | Whether a hook failure makes the build fail. **Reserved** — only `false` is accepted while this is experimental | + +The first three intervals are **self-closing** — they end when the command +does. "Synchronous" is not a separate mode here; it is what a self-closing +interval looks like. `during_build` is the one interval closed by something +else, and the two keys that only make sense for one shape follow from that +rather than being exceptions. + +A command is a string, or a table when it needs options: + +| Table key | Applies to | Meaning | +|---|---|---| +| `cmd` | every event | The command. Required. | +| `timeout_seconds` | self-closing events | Overrides the table default for this event | +| `loop` | `during_build` | Restart the command if it exits before the build ends | + +`loop` on a self-closing event and `timeout_seconds` on `during_build` are both +**errors**, not ignored keys: a self-closing interval ends when its command +exits, so there is nothing to restart, and `during_build` is already bounded by +the build. A key that is accepted and does nothing reads as a broken feature. + +Commands run through the host shell (`/bin/sh` or `cmd.exe`), with the +**project root** as their working directory — not the directory `mcpp build` +was typed in, so a relative path in a hook means the same thing wherever the +build was started. A self-closing command keeps ordinary terminal +input/output. Missing event commands are skipped. + +The lifecycle is: + +```text +during_build opens +build_start + ├─ build succeeds → during_build closes → build_finished + └─ build fails → during_build closes → build_failed +``` + +`during_build` closes **before** the terminal hook, so the two commands never +overlap. + +`build_failed` and `build_finished` are mutually exclusive, and both are +reachable only after `build_start` has run. A project that cannot be *prepared* +— an invalid manifest, an unresolvable dependency, no usable toolchain — fires +nothing: it has not started building, and its hook program may be exactly what +preparation would have installed. + +A hook command that cannot start, returns non-zero, or exceeds its timeout is a +hook failure. For `during_build` there is one more: a looped command that +**fails to stay up** — five consecutive runs ending unsuccessfully within a +second — stops being restarted and is reported. (A command that finishes +quickly and *successfully* is doing exactly what `loop` was asked to repeat, +and is not a failure.) Every one of those is reported as a **warning**, and the +build keeps the result it earned on its own — while `[hooks]` is experimental +it does not get a vote. A hook's own failure does not trigger another hook. + +`side_effect = true` is what will change that, and asking for it today is an +error: + +```text +error: mcpp.toml: error: [hooks].side_effect = true is not available yet: +[hooks] is experimental and cannot decide whether a build succeeded. … +``` + +Refused rather than quietly downgraded, because both silent options are worse: +honouring it would give an experimental feature a veto over every build, and +ignoring it would leave a project believing its build is gated on a notifier +when nothing is. When the feature is promoted, `true` will mean "a hook failure +fails the build" — and a build that failed on its own will still keep its own +exit code, so `mcpp build` never reports a compile error as a notifier problem. + +Two things are worth knowing about a `during_build` command specifically: + +- **Its output is discarded**, because it writes concurrently with the build + and would otherwise land in the middle of a compiler diagnostic. Run + `mcpp build --verbose` to see it. +- **It is stopped as a process tree**, not as a process. `player & wait` makes + the player a grandchild of the command mcpp started, and stopping only the + latter would leave the audio device held after the build. mcpp puts the + command in its own process group (a job object on Windows) and stops that, + including when the build is interrupted with Ctrl-C. + +Scope, precisely: + +- Only `mcpp build` runs hooks. `mcpp run`, `mcpp test` and + `mcpp build --configure-only` build too, and deliberately do not. +- Hooks belong to the **package being built**. In a workspace fan-out that is + each member in turn — its own `[hooks]`, around its own build, in its own + root. A *virtual* workspace root (`[workspace]` with no `[package]`) builds + nothing, so a `[hooks]` table there never fires. +- A dependency's `[hooks]` is **skipped**, always. Only the root project's run. + Every manifest mcpp parses carries the section, a dependency's included, and + nothing reads it — which is what keeps `mcpp add` from meaning "run this + author's shell command on my next build". This is a property of the design, + not a default awaiting a switch. +- Declaring an active hook opts the project out of the no-op fast path, because + `build_start` is specified to run after preparation. Expect `mcpp build` on an + already-current hooked project to cost a preparation pass rather than + milliseconds. + +An unrecognised key in `[hooks]`, or inside one event's table, is a warning (an +error under `--strict`), so a manifest written for a newer mcpp still loads. An +unrecognised *value* — a missing or non-string `cmd`, a `timeout_seconds` +outside 1–86400, a key offered to the wrong interval — is a manifest error. + +> **A hook is code, and `mcpp.toml` is part of the repository.** Building a +> freshly cloned project runs whatever its `[hooks]` say, with the privileges +> of whoever invoked `mcpp build`. This is the same trust `build.mcpp` already +> asks for ([07 — build.mcpp](07-build-mcpp.md)); `[hooks]` widens its reach +> rather than introducing it. + +Hook programs can be installed as ordinary xlings dependencies. For example, +an audio notifier can keep its sound files inside its own executable rather +than adding media handling to mcpp: + +```toml +[hooks] +build_finished = "mcpp-hooks-audioplayer niulai-mm" +build_failed = "mcpp-hooks-audioplayer niulai-niulai" +side_effect = false + +[xlings.workspace] +"xim:mcpp-hooks-audioplayer" = "0.0.1" +``` + +A different sound for a successful or failed build. `side_effect = false` is +written out rather than left to the default: it is the value this manifest +wants on its own terms — a missing audio device should never fail a build — so +it will still say so once the key has more than one accepted value. diff --git a/docs/zh/03-toolchains.md b/docs/zh/03-toolchains.md index 6c9e67c2..d0d88672 100644 --- a/docs/zh/03-toolchains.md +++ b/docs/zh/03-toolchains.md @@ -603,3 +603,218 @@ inline MA& operator+=(MA& a, const MB& b); `tests/e2e/150_clang_module_operator_template.sh` 是一只跑在内置 LLVM 工具链上的金丝雀 —— 未来某次 Clang 升级修好(或再次弄坏)这一点时, 它会显式暴露出来,而不是悄悄改变包能表达的东西。 + +## C++ 运行时契约(`cxx_runtime`) + +`cxx_runtime` 声明的是**产物对运行它的机器做出的承诺**。它是**分发**属性而非 +构建属性 —— 它描述的是运行期依赖集,而兑现它的 flag 逐平台不同。 + +> **不含 C++ 的目标没有 C++ 运行时契约需要兑现。** mcpp 用 C 驱动链接它, +> 并且完全不发 C++ 运行时相关的 flag,因此一个纯 C 的共享库不会平白拿到 +> `libstdc++` / `libc++` 依赖。目标里只要有一个 C++ 翻译单元,整个目标就回到 +> C++ 驱动。这一判定由源码推导,没有对应的配置键。 + +```toml +[build] +cxx_runtime = "self-contained" # 作用于所有目标(默认值) + +# 或者按角色分别指定: +[build.cxx_runtime] +default = "self-contained" # 可执行文件 +tests = "host-coupled" # 测试二进制从不离开本机 +shared = "self-contained" # 共享库(见下 —— 默认值随目标格式而变) + +# 或者按目标三元组 —— 与 `linkage` 并列,因为它们是同一根轴: +[target.x86_64-linux-gnu] +cxx_runtime = "host-coupled" # 例如这次构建是为发行版打包 +``` + +| 取值 | 产物运行时需要 | 典型场景 | +|---|---|---| +| `self-contained`(默认) | 自身之外不需要任何 C++ 运行时 | 分发二进制 | +| `toolchain-coupled` | mcpp 装的那份工具链的 C++ 运行时 | 本地迭代 | +| `host-coupled` | 驱动默认解析到的那份(通常是系统运行时) | 发行版打包;必须与宿主共用同一份运行时的 `dlopen` 插件 | + +**默认即自包含(portable by default)**:macOS 上这会静态链入 LLVM 自带的 +libc++/libc++abi —— 系统 libc++ 会把实际可运行版本固定在构建机的 OS(老系统 +缺新符号,如 `std::print` 的支撑符号),只有静态化才能真正兑现 +`macos_deployment_target` 的 floor。Linux/MinGW 上它是 `-static-libstdc++` +(GCC)或整条链的 `-static`(MinGW);Linux 上的 clang/libc++ 工具链则显式链入 +libc++.a/libc++abi.a/libunwind.a。更低的 macOS floor(11–13)需自建 libc++ +归档(已验证可行,数据级切换,按需提供)。 + +**共享库是唯一一个默认值随目标格式变化的角色**,因为危害本身随格式变化。 +`.so`/`.dylib`/`.dll` 不是一个小号可执行文件 —— 它被加载**进**一个已经有 +C++ 运行时的进程。 + +| 目标格式 | `kind = "shared"` 的默认契约 | 原因 | +|---|---|---| +| ELF(Linux 等) | `toolchain-coupled` | ELF 只有一个全局符号命名空间,先加载的定义胜出。静态内嵌了 libstdc++ 的 `.so` 会把它**导出**,链接该库的可执行文件于是把自己的 `std::` 引用绑到那里 —— 它自己的 `self-contained` 契约静默变成空操作,它的 C++ 运行时变成"碰巧加载的那一份该库"。 | +| Mach-O | `self-contained` | 那里的机制本来就是 `-load_hidden`(hidden 可见性),dyld 不会归一这些符号;而且 macOS 上根本没有 toolchain-coupled 这一档(见下文注)。 | +| PE(Windows) | `self-contained` | PE 没有全局符号命名空间 —— 导入按 DLL 逐个按名解析,一个 DLL 的私有运行时不可能被别人捡走。 | + +在 ELF 上显式写 `shared = "self-contained"` 是支持的,而且就是字面意思:库会内嵌 +运行时。此时 mcpp 会额外发 `-Wl,--exclude-libs`(针对标准库归档),让内嵌的那份 +留在库的动态符号表之外,链接它的任何东西都捡不走。本工程代码产生的模板实例化 +(`std::string` 之类的 weak/COMDAT 符号)仍然会导出 —— 那是 C++ ABI 的预期行为, +不是这里要防的泄漏。 + +工程级的 `cxx_runtime = "…"`(或 `static_stdlib = false`)同样作用于共享库: +有人写下了整个工程的承诺。只有在**没人写**的时候,随格式变化的默认值才生效。 + +`static_stdlib` 是旧拼写,仍然有效:`true` 等价于 `self-contained`,`false` +等价于 `host-coupled`。显式写了 `cxx_runtime` 时以后者为准。 + +**兑现不了的契约会被报出来,绝不静默降级。** 若工具链不带 `libc++.a`,或某个 +契约在该平台上没有对应机制,构建会打印实际退到了哪一档,而不是悄悄交付一个与 +manifest 所述不同的产物。 + +### 在 MSVC 运行时上 + +这里的机制就是 CRT 模型,而它是**整个工程级**的开关:cl 会把 `_MSVC_MT` / +`_MSVC_MD` 烘进工程唯一的那份 `std` 模块,所以与工程不一致的按角色契约无法兑现, +会被报出来而不是被忽略。 + +| 取值 | 在 MSVC 上是什么 | +|---|---| +| `self-contained` | `/MT`,静态 CRT。`linkage = "static"` 从 libc 那根轴选中的是同一件事。 | +| `host-coupled`(`/MD` 下的默认) | 由目标机器提供 `vcruntime140.dll` / `msvcp140.dll` —— 即那台机器装了 Visual Studio 或 redistributable。 | +| `toolchain-coupled` | toolset **自带**的那份 DLL 跟着产物走。 | + +`toolchain-coupled` 值得说清楚,因为直觉上的理解是错的。`ucrtbase.dll` **是** +Windows 组件(Win10 起),mcpp 从不分发它;而 `vcruntime140.dll` / +`msvcp140.dll` **不是**:每个 MSVC toolset 都在 +`VC\Redist\MSVC\\\` 下带着它们,和 gcc payload 带着 +`libstdc++.so` 是同一件事。在这个契约下 mcpp 会把它们放到产物旁边 —— 这正是让 +默认的 `/MD` 产物能在"只装了 pinned toolset、根本没有 Visual Studio"的机器上跑 +起来的原因。 + +调试版 CRT(`debug_nonredist\` 下的 `vcruntime140d.dll` 等)永远不会被放进去: +它不可再分发。 + +> **从 2026.8.15 或更早版本升上来?** 这条键在 MSVC ABI 上曾经是**空操作** —— +> 它会报 `not implemented for the MSVC runtime yet`,写什么都退回 `/MD`。 +> 自 2026.8.16 起它真的生效,于是一份从那个年代带着 +> `cxx_runtime = "self-contained"` 的 manifest **会在升级时换掉 CRT 模型**: +> 从 `/MD` 变成 `/MT`。它不是同一个模型的更严格版本,而且因为这个值一直是合法的, +> 切换是**静默**的。若工程是在这条键尚未生效时写下它的,应重新确认所需的取值。 + +把它和 `/MT` 一起用是**矛盾**而不是缺功能 —— 静态 CRT 根本没有 DLL 可以耦合 —— +所以会被报出来并落到 `self-contained`。另一半由 `mcpp pack` 兜底:什么都不打包的 +模式(`--mode system`、`--mode static`)兑现不了 `toolchain-coupled`,会直接拒绝。 + +**边界。** 该契约只管 C++ 运行时。静态 **libc** 是另一根轴(`linkage = "static"` +/ `--static`,如 musl 目标),部署下限是第三根轴(`macos_deployment_target`)。 +另外,`host-coupled` 只承诺 mcpp 不做任何"把 C++ 运行时打进产物"的动作,它不会 +去掉链接因其它原因已经携带的工具链 rpath —— 所以在 ELF 上这类产物仍可能优先 +找到工具链的库。 + +> **macOS + `self-contained` 与静态初始化次序。** Mach-O 没有按优先级排序的 +> 初始化段,而 libc++ 的 `` 也不像 libstdc++ / MSVC STL 那样自带 +> `ios_base::Init` 守卫 —— 于是从 `libc++.a` 里拉出来的流初始化器本来会排在 +> 程序自己的全局构造函数**之后**:一个在构造函数里碰 `std::cout` 的全局对象会 +> 读到尚未构造的流,进程启动即崩。mcpp 会把一个极小的生成对象排在链接最前面 +> 把流顶上去,调用方代码无需改动。详见 mcpp-community/mcpp#336。 + +`defines` 接受**裸**宏名(不带 `-D`),把每个条目脱糖为 `-D`,同时作用于 C 和 +C++ 编译通道。它覆盖包内每个 TU(含模块接口单元),因此也会进入**编译器自己的** +P1689 模块扫描。 + +> **但它不会让被宏保护的 `import` 变得可用。** mcpp 在编译器看到文件之前先跑 +> 自己的词法预扫描,而那个扫描器对**任何** `#if` / `#ifdef` 块内的 `import` +> 一律拒绝,不求值条件: +> +> ``` +> error: import statement inside conditional preprocessor block (forbidden in M1) +> ``` +> +> 所以即使 `FOO` 写在 `defines` 里,`#ifdef FOO` / `import bar;` 仍然会失败。 +> 替代写法是把条件放在全局模块片段的 `#include` 上。见 +> mcpp-community/mcpp#421。 + +汇编单元同样能拿到。它是普通的构建 +输入,所以 `[target.'cfg(...)'.build]` 也能承载它: + +```toml +[build] +defines = ["APP_NAME=\"demo\""] + +[target.'cfg(windows)'.build] +defines = ["USE_WIN32", "WINVER=0x0A00"] +``` + +选择合适的轴: + +| 想让宏作用于… | 用 | +|---|---| +| 本包的每个 TU | `[build].defines`(本节) | +| 仅某个二进制自己的入口源 | `[targets.].defines` | +| 指定的一批文件 | `[build].flags` 配 `glob` + `defines` | +| 本包每个 TU **以及**消费者的 TU | `[features.].defines`(接口贡献) | + +`[build].defines` 是包私有的:不会传播给消费者。 + +`[build]` 下不支持的键会作为警告报出(`--strict` 下为错误),而不是被静默忽略。 + +C++ 标准不要通过 `build.cxxflags = ["-std=..."]` 配置。请使用: + +```toml +[package] +standard = "c++26" +``` + +mcpp 会把同一个标准用于普通 C++ 编译、模块扫描、`compile_commands.json` 和 `import std` 的标准库 BMI 构建。 + +**glob 排除**(`!` 前缀,mcpp 0.0.4+): + +```toml +[build] +sources = [ + "src/**/*.cpp", + "!src/**/*_test.cpp", # 排除测试文件 + "!src/**/*_fuzzer.cpp", # 排除 fuzzer +] +``` + +**per-glob 旗标**(mcpp 0.0.95+):`[build] flags` 是**有序**的内联表数组,把额外 +编译旗标只附加到 glob 命中的源文件——SIMD 多档 dispatch TU 与三方代码告警隔离的 +正解: + +```toml +[build] +flags = [ + { glob = "third_party/**", cflags = ["-w"], cxxflags = ["-w"] }, + { glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"], defines = ["HAVE_AVX2"] }, + { glob = "src/x86/**/*.asm", asmflags = ["-DPREFIX"] }, +] +``` + +每条目键:`glob`(相对包根,必填)+ `cflags` / `cxxflags` / `asmflags` / +`defines`(没有 `ldflags`——链接没有 per-TU 作用域)。声明顺序即应用顺序:靠后 +条目的旗标排在命令行更后,配合 GNU "后旗标胜",窄 glob 放在宽 glob 之后即可覆盖。 +所有命中条目都生效;这些是私有构建旗标,不会传播给消费者。glob 零命中会打印 +warning(打错的 glob 不允许静默无效)。 + +**生成文件**(mcpp 0.0.95+):`[generated_files]` 把相对路径映射到文件内容(支持 +TOML 多行字符串)。条目在源 glob 展开之前写入工程树——与 index 描述符合成模块 +包装文件是同一机制——内容进指纹,改内容即重建: + +```toml +[generated_files] +"src/gen/wrap.cppm" = """ +module; +#include +export module wrap; +""" +``` + +路径必须留在工程根之内(`..` / 绝对路径是解析错误)。 + +**汇编源**(mcpp 0.0.95+):`.S`/`.s`(GAS——由 C 驱动器预处理,覆盖 ARM 与 +AT&T 语法 x86)和 `.asm`(NASM——Intel 语法 x86)是一等源文件:默认 glob 收录、 +进指纹、增量并行构建、像任何对象一样链接。NASM 的输出格式由目标三元组推导 +(`elf64`/`win64`/`macho64`/...——交叉构建零特判);`nasm` 仅在存在 `.asm` 单元时 +惰性解析:先 `PATH`,再 mcpp 沙箱,再 `xlings install nasm`;找不到 ≥2.16 的 +nasm 则**硬失败**(汇编绝不静默跳过)。限制:`.asm` 仅限 x86 目标(其他目标硬 +报错——用条件 sources 门控)、MSVC 工具链不支持 `.S`、`.asm` 即 NASM 语法 +(MASM 源请用 `!` 排除)。 diff --git a/docs/zh/05-mcpp-toml.md b/docs/zh/05-mcpp-toml.md index b5938edf..0c3f05a1 100644 --- a/docs/zh/05-mcpp-toml.md +++ b/docs/zh/05-mcpp-toml.md @@ -496,221 +496,10 @@ MCPP_BUILD_PROGRAM_TIMEOUT=<秒> 本次调用(最高) 只有构建**程序**受限,**编译**不受限。原因见 [07-build-mcpp.md](07-build-mcpp.md)。 - ### C++ 运行时契约(`cxx_runtime`) -`cxx_runtime` 声明的是**产物对运行它的机器做出的承诺**。它是**分发**属性而非 -构建属性 —— 它描述的是运行期依赖集,而兑现它的 flag 逐平台不同。 - -> **不含 C++ 的目标没有 C++ 运行时契约需要兑现。** mcpp 用 C 驱动链接它, -> 并且完全不发 C++ 运行时相关的 flag,因此一个纯 C 的共享库不会平白拿到 -> `libstdc++` / `libc++` 依赖。目标里只要有一个 C++ 翻译单元,整个目标就回到 -> C++ 驱动。这一判定由源码推导,没有对应的配置键。 - -```toml -[build] -cxx_runtime = "self-contained" # 作用于所有目标(默认值) - -# 或者按角色分别指定: -[build.cxx_runtime] -default = "self-contained" # 可执行文件 -tests = "host-coupled" # 测试二进制从不离开本机 -shared = "self-contained" # 共享库(见下 —— 默认值随目标格式而变) - -# 或者按目标三元组 —— 与 `linkage` 并列,因为它们是同一根轴: -[target.x86_64-linux-gnu] -cxx_runtime = "host-coupled" # 例如这次构建是为发行版打包 -``` - -| 取值 | 产物运行时需要 | 典型场景 | -|---|---|---| -| `self-contained`(默认) | 自身之外不需要任何 C++ 运行时 | 分发二进制 | -| `toolchain-coupled` | mcpp 装的那份工具链的 C++ 运行时 | 本地迭代 | -| `host-coupled` | 驱动默认解析到的那份(通常是系统运行时) | 发行版打包;必须与宿主共用同一份运行时的 `dlopen` 插件 | - -**默认即自包含(portable by default)**:macOS 上这会静态链入 LLVM 自带的 -libc++/libc++abi —— 系统 libc++ 会把实际可运行版本固定在构建机的 OS(老系统 -缺新符号,如 `std::print` 的支撑符号),只有静态化才能真正兑现 -`macos_deployment_target` 的 floor。Linux/MinGW 上它是 `-static-libstdc++` -(GCC)或整条链的 `-static`(MinGW);Linux 上的 clang/libc++ 工具链则显式链入 -libc++.a/libc++abi.a/libunwind.a。更低的 macOS floor(11–13)需自建 libc++ -归档(已验证可行,数据级切换,按需提供)。 - -**共享库是唯一一个默认值随目标格式变化的角色**,因为危害本身随格式变化。 -`.so`/`.dylib`/`.dll` 不是一个小号可执行文件 —— 它被加载**进**一个已经有 -C++ 运行时的进程。 - -| 目标格式 | `kind = "shared"` 的默认契约 | 原因 | -|---|---|---| -| ELF(Linux 等) | `toolchain-coupled` | ELF 只有一个全局符号命名空间,先加载的定义胜出。静态内嵌了 libstdc++ 的 `.so` 会把它**导出**,链接该库的可执行文件于是把自己的 `std::` 引用绑到那里 —— 它自己的 `self-contained` 契约静默变成空操作,它的 C++ 运行时变成"碰巧加载的那一份该库"。 | -| Mach-O | `self-contained` | 那里的机制本来就是 `-load_hidden`(hidden 可见性),dyld 不会归一这些符号;而且 macOS 上根本没有 toolchain-coupled 这一档(见下文注)。 | -| PE(Windows) | `self-contained` | PE 没有全局符号命名空间 —— 导入按 DLL 逐个按名解析,一个 DLL 的私有运行时不可能被别人捡走。 | - -在 ELF 上显式写 `shared = "self-contained"` 是支持的,而且就是字面意思:库会内嵌 -运行时。此时 mcpp 会额外发 `-Wl,--exclude-libs`(针对标准库归档),让内嵌的那份 -留在库的动态符号表之外,链接它的任何东西都捡不走。本工程代码产生的模板实例化 -(`std::string` 之类的 weak/COMDAT 符号)仍然会导出 —— 那是 C++ ABI 的预期行为, -不是这里要防的泄漏。 - -工程级的 `cxx_runtime = "…"`(或 `static_stdlib = false`)同样作用于共享库: -有人写下了整个工程的承诺。只有在**没人写**的时候,随格式变化的默认值才生效。 - -`static_stdlib` 是旧拼写,仍然有效:`true` 等价于 `self-contained`,`false` -等价于 `host-coupled`。显式写了 `cxx_runtime` 时以后者为准。 +已移入 [03 —— 工具链管理](03-toolchains.md)。 -**兑现不了的契约会被报出来,绝不静默降级。** 若工具链不带 `libc++.a`,或某个 -契约在该平台上没有对应机制,构建会打印实际退到了哪一档,而不是悄悄交付一个与 -manifest 所述不同的产物。 - -#### 在 MSVC 运行时上 - -这里的机制就是 CRT 模型,而它是**整个工程级**的开关:cl 会把 `_MSVC_MT` / -`_MSVC_MD` 烘进工程唯一的那份 `std` 模块,所以与工程不一致的按角色契约无法兑现, -会被报出来而不是被忽略。 - -| 取值 | 在 MSVC 上是什么 | -|---|---| -| `self-contained` | `/MT`,静态 CRT。`linkage = "static"` 从 libc 那根轴选中的是同一件事。 | -| `host-coupled`(`/MD` 下的默认) | 由目标机器提供 `vcruntime140.dll` / `msvcp140.dll` —— 即那台机器装了 Visual Studio 或 redistributable。 | -| `toolchain-coupled` | toolset **自带**的那份 DLL 跟着产物走。 | - -`toolchain-coupled` 值得说清楚,因为直觉上的理解是错的。`ucrtbase.dll` **是** -Windows 组件(Win10 起),mcpp 从不分发它;而 `vcruntime140.dll` / -`msvcp140.dll` **不是**:每个 MSVC toolset 都在 -`VC\Redist\MSVC\\\` 下带着它们,和 gcc payload 带着 -`libstdc++.so` 是同一件事。在这个契约下 mcpp 会把它们放到产物旁边 —— 这正是让 -默认的 `/MD` 产物能在"只装了 pinned toolset、根本没有 Visual Studio"的机器上跑 -起来的原因。 - -调试版 CRT(`debug_nonredist\` 下的 `vcruntime140d.dll` 等)永远不会被放进去: -它不可再分发。 - -> **从 2026.8.15 或更早版本升上来?** 这条键在 MSVC ABI 上曾经是**空操作** —— -> 它会报 `not implemented for the MSVC runtime yet`,写什么都退回 `/MD`。 -> 自 2026.8.16 起它真的生效,于是一份从那个年代带着 -> `cxx_runtime = "self-contained"` 的 manifest **会在升级时换掉 CRT 模型**: -> 从 `/MD` 变成 `/MT`。它不是同一个模型的更严格版本,而且因为这个值一直是合法的, -> 切换是**静默**的。若工程是在这条键尚未生效时写下它的,应重新确认所需的取值。 - -把它和 `/MT` 一起用是**矛盾**而不是缺功能 —— 静态 CRT 根本没有 DLL 可以耦合 —— -所以会被报出来并落到 `self-contained`。另一半由 `mcpp pack` 兜底:什么都不打包的 -模式(`--mode system`、`--mode static`)兑现不了 `toolchain-coupled`,会直接拒绝。 - -**边界。** 该契约只管 C++ 运行时。静态 **libc** 是另一根轴(`linkage = "static"` -/ `--static`,如 musl 目标),部署下限是第三根轴(`macos_deployment_target`)。 -另外,`host-coupled` 只承诺 mcpp 不做任何"把 C++ 运行时打进产物"的动作,它不会 -去掉链接因其它原因已经携带的工具链 rpath —— 所以在 ELF 上这类产物仍可能优先 -找到工具链的库。 - -> **macOS + `self-contained` 与静态初始化次序。** Mach-O 没有按优先级排序的 -> 初始化段,而 libc++ 的 `` 也不像 libstdc++ / MSVC STL 那样自带 -> `ios_base::Init` 守卫 —— 于是从 `libc++.a` 里拉出来的流初始化器本来会排在 -> 程序自己的全局构造函数**之后**:一个在构造函数里碰 `std::cout` 的全局对象会 -> 读到尚未构造的流,进程启动即崩。mcpp 会把一个极小的生成对象排在链接最前面 -> 把流顶上去,调用方代码无需改动。详见 mcpp-community/mcpp#336。 - -`defines` 接受**裸**宏名(不带 `-D`),把每个条目脱糖为 `-D`,同时作用于 C 和 -C++ 编译通道。它覆盖包内每个 TU(含模块接口单元),因此也会进入**编译器自己的** -P1689 模块扫描。 - -> **但它不会让被宏保护的 `import` 变得可用。** mcpp 在编译器看到文件之前先跑 -> 自己的词法预扫描,而那个扫描器对**任何** `#if` / `#ifdef` 块内的 `import` -> 一律拒绝,不求值条件: -> -> ``` -> error: import statement inside conditional preprocessor block (forbidden in M1) -> ``` -> -> 所以即使 `FOO` 写在 `defines` 里,`#ifdef FOO` / `import bar;` 仍然会失败。 -> 替代写法是把条件放在全局模块片段的 `#include` 上。见 -> mcpp-community/mcpp#421。 - -汇编单元同样能拿到。它是普通的构建 -输入,所以 `[target.'cfg(...)'.build]` 也能承载它: - -```toml -[build] -defines = ["APP_NAME=\"demo\""] - -[target.'cfg(windows)'.build] -defines = ["USE_WIN32", "WINVER=0x0A00"] -``` - -选择合适的轴: - -| 想让宏作用于… | 用 | -|---|---| -| 本包的每个 TU | `[build].defines`(本节) | -| 仅某个二进制自己的入口源 | `[targets.].defines` | -| 指定的一批文件 | `[build].flags` 配 `glob` + `defines` | -| 本包每个 TU **以及**消费者的 TU | `[features.].defines`(接口贡献) | - -`[build].defines` 是包私有的:不会传播给消费者。 - -`[build]` 下不支持的键会作为警告报出(`--strict` 下为错误),而不是被静默忽略。 - -C++ 标准不要通过 `build.cxxflags = ["-std=..."]` 配置。请使用: - -```toml -[package] -standard = "c++26" -``` - -mcpp 会把同一个标准用于普通 C++ 编译、模块扫描、`compile_commands.json` 和 `import std` 的标准库 BMI 构建。 - -**glob 排除**(`!` 前缀,mcpp 0.0.4+): - -```toml -[build] -sources = [ - "src/**/*.cpp", - "!src/**/*_test.cpp", # 排除测试文件 - "!src/**/*_fuzzer.cpp", # 排除 fuzzer -] -``` - -**per-glob 旗标**(mcpp 0.0.95+):`[build] flags` 是**有序**的内联表数组,把额外 -编译旗标只附加到 glob 命中的源文件——SIMD 多档 dispatch TU 与三方代码告警隔离的 -正解: - -```toml -[build] -flags = [ - { glob = "third_party/**", cflags = ["-w"], cxxflags = ["-w"] }, - { glob = "src/simd/**/*.avx2.cpp", cxxflags = ["-mavx2"], defines = ["HAVE_AVX2"] }, - { glob = "src/x86/**/*.asm", asmflags = ["-DPREFIX"] }, -] -``` - -每条目键:`glob`(相对包根,必填)+ `cflags` / `cxxflags` / `asmflags` / -`defines`(没有 `ldflags`——链接没有 per-TU 作用域)。声明顺序即应用顺序:靠后 -条目的旗标排在命令行更后,配合 GNU "后旗标胜",窄 glob 放在宽 glob 之后即可覆盖。 -所有命中条目都生效;这些是私有构建旗标,不会传播给消费者。glob 零命中会打印 -warning(打错的 glob 不允许静默无效)。 - -**生成文件**(mcpp 0.0.95+):`[generated_files]` 把相对路径映射到文件内容(支持 -TOML 多行字符串)。条目在源 glob 展开之前写入工程树——与 index 描述符合成模块 -包装文件是同一机制——内容进指纹,改内容即重建: - -```toml -[generated_files] -"src/gen/wrap.cppm" = """ -module; -#include -export module wrap; -""" -``` - -路径必须留在工程根之内(`..` / 绝对路径是解析错误)。 - -**汇编源**(mcpp 0.0.95+):`.S`/`.s`(GAS——由 C 驱动器预处理,覆盖 ARM 与 -AT&T 语法 x86)和 `.asm`(NASM——Intel 语法 x86)是一等源文件:默认 glob 收录、 -进指纹、增量并行构建、像任何对象一样链接。NASM 的输出格式由目标三元组推导 -(`elf64`/`win64`/`macho64`/...——交叉构建零特判);`nasm` 仅在存在 `.asm` 单元时 -惰性解析:先 `PATH`,再 mcpp 沙箱,再 `xlings install nasm`;找不到 ≥2.16 的 -nasm 则**硬失败**(汇编绝不静默跳过)。限制:`.asm` 仅限 x86 目标(其他目标硬 -报错——用条件 sources 门控)、MSVC 工具链不支持 `.S`、`.asm` 即 NASM 语法 -(MASM 源请用 `!` 排除)。 ### 宿主代码页之外的文件名 @@ -1003,119 +792,10 @@ default = "gcc@16.1.0" toolchain = "gcc@16.1.0" linkage = "static" ``` - ### 2.7.1 `[target.*]` —— 平台条件依赖与 flag -用 `[target.]` 表把依赖与构建 flag 限定到某个平台。选择器 `` 有三种形式: - -| 选择器 | 含义 | 示例 | -|---|---|---| -| **裸 OS 别名** | 单个 OS / 族 —— 简洁且常用的形式 | `[target.windows]`、`[target.unix]` | -| **`cfg(...)` 谓词** | 复合条件(arch / env / 组合子) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 §2.7.3) | `[target.x86_64-linux-musl]` | +已移入 [14 —— 目标侧](14-target-side.md)。 -一个选择器可以承载平台条件的**依赖**与**构建 flag**: - -```toml -# 简洁的裸别名形式 —— 仅在 Windows 上拉取并链接 OpenBLAS。 -[target.windows.dependencies.compat] -openblas = "0.3.33" -[target.windows.build] -ldflags = ["-Llib", "-llibopenblas"] - -# cfg(...) 用于复合谓词(文法:all/any/not 作用于 os/arch/family/env, -# 以及裸别名 windows/unix/linux/macos)。 -[target.'cfg(all(linux, not(arch = "aarch64")))'.build] -cxxflags = ["-march=x86-64-v2"] -``` - -`[target.windows]` 与 `[target.'cfg(windows)']` 完全等价 —— 裸别名 -`windows` / `linux` / `macos` / `unix` 都不是合法的目标三元组,因此不存在歧义。 -单个 OS/族用裸形式,arch/env 条件与组合子用 `cfg(...)`。 - -- **可用键**:`dependencies` / `dev-dependencies` / `build-dependencies` / - `feature-deps.`(mcpp 2026.8.6.2+ —— 见 §2.14;feature 本身无条件注册, - 只有它的依赖集合受限定),以及带 `cflags` / `cxxflags` / `ldflags` / `sources` - 的 `build`(mcpp 0.0.95+ —— 条件源码 glob,例如把 `src/x86/**/*.asm` 收在 - `cfg(arch = "x86_64")` 之后;`!` 排除 glob 在此同样有效),再加 `flags` 与 - `include_dirs` / `include_dirs_after`(mcpp 0.0.102+), - 以及 `private_include_dirs` 与 `std-module-flags`(mcpp 2026.9.1.1+)。 -- **`build` 接受的恰好是*可叠加的构建输入*集合** —— 那些以追加方式合并、 - 并在谓词求值之后被消费的东西,也就是 `BuildInputs` 的成员表。`linkage`、`target` - 与档案开关刻意不在其中:它们是**目标选择的输入**(用一个针对 `target` 求值的谓词 - 去条件化 `target` 是循环的),或者需要覆盖而非追加的语义。 - 集合之外的键会被报出并忽略;消息里列出的正是它比对用的那份集合,因此不会与检查漂移。 -- **按解析后的目标求值** —— 交叉构建取 `--target` 三元组,否则取宿主。因此原生 - Linux 构建**根本不会下载** `[target.windows]` 依赖。 -- **谓词的键**:`os`、`arch`、`family`、`env` —— 三元组的坐标 —— 以及自 mcpp - 2026.9.1.1 起的五个目标侧层名 `compiler`、`compiler-runtime`、`kernel-abi`、 - `c-abi`、`c++-abi`(见[14 —— 目标侧](14-target-side.md))。`accelerator` 同样 - 是这里的键,由本次构建自己的 `accel`(`--accel` 或 `[build] accel` 里的后端名) - 回答,因此它是对一个集合的成员判定;`accelerator = "none"` 则是一段用来说 - 「本次构建没有命名任何后端」的写法,而不必枚举它不是的那些后端。裸词 - `linux` / `macos` / `windows` / `unix` 是对应 `os` / `family` 判定的糖。 - 集合之外的键会被报成一条 schema 警告,且该段不生效 —— 它过去静默地求值为假, - 而那与「这一段本就不该匹配」读数完全相同。 -- **被解析的层的谓词不能选择依赖。** 层是**从**依赖图解析出来的,因此由它选出的 - 依赖会决定它正在询问的那个答案。`[target.'cfg(c-abi = "musl")'.dependencies]` - 会被报出并忽略;同一谓词下的 `build` 输入照常生效。`accelerator` 不在此列 - (mcpp 2026.9.6.5):它是构建的输入而不是图给出的答案,所以 - `[target.'cfg(accelerator = "cuda")'.dependencies]` 生效。 -- **优先级**:精确三元组表胜过 `cfg`/别名表;多个命中的谓词表,其 flag 按序拼接。 - 条件项追加在无条件 `[build]` 项**之后**,因此在 GNU「最后一个 flag 生效」的 - 规则下,条件规则会覆盖更宽的无条件规则。这正是让按 OS **移除**成为可表达的原因: - - ```toml - [build] - flags = [{ glob = "third_party/zlib/**", defines = ["HAVE_UNISTD_H=1"] }] - - # clang-MSVC 没有 :撤销基础 define,加上 windows 的那个。 - [target.'cfg(windows)'.build] - flags = [{ glob = "third_party/zlib/**", - defines = ["NO_FSEEKO"], cflags = ["-UHAVE_UNISTD_H"] }] - ``` - -- **未命中当前目标的条件 `flags` 条目根本不存在**,因此它不会产生 - 「glob 未匹配到任何源文件」的警告。于是一份 manifest 可以同时携带三个 OS 的 - flag 表,而不会在另外两个上制造噪声 —— 与未启用 feature 的条目根本不存在是 - 同一个道理。**无条件**表里的零命中 glob 仍然告警,因为那里它是真实缺陷。 -- **`toolchain` / `linkage` / `sysroot` 仅限精确三元组** —— 它们描述某一个具体的交叉目标, - 因此写在 `[target.]` 下(见上),而不是裸别名或 `cfg(...)` 下。 - -#### `sysroot` —— 目标的 C 库 - -`sysroot`(mcpp 2026.8.20.2+)覆盖目标表为某个三元组绑定的 C 库,与 `toolchain` -覆盖编译器 pin 同轴:一个指名目标所解析的编译器,另一个指名它的 C 库,两者在工程 -有理由与之分歧之前都只由引擎决定。 - -```toml -[target.riscv64-none-elf] -sysroot = "xim:newlib-riscv@4.4" # a different C library -``` - -```toml -[target.riscv64-none-elf] -sysroot = "" # no C library at all -``` - -**键缺席与键为空是两个不同的答案。** 缺席继承目标表的 C 库。存在且为空是 -**零 libc 档**:不解析任何 C 库,不加入头文件与库目录,链接行上只有工程与其依赖 -提供的内容,`#include ` 不再解析。内核与 bootloader 要的正是这一档,而把 -两种情形合并会让这类工程静默地把目标的 C 库拿回去。 - -取值是 xpkg 引用或空字符串;裸名在解析清单时即被拒绝,因为接受它会导致什么都不安装, -然后在很晚的时候以「缺少 libc」失败。 - -构建程序可以询问供给 sysroot 的是哪个 C 库**载荷**:`mcpp::target_libc()` 返回该包的 -名字,`mcpp::target_libc_profile()` 返回目标 ISA 档位对应的子目录。零 libc 档上两者均 -为空。 - -**这与「目标侧解析出的 C 库是哪一个」不是同一个问题。** `target_libc()` 命名的是 -mcpp 装上的那个载荷,而这个值是目标侧解析的一项**输入** —— 依赖图里的包可以改为供给 -C 库,那时解析出的 `c-abi` 就不是这里返回的东西。要按已解析的层分支,请用层谓词: -`[target.'cfg(c-abi = "musl")'.build]`(见[14 —— 目标侧](14-target-side.md))。 -这一段在 2026.9.1.1 之前写的是「解析到的是哪份 C 库」,那是两者里错的那一个。 -参见[13 —— 裸机与 freestanding 目标](13-baremetal.md)。 ### 2.7.2 裸机(`os = none`)—— freestanding target @@ -1463,509 +1143,14 @@ accelerators = ["cuda", "rocm"] 与产物的 `accel` 字段刻意不同。声明由人手写、可以是期望值;`accel` 是从产生该二进制的 那次构建测量出来的,并且是消费者被拒绝时所依据的东西。见 [20 — 异构硬件构建](20-heterogeneous-builds.md)。 +### 2.13 `[xlings]` —— 工程的环境 -### 2.13 `[xlings]` — 工程的环境 - -```toml -[xlings.workspace] # 这个工程的环境里有什么 -cmake = "3.28" -"xim:picolibc-riscv" = "1.8.12" # 带命名空间的包 —— 必须带引号 -code = "" # 存在即可,版本不限 -llvm = { macosx = "20", default = "22" } -``` - -```toml -[xlings] -subos = "dev" # 指名的隔离环境 -``` - -`[xlings]` 是 mcpp 对 **xlings local project 机制**的书写面:让一个目录拥有自己 -环境的那份项目 `.xlings.json`。子段名与含义都是那份文件的,mcpp 原样物化进 -`/.mcpp/.xlings.json`,没有翻译层。 - -**`[xlings.workspace]` 是唯一的表。** 一条条目写出工程用哪个包、用哪个版本。 -mcpp 既供给它——机器上没有就装,有就映射——也把它物化成解析用的钉,于是工程写下 -的版本就是它的工具解析到的版本。 - -#### 条目的形式 - -| 形式 | 含义 | -|---|---| -| `cmake = "3.28"` | 该版本 | -| `llvm = "22"` | 已装的最高 `22.*`;版本前缀会被解析 | -| `code = ""` | 存在即可,版本不限 | -| `"xim:picolibc-riscv" = "1.8.12"` | 来自 `xim` 索引的包 | -| `llvm = { macosx = "20", default = "22" }` | 按宿主平台 | - -**带命名空间的包写成 `"<命名空间>:<名字>" = "<版本>"`,引号必需** —— TOML 的裸键 -不能含冒号。这是**推荐形态,也是所有官方包使用的形态**:一条条目先点名一个包, -再说用它的哪个版本,所以命名空间属于名字。 - -命名空间写在版本上(`picolibc-riscv = "xim:1.8.12"`)同样接受,因为物化出来的 -`.xlings.json` 里正是那种形态 —— 那里的键是 xvm target,scope 限定的是版本。 -两套词汇,同一条条目。两半都写且不一致是错误;同一个包用两种拼法出现两次也是错误。 - -平台键是 xlings 自己的 —— `linux`、`macosx`、`windows`,外加 `default`。`macos` -与 `macosx` 是同一个平台的两套词汇(mcpp 的三元组说前者,描述符与 xlings 的项目 -文件说后者),**凡是点名平台的地方两者都接受**。表里既没有本机这一项也没有 -`default`,就表示在这里什么都不声明。 - -#### 两条解析轴 —— 宿主与目标(mcpp 2026.9.6.4+) - -一条工具条目回答的是两个不同问题中的一个,写在哪张表里决定了是哪一个: - -| 写法 | 轴 | 按什么解析 | -|---|---|---| -| `[xlings.workspace]`,平台键写在值里 | 宿主 | 跑这次构建的机器 | -| `[target..xlings.workspace]` | 目标 | 解析后的目标(`--target`,否则是宿主) | - -两种写法都是正确的,谁也不取代谁。在构建机上执行的工具属于宿主轴;产物编译或链接 -时对着的载荷属于目标轴。 - -```toml -[xlings.workspace] -"xim:dpcpp" = "7.1.0" # 一个编译器,它在本机上跑 - -[target.'cfg(os = "linux")'.xlings.workspace] -"xim:glibc" = "" # 设备单元编译时对着的东西 -"xim:linux-headers" = "" -``` - -非交叉构建时两条轴指向同一个平台,所以把目标事实写在宿主轴上的工程是碰巧正确的, -而且照常工作。它在第一次被交叉构建时不再正确。**凡是产物编译或链接时对着的东西, -推荐写在目标轴上。** - -`[target..feature-xlings.]` 把条件与门组合起来,与 -`[target..feature-deps.]` 同形:selector 说的是哪些目标, -feature 说的是要不要。 - -```toml -[target.'cfg(os = "linux")'.feature-xlings.backend-vulkan] -"xim:shaderc" = "2026.3" -``` - -**这里的 selector 禁止命名被解析的层。** `c-abi`、`c++-abi`、`compiler`、 -`compiler-runtime`、`kernel-abi` 由依赖解析回答,而依赖解析发生在工具安装之后、 -构建程序运行之后。按这些层条件化的工具会被声明却永远装不上——构建照常成功,工具 -就是不在——所以这样的 manifest 会被拒绝,并把工具与谓词都点出来。改成按目标条件化, -或者用 feature 做门:`[feature-xlings.]` 在任何东西被供给之前就已知。 - -**`accelerator` 是例外,它被接受**(mcpp 2026.9.6.5)。它不由任何东西解析而来: -它是 `--accel`,或 `[build] accel`,在查找第一个包之前就已读入。以它为谓词的载荷 -与三元组谓词在同一趟合并,并像其它载荷一样被安装。 - -```toml -[target.'cfg(accelerator = "cuda")'.xlings.workspace] -"xim:cuda-nvcc" = "12.9.86" -"xim:cuda-cudart" = "12.9.79" -``` - -带设备孤岛的工程应当用这种写法。没有它,厂商工具包只能无条件声明或者干脆不声明, -于是不带加速器的 `mcpp build`——最便宜的那次构建,也是 CI 通常跑的那次——会为一个 -它根本没在编译的设备下载数 GB。 - -依赖同理:`[target.'cfg(accelerator = "cuda")'.dependencies]` 生效,而以被解析的层 -为条件的依赖不生效,因为后者会决定它正在询问的那个答案。加速器这条路径上没有任何 -循环。 - -条件只写在 selector 一处。selector 之下的值如果又带平台键,就是同一件事说了两遍, -会被拒绝,并把两半都指出来: - -``` -[target.cfg(os = "linux").xlings.workspace] xim:tool: the value carries platform -keys (linux, macosx), but [target.cfg(os = "linux")] already says which targets -this applies to. -``` - -`subos` 不按目标条件化:一个工程只有一个环境,所以 `[target..xlings]` -拒绝这个键,而不是把它丢掉。 - -**已发布的描述符不为目标轴条目携带边**,`mcpp publish` 会说明这一点。描述符按平台 -分块,而 selector 不是平台 —— `cfg(target_arch = "aarch64")` 不对应那份文件里的任何一块。 -**使用者**装到的东西来自顶层 `[xlings.workspace]`;目标轴对"本包自己的构建对着什么" -仍然是正确的。 - -这两条轴所属的一般规则见 [SPEC-004](../specs/manifest-semantics.md)。 - -#### `when` —— 哪些命令需要这个工具(mcpp 2026.9.4.2+) - -```toml -[xlings.workspace] -"xim:qemu-arm" = "9.2.4-1" # 每次构建都装,与从前一样 -"xim:codegen" = { version = "1.0", when = "build" } -"xim:probe-rs" = { version = "0.24.0", when = "run" } -"xim:clang-tidy" = { version = "20", when = "dev" } -``` - -包依赖从一开始就有这条轴 —— `[dependencies]`、`[build-dependencies]`、 -`[dev-dependencies]`。工具只有一张表,于是一个同时点名模拟器与调试探针的板级支持包 -会把两个都装给每一位消费者,包括只想把库编出来的那一位。 - -| `when` | 由谁安装 | 是否传播到消费者 | -|---|---|---| -| *(不写)* | 每个构建命令 | 是 | -| `build` | 每个构建命令 | 是 | -| `run` | `mcpp run`、`mcpp test` | 是 | -| `dev` | 只有声明它的那个包作为根时 | **否** | +已移入 [17 —— 项目环境](17-the-project-environment.md)。 -**不写 `when` 就是 2026.9.4.2 之前的行为**,所以没有任何清单需要改。收窄是可选动作, -不是作者必须回答的新问题。 +### 2.14 依赖产出的 host 工具 -`dev` 是唯一不传播的一档。它的含义是「声明它的那个包自己在被开发时」,所以依赖的 -`dev` 条目永远不会为消费者安装。其余各档都会到达消费者 —— 这正是板级包知道自己机器 -的意义:它声明一次模拟器,每一位消费者都拿得到。 - -档位写在**条目**上而不是另开一张表,理由与 `[dependencies]` 同时接受 `dep = "1.0"` -和 `dep = { version = "1.0", features = [...] }` 是同一条。带档位的条目**必须**写出 -`version`,哪怕留空(`version = ""` 表示「存在即可,版本不限」)—— 否则 -`{ when = "run" }` 与写错的 `version` 键无法区分。 - -#### `[feature-xlings.]` —— 某个 feature 才需要的工具 - -```toml -[features] -default = ["emulator"] -emulator = {} -hardware = {} +已移入 [07 —— build.mcpp](07-build-mcpp.md)。 -[feature-xlings.hardware] -"xim:probe-rs" = "0.24.0" -``` - -同一张表,按 feature 门控,拼法沿用 `[feature-deps.]`。**不要 `hardware` -的消费者永远不会下载探针驱动。** 这里的条目同样接受 `when`。 - -`[features]` 里没有声明过的 feature 名会作为 schema 警告报出:它对谁都不激活、什么 -都不装,而这种工具的缺席只表现为「设备就是连不上」,是最难诊断的一种。 - -#### 规则包自带它的环境(2026.9.6.6+) - -上面那张表是工程**有主张**时写的。多数工程没有主张,写下的也就是空无一物: - -```toml -[build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-cuda"], host-module = true } -``` - -这一条边就是全部声明。规则包在选中它的那个 feature、它所服务的加速器之下,声明自己 -需要哪些包、最低到哪一版: - -```toml -# 写在规则包里,不写在你的工程里 -[target.'cfg(accelerator = "cuda")'.feature-xlings.rules-cuda] -"xim:cuda-nvcc" = ">=12.9.86" -"xim:cuda-cudart" = ">=12.9.79" -``` - -**两重门,都要开。** feature 说「要不要这个规则」,selector 说「哪些构建真的下载」。 -同一个工程的 CPU-only 构建两道门都不过,一个字节都不装。 - -「需要哪些包、最低到哪一版」是规则作者的知识。在每个用它的工程里重写一遍,是一份会 -悄悄过期的副本——规则动了,而那些工程不会跟着动。 - -#### 一个包一个版本(2026.9.6.6+) - -工具地址是 `[:][@<版本>]`,它的**身份是 `(namespace, name)` 二元组**。版本是 -这个包上的约束,不是它名字的一部分,所以 `xim:glibc`、`xim:glibc@2.40` 与 -`xim:glibc@>=2.38` 指的是同一个包。**一次构建只装它的一个版本。** - -装哪一个,分两步决定。 - -**裁决——离产物更近的声明赢。** 工程压过它依赖的包,于是一个钉覆盖规则的要求: - -```toml -# 工程侧,当它确实有主张时 -[target.'cfg(accelerator = "cuda")'.xlings.workspace] -"xim:cuda-nvcc" = "13.3.33" -``` - -**不带版本的声明弃权:** 它陈述了「要这个包」而没有陈述「要哪一版」,因此不会仅仅 -因为更近就压过一条下界。两条声明都带版本且不一致时,mcpp 会报出用了哪一条——一个 -只能表现为「声明了两个版本而目录里有一个」的覆盖,是要读者自己去文件系统里重建的 -事实。 - -**校验——赢家必须满足每一条落败的要求。** `>=`、`^`、`~` 以及逗号组合是**要求**。 -不满足的钉被拒绝,并同时点出两侧: - -``` -error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins - requires >=12.9.86. - One version of a package is installed, so the two cannot both hold. - fix: pin a version satisfying >=12.9.86, or drop the pin and let the - requirement decide. -``` - -裸版本是**选择**而不是要求:两条互不相同的精确钉走裁决并被报告,不被拒绝。只有被 -陈述出来的要求才谈得上违反。 - -**这是一次比较,不是一次搜索。** 版本由裁决选定、再被检查,所以 mcpp 从不需要问索引 -「有哪些版本」,也就不带约束求解器。代价被写出来而不是藏起来:一个求解器本可满足的 -组合——工程写 `>=8.0`、规则写 `8.5.0`、而索引里最新是 8.3——会被拒绝,而拒绝消息里 -写着怎么往下走。 - -范围在两个方向上都被求解。`>=2026.1` 装到满足它的最高已发布版本,`>=2099.1` 作为 -不可满足被拒绝,而 `mcpp::xpkg_dir` 回答满足该范围的最高**已安装**版本——声明了下界 -的规则找得到下界带进来的东西。 - -#### 工程没点名的工具,其版本的来源 - -| 工程声明了 | 版本来自 | -|---|---| -| `[xlings.workspace]`,无 `subos` | 机器的环境,工程自己的条目叠在上面 | -| `[xlings.workspace]` 与 `subos = "<名>"` | 那个环境自己的 workspace;机器的不适用 | -| 两者都没有 | 机器的环境 | - -中间那行不是遗漏。指名的环境有自己的已安装集合,把机器的版本带进去会指向那里不 -存在的版本。**写 subos 就是要隔离,不写就是要机器的环境加上自己的条目。** - -在工程内执行的 `xlings use` 压过这张表,直到 mcpp 重写环境为止——它是最后合并的 -那一层。 - -#### `deps`,已被取代 - -`deps = ["xim:qemu-riscv@9.2.4-1"]` 是同一句话在 2026.9.3 之前的拼法。它仍然生效, -并且会被报告一次,同时给出该写的 `[xlings.workspace]` 那一行。**不拒绝**——拒绝会 -落到**依赖**的 manifest 上,而钉了那个包精确版本的工程改不了它。 - -#### `envs`,已移除 - -`[xlings.envs]` 曾被物化进 `.xlings.json`,而没有任何东西读它:程序的环境由它自己 -的包声明,环境的环境由那个环境声明。现在这个键是错误,并同时点名这两者。索引里没有 -任何包用过它。 - -### 2.14 依赖产出的 host 工具(mcpp 2026.8.5.1+) - -一个包能构建出消费者在**构建期**需要的二进制 —— `protoc`、`grpc_cpp_plugin`、 -`flatc`、`moc`、转译器。在依赖上声明: - -```toml -[dependencies] -protobuf = { version = "35.1", tools = ["protoc"] } -grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } -``` - -每个名字必须是该包的一个 `kind = "bin"` target。mcpp 会**为构建机器**构建它, -并把绝对路径以 `MCPP_DEP__BIN_` 交给 `build.mcpp` —— 用 -`mcpp::dep_bin("protobuf", "protoc")` 读取(见 [07 — build.mcpp](07-build-mcpp.md))。 - -四条值得知道的性质: - -- **永远是 host 二进制。** 即使 `mcpp build --target `,工具依然为**本机** - 构建 —— 代码生成器必须在这里跑。它是一次独立的、面向 host 的子构建:工具包 - 自己的 `[toolchain]`、自己的依赖解析生效,不需要与当前构建一致。安全的原因是 - 可执行文件与工程代码**零 ABI 接触**。 -- **单一版本轴。** 工具的版本**就是**依赖的版本,所以「protoc 与其运行时不匹配」 - 这种情况**不可表达**。(把工具单独打包正是会出这个问题,而且它在**运行期**才咬人, - 不是编译期。) -- **默认关闭。** 没人要就什么都不构建,成本由消费者付。包用 `[features]` + - `required_features` 给昂贵的部分加门(protobuf 的 `protoc` 需要 libprotoc 的 - ~157 个额外 TU,只用运行时的人绝不该编译它)。 -- **全局缓存**,按 包版本 × host 工具链 × feature × 自身依赖闭包 键控 —— 每台机器 - 构建一次,而不是每个工程一次。 - -#### `[tools.overrides]` —— 使用已有的二进制 - -```toml -[tools.overrides] -"compat.protobuf:protoc" = "/usr/bin/protoc" -``` - -或者不改 manifest(CI、发行版打包): - -```bash -MCPP_TOOL_PROTOBUF_PROTOC=/usr/bin/protoc mcpp build -``` - -命中 override 会**完全跳过构建**。每个同类系统都提供这条逃生舱(vcpkg 的 -`VCPKG_HOST_TRIPLET`、CMake 的 `LLVM_NATIVE_TOOL_DIR`、Qt 的 `QT_HOST_PATH`), -理由一样:一个在本机构建不出来的工具**不能是死路**。它**刻意不进** cache key —— -逃生舱不是可复现输入。 - -#### `host-module = true` —— 可复用的构建规则以包分发 - -一条规则(比如「对这些 `.proto` 跑 protoc」)应该**写一次**,而不是复制进每个 -消费者的 `build.mcpp`。把它做成普通的 mcpp 库包再 import: - -```toml -[dependencies] -protobufgen = { version = "0.1.0", host-module = true } -``` - -```cpp -// build.mcpp -import mcpp; -import protobufgen; -int main() { return protobufgen::generate({"schema"}) ? 0 : 1; } -``` - -mcpp 会把该包的 lib 根模块**为 host 编译,且与 `build.mcpp` 在同一条命令里** —— -这正是 BMI 能用的前提:一个模块接口只对「在 standard / dialect / 编译器身份上与 -它一致」的编译可导入。 - -于是规则**有版本、能测试、能通过既有的包管理器分发**,而且是用 **C++** 写的 -—— 不引入第二门语言,这正是 `build.mcpp` 存在的理由。 - -**模块名是规则源码自己声明的那个**(mcpp 2026.8.29.1+)。`export module -acme.rules.protobuf;` 就以 `acme.rules.protobuf` 被 import,与包叫什么无关。 -模块名是作者定义的 API,不镜像包身份 —— 普通库包一直遵循的就是这条规则。 - -2026.8.29.1 之前 host 模块这条路径注册的是裸 `package.name`,于是声明名与包名 -分叉的规则包在 GCC 上能构建、在 Clang 与 MSVC 上失败:GCC 的 BMI 隐式落在 -`gcm.cache` 且按**声明名**索引,而另外两者拿到的是显式的 `=` 映射。 -因此包名不再承担任何 C++ 命名约束,`grpc-rules` 重新是合法包名。 - -**两个规则不得声明同一个模块名。** `import` 寻址的是模块,所以两个这样的包对编译器 -不可区分,而它们的 BMI 与对象文件同名 —— 后者覆盖前者,存活的那个对象被送进链接两次。 -mcpp 拒绝这种情形,并点名两个包与各自的 interface 路径。检查的范围是一次 `build.mcpp` -能看见的那些规则,不是索引级的全局唯一性 —— `path` 依赖与私有 registry 本来就绕得开。 - -**`mcpp.` 前缀保留给由 mcpp 项目维护的规则。** 不在 `mcpp` 命名空间下的包声明该前缀 -的模块名时给出一条同时点名两者的警告,构建继续。之所以是警告:引擎判定不了谁是官方, -`path` 依赖、私有镜像与内部 fork 都合法,而且从这里看都一样。 - -lib 根必须在 `src/.cppm`(或 `[lib] path` 指向的位置);缺失时报 -*"host module 'x': no interface unit at …"*。 - -**一个包可以提供多条规则,由 feature 选择**(mcpp 2026.9.5.3+)。包解析后的 -`[build] sources` 里 —— 含 feature 加入的源文件 —— 每一个模块接口单元都以它自己声明的 -名字编成一个 host 模块,lib 根排在最前。feature 单元可以 import lib 根;除此之外每个 -单元单独编译,因此只 import `std` 与 `mcpp`。只有写在清单里的源文件参与:未声明 -`sources` 的包所推断出的 `src/**` 不被读取,所以此前发布的规则包暴露的仍是它当时暴露 -的那一个模块。 - -```toml -# 集合包的 manifest -[build] -sources = ["src/plugins.cppm"] # export module mcpp.plugins; - -[features] -rules-cuda = { sources = ["rules/cuda.cppm"] } # export module mcpp.rules.cuda; -rules-spirv = { sources = ["rules/spirv.cppm"] } # export module mcpp.rules.spirv; -``` - -```toml -# 消费者 -[build-dependencies.mcpp] -plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } -``` - -**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 §2.6.1 描述的那种 -情形:它的库绝不该到达目标,而它的规则仍然被需要。两条轴是分开的: -`host-module = true` 说的是**要哪一种构建期产物**,而 section 说的是**这个包是否到达 -目标**;规则包在第二条轴上的答案是"否",而 section 就是说这件事的地方。写在 -`[dependencies]` 里同样能工作 —— 这恰恰是为什么这条区分必须被**陈述**,而不能指望由 -一次失败来教会。 - -模块集合就是 feature 集合:feature 未激活的单元不编译,import 它会以未知模块失败。 -`mcpp:plugins` 是 mcpp 项目维护的集合(仓库 `mcpp-community/mcpp-plugins`);其成员 -命名为 `mcpp.rules.`(规则包)与 `mcpp.tools.`(构建期工具)。 - -*仅构建期:* `host-module = true` 的依赖**不会**被编进、也不会被链进本工程的 target, -它所依赖的东西也不会。它只在 `build.mcpp` 期间运行,别处都不出现。(2026.8.5.2 之前 -它还会被当作普通库再编一遍,这正是规则里 `import mcpp;` 失败的原因:在那第二次编译里 -内置模块并不存在。2026.8.29.1 之前被排除的只有规则本身,它自己的 `[dependencies]` -仍会被编译并链进消费者的二进制,而规则却 import 不到它们。) - -#### 依赖另一个规则的规则(mcpp 2026.8.29.1+) - -规则在自己的 `[build-dependencies]` 里声明所需之物,并可以 import 其中标了 -`host-module = true` 的条目: - -```toml -# 写在规则包自己的清单里 -[build-dependencies] -globbing = { path = "../globbing", host-module = true } -``` - -```cpp -// 规则自己的接口 -export module tidyrule; -import std; -import mcpp; -import globbing; -``` - -mcpp 先编译内层规则,同一条命令、同一套 flag,因此 BMI 的一致性仍是结构性事实而不是 -需要事后校验的性质。 - -消费者**不可以** import `globbing`:构建期的 provision 只在 `reexport = true` 的边上 -再跨一跳,而 mcpp 自己执行这条规则,不交给编译器 —— 在 GCC 上那个 import 会成功, -然后在别人的机器上失败。 - -*限制:* 每个 host 模块只有一个接口单元。带实现单元或多个模块的库还不能作为规则的 -构建期依赖。 - -#### `reexport = true` —— 由库替用户拉起整条工具链(2026.8.6.2+) - -上面这些都由**使用工具的人**声明。当知识本来属于库时,这个位置就错了:gRPC -的代码生成需要 protobuf 的 `protoc`,而 gRPC 包的任何使用者都不应该知道这件事。 - -`reexport = true` 把一条边上的构建期提供物 —— 它的 `tools`、它的 -`host-module`、以及该依赖的目录 —— 交给**本包自己的消费者**: - -```toml -# 写在 grpc 包自己的 manifest 里 -[feature-deps.codegen] -"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true } -grpc-plugin = { version = "1.83.0", tools = ["grpc_cpp_plugin"], reexport = true } -grpcgen = { version = "1.83.0", host-module = true, reexport = true } -``` - -于是使用者只写一行,再 import 那个规则: - -```toml -[dependencies] -grpc = { version = "1.83.0", features = ["codegen"] } -``` - -```cpp -// build.mcpp -import mcpp; -import grpcgen; -int main() { return grpcgen::generate_all() ? 0 : 1; } -``` - -- **默认关闭,并且刻意不复用边上的 `visibility`。** `visibility` 本身默认就是 - `"public"`,复用该可见性意味着任意深度的依赖都能静默地向构建程序的工具 - 命名空间里塞东西。「把某样东西交给消费者」是一条供应链主张,必须写下来。 -- **一次声明只走一跳。** 被再导出的提供物到达声明它的那个包的消费者;要继续 - 往上走,下一个包必须自己也写 `reexport`。每个包只决定**它**交出什么。 -- **feature 可以往一条已经声明过的依赖上追加请求。** gRPC 无条件依赖 protobuf, - 而它的 `codegen` feature 往同一条边加 `tools = ["protoc"], reexport = true`。 - `tools` 与 `features` 取并集,`host-module` 与 `reexport` 取或;`version` / - `path` / `git` 不合并 —— feature 仍然无法静默覆盖无条件条目的身份。 -- **传播的是可见性,不是执行。** `dep_bin()` 只返回路径,跑不跑仍由消费者的 - `build.mcpp` 决定;谁构建了这个工具、tool store 怎么做键,都不改变。 -- **裸名由阶梯决定,而不是靠运气。** 一旦两个库都能再导出,它们可能同时提供 - 尾名 `protobuf`。全限定的 `MCPP_DEP___BIN_` 总是发布;裸名 - 依次绑定到 `mcpplibs.`、`compat.`、无命名空间的 ``,最后才是「剩下 - 的唯一候选」——存在争用时 mcpp 会说出来,而不是默默选一个。 - -##### 旧版 mcpp 读到用了这些键的 manifest - -不认识的依赖键会被**记为降级**并忽略(mcpp 2026.8.6.2+),因此一份为更新的 -mcpp 写的包仍然能加载,这个读取器认识的部分照常生效。在那之前它是**整份加载 -失败**且报错误导,这正是「已发布的包永远无法采用新键」的原因——与索引下限确立 -的是同一条性质:**数据不得决定程序是否可用**。 - -因此,一个**依赖** `reexport` 才有那套人机工程的包,仍然需要足够新的客户端; -变化在于该包的其余部分在旧客户端上不再一起失效。 - -##### 按平台裁剪提供物 - -一个包可能只在部分平台声明 `bin` 目标。既然现在是**库**决定请求什么,无条件的 -请求就会把「不支持的平台」变成用户改不掉的硬错。用条件段裁剪: - -```toml -[target.'cfg(not(windows))'.feature-deps.codegen] -"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true } -``` - -`[target..feature-deps.]`(2026.8.6.2+)与 `[target.]` 下的 -其余依赖表(`dependencies` / `dev-dependencies` / `build-dependencies`)遵循同 -一套谓词规则,针对**解析后的 target** 求值。**feature 本身在所有平台都注册** -—— 只有它拉进来的东西是条件性的 —— 因此在没有任何谓词匹配的平台上请求它,不是 -「未知 feature」错误。 ### 2.15 `[resources]` —— 编译进产物的元数据与资产(2026.8.7.1+) @@ -2053,138 +1238,10 @@ o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o") 见 [07 — build.mcpp](07-build-mcpp.md)。把这类文件写进 `[build].ldflags` 也「能用」, 但 ldflags 是链接命令里的一串字符:没有任何东西跟踪它,改了它得到的是 `ninja: no work to do`。 +### 2.16 `[hooks]` —— 项目构建生命周期命令 -### 2.16 `[hooks]` —— 项目构建生命周期命令(实验性) - -> **实验性。** Hook 目前**不能**决定一次构建成功与否。所有 Hook 失败都以 -> **warning** 报出,`mcpp build` 保留它自己挣来的结果;`side_effect = true` -> 会被报错拒绝,而不是被采纳。这个键保留在 schema 里,这样今天写下的 manifest -> 在该功能转正时无需改动。另有两条限制是永久的、不是临时的:**只有根项目的 Hook -> 会执行**,而且**只有 `mcpp build` 会执行它们**。 - -Hook 是 `mcpp build` **在一段区间内持有**的命令,事件名就是那段区间: - -```toml -[hooks] -build_start = "echo build started" -build_failed = "notify-send 'build failed'" -build_finished = "notify-send 'build finished'" - -# 可选;以下是默认值。 -timeout_seconds = 10 -enabled = true -side_effect = false # 实验期内 `true` 会被拒绝 -``` - -| 键 | 类型 | 默认值 | 它命名的区间 | -|---|---|---:|---| -| `build_start` | 命令 | — | 项目准备完成后开启,命令退出时闭合 | -| `build_finished` | 命令 | — | 构建成功后开启,命令退出时闭合 | -| `build_failed` | 命令 | — | 构建失败后开启,命令退出时闭合 | -| `during_build` | 命令 | — | 构建开始前开启,构建结束后闭合 | -| `timeout_seconds` | 整数,1–86400 | `10` | 单次运行的时限 | -| `enabled` | 布尔 | `true` | 是否启用本表中的全部命令 | -| `side_effect` | 布尔 | `false` | Hook 失败是否让本次构建失败。**保留键**——实验期内只接受 `false` | - -前三个区间是**自闭合**的——命令退出,区间就结束。"同步"在这里不是一种单独的模式, -它就是自闭合区间的样子。`during_build` 是唯一由别的东西闭合的区间,而那两个只对其中 -一种形状有意义的键,是从这一点推出来的,不是额外规定的例外。 - -命令写成字符串;需要选项时写成表: - -| 表内键 | 适用于 | 含义 | -|---|---|---| -| `cmd` | 所有事件 | 命令本身,必填 | -| `timeout_seconds` | 自闭合事件 | 覆盖本表默认值 | -| `loop` | `during_build` | 命令在区间闭合前退出时重新启动 | - -`loop` 写在自闭合事件上、`timeout_seconds` 写在 `during_build` 上,都是**错误**而不是 -被忽略的键:自闭合区间随命令退出而结束,没有东西可重启;而 `during_build` 已经由构建 -定界。一个被接受却什么都不做的键,读起来就是"这功能坏了"。 - -命令通过宿主 Shell(`/bin/sh` 或 `cmd.exe`)执行,工作目录是**项目根目录**——不是敲 -`mcpp build` 的那个目录,所以 Hook 里的相对路径在哪儿发起构建都指同一处。自闭合命令 -的标准输入、输出和错误沿用普通终端行为。没有配置的事件直接跳过。 - -生命周期为: - -```text -during_build 开启 -build_start - ├─ 构建成功 → during_build 闭合 → build_finished - └─ 构建失败 → during_build 闭合 → build_failed -``` - -`during_build` 在终止 Hook **之前**闭合,因此两条命令不会重叠执行。 - -`build_failed` 与 `build_finished` 互斥,而且两者都只在 `build_start` 已经执行之后 -才可达。项目**准备**阶段就失败的情况——manifest 非法、依赖无法解析、没有可用工具链 -——一个 Hook 都不触发:此时构建尚未开始,而 Hook 程序本身可能正是准备阶段要装的东西。 - -命令无法启动、返回非零或超过时限均视为 Hook 失败。`during_build` 还多一种:开了 `loop` -的命令**起不来**——连续五次在一秒内以非零状态结束——就不再重启,并被报出来。(很快就 -成功结束的命令,正是 `loop` 被要求重复的那件事,不算失败。)以上每一种都以 **warning** -报出,构建保留它自己挣来的结果——`[hooks]` 还在实验期,它没有投票权。Hook 自身失败不会 -再触发另一个 Hook。 - -改变这一点的正是 `side_effect = true`,而今天写它是一个错误: - -```text -error: mcpp.toml: error: [hooks].side_effect = true is not available yet: -[hooks] is experimental and cannot decide whether a build succeeded. … -``` - -是拒绝而不是悄悄降级,因为两种沉默的做法都更糟:采纳它等于让一个实验性功能对每一次 -构建都有否决权;忽略它则让项目以为自己的构建被通知程序把着关,而实际上没有。功能转正 -后,`true` 的含义是"Hook 失败让构建失败"——而构建自身失败时仍保留它自己的退出码,所以 -`mcpp build` 不会把一次编译错误报成通知程序的问题。 - -关于 `during_build` 有两件事值得单独知道: - -- **它的输出被丢弃**,因为它与构建并发写出,否则会插进某条编译诊断的中间。要看它的 - 输出就跑 `mcpp build --verbose`。 -- **停止的单位是进程树,不是进程。** `player & wait` 让播放器成为 mcpp 所启动那条命令 - 的孙子进程,只停掉后者会让音频设备在构建结束后仍被占着。mcpp 把命令放进它自己的 - 进程组(Windows 上是 job object)并停止整组,构建被 Ctrl-C 打断时也一样。 - -作用范围: - -- 只有 `mcpp build` 执行 Hook。`mcpp run`、`mcpp test` 和 - `mcpp build --configure-only` 同样会构建,但有意不执行。 -- Hook 属于**被构建的那个包**。workspace 展开时就是逐个成员:各自的 `[hooks]`、 - 各自的构建、各自的根目录。**虚拟** workspace 根(只有 `[workspace]` 没有 - `[package]`)不构建任何东西,写在那里的 `[hooks]` 永不触发。 -- 依赖的 `[hooks]` **一律跳过**,只有根项目的会执行。mcpp 解析的每一份 manifest 都 - 带着这一节,依赖的也带,而没有任何东西去读它——这正是"装一个包"不会变成"在我下次 - 构建时跑包作者的 Shell 命令"的原因。这是设计的性质,不是一个等着被打开的默认值。 -- 声明了生效的 Hook 就等于让项目放弃空转快路径,因为 `build_start` 规定在准备阶段之后 - 执行。对已经是最新状态的带 Hook 项目,`mcpp build` 的代价是一次准备,而不是毫秒级。 - -`[hooks]` 里、以及某个事件表里不认识的**键**都是 warning(`--strict` 下为错误),所以为更新版 mcpp 写的 -manifest 在这一版仍能加载;不认识的**值**——`cmd` 缺失或不是字符串、`timeout_seconds` 不在 -1–86400 之间、键写给了错误的区间——是 manifest 错误。 - -> **Hook 是代码,而 `mcpp.toml` 是仓库的一部分。** 构建一个刚克隆下来的项目,会以 -> 执行 `mcpp build` 的那个账户的权限,运行它 `[hooks]` 里写的任何东西。这与 -> `build.mcpp`([07 — build.mcpp](07-build-mcpp.md))已经要求的信任是同一份; -> `[hooks]` 扩大的是它的范围,而不是引入了一份新的信任。 - -Hook 程序可以作为普通 xlings 依赖安装。例如,音频通知程序可以把音频内置进自己的 -可执行文件,无需让 mcpp 处理媒体资源: - -```toml -[hooks] -build_finished = "mcpp-hooks-audioplayer niulai-mm" -build_failed = "mcpp-hooks-audioplayer niulai-niulai" -side_effect = false - -[xlings] -deps = ["xim:mcpp-hooks-audioplayer@0.0.1"] -``` +已移入 [21 —— 按场景选命令](21-commands-by-scenario.md)。 -根据构建成功或失败播放不同提示音。`side_effect = false` 写出来而不是靠默认值:它是 -这份 manifest 自己就想要的值——缺个音频设备不该让构建失败——所以等这个键有了不止 -一个可接受的值之后,它仍然会这么写。 ## 附录 A. Schema 所有权原则(新字段准入标准) diff --git a/docs/zh/07-build-mcpp.md b/docs/zh/07-build-mcpp.md index 6b41e926..afa11c4a 100644 --- a/docs/zh/07-build-mcpp.md +++ b/docs/zh/07-build-mcpp.md @@ -790,3 +790,237 @@ mcpp **不会**每次构建都重跑 `build.mcpp`。它会缓存程序产出的 > 多数构建发生在没有人看的地方(CI、流水线、ninja 的子进程),而一个卡在提示上的 > 构建比一个失败的构建更难诊断;并且构建结果不应该取决于一次击键。 > 可配置的上限 + 一条点名要改哪个文件的报错,回答的是同一个需求。 + +## 依赖产出的 host 工具(mcpp 2026.8.5.1+) + +一个包能构建出消费者在**构建期**需要的二进制 —— `protoc`、`grpc_cpp_plugin`、 +`flatc`、`moc`、转译器。在依赖上声明: + +```toml +[dependencies] +protobuf = { version = "35.1", tools = ["protoc"] } +grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } +``` + +每个名字必须是该包的一个 `kind = "bin"` target。mcpp 会**为构建机器**构建它, +并把绝对路径以 `MCPP_DEP__BIN_` 交给 `build.mcpp` —— 用 +`mcpp::dep_bin("protobuf", "protoc")` 读取(见 [07 — build.mcpp](07-build-mcpp.md))。 + +四条值得知道的性质: + +- **永远是 host 二进制。** 即使 `mcpp build --target `,工具依然为**本机** + 构建 —— 代码生成器必须在这里跑。它是一次独立的、面向 host 的子构建:工具包 + 自己的 `[toolchain]`、自己的依赖解析生效,不需要与当前构建一致。安全的原因是 + 可执行文件与工程代码**零 ABI 接触**。 +- **单一版本轴。** 工具的版本**就是**依赖的版本,所以「protoc 与其运行时不匹配」 + 这种情况**不可表达**。(把工具单独打包正是会出这个问题,而且它在**运行期**才咬人, + 不是编译期。) +- **默认关闭。** 没人要就什么都不构建,成本由消费者付。包用 `[features]` + + `required_features` 给昂贵的部分加门(protobuf 的 `protoc` 需要 libprotoc 的 + ~157 个额外 TU,只用运行时的人绝不该编译它)。 +- **全局缓存**,按 包版本 × host 工具链 × feature × 自身依赖闭包 键控 —— 每台机器 + 构建一次,而不是每个工程一次。 + +### `[tools.overrides]` —— 使用已有的二进制 + +```toml +[tools.overrides] +"compat.protobuf:protoc" = "/usr/bin/protoc" +``` + +或者不改 manifest(CI、发行版打包): + +```bash +MCPP_TOOL_PROTOBUF_PROTOC=/usr/bin/protoc mcpp build +``` + +命中 override 会**完全跳过构建**。每个同类系统都提供这条逃生舱(vcpkg 的 +`VCPKG_HOST_TRIPLET`、CMake 的 `LLVM_NATIVE_TOOL_DIR`、Qt 的 `QT_HOST_PATH`), +理由一样:一个在本机构建不出来的工具**不能是死路**。它**刻意不进** cache key —— +逃生舱不是可复现输入。 + +### `host-module = true` —— 可复用的构建规则以包分发 + +一条规则(比如「对这些 `.proto` 跑 protoc」)应该**写一次**,而不是复制进每个 +消费者的 `build.mcpp`。把它做成普通的 mcpp 库包再 import: + +```toml +[dependencies] +protobufgen = { version = "0.1.0", host-module = true } +``` + +```cpp +// build.mcpp +import mcpp; +import protobufgen; +int main() { return protobufgen::generate({"schema"}) ? 0 : 1; } +``` + +mcpp 会把该包的 lib 根模块**为 host 编译,且与 `build.mcpp` 在同一条命令里** —— +这正是 BMI 能用的前提:一个模块接口只对「在 standard / dialect / 编译器身份上与 +它一致」的编译可导入。 + +于是规则**有版本、能测试、能通过既有的包管理器分发**,而且是用 **C++** 写的 +—— 不引入第二门语言,这正是 `build.mcpp` 存在的理由。 + +**模块名是规则源码自己声明的那个**(mcpp 2026.8.29.1+)。`export module +acme.rules.protobuf;` 就以 `acme.rules.protobuf` 被 import,与包叫什么无关。 +模块名是作者定义的 API,不镜像包身份 —— 普通库包一直遵循的就是这条规则。 + +2026.8.29.1 之前 host 模块这条路径注册的是裸 `package.name`,于是声明名与包名 +分叉的规则包在 GCC 上能构建、在 Clang 与 MSVC 上失败:GCC 的 BMI 隐式落在 +`gcm.cache` 且按**声明名**索引,而另外两者拿到的是显式的 `=` 映射。 +因此包名不再承担任何 C++ 命名约束,`grpc-rules` 重新是合法包名。 + +**两个规则不得声明同一个模块名。** `import` 寻址的是模块,所以两个这样的包对编译器 +不可区分,而它们的 BMI 与对象文件同名 —— 后者覆盖前者,存活的那个对象被送进链接两次。 +mcpp 拒绝这种情形,并点名两个包与各自的 interface 路径。检查的范围是一次 `build.mcpp` +能看见的那些规则,不是索引级的全局唯一性 —— `path` 依赖与私有 registry 本来就绕得开。 + +**`mcpp.` 前缀保留给由 mcpp 项目维护的规则。** 不在 `mcpp` 命名空间下的包声明该前缀 +的模块名时给出一条同时点名两者的警告,构建继续。之所以是警告:引擎判定不了谁是官方, +`path` 依赖、私有镜像与内部 fork 都合法,而且从这里看都一样。 + +lib 根必须在 `src/.cppm`(或 `[lib] path` 指向的位置);缺失时报 +*"host module 'x': no interface unit at …"*。 + +**一个包可以提供多条规则,由 feature 选择**(mcpp 2026.9.5.3+)。包解析后的 +`[build] sources` 里 —— 含 feature 加入的源文件 —— 每一个模块接口单元都以它自己声明的 +名字编成一个 host 模块,lib 根排在最前。feature 单元可以 import lib 根;除此之外每个 +单元单独编译,因此只 import `std` 与 `mcpp`。只有写在清单里的源文件参与:未声明 +`sources` 的包所推断出的 `src/**` 不被读取,所以此前发布的规则包暴露的仍是它当时暴露 +的那一个模块。 + +```toml +# 集合包的 manifest +[build] +sources = ["src/plugins.cppm"] # export module mcpp.plugins; + +[features] +rules-cuda = { sources = ["rules/cuda.cppm"] } # export module mcpp.rules.cuda; +rules-spirv = { sources = ["rules/spirv.cppm"] } # export module mcpp.rules.spirv; +``` + +```toml +# 消费者 +[build-dependencies.mcpp] +plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } +``` + +**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 §2.6.1 描述的那种 +情形:它的库绝不该到达目标,而它的规则仍然被需要。两条轴是分开的: +`host-module = true` 说的是**要哪一种构建期产物**,而 section 说的是**这个包是否到达 +目标**;规则包在第二条轴上的答案是"否",而 section 就是说这件事的地方。写在 +`[dependencies]` 里同样能工作 —— 这恰恰是为什么这条区分必须被**陈述**,而不能指望由 +一次失败来教会。 + +模块集合就是 feature 集合:feature 未激活的单元不编译,import 它会以未知模块失败。 +`mcpp:plugins` 是 mcpp 项目维护的集合(仓库 `mcpp-community/mcpp-plugins`);其成员 +命名为 `mcpp.rules.`(规则包)与 `mcpp.tools.`(构建期工具)。 + +*仅构建期:* `host-module = true` 的依赖**不会**被编进、也不会被链进本工程的 target, +它所依赖的东西也不会。它只在 `build.mcpp` 期间运行,别处都不出现。(2026.8.5.2 之前 +它还会被当作普通库再编一遍,这正是规则里 `import mcpp;` 失败的原因:在那第二次编译里 +内置模块并不存在。2026.8.29.1 之前被排除的只有规则本身,它自己的 `[dependencies]` +仍会被编译并链进消费者的二进制,而规则却 import 不到它们。) + +### 依赖另一个规则的规则(mcpp 2026.8.29.1+) + +规则在自己的 `[build-dependencies]` 里声明所需之物,并可以 import 其中标了 +`host-module = true` 的条目: + +```toml +# 写在规则包自己的清单里 +[build-dependencies] +globbing = { path = "../globbing", host-module = true } +``` + +```cpp +// 规则自己的接口 +export module tidyrule; +import std; +import mcpp; +import globbing; +``` + +mcpp 先编译内层规则,同一条命令、同一套 flag,因此 BMI 的一致性仍是结构性事实而不是 +需要事后校验的性质。 + +消费者**不可以** import `globbing`:构建期的 provision 只在 `reexport = true` 的边上 +再跨一跳,而 mcpp 自己执行这条规则,不交给编译器 —— 在 GCC 上那个 import 会成功, +然后在别人的机器上失败。 + +*限制:* 每个 host 模块只有一个接口单元。带实现单元或多个模块的库还不能作为规则的 +构建期依赖。 + +### `reexport = true` —— 由库替用户拉起整条工具链(2026.8.6.2+) + +上面这些都由**使用工具的人**声明。当知识本来属于库时,这个位置就错了:gRPC +的代码生成需要 protobuf 的 `protoc`,而 gRPC 包的任何使用者都不应该知道这件事。 + +`reexport = true` 把一条边上的构建期提供物 —— 它的 `tools`、它的 +`host-module`、以及该依赖的目录 —— 交给**本包自己的消费者**: + +```toml +# 写在 grpc 包自己的 manifest 里 +[feature-deps.codegen] +"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true } +grpc-plugin = { version = "1.83.0", tools = ["grpc_cpp_plugin"], reexport = true } +grpcgen = { version = "1.83.0", host-module = true, reexport = true } +``` + +于是使用者只写一行,再 import 那个规则: + +```toml +[dependencies] +grpc = { version = "1.83.0", features = ["codegen"] } +``` + +```cpp +// build.mcpp +import mcpp; +import grpcgen; +int main() { return grpcgen::generate_all() ? 0 : 1; } +``` + +- **默认关闭,并且刻意不复用边上的 `visibility`。** `visibility` 本身默认就是 + `"public"`,复用该可见性意味着任意深度的依赖都能静默地向构建程序的工具 + 命名空间里塞东西。「把某样东西交给消费者」是一条供应链主张,必须写下来。 +- **一次声明只走一跳。** 被再导出的提供物到达声明它的那个包的消费者;要继续 + 往上走,下一个包必须自己也写 `reexport`。每个包只决定**它**交出什么。 +- **feature 可以往一条已经声明过的依赖上追加请求。** gRPC 无条件依赖 protobuf, + 而它的 `codegen` feature 往同一条边加 `tools = ["protoc"], reexport = true`。 + `tools` 与 `features` 取并集,`host-module` 与 `reexport` 取或;`version` / + `path` / `git` 不合并 —— feature 仍然无法静默覆盖无条件条目的身份。 +- **传播的是可见性,不是执行。** `dep_bin()` 只返回路径,跑不跑仍由消费者的 + `build.mcpp` 决定;谁构建了这个工具、tool store 怎么做键,都不改变。 +- **裸名由阶梯决定,而不是靠运气。** 一旦两个库都能再导出,它们可能同时提供 + 尾名 `protobuf`。全限定的 `MCPP_DEP___BIN_` 总是发布;裸名 + 依次绑定到 `mcpplibs.`、`compat.`、无命名空间的 ``,最后才是「剩下 + 的唯一候选」——存在争用时 mcpp 会说出来,而不是默默选一个。 + +#### 旧版 mcpp 读到用了这些键的 manifest + +不认识的依赖键会被**记为降级**并忽略(mcpp 2026.8.6.2+),因此一份为更新的 +mcpp 写的包仍然能加载,这个读取器认识的部分照常生效。在那之前它是**整份加载 +失败**且报错误导,这正是「已发布的包永远无法采用新键」的原因——与索引下限确立 +的是同一条性质:**数据不得决定程序是否可用**。 + +因此,一个**依赖** `reexport` 才有那套人机工程的包,仍然需要足够新的客户端; +变化在于该包的其余部分在旧客户端上不再一起失效。 + +#### 按平台裁剪提供物 + +一个包可能只在部分平台声明 `bin` 目标。既然现在是**库**决定请求什么,无条件的 +请求就会把「不支持的平台」变成用户改不掉的硬错。用条件段裁剪: + +```toml +[target.'cfg(not(windows))'.feature-deps.codegen] +"compat.protobuf" = { version = "35.1", tools = ["protoc"], reexport = true } +``` + +`[target..feature-deps.]`(2026.8.6.2+)与 `[target.]` 下的 +其余依赖表(`dependencies` / `dev-dependencies` / `build-dependencies`)遵循同 +一套谓词规则,针对**解析后的 target** 求值。**feature 本身在所有平台都注册** +—— 只有它拉进来的东西是条件性的 —— 因此在没有任何谓词匹配的平台上请求它,不是 +「未知 feature」错误。 diff --git a/docs/zh/14-target-side.md b/docs/zh/14-target-side.md index 2edb4304..cfc240c9 100644 --- a/docs/zh/14-target-side.md +++ b/docs/zh/14-target-side.md @@ -281,3 +281,116 @@ mcpp 不认识的键 —— 打错的字,或来自更新版本 mcpp 的谓词 该规定只约束此后的引擎。一个包若声明某个层名, 其使用者仍须运行不早于该层名被引入的那个版本。 + +## `[target.*]` —— 平台条件依赖与 flag + +用 `[target.]` 表把依赖与构建 flag 限定到某个平台。选择器 `` 有三种形式: + +| 选择器 | 含义 | 示例 | +|---|---|---| +| **裸 OS 别名** | 单个 OS / 族 —— 简洁且常用的形式 | `[target.windows]`、`[target.unix]` | +| **`cfg(...)` 谓词** | 复合条件(arch / env / 组合子) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | +| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 §2.7.3) | `[target.x86_64-linux-musl]` | + +一个选择器可以承载平台条件的**依赖**与**构建 flag**: + +```toml +# 简洁的裸别名形式 —— 仅在 Windows 上拉取并链接 OpenBLAS。 +[target.windows.dependencies.compat] +openblas = "0.3.33" +[target.windows.build] +ldflags = ["-Llib", "-llibopenblas"] + +# cfg(...) 用于复合谓词(文法:all/any/not 作用于 os/arch/family/env, +# 以及裸别名 windows/unix/linux/macos)。 +[target.'cfg(all(linux, not(arch = "aarch64")))'.build] +cxxflags = ["-march=x86-64-v2"] +``` + +`[target.windows]` 与 `[target.'cfg(windows)']` 完全等价 —— 裸别名 +`windows` / `linux` / `macos` / `unix` 都不是合法的目标三元组,因此不存在歧义。 +单个 OS/族用裸形式,arch/env 条件与组合子用 `cfg(...)`。 + +- **可用键**:`dependencies` / `dev-dependencies` / `build-dependencies` / + `feature-deps.`(mcpp 2026.8.6.2+ —— 见 §2.14;feature 本身无条件注册, + 只有它的依赖集合受限定),以及带 `cflags` / `cxxflags` / `ldflags` / `sources` + 的 `build`(mcpp 0.0.95+ —— 条件源码 glob,例如把 `src/x86/**/*.asm` 收在 + `cfg(arch = "x86_64")` 之后;`!` 排除 glob 在此同样有效),再加 `flags` 与 + `include_dirs` / `include_dirs_after`(mcpp 0.0.102+), + 以及 `private_include_dirs` 与 `std-module-flags`(mcpp 2026.9.1.1+)。 +- **`build` 接受的恰好是*可叠加的构建输入*集合** —— 那些以追加方式合并、 + 并在谓词求值之后被消费的东西,也就是 `BuildInputs` 的成员表。`linkage`、`target` + 与档案开关刻意不在其中:它们是**目标选择的输入**(用一个针对 `target` 求值的谓词 + 去条件化 `target` 是循环的),或者需要覆盖而非追加的语义。 + 集合之外的键会被报出并忽略;消息里列出的正是它比对用的那份集合,因此不会与检查漂移。 +- **按解析后的目标求值** —— 交叉构建取 `--target` 三元组,否则取宿主。因此原生 + Linux 构建**根本不会下载** `[target.windows]` 依赖。 +- **谓词的键**:`os`、`arch`、`family`、`env` —— 三元组的坐标 —— 以及自 mcpp + 2026.9.1.1 起的五个目标侧层名 `compiler`、`compiler-runtime`、`kernel-abi`、 + `c-abi`、`c++-abi`(见[14 —— 目标侧](14-target-side.md))。`accelerator` 同样 + 是这里的键,由本次构建自己的 `accel`(`--accel` 或 `[build] accel` 里的后端名) + 回答,因此它是对一个集合的成员判定;`accelerator = "none"` 则是一段用来说 + 「本次构建没有命名任何后端」的写法,而不必枚举它不是的那些后端。裸词 + `linux` / `macos` / `windows` / `unix` 是对应 `os` / `family` 判定的糖。 + 集合之外的键会被报成一条 schema 警告,且该段不生效 —— 它过去静默地求值为假, + 而那与「这一段本就不该匹配」读数完全相同。 +- **被解析的层的谓词不能选择依赖。** 层是**从**依赖图解析出来的,因此由它选出的 + 依赖会决定它正在询问的那个答案。`[target.'cfg(c-abi = "musl")'.dependencies]` + 会被报出并忽略;同一谓词下的 `build` 输入照常生效。`accelerator` 不在此列 + (mcpp 2026.9.6.5):它是构建的输入而不是图给出的答案,所以 + `[target.'cfg(accelerator = "cuda")'.dependencies]` 生效。 +- **优先级**:精确三元组表胜过 `cfg`/别名表;多个命中的谓词表,其 flag 按序拼接。 + 条件项追加在无条件 `[build]` 项**之后**,因此在 GNU「最后一个 flag 生效」的 + 规则下,条件规则会覆盖更宽的无条件规则。这正是让按 OS **移除**成为可表达的原因: + + ```toml + [build] + flags = [{ glob = "third_party/zlib/**", defines = ["HAVE_UNISTD_H=1"] }] + + # clang-MSVC 没有 :撤销基础 define,加上 windows 的那个。 + [target.'cfg(windows)'.build] + flags = [{ glob = "third_party/zlib/**", + defines = ["NO_FSEEKO"], cflags = ["-UHAVE_UNISTD_H"] }] + ``` + +- **未命中当前目标的条件 `flags` 条目根本不存在**,因此它不会产生 + 「glob 未匹配到任何源文件」的警告。于是一份 manifest 可以同时携带三个 OS 的 + flag 表,而不会在另外两个上制造噪声 —— 与未启用 feature 的条目根本不存在是 + 同一个道理。**无条件**表里的零命中 glob 仍然告警,因为那里它是真实缺陷。 +- **`toolchain` / `linkage` / `sysroot` 仅限精确三元组** —— 它们描述某一个具体的交叉目标, + 因此写在 `[target.]` 下(见上),而不是裸别名或 `cfg(...)` 下。 + +### `sysroot` —— 目标的 C 库 + +`sysroot`(mcpp 2026.8.20.2+)覆盖目标表为某个三元组绑定的 C 库,与 `toolchain` +覆盖编译器 pin 同轴:一个指名目标所解析的编译器,另一个指名它的 C 库,两者在工程 +有理由与之分歧之前都只由引擎决定。 + +```toml +[target.riscv64-none-elf] +sysroot = "xim:newlib-riscv@4.4" # a different C library +``` + +```toml +[target.riscv64-none-elf] +sysroot = "" # no C library at all +``` + +**键缺席与键为空是两个不同的答案。** 缺席继承目标表的 C 库。存在且为空是 +**零 libc 档**:不解析任何 C 库,不加入头文件与库目录,链接行上只有工程与其依赖 +提供的内容,`#include ` 不再解析。内核与 bootloader 要的正是这一档,而把 +两种情形合并会让这类工程静默地把目标的 C 库拿回去。 + +取值是 xpkg 引用或空字符串;裸名在解析清单时即被拒绝,因为接受它会导致什么都不安装, +然后在很晚的时候以「缺少 libc」失败。 + +构建程序可以询问供给 sysroot 的是哪个 C 库**载荷**:`mcpp::target_libc()` 返回该包的 +名字,`mcpp::target_libc_profile()` 返回目标 ISA 档位对应的子目录。零 libc 档上两者均 +为空。 + +**这与「目标侧解析出的 C 库是哪一个」不是同一个问题。** `target_libc()` 命名的是 +mcpp 装上的那个载荷,而这个值是目标侧解析的一项**输入** —— 依赖图里的包可以改为供给 +C 库,那时解析出的 `c-abi` 就不是这里返回的东西。要按已解析的层分支,请用层谓词: +`[target.'cfg(c-abi = "musl")'.build]`(见[14 —— 目标侧](14-target-side.md))。 +这一段在 2026.9.1.1 之前写的是「解析到的是哪份 C 库」,那是两者里错的那一个。 +参见[13 —— 裸机与 freestanding 目标](13-baremetal.md)。 diff --git a/docs/zh/17-the-project-environment.md b/docs/zh/17-the-project-environment.md index f85458f8..9e70edec 100644 --- a/docs/zh/17-the-project-environment.md +++ b/docs/zh/17-the-project-environment.md @@ -159,3 +159,272 @@ create/bootstrap that environment instead of falling back to active/default - [8 - 工具链内部](08-toolchain-internals.md) —— 运行时选择、`RuntimeBinding` 快照与降级规则。 - [5 - mcpp.toml](05-mcpp-toml.md) —— 全部清单键,含 `[xlings]`。 + +## `[xlings]` — 工程的环境 + +```toml +[xlings.workspace] # 这个工程的环境里有什么 +cmake = "3.28" +"xim:picolibc-riscv" = "1.8.12" # 带命名空间的包 —— 必须带引号 +code = "" # 存在即可,版本不限 +llvm = { macosx = "20", default = "22" } +``` + +```toml +[xlings] +subos = "dev" # 指名的隔离环境 +``` + +`[xlings]` 是 mcpp 对 **xlings local project 机制**的书写面:让一个目录拥有自己 +环境的那份项目 `.xlings.json`。子段名与含义都是那份文件的,mcpp 原样物化进 +`/.mcpp/.xlings.json`,没有翻译层。 + +**`[xlings.workspace]` 是唯一的表。** 一条条目写出工程用哪个包、用哪个版本。 +mcpp 既供给它——机器上没有就装,有就映射——也把它物化成解析用的钉,于是工程写下 +的版本就是它的工具解析到的版本。 + +### 条目的形式 + +| 形式 | 含义 | +|---|---| +| `cmake = "3.28"` | 该版本 | +| `llvm = "22"` | 已装的最高 `22.*`;版本前缀会被解析 | +| `code = ""` | 存在即可,版本不限 | +| `"xim:picolibc-riscv" = "1.8.12"` | 来自 `xim` 索引的包 | +| `llvm = { macosx = "20", default = "22" }` | 按宿主平台 | + +**带命名空间的包写成 `"<命名空间>:<名字>" = "<版本>"`,引号必需** —— TOML 的裸键 +不能含冒号。这是**推荐形态,也是所有官方包使用的形态**:一条条目先点名一个包, +再说用它的哪个版本,所以命名空间属于名字。 + +命名空间写在版本上(`picolibc-riscv = "xim:1.8.12"`)同样接受,因为物化出来的 +`.xlings.json` 里正是那种形态 —— 那里的键是 xvm target,scope 限定的是版本。 +两套词汇,同一条条目。两半都写且不一致是错误;同一个包用两种拼法出现两次也是错误。 + +平台键是 xlings 自己的 —— `linux`、`macosx`、`windows`,外加 `default`。`macos` +与 `macosx` 是同一个平台的两套词汇(mcpp 的三元组说前者,描述符与 xlings 的项目 +文件说后者),**凡是点名平台的地方两者都接受**。表里既没有本机这一项也没有 +`default`,就表示在这里什么都不声明。 + +### 两条解析轴 —— 宿主与目标(mcpp 2026.9.6.4+) + +一条工具条目回答的是两个不同问题中的一个,写在哪张表里决定了是哪一个: + +| 写法 | 轴 | 按什么解析 | +|---|---|---| +| `[xlings.workspace]`,平台键写在值里 | 宿主 | 跑这次构建的机器 | +| `[target..xlings.workspace]` | 目标 | 解析后的目标(`--target`,否则是宿主) | + +两种写法都是正确的,谁也不取代谁。在构建机上执行的工具属于宿主轴;产物编译或链接 +时对着的载荷属于目标轴。 + +```toml +[xlings.workspace] +"xim:dpcpp" = "7.1.0" # 一个编译器,它在本机上跑 + +[target.'cfg(os = "linux")'.xlings.workspace] +"xim:glibc" = "" # 设备单元编译时对着的东西 +"xim:linux-headers" = "" +``` + +非交叉构建时两条轴指向同一个平台,所以把目标事实写在宿主轴上的工程是碰巧正确的, +而且照常工作。它在第一次被交叉构建时不再正确。**凡是产物编译或链接时对着的东西, +推荐写在目标轴上。** + +`[target..feature-xlings.]` 把条件与门组合起来,与 +`[target..feature-deps.]` 同形:selector 说的是哪些目标, +feature 说的是要不要。 + +```toml +[target.'cfg(os = "linux")'.feature-xlings.backend-vulkan] +"xim:shaderc" = "2026.3" +``` + +**这里的 selector 禁止命名被解析的层。** `c-abi`、`c++-abi`、`compiler`、 +`compiler-runtime`、`kernel-abi` 由依赖解析回答,而依赖解析发生在工具安装之后、 +构建程序运行之后。按这些层条件化的工具会被声明却永远装不上——构建照常成功,工具 +就是不在——所以这样的 manifest 会被拒绝,并把工具与谓词都点出来。改成按目标条件化, +或者用 feature 做门:`[feature-xlings.]` 在任何东西被供给之前就已知。 + +**`accelerator` 是例外,它被接受**(mcpp 2026.9.6.5)。它不由任何东西解析而来: +它是 `--accel`,或 `[build] accel`,在查找第一个包之前就已读入。以它为谓词的载荷 +与三元组谓词在同一趟合并,并像其它载荷一样被安装。 + +```toml +[target.'cfg(accelerator = "cuda")'.xlings.workspace] +"xim:cuda-nvcc" = "12.9.86" +"xim:cuda-cudart" = "12.9.79" +``` + +带设备孤岛的工程应当用这种写法。没有它,厂商工具包只能无条件声明或者干脆不声明, +于是不带加速器的 `mcpp build`——最便宜的那次构建,也是 CI 通常跑的那次——会为一个 +它根本没在编译的设备下载数 GB。 + +依赖同理:`[target.'cfg(accelerator = "cuda")'.dependencies]` 生效,而以被解析的层 +为条件的依赖不生效,因为后者会决定它正在询问的那个答案。加速器这条路径上没有任何 +循环。 + +条件只写在 selector 一处。selector 之下的值如果又带平台键,就是同一件事说了两遍, +会被拒绝,并把两半都指出来: + +``` +[target.cfg(os = "linux").xlings.workspace] xim:tool: the value carries platform +keys (linux, macosx), but [target.cfg(os = "linux")] already says which targets +this applies to. +``` + +`subos` 不按目标条件化:一个工程只有一个环境,所以 `[target..xlings]` +拒绝这个键,而不是把它丢掉。 + +**已发布的描述符不为目标轴条目携带边**,`mcpp publish` 会说明这一点。描述符按平台 +分块,而 selector 不是平台 —— `cfg(target_arch = "aarch64")` 不对应那份文件里的任何一块。 +**使用者**装到的东西来自顶层 `[xlings.workspace]`;目标轴对"本包自己的构建对着什么" +仍然是正确的。 + +这两条轴所属的一般规则见 [SPEC-004](../specs/manifest-semantics.md)。 + +### `when` —— 哪些命令需要这个工具(mcpp 2026.9.4.2+) + +```toml +[xlings.workspace] +"xim:qemu-arm" = "9.2.4-1" # 每次构建都装,与从前一样 +"xim:codegen" = { version = "1.0", when = "build" } +"xim:probe-rs" = { version = "0.24.0", when = "run" } +"xim:clang-tidy" = { version = "20", when = "dev" } +``` + +包依赖从一开始就有这条轴 —— `[dependencies]`、`[build-dependencies]`、 +`[dev-dependencies]`。工具只有一张表,于是一个同时点名模拟器与调试探针的板级支持包 +会把两个都装给每一位消费者,包括只想把库编出来的那一位。 + +| `when` | 由谁安装 | 是否传播到消费者 | +|---|---|---| +| *(不写)* | 每个构建命令 | 是 | +| `build` | 每个构建命令 | 是 | +| `run` | `mcpp run`、`mcpp test` | 是 | +| `dev` | 只有声明它的那个包作为根时 | **否** | + +**不写 `when` 就是 2026.9.4.2 之前的行为**,所以没有任何清单需要改。收窄是可选动作, +不是作者必须回答的新问题。 + +`dev` 是唯一不传播的一档。它的含义是「声明它的那个包自己在被开发时」,所以依赖的 +`dev` 条目永远不会为消费者安装。其余各档都会到达消费者 —— 这正是板级包知道自己机器 +的意义:它声明一次模拟器,每一位消费者都拿得到。 + +档位写在**条目**上而不是另开一张表,理由与 `[dependencies]` 同时接受 `dep = "1.0"` +和 `dep = { version = "1.0", features = [...] }` 是同一条。带档位的条目**必须**写出 +`version`,哪怕留空(`version = ""` 表示「存在即可,版本不限」)—— 否则 +`{ when = "run" }` 与写错的 `version` 键无法区分。 + +### `[feature-xlings.]` —— 某个 feature 才需要的工具 + +```toml +[features] +default = ["emulator"] +emulator = {} +hardware = {} + +[feature-xlings.hardware] +"xim:probe-rs" = "0.24.0" +``` + +同一张表,按 feature 门控,拼法沿用 `[feature-deps.]`。**不要 `hardware` +的消费者永远不会下载探针驱动。** 这里的条目同样接受 `when`。 + +`[features]` 里没有声明过的 feature 名会作为 schema 警告报出:它对谁都不激活、什么 +都不装,而这种工具的缺席只表现为「设备就是连不上」,是最难诊断的一种。 + +### 规则包自带它的环境(2026.9.6.6+) + +上面那张表是工程**有主张**时写的。多数工程没有主张,写下的也就是空无一物: + +```toml +[build-dependencies.mcpp] +plugins = { version = "0.3.0", features = ["rules-cuda"], host-module = true } +``` + +这一条边就是全部声明。规则包在选中它的那个 feature、它所服务的加速器之下,声明自己 +需要哪些包、最低到哪一版: + +```toml +# 写在规则包里,不写在你的工程里 +[target.'cfg(accelerator = "cuda")'.feature-xlings.rules-cuda] +"xim:cuda-nvcc" = ">=12.9.86" +"xim:cuda-cudart" = ">=12.9.79" +``` + +**两重门,都要开。** feature 说「要不要这个规则」,selector 说「哪些构建真的下载」。 +同一个工程的 CPU-only 构建两道门都不过,一个字节都不装。 + +「需要哪些包、最低到哪一版」是规则作者的知识。在每个用它的工程里重写一遍,是一份会 +悄悄过期的副本——规则动了,而那些工程不会跟着动。 + +### 一个包一个版本(2026.9.6.6+) + +工具地址是 `[:][@<版本>]`,它的**身份是 `(namespace, name)` 二元组**。版本是 +这个包上的约束,不是它名字的一部分,所以 `xim:glibc`、`xim:glibc@2.40` 与 +`xim:glibc@>=2.38` 指的是同一个包。**一次构建只装它的一个版本。** + +装哪一个,分两步决定。 + +**裁决——离产物更近的声明赢。** 工程压过它依赖的包,于是一个钉覆盖规则的要求: + +```toml +# 工程侧,当它确实有主张时 +[target.'cfg(accelerator = "cuda")'.xlings.workspace] +"xim:cuda-nvcc" = "13.3.33" +``` + +**不带版本的声明弃权:** 它陈述了「要这个包」而没有陈述「要哪一版」,因此不会仅仅 +因为更近就压过一条下界。两条声明都带版本且不一致时,mcpp 会报出用了哪一条——一个 +只能表现为「声明了两个版本而目录里有一个」的覆盖,是要读者自己去文件系统里重建的 +事实。 + +**校验——赢家必须满足每一条落败的要求。** `>=`、`^`、`~` 以及逗号组合是**要求**。 +不满足的钉被拒绝,并同时点出两侧: + +``` +error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins + requires >=12.9.86. + One version of a package is installed, so the two cannot both hold. + fix: pin a version satisfying >=12.9.86, or drop the pin and let the + requirement decide. +``` + +裸版本是**选择**而不是要求:两条互不相同的精确钉走裁决并被报告,不被拒绝。只有被 +陈述出来的要求才谈得上违反。 + +**这是一次比较,不是一次搜索。** 版本由裁决选定、再被检查,所以 mcpp 从不需要问索引 +「有哪些版本」,也就不带约束求解器。代价被写出来而不是藏起来:一个求解器本可满足的 +组合——工程写 `>=8.0`、规则写 `8.5.0`、而索引里最新是 8.3——会被拒绝,而拒绝消息里 +写着怎么往下走。 + +范围在两个方向上都被求解。`>=2026.1` 装到满足它的最高已发布版本,`>=2099.1` 作为 +不可满足被拒绝,而 `mcpp::xpkg_dir` 回答满足该范围的最高**已安装**版本——声明了下界 +的规则找得到下界带进来的东西。 + +### 工程没点名的工具,其版本的来源 + +| 工程声明了 | 版本来自 | +|---|---| +| `[xlings.workspace]`,无 `subos` | 机器的环境,工程自己的条目叠在上面 | +| `[xlings.workspace]` 与 `subos = "<名>"` | 那个环境自己的 workspace;机器的不适用 | +| 两者都没有 | 机器的环境 | + +中间那行不是遗漏。指名的环境有自己的已安装集合,把机器的版本带进去会指向那里不 +存在的版本。**写 subos 就是要隔离,不写就是要机器的环境加上自己的条目。** + +在工程内执行的 `xlings use` 压过这张表,直到 mcpp 重写环境为止——它是最后合并的 +那一层。 + +### `deps`,已被取代 + +`deps = ["xim:qemu-riscv@9.2.4-1"]` 是同一句话在 2026.9.3 之前的拼法。它仍然生效, +并且会被报告一次,同时给出该写的 `[xlings.workspace]` 那一行。**不拒绝**——拒绝会 +落到**依赖**的 manifest 上,而钉了那个包精确版本的工程改不了它。 + +### `envs`,已移除 + +`[xlings.envs]` 曾被物化进 `.xlings.json`,而没有任何东西读它:程序的环境由它自己 +的包声明,环境的环境由那个环境声明。现在这个键是错误,并同时点名这两者。索引里没有 +任何包用过它。 diff --git a/docs/zh/21-commands-by-scenario.md b/docs/zh/21-commands-by-scenario.md index 14037f36..38a38ebd 100644 --- a/docs/zh/21-commands-by-scenario.md +++ b/docs/zh/21-commands-by-scenario.md @@ -181,3 +181,135 @@ warning: pre-v1 cache at '/home/speak/.mcpp/bmi' occupies 167.5 MiB and is no lo - `mcpp cache gc --older-than 0` 以 `bad --older-than value '0' (expected {s,m,h,d})` 被拒绝,而 `mcpp clean --stale --older-than 0` 接受。 两个选项共用一个 parser,但这一种取值上不一致。 + +## `[hooks]` —— 项目构建生命周期命令(实验性) + +> **实验性。** Hook 目前**不能**决定一次构建成功与否。所有 Hook 失败都以 +> **warning** 报出,`mcpp build` 保留它自己挣来的结果;`side_effect = true` +> 会被报错拒绝,而不是被采纳。这个键保留在 schema 里,这样今天写下的 manifest +> 在该功能转正时无需改动。另有两条限制是永久的、不是临时的:**只有根项目的 Hook +> 会执行**,而且**只有 `mcpp build` 会执行它们**。 + +Hook 是 `mcpp build` **在一段区间内持有**的命令,事件名就是那段区间: + +```toml +[hooks] +build_start = "echo build started" +build_failed = "notify-send 'build failed'" +build_finished = "notify-send 'build finished'" + +# 可选;以下是默认值。 +timeout_seconds = 10 +enabled = true +side_effect = false # 实验期内 `true` 会被拒绝 +``` + +| 键 | 类型 | 默认值 | 它命名的区间 | +|---|---|---:|---| +| `build_start` | 命令 | — | 项目准备完成后开启,命令退出时闭合 | +| `build_finished` | 命令 | — | 构建成功后开启,命令退出时闭合 | +| `build_failed` | 命令 | — | 构建失败后开启,命令退出时闭合 | +| `during_build` | 命令 | — | 构建开始前开启,构建结束后闭合 | +| `timeout_seconds` | 整数,1–86400 | `10` | 单次运行的时限 | +| `enabled` | 布尔 | `true` | 是否启用本表中的全部命令 | +| `side_effect` | 布尔 | `false` | Hook 失败是否让本次构建失败。**保留键**——实验期内只接受 `false` | + +前三个区间是**自闭合**的——命令退出,区间就结束。"同步"在这里不是一种单独的模式, +它就是自闭合区间的样子。`during_build` 是唯一由别的东西闭合的区间,而那两个只对其中 +一种形状有意义的键,是从这一点推出来的,不是额外规定的例外。 + +命令写成字符串;需要选项时写成表: + +| 表内键 | 适用于 | 含义 | +|---|---|---| +| `cmd` | 所有事件 | 命令本身,必填 | +| `timeout_seconds` | 自闭合事件 | 覆盖本表默认值 | +| `loop` | `during_build` | 命令在区间闭合前退出时重新启动 | + +`loop` 写在自闭合事件上、`timeout_seconds` 写在 `during_build` 上,都是**错误**而不是 +被忽略的键:自闭合区间随命令退出而结束,没有东西可重启;而 `during_build` 已经由构建 +定界。一个被接受却什么都不做的键,读起来就是"这功能坏了"。 + +命令通过宿主 Shell(`/bin/sh` 或 `cmd.exe`)执行,工作目录是**项目根目录**——不是敲 +`mcpp build` 的那个目录,所以 Hook 里的相对路径在哪儿发起构建都指同一处。自闭合命令 +的标准输入、输出和错误沿用普通终端行为。没有配置的事件直接跳过。 + +生命周期为: + +```text +during_build 开启 +build_start + ├─ 构建成功 → during_build 闭合 → build_finished + └─ 构建失败 → during_build 闭合 → build_failed +``` + +`during_build` 在终止 Hook **之前**闭合,因此两条命令不会重叠执行。 + +`build_failed` 与 `build_finished` 互斥,而且两者都只在 `build_start` 已经执行之后 +才可达。项目**准备**阶段就失败的情况——manifest 非法、依赖无法解析、没有可用工具链 +——一个 Hook 都不触发:此时构建尚未开始,而 Hook 程序本身可能正是准备阶段要装的东西。 + +命令无法启动、返回非零或超过时限均视为 Hook 失败。`during_build` 还多一种:开了 `loop` +的命令**起不来**——连续五次在一秒内以非零状态结束——就不再重启,并被报出来。(很快就 +成功结束的命令,正是 `loop` 被要求重复的那件事,不算失败。)以上每一种都以 **warning** +报出,构建保留它自己挣来的结果——`[hooks]` 还在实验期,它没有投票权。Hook 自身失败不会 +再触发另一个 Hook。 + +改变这一点的正是 `side_effect = true`,而今天写它是一个错误: + +```text +error: mcpp.toml: error: [hooks].side_effect = true is not available yet: +[hooks] is experimental and cannot decide whether a build succeeded. … +``` + +是拒绝而不是悄悄降级,因为两种沉默的做法都更糟:采纳它等于让一个实验性功能对每一次 +构建都有否决权;忽略它则让项目以为自己的构建被通知程序把着关,而实际上没有。功能转正 +后,`true` 的含义是"Hook 失败让构建失败"——而构建自身失败时仍保留它自己的退出码,所以 +`mcpp build` 不会把一次编译错误报成通知程序的问题。 + +关于 `during_build` 有两件事值得单独知道: + +- **它的输出被丢弃**,因为它与构建并发写出,否则会插进某条编译诊断的中间。要看它的 + 输出就跑 `mcpp build --verbose`。 +- **停止的单位是进程树,不是进程。** `player & wait` 让播放器成为 mcpp 所启动那条命令 + 的孙子进程,只停掉后者会让音频设备在构建结束后仍被占着。mcpp 把命令放进它自己的 + 进程组(Windows 上是 job object)并停止整组,构建被 Ctrl-C 打断时也一样。 + +作用范围: + +- 只有 `mcpp build` 执行 Hook。`mcpp run`、`mcpp test` 和 + `mcpp build --configure-only` 同样会构建,但有意不执行。 +- Hook 属于**被构建的那个包**。workspace 展开时就是逐个成员:各自的 `[hooks]`、 + 各自的构建、各自的根目录。**虚拟** workspace 根(只有 `[workspace]` 没有 + `[package]`)不构建任何东西,写在那里的 `[hooks]` 永不触发。 +- 依赖的 `[hooks]` **一律跳过**,只有根项目的会执行。mcpp 解析的每一份 manifest 都 + 带着这一节,依赖的也带,而没有任何东西去读它——这正是"装一个包"不会变成"在我下次 + 构建时跑包作者的 Shell 命令"的原因。这是设计的性质,不是一个等着被打开的默认值。 +- 声明了生效的 Hook 就等于让项目放弃空转快路径,因为 `build_start` 规定在准备阶段之后 + 执行。对已经是最新状态的带 Hook 项目,`mcpp build` 的代价是一次准备,而不是毫秒级。 + +`[hooks]` 里、以及某个事件表里不认识的**键**都是 warning(`--strict` 下为错误),所以为更新版 mcpp 写的 +manifest 在这一版仍能加载;不认识的**值**——`cmd` 缺失或不是字符串、`timeout_seconds` 不在 +1–86400 之间、键写给了错误的区间——是 manifest 错误。 + +> **Hook 是代码,而 `mcpp.toml` 是仓库的一部分。** 构建一个刚克隆下来的项目,会以 +> 执行 `mcpp build` 的那个账户的权限,运行它 `[hooks]` 里写的任何东西。这与 +> `build.mcpp`([07 — build.mcpp](07-build-mcpp.md))已经要求的信任是同一份; +> `[hooks]` 扩大的是它的范围,而不是引入了一份新的信任。 + +Hook 程序可以作为普通 xlings 依赖安装。例如,音频通知程序可以把音频内置进自己的 +可执行文件,无需让 mcpp 处理媒体资源: + +```toml +[hooks] +build_finished = "mcpp-hooks-audioplayer niulai-mm" +build_failed = "mcpp-hooks-audioplayer niulai-niulai" +side_effect = false + +[xlings] +deps = ["xim:mcpp-hooks-audioplayer@0.0.1"] +``` + +根据构建成功或失败播放不同提示音。`side_effect = false` 写出来而不是靠默认值:它是 +这份 manifest 自己就想要的值——缺个音频设备不该让构建失败——所以等这个键有了不止 +一个可接受的值之后,它仍然会这么写。 From 3d75224bc58990ded8897a88f1e15d27f64810c6 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:11:25 +0800 Subject: [PATCH 06/36] docs: the design tree gets an index it cannot drift from, and the specs stop being exempt by accident MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 269 design records, 109,146 lines, and the whole index was the heading `# 开发/方案文档目录`. It is generated now -- by date, newest first, with each record's own title -- and CI compares the file against the generator. What the generator derives is mechanical: the date from the filename, the title from the first heading. `subject` and `status` are read from front matter and shown only when a record declares one, because a subject inferred from filename keywords would misfile records, and a misfiled record is worse for a reader than an unfiled one. The 268 records that predate the convention are not rewritten: a record describes the moment its change was made, and a pass adding a field nobody chose would edit documents whose value is that they are not edited. `status` is what the tree lacked most. Opening the accelerator design today does not tell a reader that a v2 and then a v3 moved past it; the filename carries that for the two authors who thought of the convention. The register rules now cover `docs/specs/`. The old glob was `docs/*.md docs/zh/*.md`, which does not descend, so four specifications were exempt from the heading and second-person rules by accident. Extending it found one heading to reword and one term to mark as code -- `did-you-mean` contains `you`, which is a term of art and now reads as one. The PARITY loop still excludes them, because whether the specifications become English with a 简体中文 mirror is an open question rather than a defect a script should assert. Eight structure rules now, each falsified before it was committed. --- ...-documentation-architecture-three-trees.md | 5 + ...mples-curriculum-and-documentation-plan.md | 6 + .agents/docs/README.md | 323 +++++++++++++++++- .github/tools/check_docs_structure.sh | 29 ++ .github/tools/check_docs_style.sh | 9 +- .github/tools/gen_agents_index.py | 173 ++++++++++ docs/specs/package-identity.md | 4 +- 7 files changed, 545 insertions(+), 4 deletions(-) create mode 100644 .github/tools/gen_agents_index.py diff --git a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md index 5889f608..4d09229b 100644 --- a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md +++ b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md @@ -1,3 +1,8 @@ +--- +subject: docs +status: active +--- + # Three documentation trees, three audiences, and the rule for citing between them This restructures mcpp's documentation as a whole: the user documentation diff --git a/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md b/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md index a90bfe0e..b3712fc9 100644 --- a/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md +++ b/.agents/docs/2026-09-08-examples-curriculum-and-documentation-plan.md @@ -1,3 +1,9 @@ +--- +subject: docs +status: superseded +superseded_by: 2026-09-08-documentation-architecture-three-trees.md +--- + # A curriculum for the examples, a reference for the documentation, and a check with a denominator This plans the `examples/` tree and the `docs/` chapters together, because the diff --git a/.agents/docs/README.md b/.agents/docs/README.md index 839f7364..86f5fd97 100644 --- a/.agents/docs/README.md +++ b/.agents/docs/README.md @@ -1 +1,322 @@ -# 开发/方案文档目录 \ No newline at end of file +# Design records + +The reasoning behind changes to mcpp: what was measured, what was decided, +and what a later measurement refuted. A record describes the moment its +change was made and is not edited afterwards, so **nothing here is a +statement about the present**. What mcpp does today is in +[docs/](../../docs/README.md); what is guaranteed is in +[docs/specs/](../../docs/specs/README.md). + +**This file is generated** by `.github/tools/gen_agents_index.py` and is +checked in CI. A new record declares front matter: + +```yaml +--- +subject: heterogeneous # a short, reused word +status: landed # active | landed | superseded | abandoned +superseded_by: 2026-09-07-....md # when status is superseded +--- +``` + +269 records. + +## By subject + +Records that declare one. Everything else is listed by date below. + +### docs + +- [A curriculum for the examples, a reference for the documentation, and a check with a denominator](2026-09-08-examples-curriculum-and-documentation-plan.md) — superseded by [2026-09-08-documentation-architecture-three-trees.md](2026-09-08-documentation-architecture-three-trees.md) +- [Three documentation trees, three audiences, and the rule for citing between them](2026-09-08-documentation-architecture-three-trees.md) — active + +## By date + +### 2026-09 + +- [A curriculum for the examples, a reference for the documentation, and a check with a denominator](2026-09-08-examples-curriculum-and-documentation-plan.md) — superseded by [2026-09-08-documentation-architecture-three-trees.md](2026-09-08-documentation-architecture-three-trees.md) +- [Three documentation trees, three audiences, and the rule for citing between them](2026-09-08-documentation-architecture-three-trees.md) — active +- [第六轮生态复核:工具平面的目标轴](2026-09-07-round6-ecosystem-review.md) +- [一个包一个版本:xlings 地址的身份,以及 2026.9.6.5 之后的文档对齐](2026-09-07-package-identity-and-doc-alignment.md) +- [A module-first surface for graphics acceleration and heterogeneous computing](2026-09-07-module-first-heterogeneous-surface.md) +- [mcpp.toml 语义与风格的统一](2026-09-07-mcpp-toml-unified-semantics-design.md) +- [异构计算与图形的跨平台生态:完整矩阵与补齐方案](2026-09-07-heterogeneous-cross-platform-ecosystem.md) +- [通用构建基础设施:缺口、归属与验证](2026-09-07-general-build-infrastructure-gaps-design.md) +- [Ecosystem review — round 5b](2026-09-06-round5b-ecosystem-review.md) +- [Ecosystem review — round 5, F1 (llama.cpp on Vulkan)](2026-09-06-round5-f1-ecosystem-review.md) +- [The heterogeneous ecosystem, v3: what is built, what is not, and what decides each](2026-09-06-ecosystem-plan-v3.md) +- [多设备生态:实施计划与任务表](2026-09-05-multi-device-implementation-plan.md) +- [多设备加速生态:载荷、适配面、验证 lane 与框架验证](2026-09-05-multi-device-ecosystem-design.md) +- [Heterogeneous C++ builds and their ecosystem, v2: closing the host surface](2026-09-05-heterogeneous-build-ecosystem-design-v2.md) +- [加速器支持 实施计划](2026-09-05-accelerator-support-implementation-plan.md) +- [加速器支持:完整设计方案](2026-09-05-accelerator-support-design.md) +- [具名 runner、通用命令面、部分后端,与生态闭环](2026-09-04-named-runners-and-the-universal-command-surface.md) +- [Four gaps left by the ecosystem batch, and what to do about each](2026-09-04-four-gaps-after-the-ecosystem-batch.md) +- [商业级可用:mcpp × xlings 的裸机与嵌入式总体方案](2026-09-04-commercial-grade-baremetal-embedded-plan.md) +- [`[xlings]` is mcpp's surface for xlings' local project mechanism](2026-09-03-xlings-workspace-as-the-one-table.md) +- [The runner beyond bare metal: design](2026-09-02-runner-beyond-baremetal-design.md) +- [Issue #544: runner beyond bare metal — implementation plan](2026-09-02-issue544-runner-implementation-plan.md) +### 2026-08 + +- [Issue #540, verified: seven filed findings, six confirmed, one misaimed, four more underneath](2026-08-31-issue540-seven-audit-findings.md) +- [Project build hooks as owned intervals (#496)](2026-08-30-project-build-hooks-owned-intervals.md) +- [Three issues, measured: #532, #533, #534](2026-08-30-issues-532-533-534-analysis.md) +- [Four issues, measured: #527 (workspace half), #529, #535, #537](2026-08-30-issues-527-529-535-537-analysis-and-design.md) +- [Cross-repo fix plan: #532, #533, #534](2026-08-30-cross-repo-fix-plan-532-533-534.md) +- [The build-rule package: identity enforced, shape documented](2026-08-29-build-rule-package-spec.md) +- [issue #519:依赖的链接形态 —— 一条不变量,两个高度](2026-08-28-issue519-dependency-linkage-form.md) +- [目标侧被解析出来了,只发给了一个编译单元](2026-08-27-openkal-native-path-three-issues.md) +- [openkal 生态:接口判据、面的补全,与端口层的组合](2026-08-27-openkal-ecosystem-design-plan.md) +- [#516 解决方案:glob walk 在 Windows 上撞到 ANSI 代码页拼不出的目录名就崩](2026-08-27-issue516-windows-acp-glob-walk-fix.md) +- [支持矩阵:56 格实测,四处待修、一处生态空缺](2026-08-26-the-support-matrix-measured.md) +- [目标矩阵:六张表(载荷体系 / openkal 体系 × 三个构建机)](2026-08-26-target-matrix-six-tables.md) +- [目标矩阵:应该是什么,现在是什么,差在哪](2026-08-26-target-matrix-should-be-versus-is.md) +- [已经解析出的答案,没有被用来做决定](2026-08-26-resolved-but-not-consulted.md) +- [声明了却没被兑现:第八条,以及文档与词表的一次实测对账](2026-08-26-declared-but-not-made-to-exist.md) +- [`crossTarget` 非空被当成「系统来自图」](2026-08-26-cross-target-implies-graph.md) +- [aarch64 Linux:让整个生态可用,并且可测](2026-08-26-aarch64-linux-ecosystem-closure.md) +- [一个谓词族,六处缺陷:目标侧的分层判据](2026-08-25-the-two-layer-predicate-family.md) +- [目标体系分析:三元组承载了四件事,而它只有三段](2026-08-25-target-system-analysis.md) +- [OS × 工具链 × 目标:组合矩阵与理由](2026-08-25-os-toolchain-target-matrix.md) +- [mcpp 目标词表:一套规范,映射到各编译器](2026-08-25-mcpp-target-vocabulary-spec.md) +- [mcpp 目标侧设计](2026-08-24-target-side-design.md) +- [目标侧架构:五层、四来源、四规则](2026-08-24-target-side-architecture.md) +- [目标侧来自依赖图之后:七项优化方案](2026-08-24-graph-target-side-optimization-plan.md) +- [目标侧解析:预构建体系与构建期体系的统一架构](2026-08-23-target-side-resolution-architecture.md) +- [三项未完成事项的详细方案](2026-08-21-three-remaining-items-plan.md) +- [裸机生态的四项未完成事项:方案](2026-08-21-freestanding-outstanding-four.md) +- [裸机方向的优化方案](2026-08-21-baremetal-optimization-plan.md) +- [mcpp 在内核 / 嵌入式 / freestanding 方向的评估](2026-08-21-baremetal-ecosystem-assessment.md) +- [PR #455–#459 深度 review:裸机 freestanding 从「能编」到「能用」](2026-08-20-pr455-459-freestanding-review.md) +- [`mcpp pack` 生产侧与消费侧模型:整体架构 review](2026-08-20-pack-and-consumer-model-review.md) +- [openkal 0.4: what one portable program found, and what it says about the method](2026-08-20-openkal-portable-program-findings.md) +- [openkal: implementation plan and outcome](2026-08-20-openkal-implementation-plan.md) +- [openkal 设计方案:通用内核 ABI 规范](2026-08-20-openkal-design.md) +- [openkal: plan for industrial completeness](2026-08-20-openkal-completeness-plan.md) +- [openarch:接口层与多指令集后端的实现方案](2026-08-20-openarch-implementation-plan.md) +- [openarch 实现方案:arch 机制层](2026-08-20-openarch-implementation-design.md) +- [`mcpp pack` 的 `kind = "shared"` 产物带走了构建机:#460 的实测、根因与优化方案](2026-08-20-issue460-shared-library-runpath.md) +- [裸机 / 嵌入式 / 内核方向的生态定位与缺口(设计方案)](2026-08-20-freestanding-ecosystem-positioning.md) +- [freestanding 生态实施计划与依赖图](2026-08-20-freestanding-ecosystem-implementation-plan.md) +- [裸机:用户面能感受到的变化(场景 + 伪代码)](2026-08-20-baremetal-user-facing-scenarios.md) +- [裸机 / freestanding 支持 — 实施计划(2026-08-19)](2026-08-19-freestanding-baremetal-implementation-plan.md) +- [mcpp 裸机 / freestanding 支持 — 架构设计方案(2026-08-19)](2026-08-19-freestanding-baremetal-design.md) +- [裸机 / freestanding — 第三阶段:从「能跑」到「能用」](2026-08-19-baremetal-phase3-usable-plan.md) +- [裸机 / freestanding — 全生态打通实施计划(第二阶段)](2026-08-19-baremetal-ecosystem-closure-plan.md) +- [Windows 动态库分发、cl.exe 消费,与 `.ixx` 的默认支持](2026-08-18-windows-shared-library-and-module-extensions.md) +- [四项遗留的统一分析:判据挂错轴,以及 cl.exe 到底怎么办](2026-08-18-open-items-analysis-and-axis-discipline.md) +- [C++ 裸机 / freestanding:深度调研与 mcpp 路线分析(2026-08-18)](2026-08-18-freestanding-baremetal-analysis.md) +- [Windows 三条轴:落地报告(mcpp 2026.8.17.1)](2026-08-17-windows-three-axes-final-report.md) +- [库分发:`mcpp pack ` 与二进制包(2026-08-17)](2026-08-17-library-distribution-design.md) +- [mcpp 分发架构:全面分析与方案(2026-08-17)](2026-08-17-distribution-architecture-analysis-and-design.md) +- [Windows 工具链的三条轴:来源、SDK、运行时(2026-08-16)](2026-08-16-windows-toolchain-three-axes-design.md) +- [工具链架构 review:两种来源、选择与切换、构建与分发(2026-08-16)](2026-08-16-toolchain-architecture-review.md) +- [MSVC × xlings 生态打通 —— 综合报告(2026-08-16)](2026-08-16-msvc-ecosystem-final-report.md) +- [MSVC 在 xlings 生态里打通 —— 跨仓库计划、依赖与验收(2026-08-16)](2026-08-16-msvc-ecosystem-cross-repo-plan.md) +- [MSVC 纳入 mcpp 工具链体系 —— 设计 + 验证方案(2026-08-16)](2026-08-16-msvc-as-a-managed-toolchain.md) +- [xmake + clang 的 `import std`:错误消息把人指向了死路(2026-08-15)](2026-08-15-xmake-clang-import-std.md) +- [改一处实现,重编多少?—— 模块写法、编译器、与 BMI 的实际行为(2026-08-15)](2026-08-15-module-edit-granularity.md) +- [#426 #427 与 main 当前红 —— 核实与修复方案(2026-08-15)](2026-08-15-issues-426-427-analysis.md) +- [#412 #415 #416 #417 #418 #421 #422 —— 逐条核实与修复方案(2026-08-15)](2026-08-15-issues-412-422-analysis.md) +- [bench 可断续:每个测量点独立落盘,进度可算可显示(2026-08-15)](2026-08-15-bench-resumable-design.md) +- [bench 改为「本地真跑、CI 不跑」:方案与 Linux 实测计划(2026-08-14)](2026-08-14-bench-local-first-design.md) +- [mcpp 构建性能:架构层面的分析与方案(2026-08-13)](2026-08-13-build-performance-architecture.md) +- [构建性能优化:综合报告(2026-08-13)](2026-08-13-build-optimization-status.md) +- [模块化 C++ 构建性能深度分析与优化方案](2026-08-12-modular-build-performance-deep-analysis.md) +- [mcpp 冷构建深度优化方案](2026-08-12-cold-build-optimization-plan.md) +- [`bench/` 构建引擎基准套件 —— 架构与实施计划](2026-08-12-bench-suite-architecture-and-plan.md) +- [源文件角色表 与 build.mcpp 运行上限 —— 把两个硬编码变成两条声明](2026-08-11-source-kind-table-and-build-program-timeout.md) +- [`$ORIGIN` 被 SubOS farm 遮蔽 —— helloegui 运行期 undefined symbol 分析与修复方案](2026-08-11-runtime-search-origin-precedence-analysis.md) +- [`$ORIGIN` 优先级 + 共享库运行时契约 —— 实施计划](2026-08-11-origin-precedence-implementation-plan.md) +- [实施计划:运行期搜索闭包 与 binding 降级](2026-08-11-graphics-runtime-search-closure-implementation-plan.md) +- [图形栈剩下的那一半:链接期看得见、运行期看不见](2026-08-11-graphics-runtime-search-closure-and-binding-degradation.md) +- [PR #400 收尾设计方案 —— 重新判定阻塞点,并把串行收口改成并行](2026-08-10-pr400-completion-design.md) +- [图形栈全面不可用 —— 三层独立故障,和一条没有主人的依赖链](2026-08-10-graphics-stack-usability-design.md) +- [实施计划:图形栈闭合与分发档位](2026-08-10-graphics-closure-implementation-plan.md) +- [图形栈打通:一个标签、一条没人依赖的边、一个被钉住的 pin](2026-08-10-graphics-closure-and-distribution-tiers-design.md) +- [验收记录:图形栈闭合与分发档位(2026.8.10.2)](2026-08-10-graphics-closure-acceptance.md) +- [xlings × mcpp 生态契约收敛与优化设计](2026-08-09-xlings-mcpp-ecosystem-convergence-design.md) +- [PR #400 中文交接文档](2026-08-09-pr400-handoff-zh.md) +- [mcpp Template, Runtime, Graphics, and AUR Validation Ledger](2026-08-09-mcpp-template-runtime-graphics-aur-validation.md) +- [mcpp Template, Runtime, Graphics, and AUR Convergence Implementation Plan](2026-08-09-mcpp-template-runtime-graphics-aur-implementation-plan.md) +- [mcpp 模板、运行时、图形栈与 AUR 聚焦设计](2026-08-09-mcpp-template-runtime-graphics-aur-focused-design.md) +- [mcpp Open Issue 全量深度核验报告](2026-08-09-issue-triage-full-sweep.md) +- [xlings 运行时底座 —— 实施计划](2026-08-08-xlings-runtime-substrate-implementation-plan.md) +- [机器可读输出协议 —— 拆分实施计划](2026-08-08-wire-protocol-implementation-plan.md) +- [载荷版本与契约漂移:四个缺陷,一条线](2026-08-08-payload-version-and-contract-drift-design.md) +- [机器可读输出协议 —— 对 RFC #379 的核对与修正](2026-08-08-machine-readable-output-protocol-design.md) +- [Configure-Only Compile Database Implementation Plan](2026-08-08-configure-only-cdb-implementation-plan.md) +- [`build --configure-only` 与可靠 CDB 设计](2026-08-08-configure-only-cdb-design.md) +- [编译器是能力 —— 跨仓实施计划](2026-08-08-compiler-as-capability-implementation-plan.md) +- [xlings 作为 mcpp 的运行时底座:运行时身份、链接契约与环境契约](2026-08-07-xlings-as-runtime-substrate-design.md) +- [两处「模型比生态少一层」:Windows 资源输入 与 版本身份](2026-08-07-windows-resources-and-version-identity-design.md) +- [依赖提供物与构建期输入:两个缺口,同一个形状](2026-08-06-provisions-and-build-inputs.md) +- [命令长度:把「靠崩溃发现的规模上限」从架构上消掉](2026-08-06-command-length-architecture.md) +- [issue #355:依赖产出的 host 工具(codegen 工具链缺口)](2026-08-05-issue355-dependency-host-tools-design.md) +- [build.mcpp 机制架构设计:一个 hook,多种节点](2026-08-05-build-mcpp-extensibility-architecture.md) +- [`ci-fresh-install` 11 个 job 全红 —— 两个独立缺陷的修复方案](2026-08-04-ci-fresh-install-two-defects.md) +- [Windows → Linux 交叉工具链(路径 A:canadian-cross payload)— 设计方案](2026-08-03-windows-host-linux-cross-design.md) +- [issue #344:全局 build cache 的对象地址必须与消费方无关](2026-08-03-issue344-cache-object-address-design.md) +- [索引版本下限(E0006)不应让旧客户端不可用 —— 分析与方案](2026-08-03-index-floor-should-degrade-not-brick.md) +- [索引的可用性不得决定 mcpp 的可用性 —— 纯 mcpp 侧优化方案](2026-08-03-index-availability-must-not-decide-mcpp-availability.md) +- [B3 — 产物命名按 target 而非 host(交叉构建每次重链)— 修复方案](2026-08-03-b3-target-aware-artifact-naming.md) +- [Windows 可用性 — 实施计划](2026-08-02-windows-usability-implementation-plan.md) +- [Windows 可用性设计:裸机无感可用 + build.mcpp 全方言 + 测试面补齐](2026-08-02-windows-usability-design.md) +- [深度分析:issue #336(macOS 静态 libc++)与 mcpp-index PR #142(boost-ext.ut)](2026-08-02-issue336-pr142-analysis.md) +- [宿主编译单一生产者:让 build.mcpp 的能力等于 mcpp 的能力](2026-08-02-host-compile-single-producer-design.md) +- [宿主编译单一生产者 — 实施计划](2026-08-02-host-compile-implementation-plan.md) +- [issue #331 逐条核验 + Windows 无 MSVC 默认工具链分析](2026-08-01-issue331-windows-msvc-triage.md) +### 2026-07 + +- [`mcpp test --workspace`:跨平台模型一致性、输出差异与 macOS 停滞的根因分析](2026-07-31-test-workspace-observability-analysis.md) +- [`mcpp test` 可观测性与有界性 —— 实施计划](2026-07-31-test-observability-implementation-plan.md) +- [C++20 作为一等 `standard` 档位:`import std` 全平台可用性设计](2026-07-31-cpp20-standard-support-design.md) +- [C++20 档位支持 — 实施计划(单 PR,目标 2026.7.31.1)](2026-07-31-cpp20-implementation-plan.md) +- [索引刷新策略收敛:从「时间驱动」到「解析驱动」— 设计](2026-07-30-issue315-index-refresh-policy-design.md) +- [索引刷新策略收敛 — 实施计划](2026-07-30-issue315-implementation-plan.md) +- [BMI staging 原语 + BMI 缓存根收敛 — 实施计划](2026-07-30-issue311-implementation-plan.md) +- [BMI staging 原语 + BMI 缓存根收敛 — 设计](2026-07-30-issue311-bmi-staging-and-cache-root-design.md) +- [依赖构建产物的全局缓存收敛 — 设计](2026-07-30-dep-build-cache-scoping-design.md) +- [依赖构建产物的全局缓存收敛 — 实施计划](2026-07-30-dep-build-cache-implementation-plan.md) +- [日期版本号 + xlings pin 收敛 — 实施计划](2026-07-27-date-version-and-xlings-pin-plan.md) +- [日期版本号 + xlings pin 收敛 — 设计](2026-07-27-date-version-and-xlings-pin-design.md) +- [裸名 wire address 修复 — Implementation Plan(mcpp 0.0.109)](2026-07-26-bare-name-wire-address-implementation-plan.md) +- [裸名依赖的 wire address 收敛 — Design(mcpp 0.0.109)](2026-07-26-bare-name-wire-address-design.md) +- [xlings#381 索引键补齐命名空间维度 — 设计/优化方案](2026-07-25-xlings381-index-namespace-keying-design.md) +- [SPEC-001 落地 — Implementation Plan(mcpp 0.0.106)](2026-07-25-spec001-implementation-plan.md) +- [mcpp `name` / `namespace` 规范实现(定稿)](2026-07-25-name-namespace-canonical-implementation-spec.md) +- [`package.name` / `package.namespace` 双向验证报告](2026-07-25-name-namespace-bidirectional-verification-report.md) +- [#278 包身份双侧收敛 — Implementation Plan](2026-07-25-issue278-implementation-plan.md) +- [#278 包身份口径收敛 — 索引侧 + 依赖侧完整方案](2026-07-25-issue278-descriptor-name-form-canonicalization-design.md) +- [mcpp test 演进批次二 — Implementation Plan](2026-07-24-test-batch2-plan.md) +- [mcpp test 架构评估与设计方案](2026-07-24-mcpp-test-design-review.md) +- [批次三:#273 沙盒围栏 + CI wine 缓存 — Implementation Plan](2026-07-24-issue273-containment-plan.md) +- [嵌入式平台支持 — 方案设计 (Embedded Platform Support)](2026-07-24-embedded-platform-support-design.md) +- [mcpp test: per-test isolation, filter, JSON output — Implementation Plan](2026-07-23-test-isolation-json-plan.md) +- [v0.0.102 批次设计 —— #261 / #257 / #258 / #254(+#256 收尾)](2026-07-22-v0.0.102-batch-254-261-design.md) +- [索引组织迁移 + 采纳 xlings 0.4.68 per-repo artifact 来源 — 设计方案](2026-07-22-issue267-269-index-artifact-and-org-migration-design.md) +- [Issue #267/#269 索引组织迁移 + artifact 采纳 — 实施计划(0.0.103)](2026-07-22-issue267-269-impl-plan.md) +- [Issue 分析报告 —— #254 / #256 / #257 / #258 / #259 / #261](2026-07-22-issue-triage-254-261.md) +- [契约准入(Contract Admission)设计 —— 索引级 + 包级 floor](2026-07-22-contract-admission-design.md) +- [Issue #253 —— per-feature `flags` + per-OS `features` 设计](2026-07-20-issue-253-feature-flags-and-per-os-features-design.md) +- [v0.0.99 批次设计 —— #243 feature 转发 + #238 xlings 升级 + #230 windows 复验](2026-07-19-v0.0.99-feature-forwarding-238-230-design.md) +- [#233 对象路径消歧的两个后续缺口(#240 / #239)——根因分析与统一修复方案](2026-07-19-object-path-disambiguation-followups-239-240-design.md) +- [大型源码直编包全平台化:平台三修(#247/#248/#249)+ build.mcpp 构建期生成能力 + 描述符复杂度治理](2026-07-19-large-source-pkg-platform-fixes-and-buildmcpp-generation-design.md) +- [#230–#243 批次总账 + 架构评估(治理文档)](2026-07-19-issues-230-243-batch-ledger-and-architecture-assessment.md) +- [Issue #243 —— feature 依赖转发(`dep/feat`)设计](2026-07-19-issue-243-feature-forwarding-design.md) +- [mcpp 0.0.97 架构级修复 —— 实施设计与任务拆分(单 PR / 逐簇 commit)](2026-07-18-v0.0.97-architectural-remediation-implementation-plan.md) +- [Issue #215+ 分类分析与架构级修复方案](2026-07-18-issue-triage-215plus-architectural-remediation.md) +- [mcpp 通用构建能力需求清单(G1–G9)](2026-07-17-mcpp-feature-requests.md) +- [汇编源一等公民 + 通用构建能力(G1–G9)设计方案](2026-07-17-asm-sources-and-general-build-capabilities-design.md) +- [工具链 × 目标 命名统一 — 设计方案(实现 + 显示 + 使用)](2026-07-15-toolchain-target-naming-unification-design.md) +- [Linux → Windows MinGW 交叉工具链 — 设计方案](2026-07-15-mingw-linux-cross-windows-design.md) +- [c++fly:一键启用"最新标准 + 全部实验特性"(语言 + 标准库)设计](2026-07-14-std-features-experimental-gate-design.md) +- [c++fly(0.0.91)单 PR 实施计划](2026-07-14-single-pr-091-implementation-plan.md) +- [工具链后端抽象层 + MSVC 原生构建 + MinGW 生态入驻 — 设计方案](2026-07-13-toolchain-backend-abstraction-msvc-mingw-design.md) +- [0.0.90 单 PR 实施计划(post-089 路线图全量)](2026-07-13-single-pr-090-implementation-plan.md) +- [0.0.89 后路线:std 模块方言旗标一致性(#210)+ 遗留优化清单 — 设计方案](2026-07-13-post-089-roadmap-and-std-dialect-flags-design.md) +- [MSVC System-Toolchain Detection — Implementation Plan](2026-07-13-msvc-system-toolchain-implementation-plan.md) +- [MSVC System-Toolchain Detection — Design](2026-07-13-msvc-system-toolchain-detection-design.md) +- [编译器方言触点审计(0.0.88 基线)](2026-07-13-compiler-dialect-touchpoint-audit.md) +- [项目本地模式下的索引作用域:别把默认全局的官方索引(xim)注入项目组(架构分析 + 修复设计)](2026-07-09-project-index-scope-global-infra-fix.md) +- [Scanner backend abstraction: per-package opt-in, P1689 as lingua franca, plan-vs-ddi reconciliation (Design)](2026-07-08-scanner-backend-abstraction-design.md) +- [Root-cause remediation for the 0.0.85 rollout incidents (Design)](2026-07-08-root-cause-remediation-design.md) +- [Index version semantics + descriptor grammar v2 (long brackets) + single-source-of-truth lint (Design)](2026-07-08-index-version-semantics-and-descriptor-grammar-design.md) +- [Descriptor & index evolution — 0.0.85 release train roadmap (cross-repo)](2026-07-08-descriptor-index-evolution-roadmap.md) +- [Hermetic toolchain link model — one-shot cross-repo fix for issue #195](2026-07-07-hermetic-toolchain-link-model-design.md) +### 2026-06 + +- [Workspace-aware `mcpp test` + zero-shell self-contained mcpp-index (Design)](2026-06-30-workspace-test-and-zero-shell-index-design.md) +- [Bare OS-alias sugar for `[target.*]` conditional tables (Design)](2026-06-30-target-bare-alias-sugar-design.md) +- [L3 `build.mcpp` — native imperative build program (implementation design)](2026-06-30-l3-build-mcpp-implementation-design.md) +- [The `mcpp` build-module library for `build.mcpp` (Architecture & Design)](2026-06-30-build-mcpp-module-library-design.md) +- [Windows Runtime-DLL Deployment & `compat.openblas` Windows Support (Design)](2026-06-29-windows-runtime-dll-deployment-and-openblas.md) +- [mcpp.toml: Build Environment, Platform-Conditional Config, and `build.mcpp` (Design)](2026-06-29-manifest-environment-and-platform-design.md) +- [Feature System v2 — Stage 2: feature-activated optional dependencies (Design)](2026-06-29-feature-optional-dependencies-s2-design.md) +- [Feature System v2 — Capability-Oriented Model (Design)](2026-06-29-feature-capability-model-design.md) +- [分析报告:`abi:` 能力检查把「libc ABI」与「C++ stdlib」混为一谈 —— glfw 在 clang/libc++ 下误报 ABI mismatch](2026-06-27-glfw-abi-glibc-vs-libcxx-conflation-analysis.md) +- [设计:ABI/工具链兼容性「维度化」模型 —— 一步到位的单 PR 方案](2026-06-27-abi-compat-model-single-pr-design.md) +- [LLVM libatomic 自包含缺口分析与修复设计(libatomic.so.1 cannot open)](2026-06-26-llvm22-libatomic-self-containment-design.md) +- [Identity-First Package Resolution — Filename Is Not a Key](2026-06-26-identity-first-resolution-no-filename.md) +- [gtest_main 冲突修复:src 轨 feature 控制 + dev 轨 main 检测 + `mcpp add --dev`](2026-06-25-gtest-main-feature-and-add-dev-design.md) +- [依赖入口对象的条件链接(fix `mcpp test` duplicate `main`)设计方案](2026-06-25-dependency-archive-linking-design.md) +- [compile_commands.json 测试覆盖缺失:分析报告与设计方案](2026-06-25-cdb-test-coverage-design.md) +- [离线优先的索引刷新 + mcpp-index 发布机制](2026-06-24-offline-first-index-and-mcpp-index-publish.md) +- [macOS `mcpp build`:`library not found for -lSystem` 根因与修复](2026-06-24-macos-link-lsystem-sdk.md) +- [Issue #43 分诊与关闭记录 — macOS 全新安装首跑](2026-06-24-issue43-macos-first-run-triage.md) +- [Termux / Android (aarch64) 适配分析报告](2026-06-23-termux-android-adaptation.md) +- [原生 aarch64 musl-gcc 用 musl 1.2.5 重建(canadian-cross)— 复现指南](2026-06-23-aarch64-musl-gcc-canadian-cross-rebuild.md) +- [mcpp 后续修复:统一汇总 + 方案设计 + PR 拆分](2026-06-22-mcpp-followups-design.md) +- [aarch64 / Android 支持 —— 跨仓库 MVP 顶层设计方案](2026-06-22-aarch64-android-mvp-design.md) +- [跨仓库修复方案:残缺/被删依赖被 `.mcpp_ok` 盲区放过](2026-06-21-xcb-and-install-integrity-cross-repo-fix.md) +- [Package Resolution Architecture — Identity-First Locator Design](2026-06-20-package-resolution-architecture.md) +- [Runtime Launch Hygiene & Multi-Distro Coverage Implementation Plan](2026-06-19-runtime-launch-and-multi-distro-plan.md) +- [Pack Mode Redesign — Two-Axis Model, Clearer Names, `system` Mode](2026-06-19-pack-mode-redesign.md) +- [Per-Target 构建配置设计:配置发散归编译单元,目标只携本地标志](2026-06-18-per-target-build-config-design.md) +- [CLI Modularization — Architecture & Implementation Plan](2026-06-10-cli-modularization.md) +- [Library / Component Download Progress — Design](2026-06-09-library-download-progress-design.md) +- [mcpp.toml Schema 所有权设计:语法封闭 · 词汇开放](2026-06-04-manifest-schema-ownership.md) +- [mcpp core: runtime closure (rpath) + toolchain defaults](2026-06-03-runtime-closure-and-toolchain-defaults.md) +- [mcpp 模板系统(package-based templates)— 设计 v2](2026-06-03-package-templates.md) +- [mcpp: GL Runtime Closure Plan](2026-06-03-gl-runtime-closure-plan.md) +- [Module-First Usage Requirements Architecture](2026-06-02-usage-requirements-architecture.md) +- [2026-06-02 imgui mcpp dependency fixes](2026-06-02-imgui-mcpp-dependency-fixes.md) +- [Dotted Dependency Selector Architecture](2026-06-02-dotted-dependency-selectors.md) +- [C++ 标准一等配置设计](2026-06-01-cpp-standard-first-class-design.md) +- [CI 工具链缓存优化分析](2026-06-01-ci-toolchain-cache-optimization.md) +### 2026-05 + +- [Index Refresh And Dependency Cache Label Fix](2026-05-31-index-refresh-cache-labels-plan.md) +- [mcpp 0.0.35: Package-Owned Build Metadata Plan](2026-05-30-package-owned-build-flags-plan.md) +- [BMI Cache And Custom Index Build Fix](2026-05-30-bmi-cache-custom-index-fix.md) +- [mcpp build 报错输出优化方案](2026-05-26-build-error-output-optimization-plan.md) +- [设计方案:中断安装统一恢复机制](2026-05-23-interrupted-install-recovery-design.md) +- [mcpp 可观察性设计方案](2026-05-22-observability-design.md) +- [Bug 分析:LLVM 共享库 RUNPATH 失效的完整链路](2026-05-22-llvm-runpath-bug-analysis.md) +- [Fix: LLVM shared libraries have stale RUNPATH after install](2026-05-22-fix-llvm-shared-lib-runpath.md) +- [Fallback 代码提取方案 — 代码架构重构](2026-05-22-fallback-code-extraction-plan.md) +- [mcpp Fallback 架构设计方案](2026-05-22-fallback-architecture-design.md) +- [设计方案:ensure_base_init_ok + mcpp self init --force](2026-05-22-ensure-base-init-design.md) +- [分析:Ctrl+C 中断 bootstrap 后 mcpp 进入不可用状态](2026-05-22-ctrl-c-interrupted-bootstrap-analysis.md) +- [resolve_xpkg_path() 的 copy 优先级问题分析](2026-05-22-copy-priority-analysis.md) +- [设计方案:Payload-first 工具链环境管理](2026-05-21-payload-first-sysroot-design.md) +- [Linux sysroot 缺少内核头文件导致 std module 预编译失败](2026-05-21-linux-sysroot-missing-kernel-headers.md) +- [MSVC STL Discovery — msvc.cppm 模块设计](2026-05-20-msvc-stl-discovery-plan.md) +- [Windows 平台成熟度提升方案](2026-05-19-windows-platform-maturity-plan.md) +- [Windows 成熟度提升 V2 方案](2026-05-19-windows-maturity-v2-plan.md) +- [Windows E2E 与 macOS 对齐方案](2026-05-19-windows-e2e-parity-plan.md) +- [Windows Pack Design](2026-05-19-pack-windows-design.md) +- [Windows LLVM/Clang 支持设计方案](2026-05-17-windows-llvm-support-design.md) +- [mcpp](2026-05-16-readme-draft.md) +- [macOS Support Design — LLVM/Clang 自含工具链方案](2026-05-16-macos-support-design.md) +- [macOS LLVM 默认工具链跨平台适配方案](2026-05-16-macos-llvm-default-toolchain.md) +- [`[indices]` 功能增强设计方案](2026-05-16-indices-enhancement-design.md) +- [mcpp 功能特性清单](2026-05-16-feature-list.md) +- [MCPP 跨平台 Clang/LLVM 支持分析报告](2026-05-16-cross-platform-clang-analysis.md) +- [std.compat 支持 + cxx_scan restat + 增量零重编 E2E](2026-05-15-stdcompat-restat-e2e.md) +- [Fingerprint 稳定性与 Fast-Path 一致性 — 优化方案](2026-05-15-fingerprint-stability-and-fastpath-coherence.md) +- [Clang 编译管线平权 + 工具链抽象层设计](2026-05-15-clang-parity-and-toolchain-abstraction.md) +- [MCPP LLVM/Clang Toolchain Support Analysis and Design](2026-05-13-llvm-clang-toolchain-support-design.md) +- [Workspace Phase 1 Implementation Plan](2026-05-12-workspace-implementation-plan.md) +- [mcpp Workspace 设计方案](2026-05-12-workspace-design.md) +- [compile_commands.json 设计方案](2026-05-12-compile-commands-design.md) +- [mcpp 构建优化深度分析报告](2026-05-12-build-optimization-analysis.md) +- [Namespace Field Design — mcpp 0.0.6](2026-05-11-namespace-field-design.md) +- [2026-05-08 — 包索引仓库配置 (Package-Index Repo Configuration)](2026-05-08-package-index-config.md) + +## Undated + +Records written before the date prefix was the convention. + +- [Fix: xlings 包内 mcpp 的 MCPP_HOME 检测](fix-xlings-package-home-detection.md) +- [LLVM 工具链安装失败分析](llvm-install-failure-analysis.md) +- [Platform Abstraction Layer — Architecture & Implementation Plan](platform-abstraction-plan.md) +- [Remaining Platform Macros Outside src/platform/ — Analysis Report](platform-remaining-ifdefs-report.md) + +## todos/ + +Work items rather than records of a decision. + +- [aarch64-linux 生态适配分析报告](todos/2026-06-22-aarch64-linux-ecosystem-support-analysis.md) +- [aarch64 glibc-world / LLVM 工具链构建计划(deferred)](todos/2026-06-23-aarch64-glibc-world-llvm-buildout-plan.md) +- [TODO: e2e 套件按耗时分片(并行 matrix)](todos/2026-06-24-e2e-suite-sharding.md) +- [macOS 首跑遗留问题(已记录,待处理)](todos/2026-06-24-macos-first-run-remaining.md) diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh index bcc4e2f0..e9e574d3 100755 --- a/.github/tools/check_docs_structure.sh +++ b/.github/tools/check_docs_structure.sh @@ -10,6 +10,8 @@ # 4. every specification is listed in all three indexes # 5. every specification has a metadata table and a change record # 6. no emoji under docs/ or in a top-level README +# 7. the generated design-record index is current +# 8. a new design record declares its subject and status # # What it deliberately does NOT check: whether a chapter documents what is # implemented, whether an assertion's strength matches its evidence, or whether @@ -110,6 +112,33 @@ for f in docs/*.md docs/zh/*.md docs/specs/*.md README.md README.zh-CN.md; do fi done +# ── 7. the design-record index is current ──────────────────────────────── +# +# 269 records and the index was one heading. It is generated now, so it cannot +# drift -- and a generated file that is checked in must be compared against the +# generator or it drifts anyway. +python3 .github/tools/gen_agents_index.py --check || fail=1 + +# ── 8. a new design record declares its subject and status ─────────────── +# +# From the date the convention starts. The 268 records that predate it are not +# rewritten: a record describes the moment its change was made, and a pass that +# added a field nobody chose would edit documents whose value is that they are +# not edited. +CONVENTION_FROM="2026-09-08" +for f in .agents/docs/[0-9]*.md; do + [ -f "$f" ] || continue + d="$(basename "$f" | cut -c1-10)" + [[ "$d" < "$CONVENTION_FROM" ]] && continue + head -1 "$f" | grep -q '^---$' \ + || { bad "$f: a record dated $CONVENTION_FROM or later has no front matter"; continue; } + fmblock="$(awk 'NR>1 && /^---$/ {exit} NR>1' "$f")" + printf '%s' "$fmblock" | grep -qE '^subject: *[a-z]' \ + || bad "$f: front matter declares no \`subject\`" + printf '%s' "$fmblock" | grep -qE '^status: *(active|landed|superseded|abandoned) *$' \ + || bad "$f: front matter declares no valid \`status\` (active | landed | superseded | abandoned)" +done + if [[ "$fail" -eq 0 ]]; then echo "OK: docs structure checks pass" fi diff --git a/.github/tools/check_docs_style.sh b/.github/tools/check_docs_style.sh index 56e220ff..86b42ed3 100755 --- a/.github/tools/check_docs_style.sh +++ b/.github/tools/check_docs_style.sh @@ -32,7 +32,14 @@ headings() { ' "$1" } -for f in docs/*.md docs/zh/*.md; do +# THE REGISTER RULES COVER docs/specs/ TOO, AND USED NOT TO. +# +# The glob was `docs/*.md docs/zh/*.md`, which does not descend, so the four +# specifications were exempt from rules 1 and 2 by accident rather than by +# decision. They are in scope now. The PARITY loop below still is not: the +# specifications are 简体中文 with no docs/zh/ mirror, and whether that changes +# is an open question rather than a defect this script should assert. +for f in docs/*.md docs/zh/*.md docs/specs/*.md; do base="$(basename "$f")" # ── 1. heading register ─────────────────────────────────────────────── diff --git a/.github/tools/gen_agents_index.py b/.github/tools/gen_agents_index.py new file mode 100644 index 00000000..00fc3396 --- /dev/null +++ b/.github/tools/gen_agents_index.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Generate .agents/docs/README.md from the records themselves. + +The design tree holds 268 records and its index was one heading. This writes an +index that cannot drift, because `check_docs_structure.sh` compares the file on +disk against what this produces. + +WHAT IT DERIVES AND WHAT IT READS. + +The date comes from the filename, the title from the first `#` heading, and both +are mechanical. `subject` and `status` are read from YAML front matter when a +record declares it, and are shown only then -- a subject inferred from filename +keywords would misfile records, which is worse for a reader than no grouping. +New records declare it; the 268 that predate the convention are listed by date +with their titles, which is what they can support without anyone rewriting them. + +Usage: + python3 .github/tools/gen_agents_index.py # write + python3 .github/tools/gen_agents_index.py --check # exit 1 if stale +""" +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +DOCS = ROOT / ".agents" / "docs" +INDEX = DOCS / "README.md" + +DATED = re.compile(r"^(\d{4})-(\d{2})-(\d{2})-(.+)\.md$") +STATUSES = {"active", "landed", "superseded", "abandoned"} + + +def front_matter(text): + """Return the YAML-ish front matter as a dict, or {} when there is none.""" + if not text.startswith("---\n"): + return {} + end = text.find("\n---", 4) + if end < 0: + return {} + out = {} + for line in text[4:end].split("\n"): + if ":" not in line or line.startswith(" "): + continue + k, v = line.split(":", 1) + out[k.strip()] = v.strip() + return out + + +def title_of(text, fallback): + for line in text.split("\n"): + if line.startswith("# "): + return line[2:].strip() + return fallback + + +def collect(): + dated, undated, todos = [], [], [] + for p in sorted(DOCS.rglob("*.md")): + if p == INDEX: + continue + rel = p.relative_to(DOCS).as_posix() + text = p.read_text(errors="ignore") + fm = front_matter(text) + entry = { + "rel": rel, + "title": title_of(text, p.stem), + "status": fm.get("status", ""), + "subject": fm.get("subject", ""), + "superseded_by": fm.get("superseded_by", ""), + } + m = DATED.match(p.name) + if rel.startswith("todos/"): + todos.append(entry) + elif m: + entry["date"] = f"{m.group(1)}-{m.group(2)}-{m.group(3)}" + dated.append(entry) + else: + undated.append(entry) + dated.sort(key=lambda e: (e["date"], e["rel"]), reverse=True) + return dated, undated, todos + + +def row(e): + note = "" + if e["status"]: + note = f" — {e['status']}" + if e["status"] == "superseded" and e["superseded_by"]: + note += f" by [{e['superseded_by']}]({e['superseded_by']})" + return f"- [{e['title']}]({e['rel']}){note}" + + +def render(): + dated, undated, todos = collect() + out = [ + "# Design records", + "", + "The reasoning behind changes to mcpp: what was measured, what was decided,", + "and what a later measurement refuted. A record describes the moment its", + "change was made and is not edited afterwards, so **nothing here is a", + "statement about the present**. What mcpp does today is in", + "[docs/](../../docs/README.md); what is guaranteed is in", + "[docs/specs/](../../docs/specs/README.md).", + "", + "**This file is generated** by `.github/tools/gen_agents_index.py` and is", + "checked in CI. A new record declares front matter:", + "", + "```yaml", + "---", + "subject: heterogeneous # a short, reused word", + "status: landed # active | landed | superseded | abandoned", + "superseded_by: 2026-09-07-....md # when status is superseded", + "---", + "```", + "", + f"{len(dated) + len(undated) + len(todos)} records.", + "", + ] + + by_subject = {} + for e in dated + undated + todos: + if e["subject"]: + by_subject.setdefault(e["subject"], []).append(e) + if by_subject: + out += ["## By subject", "", + "Records that declare one. Everything else is listed by date below.", ""] + for subject in sorted(by_subject): + out.append(f"### {subject}") + out.append("") + out += [row(e) for e in by_subject[subject]] + out.append("") + + out += ["## By date", ""] + month = None + for e in dated: + m = e["date"][:7] + if m != month: + month = m + out += [f"### {month}", ""] + out.append(row(e)) + out.append("") + + if undated: + out += ["## Undated", "", + "Records written before the date prefix was the convention.", ""] + out += [row(e) for e in undated] + out.append("") + + if todos: + out += ["## todos/", "", + "Work items rather than records of a decision.", ""] + out += [row(e) for e in todos] + out.append("") + + return "\n".join(out) + + +def main(): + want = render() + if "--check" in sys.argv: + have = INDEX.read_text() if INDEX.exists() else "" + if have != want: + print("FAIL: .agents/docs/README.md is stale — run " + "`python3 .github/tools/gen_agents_index.py`") + return 1 + print("OK: .agents/docs index is current") + return 0 + INDEX.write_text(want) + print(f"wrote {INDEX.relative_to(ROOT)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/specs/package-identity.md b/docs/specs/package-identity.md index 28f83a52..0da69892 100644 --- a/docs/specs/package-identity.md +++ b/docs/specs/package-identity.md @@ -227,7 +227,7 @@ error: dependency 'asio': no package found asio = "1.38.1" ``` -该 did-you-mean 扫描**仅**在已失败路径触发,结果**只进错误文案**,禁止回灌解析、lockfile 或安装层。 +该 `did-you-mean` 扫描**仅**在已失败路径触发,结果**只进错误文案**,禁止回灌解析、lockfile 或安装层。 迁移 release 对旧的 compact dotted 搜索做两件事:已有 lock 继续固定已记录身份;无 lock 时若旧的 `mcpplibs.` primary 确实存在,warning 同时显示旧/新完整 selector,但仍不回退。 @@ -347,7 +347,7 @@ warning,规范列举命令是 `mcpp new --list-templates pkg`。 2. 某个索引开始使用短名形态时,其 `index.toml` 的 `min_mcpp` **必须**同步抬到 **0.0.106**。这是硬性的:更低版本的客户端会静默 `E_NOT_FOUND`,无技术手段绕过。 3. 同一索引内**可以**长期混存两种形态。 -### 8.2 索引作者应当怎么写 +### 8.2 索引作者的写法 ```lua package = { From 9463bfe11fa255009f1a2918618b92335536bd8a Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:12:49 +0800 Subject: [PATCH 07/36] docs: limits go last, the emit/parse mismatch is recorded, and links are checked Appending [hooks] to 21 had put it after that chapter's 'Current limitations', which is the one section that has to be last. Both languages reordered. `mcpp emit xpkg` produces a descriptor that `mcpp xpkg parse` rejects for a package that keeps its own mcpp.toml: the emitted mcpp segment carries `manifest = "mcpp.toml"` and no `sources` list, and the validator requires one. Isolated by adding the list by hand, which makes it validate. Recorded as a current limitation in 21 rather than fixed here. Rule 9: every relative Markdown link in docs/ and examples/ resolves. Rule 3 catches a chapter path named from anywhere including source comments; this catches a link whose target is not there. A chapter moved in this batch would satisfy one and break the other. --- .github/tools/check_docs_structure.sh | 21 ++++++++++++++ docs/21-commands-by-scenario.md | 42 +++++++++++++++++---------- docs/zh/21-commands-by-scenario.md | 36 +++++++++++++++-------- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh index e9e574d3..e17ac26b 100755 --- a/.github/tools/check_docs_structure.sh +++ b/.github/tools/check_docs_structure.sh @@ -12,6 +12,7 @@ # 6. no emoji under docs/ or in a top-level README # 7. the generated design-record index is current # 8. a new design record declares its subject and status +# 9. every relative link in docs/ and examples/ resolves # # What it deliberately does NOT check: whether a chapter documents what is # implemented, whether an assertion's strength matches its evidence, or whether @@ -139,6 +140,26 @@ for f in .agents/docs/[0-9]*.md; do || bad "$f: front matter declares no valid \`status\` (active | landed | superseded | abandoned)" done +# ── 9. every relative link in docs/ and examples/ resolves ─────────────── +# +# Rule 3 catches `docs/NN-*.md` named anywhere, including from source comments. +# This is the other half: a Markdown link in a document that points at a file +# which is not there. Both halves are needed -- a chapter moved in this batch +# would satisfy one and break the other. +python3 - <<'PYCHECK' || fail=1 +import re, pathlib, sys +bad = 0 +for f in list(pathlib.Path("docs").rglob("*.md")) + list(pathlib.Path("examples").rglob("*.md")): + for m in re.finditer(r"\]\(([^)#]+?)(?:#[^)]*)?\)", f.read_text(errors="ignore")): + t = m.group(1) + if t.startswith(("http", "mailto:")): + continue + if not (f.parent / t).exists(): + print(f"FAIL: {f}: link to `{t}` does not resolve") + bad += 1 +sys.exit(1 if bad else 0) +PYCHECK + if [[ "$fail" -eq 0 ]]; then echo "OK: docs structure checks pass" fi diff --git a/docs/21-commands-by-scenario.md b/docs/21-commands-by-scenario.md index 285fe1f8..4612efef 100644 --- a/docs/21-commands-by-scenario.md +++ b/docs/21-commands-by-scenario.md @@ -195,21 +195,6 @@ included. `mcpp self config --mirror CN|GLOBAL` selects the download mirror; mcpp and xlings hold this setting separately, so selecting it for one does not select it for the other. -## Current limitations - -- `mcpp why --format json` is defined for the `toolchain` topic only. The other - topics report `'' has no machine-readable shape yet` and exit non-zero. -- `mcpp search` matches a substring; there is no field selector, and no way to - restrict a search to one namespace. -- `mcpp clean --stale` reads `target/.build_cache`, which holds a bounded number - of recent entries. A project built across more (target, profile) pairs than it - holds loses its oldest entries, and a directory whose entry has been evicted - is then treated as unrecorded — kept while it is newer than `--older-than`, - removed after that. -- `mcpp cache gc --older-than 0` is rejected with `bad --older-than value '0' - (expected {s,m,h,d})`, while `mcpp clean --stale --older-than 0` accepts it. - The two options share a parser but not this case. - ## `[hooks]` — Project Build Lifecycle Commands (experimental) > **Experimental.** A hook cannot currently decide whether a build succeeded. @@ -370,3 +355,30 @@ A different sound for a successful or failed build. `side_effect = false` is written out rather than left to the default: it is the value this manifest wants on its own terms — a missing audio device should never fail a build — so it will still say so once the key has more than one accepted value. + +## Current limitations + +- `mcpp why --format json` is defined for the `toolchain` topic only. The other + topics report `'' has no machine-readable shape yet` and exit non-zero. +- `mcpp search` matches a substring; there is no field selector, and no way to + restrict a search to one namespace. +- `mcpp clean --stale` reads `target/.build_cache`, which holds a bounded number + of recent entries. A project built across more (target, profile) pairs than it + holds loses its oldest entries, and a directory whose entry has been evicted + is then treated as unrecorded — kept while it is newer than `--older-than`, + removed after that. +- `mcpp cache gc --older-than 0` is rejected with `bad --older-than value '0' + (expected {s,m,h,d})`, while `mcpp clean --stale --older-than 0` accepts it. + The two options share a parser but not this case. + +`mcpp emit xpkg` produces a descriptor that `mcpp xpkg parse` rejects for a +package that keeps its own `mcpp.toml`. The emitted `mcpp` segment carries +`manifest = "mcpp.toml"` and no `sources` list, and the validator requires one: + +``` +error: d.lua: xpkg-lua://example.mathkit@0.1.0: error: synthesised manifest + missing sources (mcpp segment must declare `sources = { ... }`) +``` + +Adding `sources = { "src/*.cppm" }` to the emitted `mcpp` segment makes it +validate. Measured on 2026.9.8.1. diff --git a/docs/zh/21-commands-by-scenario.md b/docs/zh/21-commands-by-scenario.md index 38a38ebd..429c7094 100644 --- a/docs/zh/21-commands-by-scenario.md +++ b/docs/zh/21-commands-by-scenario.md @@ -170,18 +170,6 @@ warning: pre-v1 cache at '/home/speak/.mcpp/bmi' occupies 167.5 MiB and is no lo `mcpp self config --mirror CN|GLOBAL` 选择下载镜像;mcpp 与 xlings 各自持有这个设置, 为其中一个选定不会为另一个选定。 -## 当前边界 - -- `mcpp why --format json` 只对 `toolchain` 话题有定义。其余话题报 - `'' has no machine-readable shape yet` 并以非零退出。 -- `mcpp search` 按子串匹配;没有字段选择器,也没有把搜索限定到单个命名空间的方式。 -- `mcpp clean --stale` 读 `target/.build_cache`,它保存的近期条目数量有上限。一个工程 - 如果构建过的 (目标, profile) 组合多于这个上限,最旧的条目会被挤掉;条目被挤掉的目录 - 随后按未记录处理 —— 在 `--older-than` 之内保留,超出则删除。 -- `mcpp cache gc --older-than 0` 以 `bad --older-than value '0' - (expected {s,m,h,d})` 被拒绝,而 `mcpp clean --stale --older-than 0` 接受。 - 两个选项共用一个 parser,但这一种取值上不一致。 - ## `[hooks]` —— 项目构建生命周期命令(实验性) > **实验性。** Hook 目前**不能**决定一次构建成功与否。所有 Hook 失败都以 @@ -313,3 +301,27 @@ deps = ["xim:mcpp-hooks-audioplayer@0.0.1"] 根据构建成功或失败播放不同提示音。`side_effect = false` 写出来而不是靠默认值:它是 这份 manifest 自己就想要的值——缺个音频设备不该让构建失败——所以等这个键有了不止 一个可接受的值之后,它仍然会这么写。 + +## 当前边界 + +- `mcpp why --format json` 只对 `toolchain` 话题有定义。其余话题报 + `'' has no machine-readable shape yet` 并以非零退出。 +- `mcpp search` 按子串匹配;没有字段选择器,也没有把搜索限定到单个命名空间的方式。 +- `mcpp clean --stale` 读 `target/.build_cache`,它保存的近期条目数量有上限。一个工程 + 如果构建过的 (目标, profile) 组合多于这个上限,最旧的条目会被挤掉;条目被挤掉的目录 + 随后按未记录处理 —— 在 `--older-than` 之内保留,超出则删除。 +- `mcpp cache gc --older-than 0` 以 `bad --older-than value '0' + (expected {s,m,h,d})` 被拒绝,而 `mcpp clean --stale --older-than 0` 接受。 + 两个选项共用一个 parser,但这一种取值上不一致。 + +对一个自带 `mcpp.toml` 的包,`mcpp emit xpkg` 产出的描述符会被 `mcpp xpkg parse` +拒绝。产出的 `mcpp` 段带 `manifest = "mcpp.toml"` 而没有 `sources` 列表,而校验器 +要求有一个: + +``` +error: d.lua: xpkg-lua://example.mathkit@0.1.0: error: synthesised manifest + missing sources (mcpp segment must declare `sources = { ... }`) +``` + +给产出的 `mcpp` 段补上 `sources = { "src/*.cppm" }` 即可通过校验。实测于 +2026.9.8.1。 From 95c9e5a1ad1df178e86c24f78f985987bd5eedca Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:14:57 +0800 Subject: [PATCH 08/36] docs: repoint the cross-references the split invalidated, and put chapter 17 back in order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moving six sections out of 05 left references behind that pointed at section numbers which had moved with them. Fourteen in the two languages: `§2.8.1` from inside the chapter that now contains it, `§2.13` from the chapter that now IS it, `§2.14` from the chapter it moved to, `§2.7.1` from the chapter it left. Each is repointed at the chapter that holds the content now, or at the section of the same chapter when the reference became local. Chapter 17's 'Related chapters' had ended up in the middle, and its own bullet for 05 said 'every manifest key, including [xlings]' -- the one claim the move made false. It is last again, renumbered, and 05 is described as the rest of the manifest. This is what the review pass is for: a mechanical move keeps every heading structure valid and leaves the prose pointing at the old shape. --- docs/07-build-mcpp.md | 6 +++--- docs/14-target-side.md | 4 ++-- docs/17-the-project-environment.md | 24 ++++++++++++------------ docs/22-features-and-capabilities.md | 10 +++++----- docs/zh/07-build-mcpp.md | 4 ++-- docs/zh/14-target-side.md | 4 ++-- docs/zh/17-the-project-environment.md | 24 ++++++++++++------------ docs/zh/22-features-and-capabilities.md | 10 +++++----- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/07-build-mcpp.md b/docs/07-build-mcpp.md index aab8682f..d13f58fa 100644 --- a/docs/07-build-mcpp.md +++ b/docs/07-build-mcpp.md @@ -376,7 +376,7 @@ mcpp builds that `kind = "bin"` target **for the build machine** (even under `--target`), caches it globally, and returns the path. The request lives in `mcpp.toml` rather than here for the same reason a dependency does: asking the graph for an extra artifact is a graph-level request, and the graph stays -statically analysable. See [05 §2.14](05-mcpp-toml.md) for the full contract, +statically analysable. See *Host tools from a dependency* in this chapter for the full contract, including `[tools.overrides]` and `reexport = true` (which is how a library provides the whole toolchain, so a project declares **one** dependency instead of four). @@ -1079,7 +1079,7 @@ plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } ``` **`[build-dependencies]`, not `[dependencies]`** — a rule package is the case -§2.6.1 describes exactly: its library must never reach the target while its +[05 §2.6.1](05-mcpp-toml.md) describes exactly: its library must never reach the target while its rule is still wanted. The two axes are separate, so `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" on the second axis, and the @@ -1213,6 +1213,6 @@ unsupported platform into an error its user cannot edit away. Scope it: ``` `[target..feature-deps.]` (2026.8.6.2+) follows the same rules as -the other conditional dependency tables (§2.7.1). The **feature itself is +the other conditional dependency tables ([14 — The Target Side](14-target-side.md)). The **feature itself is registered on every platform** — only what it pulls in is conditional — so requesting it where no predicate matches is not an unknown-feature error. diff --git a/docs/14-target-side.md b/docs/14-target-side.md index 87f31e98..6bf055f9 100644 --- a/docs/14-target-side.md +++ b/docs/14-target-side.md @@ -342,7 +342,7 @@ The selector `` has three forms: |---|---|---| | **bare OS alias** | a single OS / family — the concise, common form | `[target.windows]`, `[target.unix]` | | **`cfg(...)` predicate** | a compound condition (arch / env / combinators) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see §2.7.3) | `[target.x86_64-linux-musl]` | +| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see [05 §2.7.3](05-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | A selector may carry platform-conditional **dependencies** and **build flags**: @@ -365,7 +365,7 @@ there is no ambiguity. Use the bare form for a single OS/family; use `cfg(...)` for arch/env conditions and combinators. - **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies` / - `feature-deps.` (mcpp 2026.8.6.2+ — see §2.14; the feature is + `feature-deps.` (mcpp 2026.8.6.2+ — see [07 — build.mcpp](07-build-mcpp.md); the feature is registered unconditionally, only its dependency set is scoped), and `build` with `cflags` / `cxxflags` / `ldflags` / `sources` (mcpp 0.0.95+ — conditional source globs, e.g. gating `src/x86/**/*.asm` behind diff --git a/docs/17-the-project-environment.md b/docs/17-the-project-environment.md index bb5d4120..d7195ce1 100644 --- a/docs/17-the-project-environment.md +++ b/docs/17-the-project-environment.md @@ -166,16 +166,16 @@ package the host cannot install is an error, not a skipped entry. A tool that exists for one host platform only is therefore declared for that platform (2026.9.2.1): `deps = [{ linux = "qemu-user-aarch64" }]` declares the emulator on Linux and nothing elsewhere. The keys and the resolution rule are in -chapter 5, §2.13. +this chapter. **Which verbs install it.** An entry may name a tier — `{ version = "0.24.0", when = "run" }` — and a `[feature-xlings.]` table gates one on a feature. A tool the project will not use is then not -downloaded: chapter 5, §2.13. Omitting the tier is the historical behaviour. +downloaded: this chapter. Omitting the tier is the historical behaviour. **The runner.** A program under `[xlings.workspace]` is also where `[target.].runner` looks first for its first element, before `PATH` -(chapter 5, §2.7.3). The two keys together provision a user-mode emulator on a +([05 §2.7.3](05-mcpp-toml.md)). The two keys together provision a user-mode emulator on a CI host and execute a cross-built artifact through it, without the manifest naming the payload's path. @@ -190,15 +190,7 @@ naming the payload's path. | a tool only one verb or one feature needs | `when = "run"`, `[feature-xlings.]` | | which environment | `[xlings] subos` | -## 7. Related chapters - -- [7 - build.mcpp](07-build-mcpp.md) — the contract a build program receives, - including the `PATH` it runs with. -- [8 - Toolchain Internals](08-toolchain-internals.md) — runtime selection, - the `RuntimeBinding` snapshot, and the degradation rules. -- [5 - mcpp.toml](05-mcpp-toml.md) — every manifest key, including `[xlings]`. - -## `[xlings]` — the project's environment +## 7. `[xlings]` — the manifest keys ```toml [xlings.workspace] # what this project's environment contains @@ -514,3 +506,11 @@ exact version of that package cannot edit. program's environment is declared by its own package, and an environment's by that environment. The key is now an error naming both. Nothing in the index used it. + +## 8. Related chapters + +- [7 - build.mcpp](07-build-mcpp.md) — the contract a build program receives, + including the `PATH` it runs with. +- [8 - Toolchain Internals](08-toolchain-internals.md) — runtime selection, + the `RuntimeBinding` snapshot, and the degradation rules. +- [5 - mcpp.toml](05-mcpp-toml.md) — the rest of the manifest. diff --git a/docs/22-features-and-capabilities.md b/docs/22-features-and-capabilities.md index 62d57697..a64190ab 100644 --- a/docs/22-features-and-capabilities.md +++ b/docs/22-features-and-capabilities.md @@ -38,7 +38,7 @@ feature carry package-owned preprocessor `defines`, feature-gated source globs the feature is active, exactly like an index descriptor's `features..sources`; the highest-frequency shape for vendored libraries: *feature = a source set + a define*), feature-gated per-glob compile flags (`flags`, mcpp 0.0.101+), and/or -capability `requires` / `provides` (see §2.8.1) alongside its implied features: +capability `requires` / `provides` (see *`provides` / `requires`* below) alongside its implied features: ```toml [features] @@ -67,11 +67,11 @@ simd = { sources = ["src/simd/**"], flags = [ defines`. They are restricted by convention to the package's **own** namespaced macros: a feature does **not** inject free-form package-wide `cflags`/`ldflags`, which would break the additive feature-union model. Link flags come from a - provider dependency (§2.8.1), not from a feature. + provider dependency (see *`provides` / `requires`* below), not from a feature. - The automatic `-DMCPP_FEATURE_` is still defined for every active feature, so `defines` are additive to it. - `flags` (mcpp 0.0.101+) is the same ordered array-of-inline-tables grammar as - `[build].flags` (§2.3: `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ + `[build].flags` ([05 §2.3](05-mcpp-toml.md): `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ `defines`; the `[[features..flags]]` array-of-tables spelling is accepted too, like `[[build.flags]]`). When the feature is active the entries are appended **after** the base `[build].flags`, features in name order, so a @@ -348,7 +348,7 @@ implementation cannot be fetched is a feature that does not exist, and a project using a **path** dependency during development never consults the index — so the failure appears only after publication, to somebody else. -This composes with capabilities (§2.8.1): a single `backend-openblas` feature +This composes with capabilities (*`provides` / `requires`* above): a single `backend-openblas` feature both **pulls** the provider (`compat.openblas`, which `provides = ["blas"]`) and **turns on** the consumer switch (`implies = ["use_blas"]`, which `requires = ["blas"]`). With one provider in the graph the capability binds @@ -398,7 +398,7 @@ Two properties make this preferable to shipping the implementation unconditionally. A library that ships one takes a decision belonging to the program, and it cannot be undone: features are **additive**, so there is no way for a consumer to switch a default *off*. And because a dependency package's -objects link unconditionally (§2.8.1), a shipped default plus a program-supplied +objects link unconditionally (*`provides` / `requires`* above), a shipped default plus a program-supplied one is a duplicate definition rather than a replacement — the archive semantics that let a C++ standard library offer a replaceable `operator new` do not apply to a package dependency. Keeping the implementation behind a switch means the diff --git a/docs/zh/07-build-mcpp.md b/docs/zh/07-build-mcpp.md index afa11c4a..4361c85a 100644 --- a/docs/zh/07-build-mcpp.md +++ b/docs/zh/07-build-mcpp.md @@ -324,7 +324,7 @@ mcpp 会**为构建机器**构建那个 `kind = "bin"` target(即使在 `--targe 全局缓存,并把路径交回。这个请求写在 `mcpp.toml` 而不是这里,理由和依赖本身 一样:向依赖图索取一个额外产物是**图级别**的请求,而图必须保持可静态分析。 完整契约(含 `[tools.overrides]` 与 `reexport = true` —— 库据此把整条工具链交给 -调用方,因此只需写**一条**依赖而不是四条)见 [05 §2.14](05-mcpp-toml.md)。 +调用方,因此只需写**一条**依赖而不是四条)见本章*依赖产出的 host 工具*。 ### 用通配符声明输入:`rerun_if_changed_glob`(2026.8.6.2+) @@ -907,7 +907,7 @@ rules-spirv = { sources = ["rules/spirv.cppm"] } # export module mcpp.rules.spir plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } ``` -**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 §2.6.1 描述的那种 +**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 [05 §2.6.1](05-mcpp-toml.md) 描述的那种 情形:它的库绝不该到达目标,而它的规则仍然被需要。两条轴是分开的: `host-module = true` 说的是**要哪一种构建期产物**,而 section 说的是**这个包是否到达 目标**;规则包在第二条轴上的答案是"否",而 section 就是说这件事的地方。写在 diff --git a/docs/zh/14-target-side.md b/docs/zh/14-target-side.md index cfc240c9..2103cecd 100644 --- a/docs/zh/14-target-side.md +++ b/docs/zh/14-target-side.md @@ -290,7 +290,7 @@ mcpp 不认识的键 —— 打错的字,或来自更新版本 mcpp 的谓词 |---|---|---| | **裸 OS 别名** | 单个 OS / 族 —— 简洁且常用的形式 | `[target.windows]`、`[target.unix]` | | **`cfg(...)` 谓词** | 复合条件(arch / env / 组合子) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 §2.7.3) | `[target.x86_64-linux-musl]` | +| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 [05 §2.7.3](05-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | 一个选择器可以承载平台条件的**依赖**与**构建 flag**: @@ -312,7 +312,7 @@ cxxflags = ["-march=x86-64-v2"] 单个 OS/族用裸形式,arch/env 条件与组合子用 `cfg(...)`。 - **可用键**:`dependencies` / `dev-dependencies` / `build-dependencies` / - `feature-deps.`(mcpp 2026.8.6.2+ —— 见 §2.14;feature 本身无条件注册, + `feature-deps.`(mcpp 2026.8.6.2+ —— 见 [07 —— build.mcpp](07-build-mcpp.md);feature 本身无条件注册, 只有它的依赖集合受限定),以及带 `cflags` / `cxxflags` / `ldflags` / `sources` 的 `build`(mcpp 0.0.95+ —— 条件源码 glob,例如把 `src/x86/**/*.asm` 收在 `cfg(arch = "x86_64")` 之后;`!` 排除 glob 在此同样有效),再加 `flags` 与 diff --git a/docs/zh/17-the-project-environment.md b/docs/zh/17-the-project-environment.md index 9e70edec..956b1b43 100644 --- a/docs/zh/17-the-project-environment.md +++ b/docs/zh/17-the-project-environment.md @@ -131,14 +131,14 @@ create/bootstrap that environment instead of falling back to active/default 这份声明在每台构建本工程的宿主上都会供给,宿主装不了的包是错误,不是被跳过的条目。 只存在于某一个宿主平台的工具因此按平台声明(2026.9.2.1): `deps = [{ linux = "qemu-user-aarch64" }]` 在 Linux 上声明这个模拟器,在别处什么都不声明。 -键与解析规则见第 5 章 §2.13。 +键与解析规则见本章。 **哪些命令会安装它。** 一条条目可以带档位 —— `{ version = "0.24.0", when = "run" }` —— `[feature-xlings.]` 则把工具挂在某个 feature 上。用不到的工具因此不会被下载: -见第 5 章 §2.13。不写档位就是从前的行为。 +见本章。不写档位就是从前的行为。 **runner。** `[xlings.workspace]` 下的程序也是 `[target.].runner` 查找其第一个元素 -的首选位置,在 `PATH` 之前(第 5 章 §2.7.3)。两个键合起来,在 CI 宿主上供给用户态模拟器, +的首选位置,在 `PATH` 之前([05 §2.7.3](05-mcpp-toml.md))。两个键合起来,在 CI 宿主上供给用户态模拟器, 并通过它执行交叉构建的产物,而清单不必写出载荷的路径。 ## 6. 什么该写在别处 @@ -152,15 +152,7 @@ create/bootstrap that environment instead of falling back to active/default | 只有某个命令或某个 feature 需要的工具 | `when = "run"`、`[feature-xlings.]` | | 用哪个环境 | `[xlings] subos` | -## 7. 相关章节 - -- [7 - build.mcpp](07-build-mcpp.md) —— 构建程序收到的契约,含它运行时的 - `PATH`。 -- [8 - 工具链内部](08-toolchain-internals.md) —— 运行时选择、`RuntimeBinding` - 快照与降级规则。 -- [5 - mcpp.toml](05-mcpp-toml.md) —— 全部清单键,含 `[xlings]`。 - -## `[xlings]` — 工程的环境 +## 7. `[xlings]` —— manifest 键 ```toml [xlings.workspace] # 这个工程的环境里有什么 @@ -428,3 +420,11 @@ error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins `[xlings.envs]` 曾被物化进 `.xlings.json`,而没有任何东西读它:程序的环境由它自己 的包声明,环境的环境由那个环境声明。现在这个键是错误,并同时点名这两者。索引里没有 任何包用过它。 + +## 8. 相关章节 + +- [7 - build.mcpp](07-build-mcpp.md) —— 构建程序收到的契约,含它运行时的 + `PATH`。 +- [8 - 工具链内部](08-toolchain-internals.md) —— 运行时选择、`RuntimeBinding` + 快照与降级规则。 +- [5 - mcpp.toml](05-mcpp-toml.md) —— manifest 的其余部分。 diff --git a/docs/zh/22-features-and-capabilities.md b/docs/zh/22-features-and-capabilities.md index d2eb1d22..6300a6a7 100644 --- a/docs/zh/22-features-and-capabilities.md +++ b/docs/zh/22-features-and-capabilities.md @@ -17,7 +17,7 @@ Feature 是一个包提供可选内容的方式:一个编译宏、一份额外 0.0.95+——列出的 glob 离开默认构建,仅当 feature 激活时才编译,与 index 描述符的 `features..sources` 完全对等;这正是 vendored 大库最高频的形态:*feature = 一组源文件 + 一个 define*)、feature 门控的 per-glob 编译旗标(`flags`,mcpp -0.0.101+),以及 capability 的 `requires` / `provides`(见 §2.8.1): +0.0.101+),以及 capability 的 `requires` / `provides`(见下文*`provides` / `requires`*): ```toml [features] @@ -42,9 +42,9 @@ simd = { sources = ["src/simd/**"], flags = [ - `defines` 为**裸**宏名(不带 `-D`);feature 激活时每个脱糖为 `-D`,加到该包 自己的编译上——与 `[targets.*] defines` 完全一致。按约定仅限包**自有**的带命名 空间宏:feature **不**注入自由的包级 `cflags`/`ldflags`,否则会破坏加性的 feature - 并集模型。链接旗标来自 provider 依赖(§2.8.1),而非 feature。 + 并集模型。链接旗标来自 provider 依赖(见下文*`provides` / `requires`*),而非 feature。 - 每个激活的 feature 仍会得到自动的 `-DMCPP_FEATURE_`,`defines` 与之叠加。 -- `flags`(mcpp 0.0.101+)与 `[build].flags`(§2.3)共用同一有序 inline-table 数组 +- `flags`(mcpp 0.0.101+)与 `[build].flags`([05 §2.3](05-mcpp-toml.md))共用同一有序 inline-table 数组 文法(`glob` 必填,加 `cflags`/`cxxflags`/`asmflags`/`defines`;与 `[[build.flags]]` 一样也接受 `[[features..flags]]` 拼写)。feature 激活时 条目追加在 base `[build].flags` **之后**(feature 按名 @@ -284,7 +284,7 @@ compat.openblas = "0.3" feature**,而开发期使用 **path** 依赖的工程根本不查索引 —— 该失败只在发布之后才出现, 而且是出现在别人身上。 -该机制与能力(§2.8.1)组合:单个 `backend-openblas` feature 既**拉取** provider +该机制与能力(上文*`provides` / `requires`*)组合:单个 `backend-openblas` feature 既**拉取** provider (`compat.openblas`,其 `provides = ["blas"]`),又**开启**消费方开关 (`implies = ["use_blas"]`,其 `requires = ["blas"]`)。当图中只有一个 provider 时, 能力自动绑定——消费方只需写 `features = ["backend-openblas"]`。 @@ -329,6 +329,6 @@ std-freestanding-alloc-kal = "0.1.x" 有两条性质使该形状优于无条件随包提供实现。随包提供实现的库替程序做了本属程序的决定, 而且**撤销不掉**:feature 是**加性**的,消费方没有把某个默认**关掉**的手段。以及,由于 -依赖包的目标文件无条件参与链接(§2.8.1),随包的默认加上程序自备的那份是**重复定义** +依赖包的目标文件无条件参与链接(上文*`provides` / `requires`*),随包的默认加上程序自备的那份是**重复定义** 而非替换 —— 让 C++ 标准库能提供可替换 `operator new` 的那套归档语义,对包依赖并不适用。 把实现放在开关之后,意味着两者**从不共存**。 From 36476de24d9fd3ccbf62cd525c471ac3372774de Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:15:55 +0800 Subject: [PATCH 09/36] docs: chapter 22 gets the limits section its own rule requires, and the gradient rule becomes one that is followed The review found two rules written in the style skill and not applied. Chapter 22 had no 'Current limitations' section while the skill calls that section mandatory; it now records the two that were measured -- there is no `--no-default-features`, and a dependency cannot be conditioned on the accelerator. The other was the rule itself. 'Every document states which rung it is on' is not useful to a reader and no chapter did it. What carries the gradient is the entry index and a 'Related documents' line naming the rung above and below, and that is what the rule says now. 22 also joins the role index row for a library author, which is where a reader looking for features would start. --- .agents/skills/mcpp-docs-style/SKILL.md | 10 +++++++--- docs/22-features-and-capabilities.md | 13 +++++++++++++ docs/README.md | 2 +- docs/zh/22-features-and-capabilities.md | 10 ++++++++++ docs/zh/README.md | 2 +- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index 74a62227..7992c9c5 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -228,9 +228,13 @@ docs/specs/ ──▶ docs/ 允许 | 3 规范 | 语义、约束、匹配规则,每条带实现状态 | RFC 2119 | | 4 记录 | 为什么是这样,以及什么被推翻了 | 允许叙述 | -**规则:每份文档在开头点明它是哪一级,并且只链接相邻级。** 参考文档向上链到 -教程、向下链到规范;它**不直接把读者丢进设计记录**,那是跨两级 —— 也正是第一节 -那条被禁止的边。 +**规则:每份文档开头用一行「相关文档:」指出它的上一级与下一级,并且只链接 +相邻级。** 参考文档向上链到教程与示例、向下链到规范;它**不直接把读者丢进设计 +记录**,那是跨两级 —— 也正是第一节那条被禁止的边。 + +梯度本身由**入口**承载,不由每份文档自报级别:`docs/README.md` 的角色索引把 +「我想做 X」映射到章节、示例与模板。一份自称「本章是第 2 级」的文档对读者没有 +用处,而一条指出上下一级的链接有。 一个能力的文档化按这个顺序推进,不跳级:先有一个能跑的最小形态,再有字段参考, 再在「两个实现会不一致」时抽出规范。**倒过来做会得到一份没有人验证过的规范。** diff --git a/docs/22-features-and-capabilities.md b/docs/22-features-and-capabilities.md index a64190ab..2c46ab00 100644 --- a/docs/22-features-and-capabilities.md +++ b/docs/22-features-and-capabilities.md @@ -403,3 +403,16 @@ one is a duplicate definition rather than a replacement — the archive semantic that let a C++ standard library offer a replaceable `operator new` do not apply to a package dependency. Keeping the implementation behind a switch means the two never coexist. + +## Current limitations + +**A default feature is turned off in the manifest, not on the command line.** +There is no `--no-default-features`. `mcpp build --features metrics` activates +`default ∪ {metrics}`; building without a member of `default` means editing +`[features] default`. Measured on 2026.9.8.1. + +**A dependency cannot be conditioned on the accelerator.** `accelerator` is +resolved from the dependency graph, so a dependency chosen by it would decide +the answer it is asking for. mcpp reports the predicate and ignores it. Packages +are unconditional or conditioned on the platform; `[build] sources` is what the +accelerator selects. diff --git a/docs/README.md b/docs/README.md index 81d7ab4e..f0bd7019 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,7 +26,7 @@ anything a reader needs is written here or in a specification instead. | To | Read | Run | |---|---|---| | write a program | [00](00-getting-started.md), [05](05-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | -| write a library others import | [10](10-publishing-a-library.md), [05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | +| write a library others import | [10](10-publishing-a-library.md), [22](22-features-and-capabilities.md), [05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | | publish it | [02](02-pack-and-release.md), [10](10-publishing-a-library.md), [12](12-binary-distribution.md) | [`03-pack-static`](../examples/03-pack-static/), [`05-lib-distribution`](../examples/05-lib-distribution/) | | build for another machine | [16](16-the-target-triple.md), [15](15-openkal-cross.md), [13](13-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | | use a GPU or an accelerator | [20](20-heterogeneous-builds.md), [18](18-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | diff --git a/docs/zh/22-features-and-capabilities.md b/docs/zh/22-features-and-capabilities.md index 6300a6a7..161aa47f 100644 --- a/docs/zh/22-features-and-capabilities.md +++ b/docs/zh/22-features-and-capabilities.md @@ -332,3 +332,13 @@ std-freestanding-alloc-kal = "0.1.x" 依赖包的目标文件无条件参与链接(上文*`provides` / `requires`*),随包的默认加上程序自备的那份是**重复定义** 而非替换 —— 让 C++ 标准库能提供可替换 `operator new` 的那套归档语义,对包依赖并不适用。 把实现放在开关之后,意味着两者**从不共存**。 + +## 当前边界 + +**默认 feature 在 manifest 里关掉,不在命令行上关掉。** 没有 `--no-default-features`。 +`mcpp build --features metrics` 激活的是 `default ∪ {metrics}`;要在不带 `default` +某个成员的情况下构建,得改 `[features] default`。实测于 2026.9.8.1。 + +**依赖不能以加速器为条件。** `accelerator` 是从依赖图解析出来的,因此由它选择的 +依赖会决定它自己在问的那个答案。mcpp 会报告该谓词并忽略它。包要么无条件,要么以 +平台为条件;由加速器选择的是 `[build] sources`。 diff --git a/docs/zh/README.md b/docs/zh/README.md index d140b421..a3a4272e 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -23,7 +23,7 @@ | 目标 | 阅读 | 运行 | |---|---|---| | 写一个程序 | [00](00-getting-started.md)、[05](05-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | -| 写一个供他人 import 的库 | [10](10-publishing-a-library.md)、[05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | +| 写一个供他人 import 的库 | [10](10-publishing-a-library.md)、[22](22-features-and-capabilities.md)、[05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | | 发布它 | [02](02-pack-and-release.md)、[10](10-publishing-a-library.md)、[12](12-binary-distribution.md) | [`03-pack-static`](../../examples/03-pack-static/)、[`05-lib-distribution`](../../examples/05-lib-distribution/) | | 为另一台机器构建 | [16](16-the-target-triple.md)、[15](15-openkal-cross.md)、[13](13-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | | 使用 GPU 或加速器 | [20](20-heterogeneous-builds.md)、[18](18-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | From 43c6db4e553b05cc94a57d2ece62e2992156a0bc Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:18:54 +0800 Subject: [PATCH 10/36] ci: the new examples enter the denominator, and two criteria a build cannot show build_examples.sh derives its list from the tree and fails on a root in neither BUILD nor SKIP. It failed on all four new roots, which is the check working; each is classified now, and the rule package is skipped for the reason its siblings are -- a rule package is not a standalone build, because its module imports the bundled mcpp module that exists only inside a consumer's build. Two criteria are asserted in a step of their own because a build cannot show them. For 11-features, that the DEFAULT build's resolution does not name the optional package -- 'the default build works' passes while the package is resolved and merely unused. For 12-a-new-device-language, that editing the compiler reaches the artifact: the action declares its tool as an input, and without that the build stays green over the previous compiler's bytes. --- .github/tools/build_examples.sh | 15 ++++++++++++++ .github/workflows/ci-linux.yml | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/.github/tools/build_examples.sh b/.github/tools/build_examples.sh index ffcec76e..a6ffe113 100755 --- a/.github/tools/build_examples.sh +++ b/.github/tools/build_examples.sh @@ -37,6 +37,20 @@ BUILD=( # The device payloads are NOT installed: they are gated on the accelerator, # and this builds without one. examples/09-heterogeneous/multi-backend + # The island boundary with nothing on top. Its island is an ordinary C file, + # so it needs no device and no device payload -- only `mcpp:plugins` with + # `tools-island`. It is the one example that runs the arrangement the + # plugins README records as measured: a consumer importing the GENERATED + # module and linking against an implementation another compiler produced. + examples/09-heterogeneous/boundary + # Declares features rather than consuming them. The next step asserts the + # criterion that matters -- that the default build's RESOLUTION does not + # name the optional package -- which a build alone cannot show. + examples/11-features/counters + examples/11-features/greeter + # A device language the engine does not know. No payload: its compiler is a + # shell script, because the subject is the graph rather than a vendor. + examples/12-a-new-device-language/app ) # `key|reason`. @@ -47,6 +61,7 @@ SKIP=( "examples/07-project-subos|provisions a project-local sub-OS, which e2e 27_self_contained_home covers directly and far more cheaply" "examples/08-build-rules/rules-embed|a rule package is not a standalone build: its interface imports the bundled mcpp module, which exists only inside a consumer's build. Verified by building 08-build-rules/app, the same way mcpp-plugins verifies its own members" "examples/08-build-rules/rules-tidy|same" + "examples/12-a-new-device-language/rules-toy|a rule package is not a standalone build: its module imports the bundled mcpp module, which exists only inside a consumer's build program. Verified by building 12-a-new-device-language/app" "examples/09-heterogeneous/cuda/app|needs the CUDA payload set, and a device to run; the rule package is covered by mcpp-plugins' own CI" "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" diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index eb8d5d29..3151bd21 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -292,6 +292,42 @@ jobs: export MCPP_VENDORED_XLINGS="$XLINGS_BIN" bash .github/tools/build_examples.sh + # TWO CRITERIA A BUILD CANNOT SHOW, from the examples whose READMEs state + # them. Both are the shape this repository has paid for before: a green + # build over a result nobody compared. + - name: "examples: the feature criterion and the device language" + run: | + export MCPP_VENDORED_XLINGS="$XLINGS_BIN" + set -e + + # 11-features. NOT "the default build works" -- that passes while the + # optional package is resolved and merely unused. The criterion is + # that the default build's RESOLUTION does not name it. + cd examples/11-features/greeter + "$MCPP" build 2>&1 | tee /tmp/f-default.log + "$MCPP" build --features metrics 2>&1 | tee /tmp/f-metrics.log + grep -qi counters /tmp/f-default.log && { + echo "FAIL: the default build resolved 'counters'"; exit 1; } + grep -qi counters /tmp/f-metrics.log || { + echo "FAIL: --features metrics did not resolve 'counters'"; exit 1; } + echo "ok: the optional package is absent without its feature" + cd - >/dev/null + + # 12-a-new-device-language. The `.toy` compiles on an engine that does + # not know the extension, AND editing the compiler reaches the + # artifact -- the half that fails silently when the tool is not a + # declared input of the action. + cd examples/12-a-new-device-language/app + "$MCPP" run | tee /tmp/toy.log + grep -q 'toy_answer() = 42' /tmp/toy.log || { + echo "FAIL: the .toy did not reach the link"; exit 1; } + sed -i 's/print s + 0 }/print s + 100 }/' ../rules-toy/tools/toyc.sh + "$MCPP" run | tee /tmp/toy2.log + sed -i 's/print s + 100 }/print s + 0 }/' ../rules-toy/tools/toyc.sh + grep -q 'toy_answer() = 142' /tmp/toy2.log || { + echo "FAIL: editing the compiler did not reach the artifact"; exit 1; } + echo "ok: the compiler is a declared input" + - name: "Graphics example: render offscreen on lavapipe and assert the pixels" run: | export MCPP_VENDORED_XLINGS="$XLINGS_BIN" From 9bba95d02876e7880c7dfbc87638ebf03b7abe6b Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:20:59 +0800 Subject: [PATCH 11/36] docs(record): the implementation record for the documentation architecture What the measurements say afterwards, what the review pass found, what the work found while building the examples, and what is open with the reason each is open rather than late. The review section is the part worth keeping: fourteen cross-references in two languages were left pointing at section numbers that had moved with their sections, and every heading-structure and bilingual-parity check stayed green throughout, because neither is a check about meaning. A mechanical move keeps every structure valid and leaves the prose pointing at the old shape. --- ...-documentation-architecture-three-trees.md | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md index 4d09229b..478e8dc1 100644 --- a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md +++ b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md @@ -459,3 +459,112 @@ from the earlier plan and is where its example work rejoins. corrected in place by later measurement, which is how their own value was preserved. Making that a named, dated block at the end — rather than an edit in the body — would keep both properties. + +--- + +## 12. Implementation record, 2026-09-08 + +Shipped as PR #590 on `docs/architecture-three-trees`. Stages 1, 2, 3, 5 and 6 +of §8 are done; stage 4 (translating the specifications) and stage 7 (SPEC-005 / +SPEC-006) are not, and §12.4 says why. + +### 12.1 What the measurements say afterwards + +| surface | before | after | +|---|---|---| +| `docs/05-mcpp-toml.md` | 3,129 lines | 1,626 | +| largest chapter | 3,129 | 1,218 (`07`) | +| `.agents/docs/README.md` | one heading | generated, 313 lines over 269 records | +| user chapters citing a design record | 9 | 0 | +| specifications listed on the front page | 2 of 4 | 4 of 4 | +| stale `docs/NN-*.md` citations | 6 | 0 | +| manifest keys with no example | 13 | 7 | +| `build.mcpp` API names used by an example | 10 of 38 | 12 of 38 | +| structure rules enforced in CI | 0 | 9 | + +The seven manifest keys still uncovered are the ones §3.2 classifies as a code +block rather than an example — `[hooks]`, `scan_overrides`, `cxx_runtime`, +`module_extensions`, `platforms`, `[feature-xlings]` — **plus two the plan +intended to cover and did not**, for reasons worth stating rather than +carrying forward as debt: + +- `[resources]` compiles on PE targets only, and a declared file must exist on + every target. An example for it would add a binary `.ico` to the tree and + then assert nothing on the two platforms most of this repository's CI runs + on. It belongs to a Windows-facing example, which does not exist yet. +- `[runtime]` is the provider-neutral runtime contract a prebuilt library + declares. §4.2 folded it into the publishing example, which is blocked + (§12.4). + +### 12.2 What the review pass found + +Read as rendered text rather than as a diff, which is the rule the skill states +for exactly this reason: + +**Fourteen cross-references, in both languages, left pointing at section numbers +that had moved with their sections.** `§2.8.1` cited from inside the chapter +that now contains it; `§2.13` cited from the chapter that now *is* it; `§2.14` +cited from the chapter it moved to. Every heading structure stayed valid and the +bilingual parity check stayed green throughout, because neither of those is a +check about meaning. + +> A mechanical move keeps every structure valid and leaves the prose pointing at +> the old shape. The checks cannot see it; a reader can. + +**Chapter 17's "Related chapters" ended up mid-chapter**, and its own bullet for +`05` said "every manifest key, including `[xlings]`" — the one claim the move +made false. + +**Two rules were written in the skill and not applied.** Chapter 22 had no +"Current limitations" while the skill calls that section mandatory. And "every +document states which rung it is on" was followed by no chapter and is not +useful to a reader; it became "a `Related documents` line naming the rung above +and below", which is what the chapters actually do and what carries the +gradient. A rule nothing follows is worse than no rule, because it makes the +skill unfalsifiable. + +### 12.3 What the work found in the implementation + +Three defects met while building `12-a-new-device-language`, each now in its +README because a rule author will meet all three: + +- An action's command runs from the **build directory**, so a rule joins + `manifest_dir()` to the package-root-relative paths `device_sources()` gives. +- A pipeline's exit status is its last command's. The first `toyc` summed with + `… | paste -sd+ - | bc`; when an earlier stage produced nothing `bc` still + exited 0, `set -e` never fired, and the program compiled, linked, ran and + printed `0`. +- **The compiler is a declared input.** Without it, editing `toyc.sh` left every + edge clean and the artifact kept the previous compiler's bytes. + +And one in the tooling: `mcpp emit xpkg` produces a descriptor that +`mcpp xpkg parse` rejects for a package keeping its own `mcpp.toml` — the +emitted `mcpp` segment carries `manifest = "mcpp.toml"` and no `sources` list, +and the validator requires one. Isolated by adding the list by hand, which makes +it validate. Recorded in `docs/21` as a limitation; not fixed in a documentation +change. + +`build_examples.sh` refused all four new example roots for being in neither +`BUILD` nor `SKIP`. That is the denominator discipline working, and it is the +same shape §7 gives the coverage check. + +### 12.4 What is open, and why each is open rather than late + +**The specifications' language** (§6.2, §11.1). English primary with a 简体中文 +mirror is the recommendation and is 1,156 lines of translation. It is an open +question in this document, and translating before it is answered is the wrong +order. What did ship is the half that is not a question: the register rules now +cover `docs/specs/`, which they were exempt from by a glob rather than by a +decision. + +**SPEC-005 and SPEC-006** (§6.1). Each is a promotion of text that exists and +each is its own review. + +**A publishing round-trip example** (§4.3, B3). Blocked on the emit/parse +mismatch above: the loop it would teach cannot be shown end to end while the +descriptor `mcpp emit xpkg` writes is one `mcpp xpkg parse` refuses. +`[resources]` and `[runtime]` were to be folded into that track and are +therefore still uncovered. + +**14.7's `--no-accel` diagnostic and L0 of the rule ladder** are unaffected by +this batch and remain where the heterogeneous plan records them. From a1a557e8aaec399320084f550134f7dda9043d12 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:24:51 +0800 Subject: [PATCH 12/36] docs: a translation must carry the same tables and code blocks, and four that did not MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check_docs_style.sh compares heading STRUCTURE, which catches a page that has fallen a section behind. It cannot see a table row or a code block that never made it across, and four were sitting in the tree — none introduced by this batch, and none visible to any other check: docs/zh/22 (from 05) the `[features]` section had NO BODY: the TOML example that defines the syntax and all three bullets were absent, in the section that defines the mechanism docs/zh/05 §2.11 the `identity` verdict table — ok / mismatch / missing / unverified — and the two paragraphs around it docs/zh/08 §7.4 the shared-library format table and what follows it: Mach-O's install name, and the refusal of an unservable target docs/zh/16 SEVEN target rows. The 简体中文 support matrix listed 14 targets where the English listed 21, all seven missing ones Cortex-M Rule 10 compares table rows and fenced code blocks per document pair. It was written after the first two were found by hand, and it immediately found the other two, which is the argument for it: a bilingual pair can agree on every heading and disagree about a support matrix. --- .github/tools/check_docs_structure.sh | 41 +++++++++++++++++++++++++ docs/zh/05-mcpp-toml.md | 15 +++++++++ docs/zh/08-toolchain-internals.md | 24 +++++++++++++++ docs/zh/16-the-target-triple.md | 7 +++++ docs/zh/22-features-and-capabilities.md | 16 ++++++++++ 5 files changed, 103 insertions(+) diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh index e17ac26b..40f7e2cf 100755 --- a/.github/tools/check_docs_structure.sh +++ b/.github/tools/check_docs_structure.sh @@ -13,6 +13,7 @@ # 7. the generated design-record index is current # 8. a new design record declares its subject and status # 9. every relative link in docs/ and examples/ resolves +# 10. a translation carries the same tables and code blocks # # What it deliberately does NOT check: whether a chapter documents what is # implemented, whether an assertion's strength matches its evidence, or whether @@ -160,6 +161,46 @@ for f in list(pathlib.Path("docs").rglob("*.md")) + list(pathlib.Path("examples" sys.exit(1 if bad else 0) PYCHECK +# ── 10. a translation carries the same tables and code blocks ──────────── +# +# check_docs_style.sh compares HEADING STRUCTURE, which is what catches a page +# that has fallen a section behind. It does not see a table row or a code block +# that never made it across, and two of those were sitting in the tree: the +# 简体中文 `[features]` section had no body at all, and 简体中文 §2.11 was +# missing the `identity` verdict table. Both predate this check and both are +# invisible to every other one. +python3 - <<'PYPARITY' || fail=1 +import pathlib, sys, re +bad = 0 +for en in sorted(pathlib.Path("docs").glob("*.md")): + zh = pathlib.Path("docs/zh") / en.name + if not zh.exists(): + continue + def count(f): + rows = blocks = 0 + infence = False + for line in f.read_text(errors="ignore").split("\n"): + if line.startswith("```"): + if not infence: + blocks += 1 + infence = not infence + continue + if infence: + continue + if line.startswith("|"): + rows += 1 + return rows, blocks + er, eb = count(en) + zr, zb = count(zh) + if er != zr: + print(f"FAIL: {en.name}: {er} table rows in English, {zr} in 简体中文") + bad += 1 + if eb != zb: + print(f"FAIL: {en.name}: {eb} code blocks in English, {zb} in 简体中文") + bad += 1 +sys.exit(1 if bad else 0) +PYPARITY + if [[ "$fail" -eq 0 ]]; then echo "OK: docs structure checks pass" fi diff --git a/docs/zh/05-mcpp-toml.md b/docs/zh/05-mcpp-toml.md index 0c3f05a1..001d330d 100644 --- a/docs/zh/05-mcpp-toml.md +++ b/docs/zh/05-mcpp-toml.md @@ -1099,6 +1099,21 @@ requirements/providers/artifacts、LinkIntent、平台搜索机制与链接后 v `mcpp why runtime` 只是最新存储文件的纯解释器:不重新解析 manifest,也不启动图形/ 硬件 probe。需要重新诊断所选 host provider 时使用 `xlings doctor`。 +每个 artifact 还带一个仅由路径算出的 `identity` 判定: + +| `identity` | 含义 | +|---|---| +| `ok` | 声明的路径(穿过符号链接后)落在声明的那个版本里 | +| `mismatch` | 它解析到了别处 —— **该 binding 已陈旧**,后来的某次安装把它重新指向了别的地方 | +| `missing` | 声明了,但那个路径上什么都没有 | +| `unverified` | 声明时没有可供比对的版本 | + +这就是 mcpp 早已施加于私有 libc 的那条规则的推广(`glibc@2.44` 解析到那一份载荷; +陈旧或缺失是错误,而绝不是「已安装版本里哪个看起来能用就用哪个」)。它不需要知道 +该 artifact 做什么。`unverified` **有意**不等于 `ok`:一个解析到了却没有 artifact +在其背后的 provider 并未被核验过,因此 `mcpp why runtime` 打印 +`(not declared by the environment — nothing to verify)` 而不是 `(none)`。 + 能力名使用分层小写 `domain.sub.role`(如 `display.present`)和前缀类 `abi:`(如 `abi:glibc`,参与工具链 ABI 强制)。 diff --git a/docs/zh/08-toolchain-internals.md b/docs/zh/08-toolchain-internals.md index 4444be51..e42a56d8 100644 --- a/docs/zh/08-toolchain-internals.md +++ b/docs/zh/08-toolchain-internals.md @@ -464,6 +464,30 @@ C 世界(`CLibMode::Sysroot`)并有自己的 libc++ 链接处理;Windows 没有 mcpp 把运行时 DLL 部署到产物 exe 旁,这正是该平台对 §3–§4 所做一切的原生 等价物。 +**工程自己产出的共享库**(`kind = "shared"`)确实按格式而不同,而这个差别不是 +flag 的拼法 —— 它是产物记录下的关于它自己的东西: + +| 格式 | 生产方发出什么 | 消费方链接什么 | +|---|---|---| +| ELF | 声明了 soname 时发 `-Wl,-soname,` | `-L` + `-l`、`-Wl,-rpath,$ORIGIN` | +| Mach-O | **总是**发 `-Wl,-install_name,@rpath/` | `-L` + `-l`、`-Wl,-rpath,@loader_path` | +| PE / MinGW | `-Wl,--out-implib,` | **导入库**,且 `-Wl,-Bdynamic` 在前 | +| PE / MSVC | 拒绝(不做自动导出;见 docs/12) | —— | + +其中三行是新增的:在此之前,除 ELF 之外一律被拒绝,原生与交叉都是。要让它们 +可用而不只是被允许,有两处细节必须改。Mach-O 的 install name 默认取该库被**链接** +时的路径,因此「仅在声明了 `soname` 时才发出」会让其余每个 `.dylib` 都记下一个构建 +目录 —— 在构建它的那台机器上没问题,换任何一台就是 `image not found`。而这个选择 +原先由宿主上的 `#if defined(__APPLE__)` 做出,那只在原生 macOS 构建上碰巧正确, +对任何交叉链接都是错的;它现在由目标决定,正如 `target_output` 早已如此。 + +**服务不了的目标会被拒绝**,而不是悄悄按宿主构建:Linux 上的 +`--target x86_64-windows-msvc` 从前会解析到原生 `g++`、写进 +`target/x86_64-linux-gnu/` 并报告成功。词汇表的档位说的是「mcpp 支持这个目标」; +`host_can_serve`(`registry.cppm`)回答的是另一个问题「这台机器能不能产出它」, +而 `prepare.cppm` 现在会问它 —— 显式的 `[target.X] toolchain = "…"` 是作者自备 +交叉工具链时的出口。 + ### 7.5 一个 flag 由哪根轴决定 2026.8.18 那一轮改了四个 flag,每一个此前都挂在错误的轴上。而这类错误的表现 diff --git a/docs/zh/16-the-target-triple.md b/docs/zh/16-the-target-triple.md index 11741dfc..4010024a 100644 --- a/docs/zh/16-the-target-triple.md +++ b/docs/zh/16-the-target-triple.md @@ -383,6 +383,13 @@ CRT;图供给时是 `musl`。一个目标字符串,两个不同的 C 库 —— | `riscv32-none-elf` | verified | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | | `aarch64-none-elf` | preview | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | | `x86_64-none-elf` | preview | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | +| `thumbv6m-none-eabi` | verified | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | +| `thumbv7m-none-eabi` | verified | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | +| `thumbv7em-none-eabi` | preview | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | +| `thumbv7em-none-eabihf` | verified | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | +| `thumbv8m.base-none-eabi` | preview | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | +| `thumbv8m.main-none-eabi` | verified | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | +| `thumbv8m.main-none-eabihf` | preview | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 | `载荷` 这里有工具链载荷产出它 · `图` 没有载荷,但依赖可以供给系统 · `系统` 在机器上被找到,不是 mcpp 装的 · `SDK` 平台自己的 · diff --git a/docs/zh/22-features-and-capabilities.md b/docs/zh/22-features-and-capabilities.md index 161aa47f..2b4e3838 100644 --- a/docs/zh/22-features-and-capabilities.md +++ b/docs/zh/22-features-and-capabilities.md @@ -10,6 +10,22 @@ Feature 是一个包提供可选内容的方式:一个编译宏、一份额外 ## `[features]` —— Feature(Cargo 风格,可加性) +```toml +[features] +default = ["base"] # 默认激活集合 +base = [] +docking = ["extra"] # 激活 docking 即隐含激活 extra(传递闭包) +extra = [] +``` + +- 激活来源:包自己的 `default` 集合 ∪ 显式请求(根包经由 + `mcpp build --features a,b`;依赖经由长形式依赖 spec 的 `features = [...]` + 与 `backend = "..."` 糖)。 +- 每个被激活的 feature 在该包编译时得到宏 `-DMCPP_FEATURE_`(名字大写, + 非字母数字变 `_`,例如 `backend-a` → `MCPP_FEATURE_BACKEND_A`)。 +- **严格校验**:目标包声明了 `[features]` 表时,请求一个未声明的 feature 产生 + 警告,在 `--strict` 下是错误。不声明 `[features]` 的包接受任意请求(纯宏用法)。 + ### 表形式 —— 让 feature 贡献的不止是隐含 feature `[features]` 的条目除了写成数组,还可写成**表**,从而让该 feature 在隐含 feature From ec87a8b52982dce25d58ee1222235b548ebd1921 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:25:15 +0800 Subject: [PATCH 13/36] docs(record): the four translation gaps rule 10 found, and the count it brings to ten --- ...-documentation-architecture-three-trees.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md index 478e8dc1..e7469b75 100644 --- a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md +++ b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md @@ -480,7 +480,7 @@ SPEC-006) are not, and §12.4 says why. | stale `docs/NN-*.md` citations | 6 | 0 | | manifest keys with no example | 13 | 7 | | `build.mcpp` API names used by an example | 10 of 38 | 12 of 38 | -| structure rules enforced in CI | 0 | 9 | +| structure rules enforced in CI | 0 | 10 | The seven manifest keys still uncovered are the ones §3.2 classifies as a code block rather than an example — `[hooks]`, `scan_overrides`, `cxx_runtime`, @@ -515,6 +515,23 @@ check about meaning. `05` said "every manifest key, including `[xlings]`" — the one claim the move made false. +**Four translations were missing content that every check called equal.** The +style checker compares heading structure, which is what catches a page that has +fallen a section behind. It cannot see a table row or a fenced block that never +crossed. Found while comparing the two languages by hand after the split, then +by the rule written for it: + +| where | what was missing | +|---|---| +| `docs/zh/22` (from `05`) | the `[features]` section had **no body**: the TOML example that defines the syntax, and all three bullets | +| `docs/zh/05` §2.11 | the `identity` verdict table and the two paragraphs around it | +| `docs/zh/08` §7.4 | the shared-library format table, Mach-O's install name, and the refusal of an unservable target | +| `docs/zh/16` | **seven target rows** — the 简体中文 support matrix listed 14 targets where the English listed 21 | + +None was introduced by this batch. The last one is the argument for rule 10 on +its own: a bilingual pair can agree on every heading and disagree about which +targets the tool supports. + **Two rules were written in the skill and not applied.** Chapter 22 had no "Current limitations" while the skill calls that section mandatory. And "every document states which rung it is on" was followed by no chapter and is not From 4774e81303b15be01b968428eea69c711a75a651 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:22:33 +0800 Subject: [PATCH 14/36] docs: the emit/parse mismatch, read the second time from the exit code The first description of it in this batch was wrong in the direction that matters. It said the emitted descriptor lacks a `sources` list which the validator requires, and that adding the list by hand makes it validate. Measured again, this time on the STATUS rather than the output: `mcpp xpkg parse` reports `unknown mcpp-segment key 'manifest'` and exits 1. The missing `sources` error follows from it -- the ignored key means nothing derives sources from the manifest it names -- and adding the list removes the second error, prints the summary, and still exits 1. The first reading took a printed summary for success. mcpp-index uses that key in 0 of 218 descriptors. A package keeping its own mcpp.toml omits the `mcpp` field entirely. So the defect is not a missing list: `mcpp emit xpkg` writes a descriptor shape that the resolver in the same release does not implement and that no published descriptor uses. --- ...-documentation-architecture-three-trees.md | 23 ++++++++++++++----- docs/21-commands-by-scenario.md | 21 +++++++++++------ docs/23-authoring-a-rule-package.md | 9 ++++---- docs/zh/21-commands-by-scenario.md | 19 ++++++++++----- docs/zh/23-authoring-a-rule-package.md | 8 +++---- 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md index e7469b75..9abc6f57 100644 --- a/.agents/docs/2026-09-08-documentation-architecture-three-trees.md +++ b/.agents/docs/2026-09-08-documentation-architecture-three-trees.md @@ -554,12 +554,23 @@ README because a rule author will meet all three: - **The compiler is a declared input.** Without it, editing `toyc.sh` left every edge clean and the artifact kept the previous compiler's bytes. -And one in the tooling: `mcpp emit xpkg` produces a descriptor that -`mcpp xpkg parse` rejects for a package keeping its own `mcpp.toml` — the -emitted `mcpp` segment carries `manifest = "mcpp.toml"` and no `sources` list, -and the validator requires one. Isolated by adding the list by hand, which makes -it validate. Recorded in `docs/21` as a limitation; not fixed in a documentation -change. +And one in the tooling, whose first description in this document was wrong in a +way worth keeping. `mcpp emit xpkg` writes `manifest = "mcpp.toml"` into the +`mcpp` segment; `mcpp xpkg parse` reports that key as **unknown** and exits 1, +and the "missing sources" error follows from it — the ignored key means nothing +derives sources from the manifest it names. + +> The first reading said "adding `sources` by hand makes it validate". It does +> not. It removes the second error, the summary prints, and the command still +> exits 1. The conclusion came from reading the OUTPUT rather than the STATUS, +> which is the failure this repository already has a memory for. + +`mcpp-index` uses that key in **0 of 218** descriptors: a package keeping its own +`mcpp.toml` omits the `mcpp` field entirely and mcpp looks the manifest up under +the version directory. So the defect is not a missing list — it is that `emit` +writes a descriptor shape the resolver in the same release does not implement +and no published descriptor uses. Recorded in `docs/21`; not fixed in a +documentation change. `build_examples.sh` refused all four new example roots for being in neither `BUILD` nor `SKIP`. That is the denominator discipline working, and it is the diff --git a/docs/21-commands-by-scenario.md b/docs/21-commands-by-scenario.md index 4612efef..eb609138 100644 --- a/docs/21-commands-by-scenario.md +++ b/docs/21-commands-by-scenario.md @@ -371,14 +371,21 @@ it will still say so once the key has more than one accepted value. (expected {s,m,h,d})`, while `mcpp clean --stale --older-than 0` accepts it. The two options share a parser but not this case. -`mcpp emit xpkg` produces a descriptor that `mcpp xpkg parse` rejects for a -package that keeps its own `mcpp.toml`. The emitted `mcpp` segment carries -`manifest = "mcpp.toml"` and no `sources` list, and the validator requires one: +`mcpp emit xpkg` writes a key `mcpp xpkg parse` does not know. For a package +that keeps its own `mcpp.toml`, the emitted `mcpp` segment ends with +`manifest = "mcpp.toml"`, and the descriptor parser reports it as unknown: ``` -error: d.lua: xpkg-lua://example.mathkit@0.1.0: error: synthesised manifest - missing sources (mcpp segment must declare `sources = { ... }`) +error: unknown mcpp-segment key 'manifest' — silently ignored at build time + by this mcpp version +error: synthesised manifest missing sources (mcpp segment must declare + `sources = { ... }`) ``` -Adding `sources = { "src/*.cppm" }` to the emitted `mcpp` segment makes it -validate. Measured on 2026.9.8.1. +The second error follows from the first: the ignored key means no sources are +derived from the manifest it names. Adding `sources = { … }` by hand removes the +second and not the first, and `mcpp xpkg parse` still exits 1. + +No descriptor in `mcpp-index` uses that key — 0 of 218. A package that keeps its +own `mcpp.toml` omits the `mcpp` field entirely, and mcpp looks the manifest up +under the version directory. Measured on 2026.9.8.1. diff --git a/docs/23-authoring-a-rule-package.md b/docs/23-authoring-a-rule-package.md index 79b94404..4a920ba2 100644 --- a/docs/23-authoring-a-rule-package.md +++ b/docs/23-authoring-a-rule-package.md @@ -243,7 +243,8 @@ path. imply `host-module`. `mcpp:plugins` declares `default = []`; a rule package whose rule is on by default is refused with a message naming the module and the key to add. -- `mcpp emit xpkg` produces a descriptor that `mcpp xpkg parse` rejects for a - package that keeps its own `mcpp.toml`: the emitted `mcpp` segment carries - `manifest = "mcpp.toml"` and no `sources` list, and the validator requires - one. Adding `sources = { … }` to the emitted descriptor makes it validate. +- `mcpp emit xpkg` writes `manifest = "mcpp.toml"` into the `mcpp` segment, and + `mcpp xpkg parse` reports that key as unknown and exits 1. No descriptor in + `mcpp-index` uses it (0 of 218); a package keeping its own `mcpp.toml` omits + the `mcpp` field entirely. See *Current limitations* in + [21 — Commands by Scenario](21-commands-by-scenario.md). diff --git a/docs/zh/21-commands-by-scenario.md b/docs/zh/21-commands-by-scenario.md index 429c7094..d1e2befe 100644 --- a/docs/zh/21-commands-by-scenario.md +++ b/docs/zh/21-commands-by-scenario.md @@ -314,14 +314,21 @@ deps = ["xim:mcpp-hooks-audioplayer@0.0.1"] (expected {s,m,h,d})` 被拒绝,而 `mcpp clean --stale --older-than 0` 接受。 两个选项共用一个 parser,但这一种取值上不一致。 -对一个自带 `mcpp.toml` 的包,`mcpp emit xpkg` 产出的描述符会被 `mcpp xpkg parse` -拒绝。产出的 `mcpp` 段带 `manifest = "mcpp.toml"` 而没有 `sources` 列表,而校验器 -要求有一个: +`mcpp emit xpkg` 写出一个 `mcpp xpkg parse` 不认识的键。对一个自带 `mcpp.toml` +的包,产出的 `mcpp` 段以 `manifest = "mcpp.toml"` 结尾,而描述符解析器把它报为 +未知键: ``` -error: d.lua: xpkg-lua://example.mathkit@0.1.0: error: synthesised manifest - missing sources (mcpp segment must declare `sources = { ... }`) +error: unknown mcpp-segment key 'manifest' — silently ignored at build time + by this mcpp version +error: synthesised manifest missing sources (mcpp segment must declare + `sources = { ... }`) ``` -给产出的 `mcpp` 段补上 `sources = { "src/*.cppm" }` 即可通过校验。实测于 +第二个错误由第一个导出:键被忽略,于是没有从它点名的那份 manifest 推导出任何 +源。手工补上 `sources = { … }` 只消掉第二个,消不掉第一个,`mcpp xpkg parse` +仍然以 1 退出。 + +`mcpp-index` 里没有任何描述符使用那个键 —— 218 个里 0 个。自带 `mcpp.toml` 的包 +**整个省略 `mcpp` 字段**,由 mcpp 在版本目录下查找那份 manifest。实测于 2026.9.8.1。 diff --git a/docs/zh/23-authoring-a-rule-package.md b/docs/zh/23-authoring-a-rule-package.md index 2ae61a3e..cb58b680 100644 --- a/docs/zh/23-authoring-a-rule-package.md +++ b/docs/zh/23-authoring-a-rule-package.md @@ -222,7 +222,7 @@ mcpp::floor("cuda.driver", runtime_needs); - 规则包自己 `[features] default` 里的规则 feature 不隐含 `host-module`。 `mcpp:plugins` 声明 `default = []`;规则默认开启的规则包会被拒绝,消息点名 该模块与需要补的键。 -- 对一个自带 `mcpp.toml` 的包,`mcpp emit xpkg` 产出的描述符会被 - `mcpp xpkg parse` 拒绝:产出的 `mcpp` 段带 `manifest = "mcpp.toml"` 而没有 - `sources` 列表,而校验器要求有一个。给产出的描述符补上 `sources = { … }` - 之后即可通过校验。 +- `mcpp emit xpkg` 把 `manifest = "mcpp.toml"` 写进 `mcpp` 段,而 + `mcpp xpkg parse` 把该键报为未知并以 1 退出。`mcpp-index` 里没有任何描述符使用 + 它(218 个里 0 个);自带 `mcpp.toml` 的包整个省略 `mcpp` 字段。见 + [21 —— 按场景选命令](21-commands-by-scenario.md)的*当前边界*。 From d89df460382ff68679e5f8d933026009ebd556bf Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:30:13 +0800 Subject: [PATCH 15/36] docs: number the chapters by band, and write the design the reorganisation was missing Two things, and the second is the one that matters. The chapters are renumbered so the number says which part a chapter is in: 0x uses mcpp, 1x ships what was built, 2x is toolchains and targets, 3x is bare metal and devices, 5x is the machine-facing contracts, 9x is mcpp itself. Within a part the order is a reading order. The index read `00, 01, 05, 06, 07, 21, 22` under Part I, which is a list a reader cannot sort. Renumbering was refused in the previous batch on a measurement that counted 103 citing files; 73 of those are `.agents/docs` design records, where citing the number a chapter had at the time is correct. The live cost is 29 files and 56 occurrences, plus 287 intra-docs links, all scripted -- and the earlier refusal conflated an immutable record with live code. The second thing is `.agents/docs/2026-09-08-the-documentation-as-a-book.md`. A reorganisation groups the chapters that exist; it does not decide what each one is FOR. Measured: testing is a section in seven chapters, dependency resolution in seven, C++ modules in seven, and the model -- what mcpp's moving parts are -- is a section in none. Five of 24 chapters state their reader and their question; six have a limits section. So the design specifies, per chapter, the reader, the one question it answers, what it contains, what it must NOT contain, where it sits and why. It creates three chapters for the three unowned topics, moves build programs beside rule authoring because they are one subject at two scales, moves bare metal in front of devices because that is the arc, and states the rule that keeps a topic from being explained a second time. That implies a second renumbering, which is the cost of having reorganised before designing. It is stated in the design rather than hidden. --- .../2026-09-08-the-documentation-as-a-book.md | 318 ++++++++++++++++++ .agents/docs/README.md | 4 +- .github/tools/check_docs_style.sh | 2 +- .github/tools/check_version_pins.sh | 2 +- .github/workflows/ci-fresh-install.yml | 2 +- CHANGELOG.md | 30 +- README.md | 16 +- README.zh-CN.md | 14 +- docs/00-getting-started.md | 6 +- docs/01-examples.md | 12 +- docs/{05-mcpp-toml.md => 02-mcpp-toml.md} | 34 +- docs/{06-workspace.md => 03-workspace.md} | 2 +- ...ies.md => 04-features-and-capabilities.md} | 12 +- docs/{07-build-mcpp.md => 05-build-mcpp.md} | 24 +- ...scenario.md => 06-commands-by-scenario.md} | 12 +- ...-and-release.md => 10-pack-and-release.md} | 6 +- ...-library.md => 11-publishing-a-library.md} | 8 +- docs/12-binary-distribution.md | 10 +- docs/{03-toolchains.md => 20-toolchains.md} | 6 +- ...rget-triple.md => 21-the-target-triple.md} | 14 +- docs/{14-target-side.md => 22-target-side.md} | 14 +- ...nment.md => 23-the-project-environment.md} | 12 +- ...5-openkal-cross.md => 24-openkal-cross.md} | 6 +- docs/{13-baremetal.md => 30-baremetal.md} | 14 +- docs/{18-devices.md => 31-devices.md} | 10 +- ...s-builds.md => 32-heterogeneous-builds.md} | 10 +- ...kage.md => 40-authoring-a-rule-package.md} | 14 +- ...machine-output.md => 50-machine-output.md} | 4 +- ...d-versions.md => 51-supported-versions.md} | 4 +- ...from-source.md => 90-build-from-source.md} | 4 +- ...internals.md => 91-toolchain-internals.md} | 6 +- docs/{09-release.md => 92-release.md} | 4 +- docs/README.md | 83 ++--- docs/specs/exit-codes.md | 4 +- docs/specs/manifest-semantics.md | 2 +- docs/specs/target-side.md | 2 +- docs/zh/00-getting-started.md | 8 +- docs/zh/01-examples.md | 12 +- docs/zh/{05-mcpp-toml.md => 02-mcpp-toml.md} | 34 +- docs/zh/{06-workspace.md => 03-workspace.md} | 2 +- ...ies.md => 04-features-and-capabilities.md} | 12 +- .../zh/{07-build-mcpp.md => 05-build-mcpp.md} | 22 +- ...scenario.md => 06-commands-by-scenario.md} | 12 +- ...-and-release.md => 10-pack-and-release.md} | 6 +- ...-library.md => 11-publishing-a-library.md} | 8 +- docs/zh/12-binary-distribution.md | 10 +- .../zh/{03-toolchains.md => 20-toolchains.md} | 8 +- ...rget-triple.md => 21-the-target-triple.md} | 14 +- .../{14-target-side.md => 22-target-side.md} | 12 +- ...nment.md => 23-the-project-environment.md} | 12 +- ...5-openkal-cross.md => 24-openkal-cross.md} | 6 +- docs/zh/{13-baremetal.md => 30-baremetal.md} | 12 +- docs/zh/{18-devices.md => 31-devices.md} | 6 +- ...s-builds.md => 32-heterogeneous-builds.md} | 10 +- ...kage.md => 40-authoring-a-rule-package.md} | 12 +- ...machine-output.md => 50-machine-output.md} | 4 +- ...d-versions.md => 51-supported-versions.md} | 4 +- ...from-source.md => 90-build-from-source.md} | 4 +- ...internals.md => 91-toolchain-internals.md} | 6 +- docs/zh/{09-release.md => 92-release.md} | 4 +- docs/zh/README.md | 82 ++--- examples/06-openkal-cross/README.md | 6 +- examples/09-heterogeneous/README.md | 4 +- examples/09-heterogeneous/cuda/app/mcpp.toml | 2 +- mcpp.toml | 2 +- modules/manifest/src/glob.cppm | 4 +- modules/manifest/src/toml.cppm | 4 +- modules/manifest/src/types.cppm | 8 +- modules/toolchain-model/src/fingerprint.cppm | 2 +- modules/toolchain-model/src/linkmodel.cppm | 2 +- modules/toolchain-model/src/triple.cppm | 2 +- modules/versioning/src/version.cppm | 2 +- src/build/ninja_backend.cppm | 2 +- src/build/prepare.cppm | 2 +- src/cli.cppm | 10 +- src/hooks.cppm | 2 +- src/pack/pack.cppm | 2 +- src/pm/publisher.cppm | 2 +- src/publish/pipeline.cppm | 2 +- src/toolchain/stdmod.cppm | 2 +- .../e2e/150_clang_module_operator_template.sh | 2 +- .../315_blocking_check_gates_compilation.sh | 2 +- tests/unit/test_wire.cpp | 4 +- 83 files changed, 724 insertions(+), 395 deletions(-) create mode 100644 .agents/docs/2026-09-08-the-documentation-as-a-book.md rename docs/{05-mcpp-toml.md => 02-mcpp-toml.md} (98%) rename docs/{06-workspace.md => 03-workspace.md} (99%) rename docs/{22-features-and-capabilities.md => 04-features-and-capabilities.md} (98%) rename docs/{07-build-mcpp.md => 05-build-mcpp.md} (98%) rename docs/{21-commands-by-scenario.md => 06-commands-by-scenario.md} (98%) rename docs/{02-pack-and-release.md => 10-pack-and-release.md} (99%) rename docs/{10-publishing-a-library.md => 11-publishing-a-library.md} (96%) rename docs/{03-toolchains.md => 20-toolchains.md} (99%) rename docs/{16-the-target-triple.md => 21-the-target-triple.md} (98%) rename docs/{14-target-side.md => 22-target-side.md} (97%) rename docs/{17-the-project-environment.md => 23-the-project-environment.md} (98%) rename docs/{15-openkal-cross.md => 24-openkal-cross.md} (98%) rename docs/{13-baremetal.md => 30-baremetal.md} (98%) rename docs/{18-devices.md => 31-devices.md} (95%) rename docs/{20-heterogeneous-builds.md => 32-heterogeneous-builds.md} (99%) rename docs/{23-authoring-a-rule-package.md => 40-authoring-a-rule-package.md} (96%) rename docs/{11-machine-output.md => 50-machine-output.md} (99%) rename docs/{19-supported-versions.md => 51-supported-versions.md} (97%) rename docs/{04-build-from-source.md => 90-build-from-source.md} (97%) rename docs/{08-toolchain-internals.md => 91-toolchain-internals.md} (99%) rename docs/{09-release.md => 92-release.md} (99%) rename docs/zh/{05-mcpp-toml.md => 02-mcpp-toml.md} (98%) rename docs/zh/{06-workspace.md => 03-workspace.md} (99%) rename docs/zh/{22-features-and-capabilities.md => 04-features-and-capabilities.md} (97%) rename docs/zh/{07-build-mcpp.md => 05-build-mcpp.md} (98%) rename docs/zh/{21-commands-by-scenario.md => 06-commands-by-scenario.md} (98%) rename docs/zh/{02-pack-and-release.md => 10-pack-and-release.md} (99%) rename docs/zh/{10-publishing-a-library.md => 11-publishing-a-library.md} (96%) rename docs/zh/{03-toolchains.md => 20-toolchains.md} (99%) rename docs/zh/{16-the-target-triple.md => 21-the-target-triple.md} (98%) rename docs/zh/{14-target-side.md => 22-target-side.md} (97%) rename docs/zh/{17-the-project-environment.md => 23-the-project-environment.md} (98%) rename docs/zh/{15-openkal-cross.md => 24-openkal-cross.md} (98%) rename docs/zh/{13-baremetal.md => 30-baremetal.md} (98%) rename docs/zh/{18-devices.md => 31-devices.md} (96%) rename docs/zh/{20-heterogeneous-builds.md => 32-heterogeneous-builds.md} (99%) rename docs/zh/{23-authoring-a-rule-package.md => 40-authoring-a-rule-package.md} (96%) rename docs/zh/{11-machine-output.md => 50-machine-output.md} (99%) rename docs/zh/{19-supported-versions.md => 51-supported-versions.md} (97%) rename docs/zh/{04-build-from-source.md => 90-build-from-source.md} (98%) rename docs/zh/{08-toolchain-internals.md => 91-toolchain-internals.md} (99%) rename docs/zh/{09-release.md => 92-release.md} (99%) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md new file mode 100644 index 00000000..76276a19 --- /dev/null +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -0,0 +1,318 @@ +--- +subject: docs +status: active +--- + +# The documentation as a book: a chapter-by-chapter design + +The previous record in this series +(`2026-09-08-documentation-architecture-three-trees.md`) settled **which tree** a +document belongs to and published that architecture. It did not design the book +inside `docs/`. It grouped the chapters that existed, renumbered them into bands, +and moved six sections out of an over-large reference. That is a +**reorganisation**. This document is the **design**: for every chapter, who reads +it, the one question it answers, what it contains, what it must not contain, +where it sits, why there, and how it is judged. + +Written because a reorganisation cannot fix what the measurements below show. A +tree indexed by mechanism gives a *task* no home, and no amount of renumbering +gives it one. + +--- + +## 1. What the measurement says, and why renumbering does not answer it + +### 1.1 No topic has an owner + +A topic is *owned* by the chapter that carries it as a `##` section. Measured +2026-09-08 across the 24 chapters: + +| topic | chapters carrying it as a section | +|---|---| +| testing (`mcpp test`) | **7** | +| dependency resolution and versions | **7** | +| C++ modules themselves | **7** | +| diagnosis and troubleshooting | 4 | +| caching and incrementality | 4 | +| **what mcpp is — the model** | **0** | + +Seven chapters explain testing because seven mechanisms each needed to mention +it. None of them is where a reader goes to learn how to test. The reader who +asks a *task* question — "how do I test", "what happens when two packages want +different versions", "why did this rebuild" — has no chapter to open, and the +seven partial answers do not compose. + +### 1.2 There is no model to hang anything on + +`00-getting-started` goes from *Installation* to *Creating a Project* with no +section in between. mcpp is a build system, a package manager and a toolchain +provisioner in one program; a reader who does not know that reads every +subsequent chapter as an unrelated feature. The five nouns the whole tree uses +— package, graph, toolchain, target, index — are defined nowhere. + +### 1.3 The chapters were never given a shape + +| property | chapters that have it | +|---|---| +| an opening that names its reader and its question | **5 of 24** | +| a "Current limitations" section | **6 of 24** | +| length within 2× of the median (≈420 lines) | 18 of 24 | + +`02-mcpp-toml` is 1,626 lines and `05-build-mcpp` is 1,218; `51-supported-versions` +is 90. Nothing decided any of those. + +### 1.4 The conclusion + +> Grouping and renumbering fix the **index**. They do not fix the **book**. A +> chapter that was written because a mechanism existed keeps being about the +> mechanism, and a reader with a task keeps not finding it. + +--- + +## 2. The method: design from the reader's task, not from the mechanism + +Three rules produce every decision in §4 and §5. + +**R1 — One owner per topic.** Exactly one chapter owns a topic. Every other +chapter that touches it states one sentence and links. A second explanation is a +second thing to keep current, and the two diverge on their own schedule. + +**R2 — A chapter exists for a reader with a task, not for a mechanism with a +name.** "Features" is a mechanism; "make part of a package optional" is a task. +Where the two coincide the chapter keeps the mechanism's name, because that is +what the reader searches for — but the *contents* are decided by the task. + +**R3 — Every chapter states its reader, its question, and its exclusions in its +first fifteen lines.** The exclusions are the load-bearing half: they are what +stops the chapter from re-absorbing the topics R1 assigned elsewhere. + +A fourth rule governs the sequence rather than a chapter: + +**R4 — A part is a reader's arc, and its order is the order that reader needs +it in.** Not alphabetical, not chronological by when the feature shipped. + +--- + +## 3. The model the book teaches, stated once + +Everything in `docs/` is about five nouns and the seams between them. This is +the content of the new chapter §4.1 specifies, and it is written here first +because the chapter list is derived from it. + +| noun | what it is | the chapter that owns it | +|---|---|---| +| **package** | a directory with an `mcpp.toml`; identity is `(namespace, name)` | 02 | +| **graph** | what a build is: sources compile, objects link, actions extend | 05 | +| **toolchain** | a payload mcpp installs and pins, not a program found on the machine | 20 | +| **target** | the machine the artifact runs on, which is not the host | 21 | +| **index** | where packages come from, and what a descriptor promises | 11 | + +The seams, each of which is a chapter rather than a section, because each is a +place two nouns meet and a reader arrives with a question about the meeting: + +| seam | question a reader arrives with | +|---|---| +| features | how do I make part of a package optional | +| workspaces | how do several packages become one build | +| the target side | how does a manifest say "only on this target" | +| rules | how do I teach the graph something it has no rule for | +| devices | how do I run an artifact on a machine that is not this one | +| accelerators | how does part of my program get compiled for a device | + +**Why the model is a chapter and not a paragraph in `00`.** `00` is a tutorial: +a reader following it is typing, not building a mental model, and a model +inserted there is read as preamble to skip. The model chapter is short, it is +the first thing the index points at, and every other chapter may assume it. + +--- + +## 4. Chapters this design creates + +Three, each answering a question §1.1 measured as unowned. + +### 4.1 `00 — How mcpp Works` (new; current `00` becomes `01`) + +| | | +|---|---| +| **reader** | anyone, before anything else. Assumed by every other chapter | +| **question** | what are the moving parts, and which one is failing when something fails | +| **contains** | the five nouns of §3 with one paragraph each; the three seams a first build crosses (manifest → graph → toolchain); where each noun's state lives on disk (`target/`, the store, the index cache); the one diagram | +| **excludes** | any field name, any flag, any command beyond `mcpp build`. It is a model, not a reference | +| **why first** | §1.2. Without it every later chapter is an unrelated feature | +| **criterion** | a reader who has read only this chapter can say which noun a given error message is about | + +### 4.2 `04 — Dependencies and Resolution` (new) + +| | | +|---|---| +| **reader** | someone whose build now has more than their own code in it | +| **question** | where does a dependency come from, what does a version constraint mean, and what happens when two of them disagree | +| **contains** | `[dependencies]` in all its forms (index, path, git); what a bare version pins and what `>=` requires; one package one version, and the refusal when it cannot hold; `mcpp.lock` and `--locked`; `mcpp add` / `update` / `why`; `[dev-dependencies]` and `[build-dependencies]` and the difference in what each reaches | +| **excludes** | the *identity* rules (SPEC-001 owns them) and how to publish (11 owns that) | +| **why here** | it is the second thing every reader does, and today it is seven partial answers | +| **criterion** | a reader can predict which version resolves for a stated graph, and say why | + +### 4.3 `06 — Testing` (new) + +| | | +|---|---| +| **reader** | anyone with code that has to keep working | +| **question** | how do I run tests, what does mcpp consider a test, and how do I test what does not run on this machine | +| **contains** | `tests/**/*.cpp` as the convention; `mcpp test` and its selectors; `[dev-dependencies]`; the worker pool and `run_exclusive`; testing a cross or bare-metal target through a runner; `--message-format json` for a CI consumer, by reference | +| **excludes** | the runner *mechanism* (31 owns it) and the JSON schema (50 owns it) | +| **why here** | §1.1: seven chapters mention it, none owns it | +| **criterion** | a reader can run a test on a target their machine cannot execute | + +--- + +## 5. The book + +Bands are meaningful: the first digit is the part. Within a part the order is +the order that part's reader needs, which is R4. + +### 5.1 `0x` — Everyone + +The arc: understand the parts, get one program running, know where the examples +are, write the manifest, add dependencies, make things optional, test. + +| # | chapter | reader | the one question | excludes | +|---|---|---|---|---| +| 00 | How mcpp Works | anyone | what are the moving parts | fields, flags | +| 01 | Getting Started | a newcomer, typing | how do I get a program running | anything not on the path to a running program | +| 02 | Examples | a reader choosing a starting point | which example teaches what I need | the content of the examples | +| 03 | The mcpp.toml Manifest | an author | what may a manifest say | conditioning (22), features (05), the environment (23) | +| 04 | Dependencies and Resolution | an author with dependencies | where does a dependency come from, and which version wins | identity (SPEC-001), publishing (11) | +| 05 | Features and Capabilities | an author with something optional | how do I make part of a package optional | accelerator selection (32) | +| 06 | Testing | anyone | how do I run tests, including where they cannot run here | runners (31), the JSON schema (50) | +| 07 | Commands by Scenario | anyone, later | which command does the thing I want | everything each command means in depth | + +**Why `07` is last and not first.** It is a lookup, used after the reader knows +the nouns. A reader who opens it first gets a list of verbs with no model. + +**Why the manifest (03) precedes dependencies (04) and features (05).** Both are +manifest tables; a reader who has not seen a manifest cannot place them. + +### 5.2 `1x` — Publishing + +The arc: package an application, publish a library's source, publish its +binaries. + +| # | chapter | reader | the one question | excludes | +|---|---|---|---|---| +| 10 | Packaging an Application for Release | someone shipping a program | how do I produce something another machine can run | libraries (12) | +| 11 | Publishing a Library to mcpp-index | a library author | how does my package become one others can name | the descriptor grammar (SPEC-001) | +| 12 | Distributing a Prebuilt Library | a publisher of binaries | how do I ship compiled artifacts and state what they are compatible with | the compatibility tag's grammar (SPEC-005, planned) | + +**Why publishing precedes targets.** A library author publishes before they +cross-compile; an application author packages before they port. The reader who +needs `2x` knows they need it. + +### 5.3 `2x` — Toolchains and targets + +The arc: what a toolchain is and how it is chosen, how a target is named, how a +manifest conditions on one, what environment the project declares, and one +worked cross-compilation. + +| # | chapter | reader | the one question | excludes | +|---|---|---|---|---| +| 20 | Toolchain Management | anyone whose compiler matters | which compiler will build this, and how do I choose another | internals (91) | +| 21 | The Target Triple | someone building for another machine | how is a target named, and which are supported | conditioning (22) | +| 22 | The Target Side | an author supporting several targets | how does a manifest say "only there" | the accelerator axis (32) | +| 23 | The Project Environment | an author whose build needs tools | how does a project declare the tools its build runs | build programs (30-band) | +| 24 | Cross-Compilation Over openkal | someone cross-building a hosted target | how do I build for another OS from this one | bare metal (30) | + +### 5.4 `3x` — Extending the graph + +**This part is new as a grouping**, and it is where the design departs most from +what shipped. Build programs and rule packages are the same subject at two +scales: a project that needs the graph to do something it has no rule for, and a +package that supplies that rule to others. + +| # | chapter | reader | the one question | excludes | +|---|---|---|---|---| +| 30 | Build Programs: `build.mcpp` | an author whose build needs a step mcpp has no rule for | how do I add work to the graph | authoring a reusable rule (31) | +| 31 | Authoring a Rule Package | an ecosystem author | how do I package that step so other projects can use it | the shipped rules' spellings (`mcpp:plugins`) | + +**Why they are two chapters and not one.** Different readers with different +questions. The first has a project and a problem; the second has an audience. +The 1,218 lines of the current build-program chapter contain both, and its +"Writing a rule package" section is now a second copy of chapter 31. + +### 5.5 `4x` — Devices and accelerators + +| # | chapter | reader | the one question | excludes | +|---|---|---|---|---| +| 40 | Bare-Metal and Freestanding Targets | an embedded developer | how do I build for a machine with no OS | reaching it (41) | +| 41 | Reaching a Device | anyone whose artifact does not run here | how do I run and test it where it belongs | the target's construction (40) | +| 42 | Heterogeneous Builds | a GPU or accelerator developer | how does part of my program get compiled for a device | rule authoring (31) | + +**Why bare metal moved out of `3x`.** It is not a toolchain topic; it is a +target with no operating system, and everything a reader needs after that is +about reaching it and running on it — which is 41. The three chapters are one +arc. + +### 5.6 `5x` — Contracts for programs + +| # | chapter | reader | the one question | excludes | +|---|---|---|---|---| +| 50 | Machine-Readable Output | a tool or CI author | what may a program parse, and what is versioned | human-facing output | +| 51 | Supported Versions and Compatibility | anyone with a policy question | what may change between releases, and what may not | the exit-code table (SPEC-003) | + +### 5.7 `9x` — mcpp itself + +| # | chapter | reader | the one question | excludes | +|---|---|---|---|---| +| 90 | Building from Source and Contributing | a contributor | how do I build and change mcpp | how a user builds their project | +| 91 | Toolchain Internals | a contributor, or a user debugging a toolchain | how does mcpp actually resolve and assemble a toolchain | how to *choose* one (20) | +| 92 | Releasing mcpp | a maintainer | how is a release cut and verified | publishing a package (11) | + +--- + +## 6. What this changes against what shipped + +| decision | effect | +|---|---| +| three new chapters (00 model, 04 dependencies, 06 testing) | the three topics §1.1 measured as unowned get an owner | +| build programs move `0x` → `3x`, beside rule authoring | the two scales of one subject become one part | +| bare metal moves `3x` → `4x`, before devices | the embedded arc reads in order | +| every chapter gains a designed opening (reader, question, exclusions) | 5 of 24 have one today | +| every reference chapter gains a limits section | 6 of 24 have one today | +| the manifest reference sheds its worked examples and its appendix | `examples/` owns worked examples; the schema-ownership appendix is contributor material | +| `mcpp test` mentions in six chapters become one sentence and a link | R1 | + +**The renumbering this implies is the second in one batch, and that is the cost +of having reorganised before designing.** It is paid once here; §7 states the +order that keeps every citation resolving while it happens. + +--- + +## 7. Order of work + +1. Write `00`, `04` and `06` against their specs in §4. New content, no moves. +2. Apply R1: for each of the three topics, cut the six other explanations to a + sentence and a link. +3. Renumber into §5's bands, in one scripted pass with the link and label + rewrite the previous batch established. +4. Give every chapter its designed opening (R3) and, where it is a reference, a + limits section. +5. Split the manifest reference: worked examples out, appendix to `9x` or + SPEC-004. +6. Re-run the review in `.agents/skills/mcpp-docs-style` §13 against the eight + dimensions, with §5's table as the criterion for "面向人群" and "梯度". + +Steps 1 and 2 are the ones that make the book different. Step 3 is mechanical +and has a tested script. A batch that stops after step 3 has renumbered twice +and designed nothing, which is the failure this document exists to name. + +--- + +## 8. What this design does not claim + +- **It does not claim the current chapters are badly written.** Most are + accurate and several are excellent. The defect is that nothing decided what + each one is *for*, so topics landed wherever a mechanism needed them. +- **It does not merge chapters to reduce their number.** 24 chapters for a tool + that is three tools is not too many. Two of them are too long, and one + grouping was wrong; that is the whole of the structural change. +- **It does not settle the specifications' language.** That question stands + where the previous record left it. diff --git a/.agents/docs/README.md b/.agents/docs/README.md index 86f5fd97..33623f14 100644 --- a/.agents/docs/README.md +++ b/.agents/docs/README.md @@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded --- ``` -269 records. +270 records. ## By subject @@ -26,6 +26,7 @@ Records that declare one. Everything else is listed by date below. ### docs +- [The documentation as a book: a chapter-by-chapter design](2026-09-08-the-documentation-as-a-book.md) — active - [A curriculum for the examples, a reference for the documentation, and a check with a denominator](2026-09-08-examples-curriculum-and-documentation-plan.md) — superseded by [2026-09-08-documentation-architecture-three-trees.md](2026-09-08-documentation-architecture-three-trees.md) - [Three documentation trees, three audiences, and the rule for citing between them](2026-09-08-documentation-architecture-three-trees.md) — active @@ -33,6 +34,7 @@ Records that declare one. Everything else is listed by date below. ### 2026-09 +- [The documentation as a book: a chapter-by-chapter design](2026-09-08-the-documentation-as-a-book.md) — active - [A curriculum for the examples, a reference for the documentation, and a check with a denominator](2026-09-08-examples-curriculum-and-documentation-plan.md) — superseded by [2026-09-08-documentation-architecture-three-trees.md](2026-09-08-documentation-architecture-three-trees.md) - [Three documentation trees, three audiences, and the rule for citing between them](2026-09-08-documentation-architecture-three-trees.md) — active - [第六轮生态复核:工具平面的目标轴](2026-09-07-round6-ecosystem-review.md) diff --git a/.github/tools/check_docs_style.sh b/.github/tools/check_docs_style.sh index 86b42ed3..482de98d 100755 --- a/.github/tools/check_docs_style.sh +++ b/.github/tools/check_docs_style.sh @@ -20,7 +20,7 @@ fail=0 bad() { echo "FAIL: $*"; fail=1; } # Tutorials address the reader on purpose: the reader is following along. -TUTORIALS="00-getting-started.md 01-examples.md 04-build-from-source.md" +TUTORIALS="00-getting-started.md 01-examples.md 90-build-from-source.md" # Headings outside fenced code blocks. `# …` inside a ```sh block is a shell # comment, and counting it made the first version of this script report a diff --git a/.github/tools/check_version_pins.sh b/.github/tools/check_version_pins.sh index 0f7316df..aba1bcfc 100755 --- a/.github/tools/check_version_pins.sh +++ b/.github/tools/check_version_pins.sh @@ -136,7 +136,7 @@ fi # (b) The version BOOTSTRAPPED FROM (.xlings.json) names a mcpp that is already # published, and is NOT required to equal the version being built. It # deliberately lags, and is bumped in a separate commit AFTER the release -# exists in xim-pkgindex (see docs/09-release.md). Requiring equality here is +# exists in xim-pkgindex (see docs/92-release.md). Requiring equality here is # what an earlier revision of this script got wrong: it sent CI to install a # version that did not exist yet, and every job died with # `package 'mcpp@' not found`. diff --git a/.github/workflows/ci-fresh-install.yml b/.github/workflows/ci-fresh-install.yml index abb0256d..78192120 100644 --- a/.github/workflows/ci-fresh-install.yml +++ b/.github/workflows/ci-fresh-install.yml @@ -55,7 +55,7 @@ concurrency: # NOT to be confused with the .xlings.json workspace pin, which this used to be # kept equal to. That one is the BOOTSTRAP compiler for the self-host builds and # has a different requirement — it must be a released mcpp that can build the -# CURRENT source tree — so it stays hand-maintained. See docs/09-release.md. +# CURRENT source tree — so it stays hand-maintained. See docs/92-release.md. jobs: # ────────────────────────────────────────────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index dc026bd4..abc0dd3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -677,7 +677,7 @@ C++20 modules 的第二个编译器(icpx,或带 SYCL 前端的 clang)。 配套的规则包在 `mcpp:plugins` 0.2.0:`mcpp.rules.sycl` 驱动 `xim:dpcpp` 载荷, `mcpp.rules.hip` 在 NVIDIA 平台上把 HIP 当作 CUDA 运行时之上的一层头文件, `mcpp.rules.spirv` 增加 `glslc` 路线(`xim:shaderc` 使它从一句声明变成一条路线)。 -文档见 `docs/20-heterogeneous-builds.md` 的「lanes」一节。 +文档见 `docs/32-heterogeneous-builds.md` 的「lanes」一节。 ### `mcpp clean --stale`:只清 target/ 里已无构建使用的指纹目录 (#565) @@ -779,7 +779,7 @@ e2e 610 分别断言这五条(含 `mcpp` 命名空间零告警、其他命名空 文件的相反的话。 表因此扩到「由另一个编译器消费的语言」:CUDA 与 HIP,GLSL 的各个 stage 与无 stage 的 -`.glsl`,HLSL,OpenCL C,Metal。共 18 个扩展名,完整清单见 `docs/20-heterogeneous-builds.md`。 +`.glsl`,HLSL,OpenCL C,Metal。共 18 个扩展名,完整清单见 `docs/32-heterogeneous-builds.md`。 这次扩表不能改变任何今天可用的构建,理由有两条且互相独立:设备扩展名**本来就不在** 默认 source glob 里,所以没有 glob 变宽;而这些扩展名今天在 `sources` 里是**硬错误**, @@ -814,7 +814,7 @@ e2e 610 分别断言这五条(含 `mcpp` 命名空间零告警、其他命名空 ### 文档 -- 第 20 章由「加速器」更名为「异构硬件构建」(`docs/20-heterogeneous-builds.md`),副题指明 +- 第 20 章由「加速器」更名为「异构硬件构建」(`docs/32-heterogeneous-builds.md`),副题指明 GPU 与 AI 加速器目标以及宿主/设备混合编译;`accel` 键不变。 - 第 5 章与第 7 章(中英)补入 feature 选择的规则集合与 `mcpp.rules.*` / `mcpp.tools.*` 命名;第 7 章中文版此前缺少命名一节,本次补齐。 @@ -957,7 +957,7 @@ generic target 取得同样的覆盖,所以空的下界不放宽任何东西。 设计与调研:`.agents/docs/2026-09-05-accelerator-support-design.md`、 `.agents/docs/2026-09-04-ai-accelerator-toolchain-ecosystem-survey.md`。 -新增手册章节 `docs/20-heterogeneous-builds.md`(中英双份)。 +新增手册章节 `docs/32-heterogeneous-builds.md`(中英双份)。 ## [2026.9.4.3] — 2026-09-04 @@ -1159,7 +1159,7 @@ M-profile:MPU 按基址与上限描述区域,没有页表项。A-profile 有真 `mcpp why runners` 列出本工程提供的 runner,与其余解析结果并列; `mcpp run --list-runners` 是同一份读取,单独报告。 -新增 `docs/18-devices.md`、`docs/19-supported-versions.md`(中英双份)。 +新增 `docs/31-devices.md`、`docs/51-supported-versions.md`(中英双份)。 指令协议版本 6。 ## [2026.9.4.1] — 2026-09-04 @@ -2543,7 +2543,7 @@ windows-x86_64),而不是手头有哪几台 runner —— 一台拿到发布二 本次发布修的三条缺陷全部来自「一个事实在多处各自推导」。各包的载荷路径由 `MCPP_XPKG_*_DIR` 另行回答,与 `PATH` 是两个问题。 - 新增 `examples/07-project-subos/` 与 [第 17 章](docs/17-the-project-environment.md)。 + 新增 `examples/07-project-subos/` 与 [第 17 章](docs/23-the-project-environment.md)。 ### 测试 @@ -2792,7 +2792,7 @@ windows-x86_64),而不是手头有哪几台 runner —— 一台拿到发布二 ### 文档 -- 新增[第 16 章 —— 目标三元组](docs/16-the-target-triple.md)(中英双份): +- 新增[第 16 章 —— 目标三元组](docs/21-the-target-triple.md)(中英双份): 三段各是什么、第三段在每个平台上命名的是不同的轴、两种体系的分界、 以及该用哪种拼法。 @@ -2909,7 +2909,7 @@ windows-x86_64),而不是手头有哪几台 runner —— 一台拿到发布二 判据是失败模态:选错不会让链接失败,会得到一个能跑、偶尔崩的程序。 `[build] runner` 早已按同一条规则处理。 -- **`docs/14-target-side.md`(中英)与 `docs/specs/target-side.md`(SPEC-002)。** +- **`docs/22-target-side.md`(中英)与 `docs/specs/target-side.md`(SPEC-002)。** ### 变更 @@ -3890,7 +3890,7 @@ windows-x86_64),而不是手头有哪几台 runner —— 一台拿到发布二 (它不改任何一条边 —— 进了会让「抬高超时」重建全世界)。 - 旧版 mcpp 遇到 `module_extensions` 会警告+忽略,然后把那些文件当普通翻译单元 编译 —— **错误的构建**而不是干净的失败。发布用了这个键的包必须声明 mcpp 版本下限 - (见 `docs/10-publishing-a-library.md`)。 + (见 `docs/11-publishing-a-library.md`)。 ## [2026.8.10.3] — 2026-08-10 @@ -4069,7 +4069,7 @@ windows-x86_64),而不是手头有哪几台 runner —— 一台拿到发布二 机器可读输出有契约了。设计与实测见 `.agents/docs/2026-08-08-machine-readable-output-protocol-design.md`,用户文档见 -`docs/11-machine-output.md`。 +`docs/50-machine-output.md`。 ### 新增 @@ -4317,7 +4317,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 文档 -- `docs/05-mcpp-toml.md` 的示例此前写的是 `import mcpp.rules.protobuf;`,**做不到**:mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一原子段。随之澄清一条会咬人的约束:规则包的名字必须是**合法 C++ 模块名**(`grpcgen` 可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示你名字有问题。 +- `docs/02-mcpp-toml.md` 的示例此前写的是 `import mcpp.rules.protobuf;`,**做不到**:mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一原子段。随之澄清一条会咬人的约束:规则包的名字必须是**合法 C++ 模块名**(`grpcgen` 可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示你名字有问题。 ## [2026.8.5.1] — 2026-08-05 @@ -4516,7 +4516,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 修复 -- **`[build] static_stdlib = false` 对测试二进制静默失效(#336)。** #124 在 0.0.52 明确写下这个 opt-out,`docs/05-mcpp-toml.md` 至今也还这么写;但 #202(0.0.86)把测试二进制改成与分发目标相同的静态 `-load_hidden` libc++ 时,新增的那条推导**没有带上 `staticStdlib` 门**。结果是约一年时间里 macOS 上的 `mcpp test` 没有任何办法回到动态 libc++,而文档一直在承诺它可以。 +- **`[build] static_stdlib = false` 对测试二进制静默失效(#336)。** #124 在 0.0.52 明确写下这个 opt-out,`docs/02-mcpp-toml.md` 至今也还这么写;但 #202(0.0.86)把测试二进制改成与分发目标相同的静态 `-load_hidden` libc++ 时,新增的那条推导**没有带上 `staticStdlib` 门**。结果是约一年时间里 macOS 上的 `mcpp test` 没有任何办法回到动态 libc++,而文档一直在承诺它可以。 - **macOS 上全局对象在静态初始化期访问 `std::cout` 必崩(#336)。** Mach-O 没有按优先级排序的初始化段(`init_priority` 只在单个 TU 内有效),归档成员的初始化器按链接顺序排在最后;而 libc++ 的 `` 不像 libstdc++ / MSVC STL 那样自带 `ios_base::Init` 守卫,流的构造只存在于库内对象里。两件事叠起来的后果是:默认配置下,任何在构造函数里碰 `std::cout` 的全局对象都会读到 vptr 为零的流,进程启动即 SIGSEGV —— 而且**包侧无法修复**,因为 `std::ios_base::Init` 在 libc++ 的头文件里只有前向声明(`ios:70`),标准为静态初始化次序提供的官方解药在 libc++ 上用户根本写不出来。 @@ -4550,7 +4550,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 修复 -- **`--offline` 不再拒绝本地 git 远端。** `docs/05-mcpp-toml.md` 写明 `--offline` 的语义是「完全不碰网络……已安装的东西照常构建」,`prepare.cppm` 里依赖下载闸的注释也把线画在同一处:「这条线以上全是本地操作,一个依赖齐备的离线构建必须成功」。但 `git = "../sibling-repo"` 这种指向本地目录的远端,`ls-remote`/`clone` 都只是文件系统读取,拒绝它买不到任何隔离性。现在按远端形态判定——`file://`、以及不带 scheme 也不是 `git@host:path` 的存在路径,算本地(Windows 盘符 `C:\repo` 含冒号但不含 `@`,因此仍归本地)。 +- **`--offline` 不再拒绝本地 git 远端。** `docs/02-mcpp-toml.md` 写明 `--offline` 的语义是「完全不碰网络……已安装的东西照常构建」,`prepare.cppm` 里依赖下载闸的注释也把线画在同一处:「这条线以上全是本地操作,一个依赖齐备的离线构建必须成功」。但 `git = "../sibling-repo"` 这种指向本地目录的远端,`ls-remote`/`clone` 都只是文件系统读取,拒绝它买不到任何隔离性。现在按远端形态判定——`file://`、以及不带 scheme 也不是 `git@host:path` 的存在路径,算本地(Windows 盘符 `C:\repo` 含冒号但不含 `@`,因此仍归本地)。 - **克隆被中途杀掉后不再永久提供错误的 commit。** 缓存目录以 commit 命名,但内容是 `git clone` 之后再 `git checkout` 两步做出来的;进程死在两步之间,目录名和 HEAD 就对不上了,而后续构建只检查目录存不存在。现在分支依赖会比对 `git rev-parse HEAD`,不符即删除重克隆(tag/rev 以 ref 名为身份,无可比之物)。 @@ -4633,7 +4633,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 **最危险的一条判据**(`SuppressedInconclusive`):「本地查不到」单独不能推出「需要刷新」。xim 描述符不写 `namespace`,`(xim, x)` 永远匹配不上身份门 —— 把这种 miss 当真,任何带 xim 依赖的工程会**每次构建都刷**,比被删掉的 TTL 更糟。判据复用 `IndexRoute::authoritative_for`(#307),单测 + e2e 双闸锁住。 - 语义变化:`^1.2` 对**本地索引已知的版本**求解。上游新发的 1.3.0 需要 `mcpp index update` 或 `mcpp update` 才可见 —— 这正是那两个命令存在的意义,已写进 `docs/05-mcpp-toml.md`。 + 语义变化:`^1.2` 对**本地索引已知的版本**求解。上游新发的 1.3.0 需要 `mcpp index update` 或 `mcpp update` 才可见 —— 这正是那两个命令存在的意义,已写进 `docs/02-mcpp-toml.md`。 - **`mcpp update` 不再是空操作。** 它此前只删 mcpp.lock 条目、然后叫用户去跑 `mcpp build` —— 而构建路径**从不读 mcpp.lock**(`prepare` 只写不读),所以删了等于没删,行为影响为零。它现在先强制刷新索引(显式意图 ⇒ 不看 TTL、不看去抖),并报告索引 rev 的变化;工程里没有任何走共享 registry 的依赖时跳过(刷了也没用)。 @@ -5629,7 +5629,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 - `[targets.]` 下的不支持键不再被静默丢弃,而是产生 warning(`--strict` 下为 error), 并指引到正确的机制(workspace / features / profile)。 -- 文档 `docs/05-mcpp-toml.md`(及 `docs/zh`)新增"构建配置该放哪"的决策指引。 +- 文档 `docs/02-mcpp-toml.md`(及 `docs/zh`)新增"构建配置该放哪"的决策指引。 设计记录见 `.agents/docs/2026-06-18-per-target-build-config-design.md`。 ## [0.0.54] — 2026-06-10 diff --git a/README.md b/README.md index e1003f73..6529a50a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Module](https://img.shields.io/badge/module-ok-green.svg)](https://en.cppreference.com/w/cpp/language/modules) [![License](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](LICENSE) -| [Documentation](docs/) · [Getting Started](docs/00-getting-started.md) · [mcpp.toml Guide](docs/05-mcpp-toml.md) · [Examples](docs/01-examples.md) · [Toolchains](docs/03-toolchains.md) | +| [Documentation](docs/) · [Getting Started](docs/00-getting-started.md) · [mcpp.toml Guide](docs/02-mcpp-toml.md) · [Examples](docs/01-examples.md) · [Toolchains](docs/20-toolchains.md) | |:---:| | [Package index mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [Module libraries mcpplibs](https://github.com/mcpplibs) · [Community Forum](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) | | [![ci-linux](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml) [![ci-macos](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml) [![ci-windows](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml) | @@ -224,7 +224,7 @@ Then `import` it directly in your code: import mcpplibs.cmdline; ``` -> For more dependency options (version constraints, namespaces, Git references, local paths, etc.), see the [mcpp.toml guide — dependency management](docs/05-mcpp-toml.md). +> For more dependency options (version constraints, namespaces, Git references, local paths, etc.), see the [mcpp.toml guide — dependency management](docs/02-mcpp-toml.md). ## Feature Overview @@ -413,17 +413,17 @@ verified — CI builds **and executes** the artifact end-to-end (qemu/wine inclu > cross-compilers by construction, so any host that can install the LLVM > payload produces these targets. The C library, startup code, memory layout > and emulator travel with a board-support package rather than with mcpp — see -> [docs/13 — Bare-Metal and Freestanding Targets](docs/13-baremetal.md). +> [docs/13 — Bare-Metal and Freestanding Targets](docs/30-baremetal.md). ## Documentation - [Getting Started](docs/00-getting-started.md) — install → new → build → run in 5 minutes - [Examples](docs/01-examples.md) -- [Packaging & Release](docs/02-pack-and-release.md) -- [Toolchain Management](docs/03-toolchains.md) -- [Building from Source](docs/04-build-from-source.md) -- [mcpp.toml Guide](docs/05-mcpp-toml.md) -- [Workspaces](docs/06-workspace.md) +- [Packaging & Release](docs/10-pack-and-release.md) +- [Toolchain Management](docs/20-toolchains.md) +- [Building from Source](docs/90-build-from-source.md) +- [mcpp.toml Guide](docs/02-mcpp-toml.md) +- [Workspaces](docs/03-workspace.md) Full options for any command are available via `mcpp --help`. diff --git a/README.zh-CN.md b/README.zh-CN.md index 9ed95b31..e2403db9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,7 +9,7 @@ [![Module](https://img.shields.io/badge/module-ok-green.svg)](https://en.cppreference.com/w/cpp/language/modules) [![License](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](LICENSE) -| [文档](docs/zh/) · [快速开始](docs/zh/00-getting-started.md) · [mcpp.toml 指南](docs/zh/05-mcpp-toml.md) · [示例项目](docs/zh/01-examples.md) · [工具链管理](docs/zh/03-toolchains.md) | +| [文档](docs/zh/) · [快速开始](docs/zh/00-getting-started.md) · [mcpp.toml 指南](docs/zh/02-mcpp-toml.md) · [示例项目](docs/zh/01-examples.md) · [工具链管理](docs/zh/20-toolchains.md) | |:---:| | [包索引 mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [模块化库 mcpplibs](https://github.com/mcpplibs) · [社区论坛](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) | | [![ci-linux](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml) [![ci-macos](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml) [![ci-windows](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml) | @@ -221,7 +221,7 @@ cmdline = "0.0.2" import mcpplibs.cmdline; ``` -> 更多依赖配置方式(版本约束、命名空间、Git 引用、本地路径等)参见 [mcpp.toml 指南 — 依赖管理](docs/zh/05-mcpp-toml.md)。 +> 更多依赖配置方式(版本约束、命名空间、Git 引用、本地路径等)参见 [mcpp.toml 指南 — 依赖管理](docs/zh/02-mcpp-toml.md)。 ## 功能概览 @@ -389,11 +389,11 @@ mcpp 的身份模型是两条正交轴:**工具链** = `family@version`(family - [快速开始](docs/zh/00-getting-started.md) — 5 分钟完成 install → new → build → run - [示例项目](docs/zh/01-examples.md) -- [发布打包](docs/zh/02-pack-and-release.md) -- [工具链管理](docs/zh/03-toolchains.md) -- [从源码构建](docs/zh/04-build-from-source.md) -- [mcpp.toml 指南](docs/zh/05-mcpp-toml.md) -- [工作空间](docs/zh/06-workspace.md) +- [发布打包](docs/zh/10-pack-and-release.md) +- [工具链管理](docs/zh/20-toolchains.md) +- [从源码构建](docs/zh/90-build-from-source.md) +- [mcpp.toml 指南](docs/zh/02-mcpp-toml.md) +- [工作空间](docs/zh/03-workspace.md) 任意命令的完整选项可通过 `mcpp --help` 查阅。 diff --git a/docs/00-getting-started.md b/docs/00-getting-started.md index 665c171a..7d086007 100644 --- a/docs/00-getting-started.md +++ b/docs/00-getting-started.md @@ -181,13 +181,13 @@ mcpp pack --mode static # fully static musl build mcpp pack --mode self-contained # bundle loader, libc, and dependencies ``` -For the differences between the four modes and their artifact layouts, see [02 — Packaging and Release](02-pack-and-release.md). `bundle-project` and `bundle-all` remain accepted aliases for `vendored` and `self-contained`. +For the differences between the four modes and their artifact layouts, see [10 — Packaging and Release](10-pack-and-release.md). `bundle-project` and `bundle-all` remain accepted aliases for `vendored` and `self-contained`. ## Further Reading - [01 — Examples](01-examples.md) — a collection of ready-to-run minimal projects -- [02 — Packaging and Release](02-pack-and-release.md) — building distributable artifacts -- [03 — Toolchain Management](03-toolchains.md) — switching compilers and managing multiple versions +- [10 — Packaging and Release](10-pack-and-release.md) — building distributable artifacts +- [20 — Toolchain Management](20-toolchains.md) — switching compilers and managing multiple versions - The full set of options for any command is available via `mcpp --help` diff --git a/docs/01-examples.md b/docs/01-examples.md index 7af18cc6..4af8f595 100644 --- a/docs/01-examples.md +++ b/docs/01-examples.md @@ -73,7 +73,7 @@ the map; the table below is what each sub-example adds. | [`08-build-rules`](../examples/08-build-rules/) | two rule packages and a project using both; `host-module = true`, `mcpp::action` with `role = "check"` | | [`12-a-new-device-language`](../examples/12-a-new-device-language/) | `device_extensions` and `rule_module`: a rule package teaching mcpp a language the engine has never heard of | -[23 — Authoring a Rule Package](23-authoring-a-rule-package.md) is the reference +[40 — Authoring a Rule Package](40-authoring-a-rule-package.md) is the reference these two illustrate. ## Lessons that arrive as templates @@ -85,9 +85,9 @@ rather than by mcpp. | template | lesson | chapter | |---|---|---| -| `riscv-virt-rt` | a bare-metal project, its board support and its runner | [13](13-baremetal.md) | -| `riscv-virt-rt:nolibc` | the same with no C library | [13](13-baremetal.md) | -| `ocornut.imgui` | a graphical application with its window and rendering stack | [03](03-toolchains.md) | +| `riscv-virt-rt` | a bare-metal project, its board support and its runner | [30](30-baremetal.md) | +| `riscv-virt-rt:nolibc` | the same with no C library | [30](30-baremetal.md) | +| `ocornut.imgui` | a graphical application with its window and rendering stack | [20](20-toolchains.md) | ```bash mcpp new blinky --template riscv-virt-rt @@ -100,8 +100,8 @@ last one. A new example is warranted when a capability **changes the shape of a project** — the files it contains, the manifest it declares, or the commands its author types. A capability that is one line inside a project an example already contains belongs in that chapter as a code block; one reached only through a -command belongs in [21 — Commands by Scenario](21-commands-by-scenario.md). +command belongs in [06 — Commands by Scenario](06-commands-by-scenario.md). The README states what the example is the first to teach and the criterion by which it is judged to work. For contribution mechanics see -[04 — Building from Source & Contributing](04-build-from-source.md). +[90 — Building from Source & Contributing](90-build-from-source.md). diff --git a/docs/05-mcpp-toml.md b/docs/02-mcpp-toml.md similarity index 98% rename from docs/05-mcpp-toml.md rename to docs/02-mcpp-toml.md index 50e05315..9347c8c4 100644 --- a/docs/05-mcpp-toml.md +++ b/docs/02-mcpp-toml.md @@ -1,4 +1,4 @@ -# The mcpp.toml Manifest Guide +# 02 — The mcpp.toml Manifest `mcpp.toml` is the project configuration file for the mcpp build tool, analogous to Cargo's `Cargo.toml` or Node's `package.json`. Place it in the project root; `mcpp build` discovers and reads it automatically. @@ -76,7 +76,7 @@ Both spellings of the value are accepted: `standard = "c++26"` and `standard = 2 When a **dependency declares a level above the graph's**, mcpp says so before compiling rather than letting it fail somewhere inside that dependency's sources. See -[workspace §4.2](06-workspace.md). +[workspace §4.2](03-workspace.md). #### Dialect flags and the `import std` BMI @@ -220,7 +220,7 @@ required_features = ["gui"] # only built when feature `gui` is > per-test contract evaluation semantic (`-fcontract-evaluation-semantic=observe`) for a test whose > `main` exercises the violation, a feature macro the entry alone reads, or a local warning > suppression. If a flag must reach **shared** code, it does not belong here — split into a -> [workspace](06-workspace.md) member or use `[features]`, or for a whole-build mode use a +> [workspace](03-workspace.md) member or use `[features]`, or for a whole-build mode use a > `[profile.*]` (`mcpp test --profile ` builds the whole test image, code-under-test > included, under that profile). > @@ -231,7 +231,7 @@ required_features = ["gui"] # only built when feature `gui` is | You want | Use | |---|---| | Different macros/flags on a binary's **own entry** | per-target `defines` / `cxxflags` (above) | -| Two products that differ in code they **share** | split into [workspace](06-workspace.md) members, each with its own `[build]` flags over a shared `lib` | +| Two products that differ in code they **share** | split into [workspace](03-workspace.md) members, each with its own `[build]` flags over a shared `lib` | | To **select a variant** of a shared library (e.g. a backend) | `[features]` on that library (§2.8) — additive, reaches the library's own compile | | A **whole-build mode** (sanitizers, contract semantics, opt level) | `[profile.]` (§2.9) + `--profile`; also honored by `mcpp test --profile ` | @@ -266,7 +266,7 @@ the package/feature boundary, not on an individual target. > CPU-only variant. Under an `--accel` that does not cover the constraint the > build is refused naming both (`accel-mismatch`). Device-kind files the > effective set matches — CUDA and HIP, the GLSL stages, HLSL, OpenCL C and -> Metal, listed in full in [20 — Heterogeneous Builds](20-heterogeneous-builds.md) — are never +> Metal, listed in full in [32 — Heterogeneous Builds](32-heterogeneous-builds.md) — are never > compiled by the engine; they reach the build program as > `MCPP_DEVICE_SOURCES`, where the rule package the project imports turns each > into an `mcpp::action`. @@ -575,10 +575,10 @@ rebuilt the whole project, which is the opposite of what someone raising a timeout wants. The **compile** phase is not bounded, only the build *program*. See -[07-build-mcpp.md](07-build-mcpp.md) for why that asymmetry is deliberate. +[05-build-mcpp.md](05-build-mcpp.md) for why that asymmetry is deliberate. ### The C++ runtime contract (`cxx_runtime`) -Moved to [03 — Toolchain Management](03-toolchains.md). +Moved to [20 — Toolchain Management](20-toolchains.md). ### File names outside the host code page @@ -623,7 +623,7 @@ package that also publishes device builds is selected. The value is compared against the `accel` field of any prebuilt artifact the build consumes, and a build asking for none is satisfied by every artifact. See -[20 — Heterogeneous Builds](20-heterogeneous-builds.md). +[32 — Heterogeneous Builds](32-heterogeneous-builds.md). ### 2.4 `[lib]` — Library Root Module Convention @@ -912,7 +912,7 @@ linkage = "static" ``` ### 2.7.1 `[target.*]` — Platform-Conditional Dependencies & Flags -Moved to [14 — The Target Side](14-target-side.md). +Moved to [22 — The Target Side](22-target-side.md). ### 2.7.2 Bare metal (`os = none`) — freestanding targets @@ -925,7 +925,7 @@ can produce them. This section is the manifest reference. The worked examples — scaffolding, running, testing on the target, the freestanding standard-library subset and writing a board-support package — are in -[13 — Bare-Metal and Freestanding Targets](13-baremetal.md). +[30 — Bare-Metal and Freestanding Targets](30-baremetal.md). ```bash mcpp build --target riscv64-none-elf @@ -1059,10 +1059,10 @@ error: test result: NOT RUN. 0 passed; 0 failed; 1 not run (this host cannot exe Exit code 1 keeps its meaning — a test ran and failed — and 0 means every test ran and passed. `--message-format json` carries `"status":"not_run"` and a `reason` on each record, and `not_run` / `not_run_reason` on the summary -record (see [11 — Machine-Readable Output](11-machine-output.md)). +record (see [50 — Machine-Readable Output](50-machine-output.md)). ### 2.8 `[features]` — Features -Moved to [22 — Features and Capabilities](22-features-and-capabilities.md), +Moved to [04 — Features and Capabilities](04-features-and-capabilities.md), with `provides` / `requires` and `[feature-deps.]`. @@ -1334,14 +1334,14 @@ statement of intent and a CI-matrix hint, shown by `mcpp why`, never a gate. Distinct from an artifact's `accel` field on purpose. A declaration is written by hand and may be aspirational; `accel` is measured from the build that produced a binary and is what a consumer is refused against. See -[20 — Heterogeneous Builds](20-heterogeneous-builds.md). +[32 — Heterogeneous Builds](32-heterogeneous-builds.md). ### 2.13 `[xlings]` — the project's environment -Moved to [17 — The Project Environment](17-the-project-environment.md). +Moved to [23 — The Project Environment](23-the-project-environment.md). ### 2.14 Host tools from a dependency -Moved to [07 — build.mcpp](07-build-mcpp.md). +Moved to [05 — build.mcpp](05-build-mcpp.md). ### 2.15 `[resources]` — Metadata and Assets Embedded in the Artifact (2026.8.7.1+) @@ -1438,13 +1438,13 @@ o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o") .submit(); ``` -See [07 — build.mcpp](07-build-mcpp.md). Naming such a file in +See [05 — build.mcpp](05-build-mcpp.md). Naming such a file in `[build].ldflags` also "works", but ldflags is a flat string in the link command: nothing tracks it, and editing the file produces `ninja: no work to do`. ### 2.16 `[hooks]` — Project Build Lifecycle Commands -Moved to [21 — Commands by Scenario](21-commands-by-scenario.md). +Moved to [06 — Commands by Scenario](06-commands-by-scenario.md). ## Appendix A. Schema Ownership Principle (admission criteria for new fields) diff --git a/docs/06-workspace.md b/docs/03-workspace.md similarity index 99% rename from docs/06-workspace.md rename to docs/03-workspace.md index fd083625..e6f86b64 100644 --- a/docs/06-workspace.md +++ b/docs/03-workspace.md @@ -1,4 +1,4 @@ -# Workspace +# 03 — Workspaces A workspace organizes multiple related mcpp packages (libraries or applications) within a single repository. Member packages share a unified set of dependency versions and toolchain settings while each keeping its own `mcpp.toml` project file. diff --git a/docs/22-features-and-capabilities.md b/docs/04-features-and-capabilities.md similarity index 98% rename from docs/22-features-and-capabilities.md rename to docs/04-features-and-capabilities.md index 2c46ab00..34b0458a 100644 --- a/docs/22-features-and-capabilities.md +++ b/docs/04-features-and-capabilities.md @@ -1,13 +1,13 @@ -# 22 — Features and Capabilities +# 04 — Features and Capabilities Features are how a package offers something optional: a compile macro, an extra source, an extra dependency, or a choice between backends. This chapter is the reference for declaring them and for consuming them. -Related documents: [05 — mcpp.toml](05-mcpp-toml.md) is the field reference for +Related documents: [02 — mcpp.toml](02-mcpp-toml.md) is the field reference for the rest of the manifest; [`examples/11-features`](../examples/11-features/) is a package that declares all three shapes and a test that uses a -dev-dependency; [20 — Heterogeneous Builds](20-heterogeneous-builds.md) is the +dev-dependency; [32 — Heterogeneous Builds](32-heterogeneous-builds.md) is the largest consumer of the mechanism, because every accelerator lane is a feature. ## `[features]` — Features (Cargo-style, additive) @@ -71,7 +71,7 @@ simd = { sources = ["src/simd/**"], flags = [ - The automatic `-DMCPP_FEATURE_` is still defined for every active feature, so `defines` are additive to it. - `flags` (mcpp 0.0.101+) is the same ordered array-of-inline-tables grammar as - `[build].flags` ([05 §2.3](05-mcpp-toml.md): `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ + `[build].flags` ([02 §2.3](02-mcpp-toml.md): `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ `defines`; the `[[features..flags]]` array-of-tables spelling is accepted too, like `[[build.flags]]`). When the feature is active the entries are appended **after** the base `[build].flags`, features in name order, so a @@ -102,7 +102,7 @@ sources -- never scanned for imports, never a BMI, compiled by something mcpp does not drive. This is the same shape as `[build] module_extensions`: mcpp knows what a device source *is* and does not know that `.cu` is CUDA, so a NEW device language costs no engine change. It is what makes -[20 — Heterogeneous Builds](20-heterogeneous-builds.md)' claim that "a sixth +[32 — Heterogeneous Builds](32-heterogeneous-builds.md)' claim that "a sixth backend is a package rather than an engine change" true rather than aspirational; `.slang` was removed from mcpp's built-in table and now arrives this way. @@ -195,7 +195,7 @@ std-module-flags = ["--no-default-config", "-nostdinc++"] std-module-flags = ["-D_GNU_SOURCE"] ``` -See [14 - The Target Side](14-target-side.md) for the five layers, the rules +See [22 - The Target Side](22-target-side.md) for the five layers, the rules that govern them, and the diagnostics. Binding is **deterministic**: diff --git a/docs/07-build-mcpp.md b/docs/05-build-mcpp.md similarity index 98% rename from docs/07-build-mcpp.md rename to docs/05-build-mcpp.md index d13f58fa..fb5f6064 100644 --- a/docs/07-build-mcpp.md +++ b/docs/05-build-mcpp.md @@ -1,6 +1,6 @@ -# `build.mcpp` — a native build program +# 05 — Build Programs: `build.mcpp` -**English** | [简体中文](zh/07-build-mcpp.md) +**English** | [简体中文](zh/05-build-mcpp.md) Most projects need nothing more than `mcpp.toml`. When build-time logic is required — probe the host, generate a source, decide a flag from the environment — put a @@ -345,7 +345,7 @@ could not state a floor and every project repeated its rule's package list. version this build actually installed rather than the one the local manifest wrote. One package means one version: where a project and a rule both name it, the declaration nearer the artifact wins and both sides are told the same -answer. See *One package, one version* in [05 — mcpp.toml](05-mcpp-toml.md). +answer. See *One package, one version* in [02 — mcpp.toml](02-mcpp-toml.md). **`[feature-xlings.]` is answered too, while `` is active** *(2026.9.6.2+)*. That table has provisioned its packages since it existed -- @@ -524,7 +524,7 @@ link the command would never run and the build would say nothing. > should not be used for anything the build produces: ldflags is a flat string > in the link command, not a file in the graph, so nothing tracks it and editing > it reports `ninja: no work to do`. For Windows resources specifically, use -> [`[resources]`](05-mcpp-toml.md) — +> [`[resources]`](02-mcpp-toml.md) — > `object` is the escape hatch for everything else. **You must name the output files.** mcpp fixes the source set, the fingerprint @@ -701,10 +701,10 @@ measured on `qemu-system-riscv64`, where the answer was a shim that reports "is not installed in this subos" when executed, while a working copy sat in the project's own environment and was not on `PATH` at all. -The selection is the one [chapter 8](08-toolchain-internals.md) already +The selection is the one [chapter 8](91-toolchain-internals.md) already describes — the same declaration that decides which C library the project links against, delivered to one more consumer. See -[chapter 17](17-the-project-environment.md) for what a declared environment is +[chapter 17](23-the-project-environment.md) for what a declared environment is and when to want one; `examples/07-project-subos/` is a working project. ## Writing a rule package @@ -712,7 +712,7 @@ and when to want one; `examples/07-project-subos/` is a working project. A rule — "run protoc over these `.proto` files", "run clang-tidy over these sources" — belongs in a package, not copy-pasted into every consumer's `build.mcpp`. The mechanism is -[`host-module = true`](05-mcpp-toml.md); this section is about the shape of +[`host-module = true`](02-mcpp-toml.md); this section is about the shape of what goes inside. The guidance below generalises from `mcpplibs.grpcgen`, the first such package, @@ -764,7 +764,7 @@ under the package name, so `export module mcpp.rules.spirv;` is what a consumer then imports. Official plugins live in one package, `mcpp:plugins` (repository `mcpp-community/mcpp-plugins`): rule packages are named `mcpp.rules.`, build-time utilities `mcpp.tools.`, and each member is selected by a feature -of that package (see [`host-module = true`](05-mcpp-toml.md)). `mcpp.build.*` +of that package (see [`host-module = true`](02-mcpp-toml.md)). `mcpp.build.*` is the engine's own module family and is not used for plugins. The engine cannot tell who is official, so it keys the check on the package *namespace* and warns when the two disagree — @@ -892,7 +892,7 @@ When nothing changed the output is `build.mcpp up to date (cached)`; otherwise `mcpp build --target ` the program is compiled with a host-resolved toolchain, runs on the host, and sees `MCPP_TARGET` = the cross triple. For purely declarative target gating, `[target.'cfg(...)']` tables remain - the first choice — see [05 - mcpp.toml Manifest Guide](05-mcpp-toml.md). + the first choice — see [02 - mcpp.toml Manifest Guide](02-mcpp-toml.md). - **CWD is the project root**, so relative paths (`src/generated.cpp`) land where expected. - A non-zero exit from `build.mcpp` aborts the build and prints its output. @@ -957,7 +957,7 @@ grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } Each name must be a `kind = "bin"` target of that package. mcpp builds it **for the build machine** and hands `build.mcpp` its absolute path as `MCPP_DEP__BIN_` — read it with `mcpp::dep_bin("protobuf", "protoc")` -(see [07 — build.mcpp](07-build-mcpp.md)). +(see [05 — build.mcpp](05-build-mcpp.md)). Four properties worth knowing: @@ -1079,7 +1079,7 @@ plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } ``` **`[build-dependencies]`, not `[dependencies]`** — a rule package is the case -[05 §2.6.1](05-mcpp-toml.md) describes exactly: its library must never reach the target while its +[02 §2.6.1](02-mcpp-toml.md) describes exactly: its library must never reach the target while its rule is still wanted. The two axes are separate, so `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" on the second axis, and the @@ -1213,6 +1213,6 @@ unsupported platform into an error its user cannot edit away. Scope it: ``` `[target..feature-deps.]` (2026.8.6.2+) follows the same rules as -the other conditional dependency tables ([14 — The Target Side](14-target-side.md)). The **feature itself is +the other conditional dependency tables ([22 — The Target Side](22-target-side.md)). The **feature itself is registered on every platform** — only what it pulls in is conditional — so requesting it where no predicate matches is not an unknown-feature error. diff --git a/docs/21-commands-by-scenario.md b/docs/06-commands-by-scenario.md similarity index 98% rename from docs/21-commands-by-scenario.md rename to docs/06-commands-by-scenario.md index eb609138..bbd9b676 100644 --- a/docs/21-commands-by-scenario.md +++ b/docs/06-commands-by-scenario.md @@ -1,4 +1,4 @@ -# 21 — Commands by Scenario +# 06 — Commands by Scenario The command reference is `mcpp --help`, and each subcommand carries its own `--help`. This chapter answers a different question: which command applies to a @@ -8,9 +8,9 @@ may be stale. The commands collected here are the ones whose name does not announce the situation they belong to. Related documents: [00 — Getting Started](00-getting-started.md) for the -everyday build/test loop, [03 — Toolchain Management](03-toolchains.md), -[10 — Publishing a Library](10-publishing-a-library.md), -[11 — Machine-Readable Output](11-machine-output.md). +everyday build/test loop, [20 — Toolchain Management](20-toolchains.md), +[11 — Publishing a Library](11-publishing-a-library.md), +[50 — Machine-Readable Output](50-machine-output.md). Every output below was produced by the version of mcpp this chapter ships with. @@ -173,7 +173,7 @@ $ mcpp xpkg parse mcpp.plugins.lua --json ``` `mcpp emit xpkg` generates the entry to submit. See -[10 — Publishing a Library](10-publishing-a-library.md) for the full path. +[11 — Publishing a Library](11-publishing-a-library.md) for the full path. ## Environment diagnosis @@ -334,7 +334,7 @@ outside 1–86400, a key offered to the wrong interval — is a manifest error. > **A hook is code, and `mcpp.toml` is part of the repository.** Building a > freshly cloned project runs whatever its `[hooks]` say, with the privileges > of whoever invoked `mcpp build`. This is the same trust `build.mcpp` already -> asks for ([07 — build.mcpp](07-build-mcpp.md)); `[hooks]` widens its reach +> asks for ([05 — build.mcpp](05-build-mcpp.md)); `[hooks]` widens its reach > rather than introducing it. Hook programs can be installed as ordinary xlings dependencies. For example, diff --git a/docs/02-pack-and-release.md b/docs/10-pack-and-release.md similarity index 99% rename from docs/02-pack-and-release.md rename to docs/10-pack-and-release.md index 2eed3c6a..bb8588e1 100644 --- a/docs/02-pack-and-release.md +++ b/docs/10-pack-and-release.md @@ -1,4 +1,4 @@ -# 02 — Packaging an Application for Release +# 10 — Packaging an Application for Release > This page is about bundling a **program**. To ship a *library* as interface + > prebuilt binaries, see [12 - Distributing a Prebuilt Library](12-binary-distribution.md). @@ -85,7 +85,7 @@ ELF in the bundle for anything under `$MCPP_HOME` and fails on a hit. machine: a graphics driver's user-space half is version-locked to the running kernel module, and for the proprietary stacks redistribution is not permitted. Declare those as run-phase capability requirements (§2.11 of -`docs/05-mcpp-toml.md`), and the mode table gains a column: +`docs/02-mcpp-toml.md`), and the mode table gains a column: | Mode | Program needing a host-provided capability | |---|---| @@ -325,7 +325,7 @@ force_bundle` still overrides this, as it does the ELF skip list. `vcruntime140.dll` and `msvcp140.dll` are **not** Windows' own: they belong to the MSVC toolset, exactly as `libstdc++.so` belongs to gcc. Whether they -travel is decided by `cxx_runtime` (see `docs/05-mcpp-toml.md`), not by this +travel is decided by `cxx_runtime` (see `docs/02-mcpp-toml.md`), not by this list — and `mcpp pack` refuses a combination that cannot deliver what the contract promised: diff --git a/docs/10-publishing-a-library.md b/docs/11-publishing-a-library.md similarity index 96% rename from docs/10-publishing-a-library.md rename to docs/11-publishing-a-library.md index 25b65205..2f0cc5da 100644 --- a/docs/10-publishing-a-library.md +++ b/docs/11-publishing-a-library.md @@ -1,10 +1,10 @@ -# 10 - Publishing a Library to mcpp-index +# 11 — Publishing a Library to mcpp-index -**English** | [简体中文](zh/10-publishing-a-library.md) +**English** | [简体中文](zh/11-publishing-a-library.md) How a library becomes something `[dependencies]` can name. This is the -*library author's* chain; [09 - Releasing mcpp](09-release.md) is about -releasing mcpp itself, and [02 - Packaging & Release](02-pack-and-release.md) +*library author's* chain; [92 - Releasing mcpp](92-release.md) is about +releasing mcpp itself, and [10 - Packaging & Release](10-pack-and-release.md) is about `mcpp pack` bundling an application. ## Release sequence diff --git a/docs/12-binary-distribution.md b/docs/12-binary-distribution.md index 1feaa7fb..fcb82a2f 100644 --- a/docs/12-binary-distribution.md +++ b/docs/12-binary-distribution.md @@ -1,4 +1,4 @@ -# 12 - Distributing a Prebuilt Library +# 12 — Distributing a Prebuilt Library **English** | [简体中文](zh/12-binary-distribution.md) @@ -6,8 +6,8 @@ > It applies to closed-source distribution, offline environments, and builds whose > artifacts a build farm has already produced. > -> Related: [02 - Packaging & Release](02-pack-and-release.md) covers bundling an -> *application*. [10 - Publishing a Library](10-publishing-a-library.md) is the +> Related: [10 - Packaging & Release](10-pack-and-release.md) covers bundling an +> *application*. [11 - Publishing a Library](11-publishing-a-library.md) is the > source route. ## Overview @@ -31,7 +31,7 @@ mcpp pack mathkit --target x86_64-linux-gnu \ | `kind` | `mcpp pack ` produces | `--mode` | |---|---|---| -| `bin` | an application bundle (see [02](02-pack-and-release.md)) | the four depths | +| `bin` | an application bundle (see [10](10-pack-and-release.md)) | the four depths | | `lib` | a **static library package** | — | | `shared` | a **dynamic library package** | — | @@ -310,7 +310,7 @@ no test in this suite produces a `.dylib` to measure the edit on. ### Debug information is removed -See [docs/02](02-pack-and-release.md) for the flags, the per-shape table, and +See [docs/02](10-pack-and-release.md) for the flags, the per-shape table, and `--debug-symbols`. The rule that matters for a *library* package: a static archive is only ever `--strip-debug`ed, because `--strip-all` removes the archive symbol index and the consumer's link then fails with `archive has no diff --git a/docs/03-toolchains.md b/docs/20-toolchains.md similarity index 99% rename from docs/03-toolchains.md rename to docs/20-toolchains.md index 05a6f28f..2b6ee965 100644 --- a/docs/03-toolchains.md +++ b/docs/20-toolchains.md @@ -1,4 +1,4 @@ -# 03 — Toolchain Management +# 20 — Toolchain Management > mcpp maintains an independent toolchain sandbox, fully isolated from the system PATH. @@ -212,7 +212,7 @@ It uses the regular GCC module pipeline (`gcm.cache`, `import std` via libstdc++'s `bits/std.cc`). The target's default linkage is **static** — the produced `.exe` is fully self-contained (no `libstdc++-6.dll` to ship, runs directly under wine). To opt out, set it on the target section — -`linkage` is exact-triple only (§2.7 of [mcpp.toml](05-mcpp-toml.md)), and a +`linkage` is exact-triple only (§2.7 of [mcpp.toml](02-mcpp-toml.md)), and a `[build] linkage` key does not exist and is silently ignored: ```toml @@ -551,7 +551,7 @@ option, for a decision made a hundred lines earlier. A program classifying these reads `data.reason` from `mcpp why toolchain --format json` (`convention-unreplaced` / `capability-pin`) -rather than the sentence — see [chapter 11](11-machine-output.md). +rather than the sentence — see [chapter 11](50-machine-output.md). A project can set its *default* build target — this is where "this project ships fully-static" belongs (static output is a product property, not a diff --git a/docs/16-the-target-triple.md b/docs/21-the-target-triple.md similarity index 98% rename from docs/16-the-target-triple.md rename to docs/21-the-target-triple.md index 78f5a30f..70a07a51 100644 --- a/docs/16-the-target-triple.md +++ b/docs/21-the-target-triple.md @@ -1,4 +1,4 @@ -# The Target Triple +# 21 — The Target Triple A target triple is written `-` or `--`. This chapter states what each segment means, when the third may be declined, and why the @@ -19,7 +19,7 @@ the payload is chosen. **The build-time system.** The target side arrives as packages in the dependency graph and is compiled from source by whichever compiler is running. The third segment selects nothing, because the graph has already decided. This -is what [chapter 15](15-openkal-cross.md) describes. +is what [chapter 15](24-openkal-cross.md) describes. The two differ in what the third segment *does*, not in how it is spelled. A project does not declare which system it is in; the dependency graph decides, @@ -362,7 +362,7 @@ why the refusal names openkal in its own text. **A bare-metal row's toolchain is not a convention, and neither is `x86_64-windows-musl`'s** — no gcc payload emits a PE with a musl C library, so -those rows cannot be overridden at all. See [chapter 03](03-toolchains.md). +those rows cannot be overridden at all. See [chapter 03](20-toolchains.md). ### And a dependency graph replaces that axis entirely @@ -500,11 +500,11 @@ runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic", `sysroot = ""` selects the zero-libc tier: no C library on the compile line and none on the link. An absent `sysroot` key is a different answer — it inherits -the target row's own default. See [chapter 13](13-baremetal.md). +the target row's own default. See [chapter 13](30-baremetal.md). ## Reference -[chapter 14](14-target-side.md) for the five layers and who supplies them. -[chapter 15](15-openkal-cross.md) for the build-time system in full. -[chapter 03](03-toolchains.md) for the toolchain axis, which is separate: a +[chapter 14](22-target-side.md) for the five layers and who supplies them. +[chapter 15](24-openkal-cross.md) for the build-time system in full. +[chapter 03](20-toolchains.md) for the toolchain axis, which is separate: a target does not determine a compiler. diff --git a/docs/14-target-side.md b/docs/22-target-side.md similarity index 97% rename from docs/14-target-side.md rename to docs/22-target-side.md index 6bf055f9..5fdccc93 100644 --- a/docs/14-target-side.md +++ b/docs/22-target-side.md @@ -1,4 +1,4 @@ -# The Target Side +# 22 — The Target Side A build must answer one question before it can emit a command line: where the target's compiler runtime, platform interface, C library and C++ runtime come @@ -342,7 +342,7 @@ The selector `` has three forms: |---|---|---| | **bare OS alias** | a single OS / family — the concise, common form | `[target.windows]`, `[target.unix]` | | **`cfg(...)` predicate** | a compound condition (arch / env / combinators) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see [05 §2.7.3](05-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | +| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see [02 §2.7.3](02-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | A selector may carry platform-conditional **dependencies** and **build flags**: @@ -365,7 +365,7 @@ there is no ambiguity. Use the bare form for a single OS/family; use `cfg(...)` for arch/env conditions and combinators. - **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies` / - `feature-deps.` (mcpp 2026.8.6.2+ — see [07 — build.mcpp](07-build-mcpp.md); the feature is + `feature-deps.` (mcpp 2026.8.6.2+ — see [05 — build.mcpp](05-build-mcpp.md); the feature is registered unconditionally, only its dependency set is scoped), and `build` with `cflags` / `cxxflags` / `ldflags` / `sources` (mcpp 0.0.95+ — conditional source globs, e.g. gating `src/x86/**/*.asm` behind @@ -386,7 +386,7 @@ for arch/env conditions and combinators. - **Predicate keys**: `os`, `arch`, `family`, `env` — the triple's coordinates — and, from mcpp 2026.9.1.1, the five target-side layer names `compiler`, `compiler-runtime`, `kernel-abi`, `c-abi`, `c++-abi` - ([14 — The Target Side](14-target-side.md)). `accelerator` is a key here too + ([22 — The Target Side](22-target-side.md)). `accelerator` is a key here too and is answered from this build's own `accel` — the backend names in `--accel` or `[build] accel` — so it is a membership test over a set, and `accelerator = "none"` is how a section says "this build named no backend" @@ -460,13 +460,13 @@ A build program can ask which C library **payload** supplies the sysroot: `mcpp::target_libc()` returns that package's name and `mcpp::target_libc_profile()` the sub-directory for the target's ISA profile. Both are empty on the zero-libc tier. See -[13 — Bare-Metal and Freestanding Targets](13-baremetal.md). +[30 — Bare-Metal and Freestanding Targets](30-baremetal.md). **That is not the same question as "which C library did the target side resolve to".** `target_libc()` names the payload mcpp installed, and that value is an *input* to target-side resolution — a package in the dependency graph can supply the C library instead, in which case the resolved `c-abi` is not what this returns. To branch on the resolved layer, use a layer predicate: -`[target.'cfg(c-abi = "musl")'.build]` ([14 — The Target -Side](14-target-side.md)). This paragraph said "which C library was resolved" +`[target.'cfg(c-abi = "musl")'.build]` ([22 — The Target +Side](22-target-side.md)). This paragraph said "which C library was resolved" until 2026.9.1.1, which was the wrong one of the two. diff --git a/docs/17-the-project-environment.md b/docs/23-the-project-environment.md similarity index 98% rename from docs/17-the-project-environment.md rename to docs/23-the-project-environment.md index d7195ce1..51013f26 100644 --- a/docs/17-the-project-environment.md +++ b/docs/23-the-project-environment.md @@ -1,4 +1,4 @@ -# 17 - The Project Environment +# 23 — The Project Environment A project can declare the environment it builds in. That one declaration decides which C library the project links against and which tools its build @@ -110,7 +110,7 @@ Where a project and a dependency name **one package**, one version of it is installed: identity is `(namespace, name)` and the version is a constraint on it. The declaration nearer the artifact wins and the override is reported; a pin that fails a requirement the other side stated is refused naming both. -See *One package, one version* in [05 — mcpp.toml](05-mcpp-toml.md). +See *One package, one version* in [02 — mcpp.toml](02-mcpp-toml.md). ## 4. Reading an environment, never creating one @@ -175,7 +175,7 @@ downloaded: this chapter. Omitting the tier is the historical behaviour. **The runner.** A program under `[xlings.workspace]` is also where `[target.].runner` looks first for its first element, before `PATH` -([05 §2.7.3](05-mcpp-toml.md)). The two keys together provision a user-mode emulator on a +([02 §2.7.3](02-mcpp-toml.md)). The two keys together provision a user-mode emulator on a CI host and execute a cross-built artifact through it, without the manifest naming the payload's path. @@ -509,8 +509,8 @@ used it. ## 8. Related chapters -- [7 - build.mcpp](07-build-mcpp.md) — the contract a build program receives, +- [05 - build.mcpp](05-build-mcpp.md) — the contract a build program receives, including the `PATH` it runs with. -- [8 - Toolchain Internals](08-toolchain-internals.md) — runtime selection, +- [91 - Toolchain Internals](91-toolchain-internals.md) — runtime selection, the `RuntimeBinding` snapshot, and the degradation rules. -- [5 - mcpp.toml](05-mcpp-toml.md) — the rest of the manifest. +- [02 - mcpp.toml](02-mcpp-toml.md) — the rest of the manifest. diff --git a/docs/15-openkal-cross.md b/docs/24-openkal-cross.md similarity index 98% rename from docs/15-openkal-cross.md rename to docs/24-openkal-cross.md index 67d7a870..3abe188f 100644 --- a/docs/15-openkal-cross.md +++ b/docs/24-openkal-cross.md @@ -1,4 +1,4 @@ -# Cross-Compilation Over openkal +# 24 — Cross-Compilation Over openkal Conventional cross-compilation is served by a payload. A toolchain is built for one target, its driver has exactly one answer, and reaching a second target @@ -207,7 +207,7 @@ runner = ["qemu-aarch64-static"] Without the key, `mcpp run` reports the kernel's refusal (`Exec format error`) and the key to write, and `mcpp test` reports every test as not run and exits 2. A host that executes the artifact natively passes `--no-runner`. The rules -are in [5 — mcpp.toml](05-mcpp-toml.md), §2.7.3. +are in [02 — mcpp.toml](02-mcpp-toml.md), §2.7.3. ### The Source Is The Same, The Program Is Not @@ -304,6 +304,6 @@ the paths where a project overrides the contract explicitly. ## Reference -[docs/14 — The Target Side](14-target-side.md) for the five layers, the four +[docs/14 — The Target Side](22-target-side.md) for the five layers, the four origins and the rules. [SPEC-002](specs/target-side.md) for the normative statement of the capability grammar. diff --git a/docs/13-baremetal.md b/docs/30-baremetal.md similarity index 98% rename from docs/13-baremetal.md rename to docs/30-baremetal.md index 9a62ca5c..57f252ad 100644 --- a/docs/13-baremetal.md +++ b/docs/30-baremetal.md @@ -1,13 +1,13 @@ -# 13 — Bare-Metal and Freestanding Targets +# 30 — Bare-Metal and Freestanding Targets This document describes how mcpp builds, runs and tests software for targets with no operating system underneath, and how a board-support package supplies the parts of such a target that the engine deliberately does not know. -Related documents: [05 — mcpp.toml Manifest Guide](05-mcpp-toml.md) §2.7.2 is -the reference for the `[target.]` keys used here; [07 — build.mcpp -Build Program](07-build-mcpp.md) is the reference for the directive protocol a -board-support package speaks; [08 — Toolchain Internals](08-toolchain-internals.md) +Related documents: [02 — mcpp.toml Manifest Guide](02-mcpp-toml.md) §2.7.2 is +the reference for the `[target.]` keys used here; [05 — build.mcpp +Build Program](05-build-mcpp.md) is the reference for the directive protocol a +board-support package speaks; [91 — Toolchain Internals](91-toolchain-internals.md) covers the hosted link model this chapter departs from. ## Overview @@ -632,7 +632,7 @@ The key is not specific to bare metal. A hosted cross target — an `qemu-aarch64-static` in place of the system emulator; on such a target an absent runner is not an error until the kernel refuses the artifact. The rules for hosted targets, the `--no-runner` escape and the not-run reporting of -`mcpp test` are in [5 — mcpp.toml](05-mcpp-toml.md), §2.7.3. +`mcpp test` are in [02 — mcpp.toml](02-mcpp-toml.md), §2.7.3. ## Writing a board-support package @@ -671,7 +671,7 @@ if (const char* dir = mcpp::xpkg_dir("xim", "qemu-riscv"); dir && *dir) { Without that line the build succeeds, configures no runner, and `mcpp run` reports a missing runner with advice about writing a `runner` key — true in general, and not the cause here. See `mcpp:warning=` in -[07 — build.mcpp](07-build-mcpp.md). +[05 — build.mcpp](05-build-mcpp.md). ### The directives a board-support package emits diff --git a/docs/18-devices.md b/docs/31-devices.md similarity index 95% rename from docs/18-devices.md rename to docs/31-devices.md index ce9acd40..fbf38239 100644 --- a/docs/18-devices.md +++ b/docs/31-devices.md @@ -1,14 +1,14 @@ -# 18 — Reaching a Device +# 31 — Reaching a Device This document specifies how mcpp executes an artifact that runs somewhere other than the machine that built it, how a package supplies additional ways of reaching it, and how a project selects between an emulator and physical hardware. -Related documents: [13 — Bare-Metal and Freestanding Targets](13-baremetal.md) -covers the targets this most often applies to; [07 — build.mcpp](07-build-mcpp.md) -is the reference for the directive protocol a package speaks; [11 — Machine -Output](11-machine-output.md) is the interface a debugger client or IDE uses. +Related documents: [30 — Bare-Metal and Freestanding Targets](30-baremetal.md) +covers the targets this most often applies to; [05 — build.mcpp](05-build-mcpp.md) +is the reference for the directive protocol a package speaks; [50 — Machine +Output](50-machine-output.md) is the interface a debugger client or IDE uses. ## One command, and named exceptions diff --git a/docs/20-heterogeneous-builds.md b/docs/32-heterogeneous-builds.md similarity index 99% rename from docs/20-heterogeneous-builds.md rename to docs/32-heterogeneous-builds.md index b8ab0a0c..e785d9f3 100644 --- a/docs/20-heterogeneous-builds.md +++ b/docs/32-heterogeneous-builds.md @@ -1,4 +1,4 @@ -# 20 — Heterogeneous Builds +# 32 — Heterogeneous Builds GPU and AI accelerator targets, and mixed host/device compilation: how mcpp builds device code, and how a prebuilt artifact states which devices it can @@ -107,7 +107,7 @@ and less clearly. The table above is what mcpp knows **without being told**: the languages whose support shipped before a package could declare one. A rule package adds to it, through `[features]..device_extensions` (see -[05 — mcpp.toml](05-mcpp-toml.md) §2.8), and that is how a NEW device language +[02 — mcpp.toml](02-mcpp-toml.md) §2.8), and that is how a NEW device language arrives -- with no engine change and no engine release. Slang is the first: `.slang` is not in the list above, and `mcpp:plugins`' `rules-slang` declares it. @@ -240,7 +240,7 @@ object rather than in an array, and there is then nothing to take the size of. The command that invokes a device compiler is not built into mcpp. It is supplied by a **build-rule package**, consumed with `host-module = true`, which emits build-graph edges whose outputs join the link. See -[07 — build.mcpp](07-build-mcpp.md) for the mechanism and +[05 — build.mcpp](05-build-mcpp.md) for the mechanism and `examples/09-heterogeneous/cuda` for a working CUDA rule. The division is deliberate. mcpp owns the graph, the artifact's identity and @@ -293,7 +293,7 @@ the machines it will run on: The nearer declaration wins, one version is installed, and mcpp says which. A pin that does not satisfy the rule's floor is refused naming both sides rather than installed alongside it. See *One package, one version* in -[05 — mcpp.toml](05-mcpp-toml.md) for the full rule; +[02 — mcpp.toml](02-mcpp-toml.md) for the full rule; `examples/09-heterogeneous/multi-backend` is the one example in this repository that takes the override path, and every other one writes only the edge. @@ -331,7 +331,7 @@ version is insufficient for CUDA runtime version"*. The rule reads the driver's version through the driver's own library (reached through the sentinel package, never through `/usr/lib`) and states it as a fact; it states the floor its runtime needs; and the engine compares the two before anything -is compiled -- see the probe channel in [07 — build.mcpp](07-build-mcpp.md). +is compiled -- see the probe channel in [05 — build.mcpp](05-build-mcpp.md). The engine reads a name, a relation and a version; `cuda.driver` is data flowing through. diff --git a/docs/23-authoring-a-rule-package.md b/docs/40-authoring-a-rule-package.md similarity index 96% rename from docs/23-authoring-a-rule-package.md rename to docs/40-authoring-a-rule-package.md index 4a920ba2..0eebbf5d 100644 --- a/docs/23-authoring-a-rule-package.md +++ b/docs/40-authoring-a-rule-package.md @@ -1,13 +1,13 @@ -# 23 — Authoring a Rule Package +# 40 — Authoring a Rule Package A rule package teaches mcpp to build something the engine has no rule for: a device language, a shader, a generated interface, a check. It is an ordinary package whose module a consumer's build program imports. -Related documents: [07 — build.mcpp](07-build-mcpp.md) is the reference for the -directive protocol and the typed API this chapter uses; [22 — Features and -Capabilities](22-features-and-capabilities.md) covers the feature keys; -[20 — Heterogeneous Builds](20-heterogeneous-builds.md) is where the shipped +Related documents: [05 — build.mcpp](05-build-mcpp.md) is the reference for the +directive protocol and the typed API this chapter uses; [04 — Features and +Capabilities](04-features-and-capabilities.md) covers the feature keys; +[32 — Heterogeneous Builds](32-heterogeneous-builds.md) is where the shipped rules are listed. Two examples: [`08-build-rules`](../examples/08-build-rules/) for a rule that checks and embeds, [`12-a-new-device-language`](../examples/12-a-new-device-language/) for @@ -166,7 +166,7 @@ neither. A bare version is a **choice** a project may override; `>=` is a **requirement** a project may not go below. See *One package, one version* in -[05 — mcpp.toml](05-mcpp-toml.md). +[02 — mcpp.toml](02-mcpp-toml.md). ## Generating an island's boundary @@ -247,4 +247,4 @@ path. `mcpp xpkg parse` reports that key as unknown and exits 1. No descriptor in `mcpp-index` uses it (0 of 218); a package keeping its own `mcpp.toml` omits the `mcpp` field entirely. See *Current limitations* in - [21 — Commands by Scenario](21-commands-by-scenario.md). + [06 — Commands by Scenario](06-commands-by-scenario.md). diff --git a/docs/11-machine-output.md b/docs/50-machine-output.md similarity index 99% rename from docs/11-machine-output.md rename to docs/50-machine-output.md index 68b87916..3a04334e 100644 --- a/docs/11-machine-output.md +++ b/docs/50-machine-output.md @@ -1,4 +1,4 @@ -# 11 — Machine-Readable Output +# 50 — Machine-Readable Output mcpp writes for two audiences. This chapter is the contract for the second one: programs. Editor extensions, CI scripts, and anything else that parses mcpp's @@ -353,7 +353,7 @@ never changes. field's meaning is unchanged (it still names the implementation), so §7 holds; what changed is that it stopped reporting the triple's env segment, which is a request rather than an implementation and is not the name of any C library. The -values are now the ones [14 — The Target Side](14-target-side.md) has always +values are now the ones [22 — The Target Side](22-target-side.md) has always listed, and a package may compare against them in a `cfg(c-abi = …)` predicate. A client keying on the literal `gnu` needs updating; `musl`, `picolibc` and `libSystem` are unaffected. diff --git a/docs/19-supported-versions.md b/docs/51-supported-versions.md similarity index 97% rename from docs/19-supported-versions.md rename to docs/51-supported-versions.md index fbbf7a69..e4ecd055 100644 --- a/docs/19-supported-versions.md +++ b/docs/51-supported-versions.md @@ -1,4 +1,4 @@ -# 19 — Supported Versions and Compatibility +# 51 — Supported Versions and Compatibility This document states which releases are supported, for how long, and what may change between them. It exists because a project adopting mcpp is asked these @@ -33,7 +33,7 @@ worth stating precisely. |---|---| | `mcpp.toml` keys | Additive. An existing key keeps its meaning; an unrecognised key is reported, never silently ignored | | CLI commands and flags | Additive. A removed spelling is kept as an alias | -| Machine output (`--message-format json`) | Versioned by `schemaVersion`; see [11](11-machine-output.md) | +| Machine output (`--message-format json`) | Versioned by `schemaVersion`; see [50](50-machine-output.md) | | `build.mcpp` directive protocol | Versioned; see `kProtocolVersion`. An engine refuses a program declaring a **higher** version rather than guessing | | `mcpp.lock` format | Versioned by `schemaVersion`; older files are migrated on read | | Target table rows | Additive. A row's tier may rise; a row is not removed while a published package targets it | diff --git a/docs/04-build-from-source.md b/docs/90-build-from-source.md similarity index 97% rename from docs/04-build-from-source.md rename to docs/90-build-from-source.md index df50a451..ef5d8e99 100644 --- a/docs/04-build-from-source.md +++ b/docs/90-build-from-source.md @@ -1,4 +1,4 @@ -# 04 — Building from Source & Contributing +# 90 — Building from Source and Contributing > mcpp is self-hosting — mcpp builds mcpp from source using mcpp itself. > Any environment that already has a working mcpp binary can build from source. @@ -23,7 +23,7 @@ mcpp test # build and run C++ tests discovered under tests/**/*.cp `mcpp test` does not run the shell end-to-end suite under `tests/e2e/`; run those separately against the freshly built binary. -The first build automatically fetches the default toolchain; see [03 — Toolchain Management](03-toolchains.md) for details. +The first build automatically fetches the default toolchain; see [20 — Toolchain Management](20-toolchains.md) for details. To produce a fully static binary identical to a release (the path taken by `release.yml`): diff --git a/docs/08-toolchain-internals.md b/docs/91-toolchain-internals.md similarity index 99% rename from docs/08-toolchain-internals.md rename to docs/91-toolchain-internals.md index 71e4961f..520c9afa 100644 --- a/docs/08-toolchain-internals.md +++ b/docs/91-toolchain-internals.md @@ -1,8 +1,8 @@ -# 08 — Toolchain Internals +# 91 — Toolchain Internals > How mcpp's toolchain machinery works under the hood, and how to extend it > with new toolchains, new architectures, and (eventually) embedded targets. -> Companion to [03 — Toolchain Management](03-toolchains.md), which covers the +> Companion to [20 — Toolchain Management](20-toolchains.md), which covers the > user-facing CLI. This document is for contributors and maintainers. ## 1. The model in one picture @@ -530,7 +530,7 @@ The machinery is already arch-parameterized; the work is data: ### 7.3 Embedded and bare-metal toolchains `riscv64-none-elf` and `riscv32-none-elf` are implemented, and the user-facing -account is [13 — Bare-Metal and Freestanding Targets](13-baremetal.md). This +account is [30 — Bare-Metal and Freestanding Targets](30-baremetal.md). This section records how the resulting shape relates to the hosted model above. Three of this section's earlier predictions held: diff --git a/docs/09-release.md b/docs/92-release.md similarity index 99% rename from docs/09-release.md rename to docs/92-release.md index 29871f24..7c1517a0 100644 --- a/docs/09-release.md +++ b/docs/92-release.md @@ -1,7 +1,7 @@ -# 09 — Releasing mcpp +# 92 — Releasing mcpp How a release of **mcpp itself** reaches users. This is maintainer-facing; for -packaging an *ordinary* project see [02 — Packaging for Release](02-pack-and-release.md). +packaging an *ordinary* project see [10 — Packaging for Release](10-pack-and-release.md). Until now this process lived only in commit messages and workflow comments. One of those commit messages contains a misdiagnosis that is corrected in §5. diff --git a/docs/README.md b/docs/README.md index f0bd7019..8dd3abd1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,14 +25,14 @@ anything a reader needs is written here or in a specification instead. | To | Read | Run | |---|---|---| -| write a program | [00](00-getting-started.md), [05](05-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | -| write a library others import | [10](10-publishing-a-library.md), [22](22-features-and-capabilities.md), [05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | -| publish it | [02](02-pack-and-release.md), [10](10-publishing-a-library.md), [12](12-binary-distribution.md) | [`03-pack-static`](../examples/03-pack-static/), [`05-lib-distribution`](../examples/05-lib-distribution/) | -| build for another machine | [16](16-the-target-triple.md), [15](15-openkal-cross.md), [13](13-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | -| use a GPU or an accelerator | [20](20-heterogeneous-builds.md), [18](18-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | -| add a rule, a language or a generator | [23](23-authoring-a-rule-package.md), [07](07-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`12-a-new-device-language`](../examples/12-a-new-device-language/) | -| add a package to the index | [10](10-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [21](21-commands-by-scenario.md) — the publishing scenarios | -| change mcpp itself | [04](04-build-from-source.md), [09](09-release.md), [19](19-supported-versions.md) | — | +| write a program | [00](00-getting-started.md), [02](02-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | +| write a library others import | [11](11-publishing-a-library.md), [04](04-features-and-capabilities.md), [02](02-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | +| publish it | [10](10-pack-and-release.md), [11](11-publishing-a-library.md), [12](12-binary-distribution.md) | [`03-pack-static`](../examples/03-pack-static/), [`05-lib-distribution`](../examples/05-lib-distribution/) | +| build for another machine | [21](21-the-target-triple.md), [24](24-openkal-cross.md), [30](30-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | +| use a GPU or an accelerator | [32](32-heterogeneous-builds.md), [31](31-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | +| add a rule, a language or a generator | [40](40-authoring-a-rule-package.md), [05](05-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`12-a-new-device-language`](../examples/12-a-new-device-language/) | +| add a package to the index | [11](11-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [06](06-commands-by-scenario.md) — the publishing scenarios | +| change mcpp itself | [90](90-build-from-source.md), [92](92-release.md), [51](51-supported-versions.md) | — | Lessons also arrive as project templates, which a package ships and `mcpp new --template` instantiates. `riscv-virt-rt` (bare metal) and `ocornut.imgui` @@ -41,50 +41,55 @@ one names it. ## Chapters -### Part I — Using mcpp +The number says which part a chapter is in: `0x` uses mcpp, `1x` ships what was +built, `2x` is toolchains and targets, `3x` is bare metal and devices, `4x` +extends mcpp, `5x` is the machine-facing contracts, `9x` is mcpp itself. Within +a part the order is a reading order. -- [00 - Getting Started](00-getting-started.md) -- [01 - Examples](01-examples.md) -- [05 - mcpp.toml Manifest Guide](05-mcpp-toml.md) -- [06 - Workspaces](06-workspace.md) -- [07 - build.mcpp Build Program](07-build-mcpp.md) -- [21 - Commands by Scenario](21-commands-by-scenario.md) -- [22 - Features and Capabilities](22-features-and-capabilities.md) +### 0x — Using mcpp -### Part II — Shipping what was built +- [00 — Getting Started](00-getting-started.md) +- [01 — Examples](01-examples.md) +- [02 — The mcpp.toml Manifest](02-mcpp-toml.md) +- [03 — Workspaces](03-workspace.md) +- [04 — Features and Capabilities](04-features-and-capabilities.md) +- [05 — Build Programs: `build.mcpp`](05-build-mcpp.md) +- [06 — Commands by Scenario](06-commands-by-scenario.md) -- [02 - Packaging & Release](02-pack-and-release.md) -- [10 - Publishing a Library to mcpp-index](10-publishing-a-library.md) -- [12 - Distributing a Prebuilt Library](12-binary-distribution.md) +### 1x — Shipping what was built -### Part III — Toolchains and targets +- [10 — Packaging an Application for Release](10-pack-and-release.md) +- [11 — Publishing a Library to mcpp-index](11-publishing-a-library.md) +- [12 — Distributing a Prebuilt Library](12-binary-distribution.md) -- [03 - Toolchain Management](03-toolchains.md) -- [13 - Bare-Metal and Freestanding Targets](13-baremetal.md) -- [14 - The Target Side](14-target-side.md) -- [15 - Cross-Compilation Over openkal](15-openkal-cross.md) -- [16 - The Target Triple](16-the-target-triple.md) -- [17 - The Project Environment](17-the-project-environment.md) +### 2x — Toolchains and targets -### Part IV — Devices and accelerators +- [20 — Toolchain Management](20-toolchains.md) +- [21 — The Target Triple](21-the-target-triple.md) +- [22 — The Target Side](22-target-side.md) +- [23 — The Project Environment](23-the-project-environment.md) +- [24 — Cross-Compilation Over openkal](24-openkal-cross.md) -- [18 - Reaching a Device](18-devices.md) -- [20 - Heterogeneous Builds](20-heterogeneous-builds.md) +### 3x — Bare metal, devices and accelerators -### Part V — Extending mcpp from outside +- [30 — Bare-Metal and Freestanding Targets](30-baremetal.md) +- [31 — Reaching a Device](31-devices.md) +- [32 — Heterogeneous Builds](32-heterogeneous-builds.md) -- [23 - Authoring a Rule Package](23-authoring-a-rule-package.md) +### 4x — Extending mcpp from outside -### Part VI — Machine interfaces and compatibility +- [40 — Authoring a Rule Package](40-authoring-a-rule-package.md) -- [11 - Machine-Readable Output](11-machine-output.md) -- [19 - Supported Versions and Compatibility](19-supported-versions.md) +### 5x — Machine interfaces and compatibility -### Part VII — Contributing to mcpp itself +- [50 — Machine-Readable Output](50-machine-output.md) +- [51 — Supported Versions and Compatibility](51-supported-versions.md) -- [04 - Building from Source & Contributing](04-build-from-source.md) -- [08 - Toolchain Internals](08-toolchain-internals.md) -- [09 - Releasing mcpp](09-release.md) +### 9x — Contributing to mcpp itself + +- [90 — Building from Source and Contributing](90-build-from-source.md) +- [91 — Toolchain Internals](91-toolchain-internals.md) +- [92 — Releasing mcpp](92-release.md) ## Specifications diff --git a/docs/specs/exit-codes.md b/docs/specs/exit-codes.md index a50bab92..9aa954d6 100644 --- a/docs/specs/exit-codes.md +++ b/docs/specs/exit-codes.md @@ -19,7 +19,7 @@ > 并覆盖异常边界 —— 否则客户端仍然要靠猜。**这条现在是 `docs/specs/` 的内容,不是 > 代码。** -被指派的那份契约一直没有写。`docs/11-machine-output.md` 落地了其中的 +被指派的那份契约一直没有写。`docs/50-machine-output.md` 落地了其中的 usage / internal 一半(`2`、`70`、`127`),runtime 的一半 —— 也就是命令**跑了并且 失败**时返回的 `1` —— 既不在那张表里,也不在别处。#540 由此把表读成「不完整」,并 提出补 `4`;而 `4` 恰恰是那几个带信封的命令给不出的码。 @@ -73,7 +73,7 @@ issue;`1` 通常不是。 ### 3.3 退出码禁止用于协议识别 已实现 客户端**禁止**用退出码判断「这个 mcpp 支不支持某项功能」。理由见 -`docs/11-machine-output.md` §1:在该协议出现之前发布的每个版本上,未知选项本身就是 +`docs/50-machine-output.md` §1:在该协议出现之前发布的每个版本上,未知选项本身就是 一次错误,而它当年走的是 stdout + 退出码 1。唯一跨版本成立的判据是**解析 stdout**。 ### 3.4 `1` 可以与 stdout 上的信封同时出现 已实现 diff --git a/docs/specs/manifest-semantics.md b/docs/specs/manifest-semantics.md index 3b208143..f32be0af 100644 --- a/docs/specs/manifest-semantics.md +++ b/docs/specs/manifest-semantics.md @@ -10,7 +10,7 @@ | **最低实现版本** | 条件化形状:mcpp **2026.8.29.1**(`[target..build-dependencies]` 起齐备);目标轴:mcpp **2026.9.6.4** | | **作者/维护** | mcpp-community | | **相关设计文档** | `.agents/docs/2026-09-07-mcpp-toml-unified-semantics-design.md`
`.agents/docs/2026-06-04-manifest-schema-ownership.md`
`.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md` | -| **相关使用文档** | [docs/05 —— mcpp.toml 字段参考](../05-mcpp-toml.md) | +| **相关使用文档** | [docs/05 —— mcpp.toml 字段参考](../02-mcpp-toml.md) | ## 规范用语 diff --git a/docs/specs/target-side.md b/docs/specs/target-side.md index b5ddbde6..d17eac9c 100644 --- a/docs/specs/target-side.md +++ b/docs/specs/target-side.md @@ -8,7 +8,7 @@ | 最后修改 | 2026-08-24 | | 对应实现 | mcpp >= 2026.8.24.2 | | 相关设计文档 | `.agents/docs/2026-08-24-target-side-design.md` | -| 使用文档 | [docs/14 - 目标侧](../zh/14-target-side.md) | +| 使用文档 | [docs/14 - 目标侧](../zh/22-target-side.md) | 本规范定义一次构建的目标侧由哪些层构成、每一层可以由谁供给、 供给者与需求者如何声明,以及引擎据此执行的规则。 diff --git a/docs/zh/00-getting-started.md b/docs/zh/00-getting-started.md index 25d54178..3a2feed1 100644 --- a/docs/zh/00-getting-started.md +++ b/docs/zh/00-getting-started.md @@ -1,4 +1,4 @@ -# 00 — 快速开始 +# 00 —— 快速开始 > 5 分钟完成 install → new → build → run → pack 全流程。 @@ -167,13 +167,13 @@ mcpp pack --mode static # musl 全静态构建 mcpp pack --mode self-contained # 打包 loader、libc 与依赖 ``` -四种模式的差异及产物布局参见 [02 — 发布打包](02-pack-and-release.md)。`bundle-project` 与 `bundle-all` 仍分别是 `vendored` 与 `self-contained` 的兼容别名。 +四种模式的差异及产物布局参见 [10 — 发布打包](10-pack-and-release.md)。`bundle-project` 与 `bundle-all` 仍分别是 `vendored` 与 `self-contained` 的兼容别名。 ## 后续阅读 - [01 — 示例项目](01-examples.md) — 可直接运行的最小工程集合 -- [02 — 发布打包](02-pack-and-release.md) — 构建可分发产物 -- [03 — 工具链管理](03-toolchains.md) — 切换编译器与多版本管理 +- [10 — 发布打包](10-pack-and-release.md) — 构建可分发产物 +- [20 — 工具链管理](20-toolchains.md) — 切换编译器与多版本管理 - 任意命令的完整选项可通过 `mcpp --help` 查阅 diff --git a/docs/zh/01-examples.md b/docs/zh/01-examples.md index df2a1d22..5e823957 100644 --- a/docs/zh/01-examples.md +++ b/docs/zh/01-examples.md @@ -70,7 +70,7 @@ mcpp build && mcpp run | [`08-build-rules`](../../examples/08-build-rules/) | 两个规则包与同时使用它们的工程;`host-module = true`、`role = "check"` 的 `mcpp::action` | | [`12-a-new-device-language`](../../examples/12-a-new-device-language/) | `device_extensions` 与 `rule_module`:规则包教会 mcpp 一门引擎从未听说过的语言 | -[23 —— 编写规则包](23-authoring-a-rule-package.md) 是这两个示例所演示内容的参考。 +[40 —— 编写规则包](40-authoring-a-rule-package.md) 是这两个示例所演示内容的参考。 ## 以模板形式到达的课程 @@ -79,9 +79,9 @@ mcpp build && mcpp run | 模板 | 课程 | 章节 | |---|---|---| -| `riscv-virt-rt` | 一个裸机工程、它的板级支持与它的 runner | [13](13-baremetal.md) | -| `riscv-virt-rt:nolibc` | 同上,但没有 C 库 | [13](13-baremetal.md) | -| `ocornut.imgui` | 一个带窗口与渲染栈的图形应用 | [03](03-toolchains.md) | +| `riscv-virt-rt` | 一个裸机工程、它的板级支持与它的 runner | [30](30-baremetal.md) | +| `riscv-virt-rt:nolibc` | 同上,但没有 C 库 | [30](30-baremetal.md) | +| `ocornut.imgui` | 一个带窗口与渲染栈的图形应用 | [20](20-toolchains.md) | ```bash mcpp new blinky --template riscv-virt-rt @@ -93,7 +93,7 @@ mcpp new blinky --template riscv-virt-rt 值得新增一个示例:当一个能力**改变工程的形状** —— 它包含的文件、它声明的 manifest、 或者作者敲的命令。如果一个能力只是既有示例工程里的一行,它属于对应章节里的一个 代码块;如果它只经由命令到达,它属于 -[21 —— 按场景选命令](21-commands-by-scenario.md)。 +[06 —— 按场景选命令](06-commands-by-scenario.md)。 README 要写明这个示例第一个教什么,以及判断它是否成立的判据。贡献流程见 -[04 —— 从源码构建 & 参与贡献](04-build-from-source.md)。 +[90 —— 从源码构建 & 参与贡献](90-build-from-source.md)。 diff --git a/docs/zh/05-mcpp-toml.md b/docs/zh/02-mcpp-toml.md similarity index 98% rename from docs/zh/05-mcpp-toml.md rename to docs/zh/02-mcpp-toml.md index 001d330d..92e59f41 100644 --- a/docs/zh/05-mcpp-toml.md +++ b/docs/zh/02-mcpp-toml.md @@ -1,4 +1,4 @@ -# mcpp.toml 工程文件指南 +# 02 —— mcpp.toml 工程文件指南 `mcpp.toml` 是 mcpp 构建工具的项目配置文件,类似 Cargo 的 `Cargo.toml` 或 Node 的 `package.json`。放在项目根目录下,`mcpp build` 会自动发现并读取它。 @@ -73,7 +73,7 @@ mcpp 会在编译前失败,并同时报出工具链与工程档位。 值的两种拼法都接受:`standard = "c++26"` 与 `standard = 26`。 当**依赖声明的档位高于当前图**时,mcpp 会在编译前说出来,而不是让它在那个依赖的源码里 -某处失败。见 [workspace §4.2](06-workspace.md)。 +某处失败。见 [workspace §4.2](03-workspace.md)。 #### 方言标志与 `import std` BMI @@ -204,7 +204,7 @@ required_features = ["gui"] # 仅当 feature `gui` 激活时 > compile-once 模型)。当标志只需影响某个二进制(或测试)**自己的入口**时,这正是合适的工具 —— > 例如某个测试的 `main` 里触发契约违规、需要按测试设置契约求值语义 > (`-fcontract-evaluation-semantic=observe`),或入口独享的 feature 宏、局部告警抑制。 -> 若标志必须穿透**共享**代码,就不该放在这里 —— 改用 [workspace](06-workspace.md) member 或 +> 若标志必须穿透**共享**代码,就不该放在这里 —— 改用 [workspace](03-workspace.md) member 或 > `[features]`;若是整次构建的模式,用 `[profile.*]`(`mcpp test --profile ` 会让包括被测 > 代码在内的整个测试镜像都在该 profile 下编译)。 > @@ -215,7 +215,7 @@ required_features = ["gui"] # 仅当 feature `gui` 激活时 | 目标 | 使用 | |---|---| | 某二进制**自己入口**上的不同宏/标志 | per-target `defines` / `cxxflags`(见上) | -| 两个产品差异在它们**共享**的代码里 | 拆成 [workspace](06-workspace.md) member,各自 `[build]` 标志,共享一个 `lib` | +| 两个产品差异在它们**共享**的代码里 | 拆成 [workspace](03-workspace.md) member,各自 `[build]` 标志,共享一个 `lib` | | **选择**某共享库的变体(如某后端) | 在该库上用 `[features]`(§2.8)——additive,作用到库自己的编译 | | **整次构建的模式**(sanitizer、契约语义、优化档) | `[profile.]`(§2.9)+ `--profile`;`mcpp test --profile ` 同样支持 | @@ -243,7 +243,7 @@ mcpp 刻意不在一次构建里把同一个共享源编译成两份:一个源 > 那会让这个设备无东西可编,而只在链接时才说话)。`--no-accel` 下该 glob 被排除, > 一个工程由此产出它的 CPU-only 变体。`--accel` 未覆盖该约束时构建被拒并给出两侧 > (`accel-mismatch`)。有效集合匹配到的设备类源文件 —— CUDA 与 HIP、GLSL 各 stage、 -> HLSL、OpenCL C 与 Metal,完整清单见 [20 — 异构硬件构建](20-heterogeneous-builds.md) —— 引擎 +> HLSL、OpenCL C 与 Metal,完整清单见 [32 — 异构硬件构建](32-heterogeneous-builds.md) —— 引擎 > 从不编译;它们以 `MCPP_DEVICE_SOURCES` 到达构建程序,由工程引入的规则包把每一个 > 变成一条 `mcpp::action`。 @@ -495,10 +495,10 @@ MCPP_BUILD_PROGRAM_TIMEOUT=<秒> 本次调用(最高) 「抬高超时」触发全量重建,这恰好与抬高超时的人想要的相反。 只有构建**程序**受限,**编译**不受限。原因见 -[07-build-mcpp.md](07-build-mcpp.md)。 +[05-build-mcpp.md](05-build-mcpp.md)。 ### C++ 运行时契约(`cxx_runtime`) -已移入 [03 —— 工具链管理](03-toolchains.md)。 +已移入 [20 —— 工具链管理](20-toolchains.md)。 ### 宿主代码页之外的文件名 @@ -537,7 +537,7 @@ accel = "cuda12.8+{sm_80,sm_90f} ptx>=90" 也就是在一个同时发布了设备构建的包中选中 CPU-only 变体的方式。 该取值会与构建所消费的任何预建产物的 `accel` 字段比较,而请求为空的构建被任何产物满足。 -见 [20 — 异构硬件构建](20-heterogeneous-builds.md)。 +见 [32 — 异构硬件构建](32-heterogeneous-builds.md)。 ### 2.4 `[lib]` — 库根模块约定 @@ -794,7 +794,7 @@ linkage = "static" ``` ### 2.7.1 `[target.*]` —— 平台条件依赖与 flag -已移入 [14 —— 目标侧](14-target-side.md)。 +已移入 [22 —— 目标侧](22-target-side.md)。 ### 2.7.2 裸机(`os = none`)—— freestanding target @@ -805,7 +805,7 @@ linkage = "static" 本节是清单参考。示例部分 —— 生成工程、运行、在目标上测试、freestanding 标准库 子集,以及编写板级支持包 —— 在 -[13 — 裸机与 freestanding 目标](13-baremetal.md)。 +[30 — 裸机与 freestanding 目标](30-baremetal.md)。 ```bash mcpp build --target riscv64-none-elf @@ -922,10 +922,10 @@ error: test result: NOT RUN. 0 passed; 0 failed; 1 not run (this host cannot exe 退出码 1 含义不变 —— 有测试运行并失败;0 表示每个测试都运行并通过。 `--message-format json` 在每条记录上带 `"status":"not_run"` 与 `reason`,在汇总记录上 -带 `not_run` / `not_run_reason`(见 [11 —— 机器可读输出](11-machine-output.md))。 +带 `not_run` / `not_run_reason`(见 [50 —— 机器可读输出](50-machine-output.md))。 ### 2.8 `[features]` —— Feature -已移入 [22 —— Feature 与能力](22-features-and-capabilities.md), +已移入 [04 —— Feature 与能力](04-features-and-capabilities.md), 连同 `provides` / `requires` 与 `[feature-deps.]`。 @@ -1157,14 +1157,14 @@ accelerators = ["cuda", "rocm"] 与产物的 `accel` 字段刻意不同。声明由人手写、可以是期望值;`accel` 是从产生该二进制的 那次构建测量出来的,并且是消费者被拒绝时所依据的东西。见 -[20 — 异构硬件构建](20-heterogeneous-builds.md)。 +[32 — 异构硬件构建](32-heterogeneous-builds.md)。 ### 2.13 `[xlings]` —— 工程的环境 -已移入 [17 —— 项目环境](17-the-project-environment.md)。 +已移入 [23 —— 项目环境](23-the-project-environment.md)。 ### 2.14 依赖产出的 host 工具 -已移入 [07 —— build.mcpp](07-build-mcpp.md)。 +已移入 [05 —— build.mcpp](05-build-mcpp.md)。 ### 2.15 `[resources]` —— 编译进产物的元数据与资产(2026.8.7.1+) @@ -1250,12 +1250,12 @@ o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o") .submit(); ``` -见 [07 — build.mcpp](07-build-mcpp.md)。把这类文件写进 `[build].ldflags` 也「能用」, +见 [05 — build.mcpp](05-build-mcpp.md)。把这类文件写进 `[build].ldflags` 也「能用」, 但 ldflags 是链接命令里的一串字符:没有任何东西跟踪它,改了它得到的是 `ninja: no work to do`。 ### 2.16 `[hooks]` —— 项目构建生命周期命令 -已移入 [21 —— 按场景选命令](21-commands-by-scenario.md)。 +已移入 [06 —— 按场景选命令](06-commands-by-scenario.md)。 ## 附录 A. Schema 所有权原则(新字段准入标准) diff --git a/docs/zh/06-workspace.md b/docs/zh/03-workspace.md similarity index 99% rename from docs/zh/06-workspace.md rename to docs/zh/03-workspace.md index baae2cff..3a7f12a0 100644 --- a/docs/zh/06-workspace.md +++ b/docs/zh/03-workspace.md @@ -1,4 +1,4 @@ -# 工作空间 (Workspace) +# 03 —— 工作空间 工作空间允许在同一个仓库中组织和管理多个相关的 mcpp 包(库或应用程序)。各成员包共享统一的依赖版本配置和工具链设置,同时保持独立的 `mcpp.toml` 工程文件。 diff --git a/docs/zh/22-features-and-capabilities.md b/docs/zh/04-features-and-capabilities.md similarity index 97% rename from docs/zh/22-features-and-capabilities.md rename to docs/zh/04-features-and-capabilities.md index 2b4e3838..59596b26 100644 --- a/docs/zh/22-features-and-capabilities.md +++ b/docs/zh/04-features-and-capabilities.md @@ -1,11 +1,11 @@ -# 22 —— Feature 与能力 +# 04 —— Feature 与能力 Feature 是一个包提供可选内容的方式:一个编译宏、一份额外的源文件、一个额外的 依赖,或者在多个后端之间做选择。本章是声明与消费 feature 的参考。 -相关文档:[05 —— mcpp.toml](05-mcpp-toml.md) 是 manifest 其余部分的字段参考; +相关文档:[02 —— mcpp.toml](02-mcpp-toml.md) 是 manifest 其余部分的字段参考; [`examples/11-features`](../../examples/11-features/) 是一个把三种形态都声明了 -一遍、并且用 dev-dependency 写测试的包;[20 —— 异构硬件构建](20-heterogeneous-builds.md) +一遍、并且用 dev-dependency 写测试的包;[32 —— 异构硬件构建](32-heterogeneous-builds.md) 是这套机制最大的消费者,因为每条加速器 lane 都是一个 feature。 ## `[features]` —— Feature(Cargo 风格,可加性) @@ -60,7 +60,7 @@ simd = { sources = ["src/simd/**"], flags = [ 空间宏:feature **不**注入自由的包级 `cflags`/`ldflags`,否则会破坏加性的 feature 并集模型。链接旗标来自 provider 依赖(见下文*`provides` / `requires`*),而非 feature。 - 每个激活的 feature 仍会得到自动的 `-DMCPP_FEATURE_`,`defines` 与之叠加。 -- `flags`(mcpp 0.0.101+)与 `[build].flags`([05 §2.3](05-mcpp-toml.md))共用同一有序 inline-table 数组 +- `flags`(mcpp 0.0.101+)与 `[build].flags`([02 §2.3](02-mcpp-toml.md))共用同一有序 inline-table 数组 文法(`glob` 必填,加 `cflags`/`cxxflags`/`asmflags`/`defines`;与 `[[build.flags]]` 一样也接受 `[[features..flags]]` 拼写)。feature 激活时 条目追加在 base `[build].flags` **之后**(feature 按名 @@ -87,7 +87,7 @@ device_extensions = [".comp", ".vert", ".frag", ".glsl"] 会把它们分类为设备源 —— 不扫描 import、不产 BMI、由 mcpp 不驱动的编译器编译。这与 `[build] module_extensions` 是同一个形状:mcpp 知道设备源*是什么*,不知道 `.cu` 是 CUDA,所以**一门新设备语言不需要引擎改动**。 -[20 — 异构硬件构建](20-heterogeneous-builds.md) 里那句「第六个后端是一个包而不是一次 +[32 — 异构硬件构建](32-heterogeneous-builds.md) 里那句「第六个后端是一个包而不是一次 引擎改动」由此才成立;`.slang` 已从 mcpp 的内置表中移除,现在正是经由这条路到达的。 `rule_module` 给出消费者的构建程序为够到这条规则而 import 的模块,以及它调用的 @@ -170,7 +170,7 @@ std-module-flags = ["--no-default-config", "-nostdinc++"] std-module-flags = ["-D_GNU_SOURCE"] ``` -五个层、约束它们的规则与相应诊断,见 [14 - 目标侧](14-target-side.md)。 +五个层、约束它们的规则与相应诊断,见 [22 - 目标侧](22-target-side.md)。 绑定是**确定性**的: diff --git a/docs/zh/07-build-mcpp.md b/docs/zh/05-build-mcpp.md similarity index 98% rename from docs/zh/07-build-mcpp.md rename to docs/zh/05-build-mcpp.md index 4361c85a..785b0414 100644 --- a/docs/zh/07-build-mcpp.md +++ b/docs/zh/05-build-mcpp.md @@ -1,6 +1,6 @@ -# `build.mcpp` —— 原生构建程序 +# 05 —— 构建程序:`build.mcpp` -[English](../07-build-mcpp.md) | **简体中文** +[English](../05-build-mcpp.md) | **简体中文** 绝大多数工程只需要 `mcpp.toml`。需要构建期逻辑时 —— 探测主机、生成源码、依据环境 决定某个编译开关——就在工程根目录放一个 `build.mcpp`。它是 mcpp 版的 Zig `build.zig` @@ -295,7 +295,7 @@ store 内部结构 —— 与 `dep_dir` 存在的理由相同。 **依赖声明的包同样被作答**(2026.9.6.6+),而且答的是这次构建**真正装上**的版本,不是 本地 manifest 写下的那个。一个包只有一个版本:工程与规则都命名它时,离产物更近的声明赢, -而两侧被告知同一个答案。见 [05 — mcpp.toml](05-mcpp-toml.md) 的「一个包一个版本」。 +而两侧被告知同一个答案。见 [02 — mcpp.toml](02-mcpp-toml.md) 的「一个包一个版本」。 **`[feature-xlings.]` 在 `` 生效时同样被作答**(2026.9.6.2+)。这张表从诞生 起就参与供给 —— 在那里写下一个包,它就会被下载并安装 —— 但构建程序的环境只由 @@ -443,7 +443,7 @@ mcpp 为那条边写出 `depfile =` 与 `deps = gcc`,ninja 读取该文件并把 > 把预编译对象写进 `[build].ldflags` 同样能到达链接器,但**不要**用它承载构建产物: > ldflags 是链接命令里的一串字符、不是图里的文件,没有任何东西跟踪它,改了它得到的是 -> `ninja: no work to do`。Windows 资源请用 [`[resources]`](05-mcpp-toml.md); +> `ninja: no work to do`。Windows 资源请用 [`[resources]`](02-mcpp-toml.md); > `object` 是其余一切的出口。 **必须写出输出文件名。** mcpp 在 prepare 期就定死源码集、fingerprint 与模块图, @@ -600,16 +600,16 @@ SubOS 里。前置让被声明的环境成为默认答案;宿主仍在其后可 `qemu-system-riscv64`:答案是一个执行时报「is not installed in this subos」的 shim,而可用的那份就在项目自己的环境里,根本不在 `PATH` 上。 -这个选择就是[第 8 章](08-toolchain-internals.md)已经描述的那一个——决定项目链接 +这个选择就是[第 8 章](91-toolchain-internals.md)已经描述的那一个——决定项目链接 哪个 C 库的同一条声明,多交付给了一个消费者。被声明的环境是什么、什么时候需要 -它,见[第 17 章](17-the-project-environment.md);`examples/07-project-subos/` 是 +它,见[第 17 章](23-the-project-environment.md);`examples/07-project-subos/` 是 一个可运行的工程。 ## 写一个规则包 一条规则 ——「对这些 `.proto` 跑 protoc」「对这些源码跑 clang-tidy」—— 属于一个包, 而不该被复制到每个消费者的 `build.mcpp` 里。机制是 -[`host-module = true`](../05-mcpp-toml.md);本节讲的是它里面应该长什么样。 +[`host-module = true`](../02-mcpp-toml.md);本节讲的是它里面应该长什么样。 下面这些从第一个规则包 `mcpplibs.grpcgen` 归纳而来,每一条特征都单独判过是必然还是偶然。 它们是指引而非规则,因为其中没有一条能给出引擎可以检查的判据。 @@ -649,7 +649,7 @@ shim,而可用的那份就在项目自己的环境里,根本不在 `PATH` 上。 而不是以包名注册,所以 `export module mcpp.rules.spirv;` 就是消费者 import 的那个名字。 官方插件集中在一个包里,`mcpp:plugins`(仓库 `mcpp-community/mcpp-plugins`):规则包命名为 `mcpp.rules.`,构建期工具命名为 `mcpp.tools.`,每个成员由该包的一个 feature 选择 -(见 [`host-module = true`](../05-mcpp-toml.md))。`mcpp.build.*` 是引擎自己的模块族, +(见 [`host-module = true`](../02-mcpp-toml.md))。`mcpp.build.*` 是引擎自己的模块族, 不用于插件。引擎判定不了谁是官方,所以检查以包的**命名空间**为键,两者不一致时告警 —— warning: build rule 'mcpplibs.plugins' declares the module @@ -750,7 +750,7 @@ mcpp **不会**每次构建都重跑 `build.mcpp`。它会缓存程序产出的 - **在主机上运行——交叉构建下也是**(mcpp 0.0.95+)。`mcpp build --target ` 下,程序用宿主解析的工具链编译、在宿主运行,并看到 `MCPP_TARGET` = 交叉三元组。 纯声明式的目标门控仍首选 `[target.'cfg(...)']` 表——参见 - [05 - mcpp.toml 工程文件指南](05-mcpp-toml.md)。 + [02 - mcpp.toml 工程文件指南](02-mcpp-toml.md)。 - **当前工作目录是工程根目录**,因此相对路径(`src/generated.cpp`)会落在预期位置。 - `build.mcpp` 非零退出会中止构建并打印其输出。 - **运行有时间上限**(mcpp 2026.8.5.1+):构建程序默认有 **600 秒**,超时后 mcpp @@ -804,7 +804,7 @@ grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } 每个名字必须是该包的一个 `kind = "bin"` target。mcpp 会**为构建机器**构建它, 并把绝对路径以 `MCPP_DEP__BIN_` 交给 `build.mcpp` —— 用 -`mcpp::dep_bin("protobuf", "protoc")` 读取(见 [07 — build.mcpp](07-build-mcpp.md))。 +`mcpp::dep_bin("protobuf", "protoc")` 读取(见 [05 — build.mcpp](05-build-mcpp.md))。 四条值得知道的性质: @@ -907,7 +907,7 @@ rules-spirv = { sources = ["rules/spirv.cppm"] } # export module mcpp.rules.spir plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } ``` -**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 [05 §2.6.1](05-mcpp-toml.md) 描述的那种 +**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 [02 §2.6.1](02-mcpp-toml.md) 描述的那种 情形:它的库绝不该到达目标,而它的规则仍然被需要。两条轴是分开的: `host-module = true` 说的是**要哪一种构建期产物**,而 section 说的是**这个包是否到达 目标**;规则包在第二条轴上的答案是"否",而 section 就是说这件事的地方。写在 diff --git a/docs/zh/21-commands-by-scenario.md b/docs/zh/06-commands-by-scenario.md similarity index 98% rename from docs/zh/21-commands-by-scenario.md rename to docs/zh/06-commands-by-scenario.md index d1e2befe..20006c43 100644 --- a/docs/zh/21-commands-by-scenario.md +++ b/docs/zh/06-commands-by-scenario.md @@ -1,13 +1,13 @@ -# 21 — 按场景选命令 +# 06 —— 按场景选命令 命令清单是 `mcpp --help`,每个子命令还有自己的 `--help`。本章回答的是另一个问题: 某个情形已经发生时该用哪条命令 —— 构建目录一直变大、解析结果出乎意料、描述符即将 发布、索引可能陈旧。这里收的都是名字本身没有说出它所属场景的命令。 相关文档:[00 — 快速开始](00-getting-started.md)(日常构建与测试循环)、 -[03 — 工具链管理](03-toolchains.md)、 -[10 — 发布库到 mcpp-index](10-publishing-a-library.md)、 -[11 — 机器可读输出](11-machine-output.md)。 +[20 — 工具链管理](20-toolchains.md)、 +[11 — 发布库到 mcpp-index](11-publishing-a-library.md)、 +[50 — 机器可读输出](50-machine-output.md)。 下面每段输出都由本章所在版本的 mcpp 实际产生。 @@ -150,7 +150,7 @@ $ mcpp xpkg parse mcpp.plugins.lua --json ``` `mcpp emit xpkg` 生成要提交的条目。完整路径见 -[10 — 发布库到 mcpp-index](10-publishing-a-library.md)。 +[11 — 发布库到 mcpp-index](11-publishing-a-library.md)。 ## 环境诊断 @@ -282,7 +282,7 @@ manifest 在这一版仍能加载;不认识的**值**——`cmd` 缺失或不是 > **Hook 是代码,而 `mcpp.toml` 是仓库的一部分。** 构建一个刚克隆下来的项目,会以 > 执行 `mcpp build` 的那个账户的权限,运行它 `[hooks]` 里写的任何东西。这与 -> `build.mcpp`([07 — build.mcpp](07-build-mcpp.md))已经要求的信任是同一份; +> `build.mcpp`([05 — build.mcpp](05-build-mcpp.md))已经要求的信任是同一份; > `[hooks]` 扩大的是它的范围,而不是引入了一份新的信任。 Hook 程序可以作为普通 xlings 依赖安装。例如,音频通知程序可以把音频内置进自己的 diff --git a/docs/zh/02-pack-and-release.md b/docs/zh/10-pack-and-release.md similarity index 99% rename from docs/zh/02-pack-and-release.md rename to docs/zh/10-pack-and-release.md index 1993679d..0cec42f1 100644 --- a/docs/zh/02-pack-and-release.md +++ b/docs/zh/10-pack-and-release.md @@ -1,4 +1,4 @@ -# 02 — 发布打包 +# 10 —— 发布打包 > 默认的动态链接 `mcpp build` 产物会把 loader 与 RUNPATH 指向构建沙盒。它是 > 开发产物,不是交付物。有三条路把它变成交付物 —— **没有一条使用宿主的 C 库**。 @@ -55,7 +55,7 @@ mcpp 之所以针对私有 glibc 构建,正是为了让产物的行为不取决 「自包含」有一个下限。有些库只能来自目标机器:图形驱动的用户态部分与正在运行的 内核模块版本绑定,而对专有栈而言,再分发是不被允许的。这类依赖应声明为运行期能力 -需求(`docs/zh/05-mcpp-toml.md` §2.11),模式表随之多出一列: +需求(`docs/zh/02-mcpp-toml.md` §2.11),模式表随之多出一列: | Mode | 需要宿主提供能力的程序 | |---|---| @@ -270,7 +270,7 @@ Win32 loader 解析 DLL 的第一顺位就是**可执行文件所在目录**,而 `vcruntime140.dll` / `msvcp140.dll` **不是** Windows 自己的:它们属于 MSVC toolset,就像 `libstdc++.so` 属于 gcc。它们要不要跟着产物走,由 `cxx_runtime` -决定(见 `docs/zh/05-mcpp-toml.md`),不由这张表决定 —— 而 `mcpp pack` 会拒绝 +决定(见 `docs/zh/02-mcpp-toml.md`),不由这张表决定 —— 而 `mcpp pack` 会拒绝 那些无法兑现契约的组合: ``` diff --git a/docs/zh/10-publishing-a-library.md b/docs/zh/11-publishing-a-library.md similarity index 96% rename from docs/zh/10-publishing-a-library.md rename to docs/zh/11-publishing-a-library.md index 8a059172..5eef21f5 100644 --- a/docs/zh/10-publishing-a-library.md +++ b/docs/zh/11-publishing-a-library.md @@ -1,10 +1,10 @@ -# 10 - 发布一个库到 mcpp-index +# 11 —— 发布一个库到 mcpp-index -[English](../10-publishing-a-library.md) | **简体中文** +[English](../11-publishing-a-library.md) | **简体中文** 一个库如何变成 `[dependencies]` 可以写出来的东西。这是**库作者**的链路; -[09 - 发布 mcpp](09-release.md) 讲的是发布 mcpp 自身, -[02 - 发布打包](02-pack-and-release.md) 讲的是 `mcpp pack` 打包应用。 +[92 - 发布 mcpp](92-release.md) 讲的是发布 mcpp 自身, +[10 - 发布打包](10-pack-and-release.md) 讲的是 `mcpp pack` 打包应用。 ## 发布顺序 diff --git a/docs/zh/12-binary-distribution.md b/docs/zh/12-binary-distribution.md index 7adaf0da..e66f14a0 100644 --- a/docs/zh/12-binary-distribution.md +++ b/docs/zh/12-binary-distribution.md @@ -1,12 +1,12 @@ -# 12 - 分发预编译库 +# 12 —— 分发预编译库 [English](../12-binary-distribution.md) | **简体中文** > 把一个库以**接口 + 预编译二进制**的形式分发,而不是发源码。 > 适用于闭源分发、离线环境,以及构建产物已在构建农场生成的场景。 > -> 相关文档:[02 - 打包应用](02-pack-and-release.md) 说明**程序**的打包; -> [10 - 发布一个库](10-publishing-a-library.md) 说明源码分发通路。 +> 相关文档:[10 - 打包应用](10-pack-and-release.md) 说明**程序**的打包; +> [11 - 发布一个库](11-publishing-a-library.md) 说明源码分发通路。 ## 概述 @@ -28,7 +28,7 @@ mcpp pack mathkit --target x86_64-linux-gnu \ | `kind` | `mcpp pack ` 产出 | `--mode` | |---|---|---| -| `bin` | 应用 bundle(见 [02](02-pack-and-release.md)) | 四档 | +| `bin` | 应用 bundle(见 [10](10-pack-and-release.md)) | 四档 | | `lib` | **静态库包** | — | | `shared` | **动态库包** | — | @@ -279,7 +279,7 @@ Mach-O 上打包器会读出 `LC_RPATH` 并在包会携带它时告警;自动改 ### 调试信息会被剥掉 -参数、分档表与 `--debug-symbols` 见 [docs/02](02-pack-and-release.md)。 +参数、分档表与 `--debug-symbols` 见 [docs/02](10-pack-and-release.md)。 对**库**包最要紧的一条:静态归档只做 `--strip-debug`,因为 `--strip-all` 会删掉 归档的符号索引,消费方链接时会报 `archive has no index; run ranlib to add one`。 diff --git a/docs/zh/03-toolchains.md b/docs/zh/20-toolchains.md similarity index 99% rename from docs/zh/03-toolchains.md rename to docs/zh/20-toolchains.md index d0d88672..0e2ba371 100644 --- a/docs/zh/03-toolchains.md +++ b/docs/zh/20-toolchains.md @@ -1,4 +1,4 @@ -# 03 — 工具链管理 +# 20 —— 工具链管理 > mcpp 维护一个独立的工具链沙盒,与系统 PATH 完全隔离。 @@ -200,7 +200,7 @@ mcpp toolchain default gcc@16 --target x86_64-windows-gnu `import std`)。该 target 默认 linkage 为 **static**——产出的 `.exe` 完全自包含(无需随包分发 `libstdc++-6.dll`,可直接在 wine 下运行); 要退出请写在 target 段上——`linkage` 只认精确 triple(见 -[mcpp.toml](05-mcpp-toml.md) §2.7),`[build] linkage` 这个键并不存在,写了会被静默忽略: +[mcpp.toml](02-mcpp-toml.md) §2.7),`[build] linkage` 这个键并不存在,写了会被静默忽略: ```toml [target.x86_64-windows-gnu] @@ -437,7 +437,7 @@ cxx_runtime = "self-contained" # C++ 运行时那根轴 CRT。 toolset 自带的那份可再分发 CRT(`vcruntime140.dll` / `msvcp140.dll`)可以跟着 -产物走 —— 见 `docs/zh/05-mcpp-toml.md` 的 `cxx_runtime = "toolchain-coupled"`。 +产物走 —— 见 `docs/zh/02-mcpp-toml.md` 的 `cxx_runtime = "toolchain-coupled"`。 ## 项目级版本锁定 @@ -506,7 +506,7 @@ error: target 'riscv64-none-elf' cannot be emitted by 'gcc@16.1.0'. 要给这些结果分类的程序读 `mcpp why toolchain --format json` 的 `data.reason` (`convention-unreplaced` / `capability-pin`),而不是那句话 —— -见[第 11 章](11-machine-output.md)。 +见[第 11 章](50-machine-output.md)。 项目还可以声明自己的*默认*构建 target——"本项目发布全静态"这类语义 就该放在这里(全静态是产物属性,不是编译器家族属性): diff --git a/docs/zh/16-the-target-triple.md b/docs/zh/21-the-target-triple.md similarity index 98% rename from docs/zh/16-the-target-triple.md rename to docs/zh/21-the-target-triple.md index 4010024a..48e2d4f3 100644 --- a/docs/zh/16-the-target-triple.md +++ b/docs/zh/21-the-target-triple.md @@ -1,4 +1,4 @@ -# 目标三元组 +# 21 —— 目标三元组 目标三元组写作 `-` 或 `--`。本章说明每一段的含义、 第三段何时可以省略,以及为何这个答案在 mcpp 同时支持的两种体系下并不相同。 @@ -15,7 +15,7 @@ C 库。选中 `x86_64-linux-musl` 就是选中 musl-gcc 载荷,选中 **构建期体系。** 目标侧以包的形式出现在依赖图中,由正在运行的那个编译器 从源码构建。第三段不选中任何东西,因为图已经决定了。这是 -[第 15 章](15-openkal-cross.md)所描述的体系。 +[第 15 章](24-openkal-cross.md)所描述的体系。 两者的差别在于第三段**做什么**,而不在于它怎么拼。工程不声明自己属于哪一种; 由依赖图决定,构建则报告它解析出了什么。 @@ -323,7 +323,7 @@ default = "llvm@22.1.8" 拒绝里为什么点名 openkal。 **裸机行与 `x86_64-windows-musl` 行的工具链不是约定**,根本不可被推翻—— -见[第 03 章](03-toolchains.md)。 +见[第 03 章](20-toolchains.md)。 ### 而依赖图会整个替换这一轴 @@ -450,10 +450,10 @@ runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic", `sysroot = ""` 选定零 libc 档:编译行上没有 C 库,链接上也没有。 **缺席 `sysroot` 键是另一个答案** —— 它继承该目标行自己的默认值。 -见[第 13 章](13-baremetal.md)。 +见[第 13 章](30-baremetal.md)。 ## 参考 -[第 14 章](14-target-side.md)讲五层以及每一层由谁供给。 -[第 15 章](15-openkal-cross.md)完整讲构建期体系。 -[第 03 章](03-toolchains.md)讲工具链轴,它是分开的:目标不决定编译器。 +[第 14 章](22-target-side.md)讲五层以及每一层由谁供给。 +[第 15 章](24-openkal-cross.md)完整讲构建期体系。 +[第 03 章](20-toolchains.md)讲工具链轴,它是分开的:目标不决定编译器。 diff --git a/docs/zh/14-target-side.md b/docs/zh/22-target-side.md similarity index 97% rename from docs/zh/14-target-side.md rename to docs/zh/22-target-side.md index 2103cecd..2de9a985 100644 --- a/docs/zh/14-target-side.md +++ b/docs/zh/22-target-side.md @@ -1,4 +1,4 @@ -# 目标侧 +# 22 —— 目标侧 一次构建在发出任何命令行之前必须回答一个问题:目标的编译器运行时、平台接口、 C 库与 C++ 运行时从哪里来。mcpp 在依赖图解析完成之后解析该问题一次, @@ -290,7 +290,7 @@ mcpp 不认识的键 —— 打错的字,或来自更新版本 mcpp 的谓词 |---|---|---| | **裸 OS 别名** | 单个 OS / 族 —— 简洁且常用的形式 | `[target.windows]`、`[target.unix]` | | **`cfg(...)` 谓词** | 复合条件(arch / env / 组合子) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 [05 §2.7.3](05-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | +| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 [02 §2.7.3](02-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | 一个选择器可以承载平台条件的**依赖**与**构建 flag**: @@ -312,7 +312,7 @@ cxxflags = ["-march=x86-64-v2"] 单个 OS/族用裸形式,arch/env 条件与组合子用 `cfg(...)`。 - **可用键**:`dependencies` / `dev-dependencies` / `build-dependencies` / - `feature-deps.`(mcpp 2026.8.6.2+ —— 见 [07 —— build.mcpp](07-build-mcpp.md);feature 本身无条件注册, + `feature-deps.`(mcpp 2026.8.6.2+ —— 见 [05 —— build.mcpp](05-build-mcpp.md);feature 本身无条件注册, 只有它的依赖集合受限定),以及带 `cflags` / `cxxflags` / `ldflags` / `sources` 的 `build`(mcpp 0.0.95+ —— 条件源码 glob,例如把 `src/x86/**/*.asm` 收在 `cfg(arch = "x86_64")` 之后;`!` 排除 glob 在此同样有效),再加 `flags` 与 @@ -327,7 +327,7 @@ cxxflags = ["-march=x86-64-v2"] Linux 构建**根本不会下载** `[target.windows]` 依赖。 - **谓词的键**:`os`、`arch`、`family`、`env` —— 三元组的坐标 —— 以及自 mcpp 2026.9.1.1 起的五个目标侧层名 `compiler`、`compiler-runtime`、`kernel-abi`、 - `c-abi`、`c++-abi`(见[14 —— 目标侧](14-target-side.md))。`accelerator` 同样 + `c-abi`、`c++-abi`(见[22 —— 目标侧](22-target-side.md))。`accelerator` 同样 是这里的键,由本次构建自己的 `accel`(`--accel` 或 `[build] accel` 里的后端名) 回答,因此它是对一个集合的成员判定;`accelerator = "none"` 则是一段用来说 「本次构建没有命名任何后端」的写法,而不必枚举它不是的那些后端。裸词 @@ -391,6 +391,6 @@ sysroot = "" # no C library at all **这与「目标侧解析出的 C 库是哪一个」不是同一个问题。** `target_libc()` 命名的是 mcpp 装上的那个载荷,而这个值是目标侧解析的一项**输入** —— 依赖图里的包可以改为供给 C 库,那时解析出的 `c-abi` 就不是这里返回的东西。要按已解析的层分支,请用层谓词: -`[target.'cfg(c-abi = "musl")'.build]`(见[14 —— 目标侧](14-target-side.md))。 +`[target.'cfg(c-abi = "musl")'.build]`(见[22 —— 目标侧](22-target-side.md))。 这一段在 2026.9.1.1 之前写的是「解析到的是哪份 C 库」,那是两者里错的那一个。 -参见[13 —— 裸机与 freestanding 目标](13-baremetal.md)。 +参见[30 —— 裸机与 freestanding 目标](30-baremetal.md)。 diff --git a/docs/zh/17-the-project-environment.md b/docs/zh/23-the-project-environment.md similarity index 98% rename from docs/zh/17-the-project-environment.md rename to docs/zh/23-the-project-environment.md index 956b1b43..641298e1 100644 --- a/docs/zh/17-the-project-environment.md +++ b/docs/zh/23-the-project-environment.md @@ -1,4 +1,4 @@ -# 17 - 项目环境 +# 23 —— 项目环境 项目可以声明自己在哪个环境里构建。这一条声明决定项目链接哪个 C 库、以及它的 构建程序找到哪些工具——于是同一份 `mcpp.toml` 在开发机和 CI 上是同一个构建, @@ -89,7 +89,7 @@ SubOS 里。前置让被声明的环境成为默认答案;其余的仍在它后 工程与依赖命名**同一个包**时,只装它的一个版本:身份是 `(namespace, name)`,版本是这个 包上的约束。离产物更近的声明赢,并且覆盖会被报出来;不满足对方所陈述之要求的钉会被拒绝 -并点出两侧。见 [05 — mcpp.toml](05-mcpp-toml.md) 的「一个包一个版本」。 +并点出两侧。见 [02 — mcpp.toml](02-mcpp-toml.md) 的「一个包一个版本」。 ## 4. 只读取环境,从不创建环境 @@ -138,7 +138,7 @@ create/bootstrap that environment instead of falling back to active/default 见本章。不写档位就是从前的行为。 **runner。** `[xlings.workspace]` 下的程序也是 `[target.].runner` 查找其第一个元素 -的首选位置,在 `PATH` 之前([05 §2.7.3](05-mcpp-toml.md))。两个键合起来,在 CI 宿主上供给用户态模拟器, +的首选位置,在 `PATH` 之前([02 §2.7.3](02-mcpp-toml.md))。两个键合起来,在 CI 宿主上供给用户态模拟器, 并通过它执行交叉构建的产物,而清单不必写出载荷的路径。 ## 6. 什么该写在别处 @@ -423,8 +423,8 @@ error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins ## 8. 相关章节 -- [7 - build.mcpp](07-build-mcpp.md) —— 构建程序收到的契约,含它运行时的 +- [05 - build.mcpp](05-build-mcpp.md) —— 构建程序收到的契约,含它运行时的 `PATH`。 -- [8 - 工具链内部](08-toolchain-internals.md) —— 运行时选择、`RuntimeBinding` +- [91 - 工具链内部](91-toolchain-internals.md) —— 运行时选择、`RuntimeBinding` 快照与降级规则。 -- [5 - mcpp.toml](05-mcpp-toml.md) —— manifest 的其余部分。 +- [02 - mcpp.toml](02-mcpp-toml.md) —— manifest 的其余部分。 diff --git a/docs/zh/15-openkal-cross.md b/docs/zh/24-openkal-cross.md similarity index 98% rename from docs/zh/15-openkal-cross.md rename to docs/zh/24-openkal-cross.md index 71a603dc..8f4bbe74 100644 --- a/docs/zh/15-openkal-cross.md +++ b/docs/zh/24-openkal-cross.md @@ -1,4 +1,4 @@ -# 基于 openkal 的交叉构建 +# 24 —— 基于 openkal 的交叉构建 传统的交叉构建由载荷承担。一份工具链为一个目标而构建,它的驱动只有一个答案, 到达第二个目标意味着获取第二份工具链。因此一个发行方必须发布的载荷数, @@ -178,7 +178,7 @@ runner = ["qemu-aarch64-static"] 没有这个键时,`mcpp run` 报告内核的拒绝(`Exec format error`)与应当写的键,`mcpp test` 把每个测试报告为未运行并以 2 退出。能原生执行该产物的宿主传 `--no-runner`。规则见 -[5 —— mcpp.toml](05-mcpp-toml.md) §2.7.3。 +[02 —— mcpp.toml](02-mcpp-toml.md) §2.7.3。 ### 源码是同一份,程序不是 @@ -257,5 +257,5 @@ g++: error: unrecognized command-line option '-fuse-ld=…/ld.lld' ## 参考 -[docs/14 — 目标侧](14-target-side.md) 给出五个层、四种来源与规则。 +[docs/14 — 目标侧](22-target-side.md) 给出五个层、四种来源与规则。 [SPEC-002](../specs/target-side.md) 给出能力语法的规范性陈述。 diff --git a/docs/zh/13-baremetal.md b/docs/zh/30-baremetal.md similarity index 98% rename from docs/zh/13-baremetal.md rename to docs/zh/30-baremetal.md index 47166623..f807b213 100644 --- a/docs/zh/13-baremetal.md +++ b/docs/zh/30-baremetal.md @@ -1,11 +1,11 @@ -# 13 — 裸机与 freestanding 目标 +# 30 —— 裸机与 freestanding 目标 本文说明 mcpp 如何为没有操作系统的目标构建、运行与测试软件,以及板级支持包如何 提供引擎刻意不去掌握的那部分目标事实。 -相关文档:[05 — mcpp.toml 清单指南](05-mcpp-toml.md) §2.7.2 是本文使用的 -`[target.]` 各键的参考;[07 — build.mcpp 构建程序](07-build-mcpp.md) -是板级支持包所用指令协议的参考;[08 — 工具链内部机制](08-toolchain-internals.md) +相关文档:[02 — mcpp.toml 清单指南](02-mcpp-toml.md) §2.7.2 是本文使用的 +`[target.]` 各键的参考;[05 — build.mcpp 构建程序](05-build-mcpp.md) +是板级支持包所用指令协议的参考;[91 — 工具链内部机制](91-toolchain-internals.md) 描述了本文所偏离的宿主链接模型。 ## 概述 @@ -558,7 +558,7 @@ error: no runner is configured for 'riscv64-none-elf' — a freestanding artifac 这个键不限于裸机。hosted 交叉目标 —— x86_64 宿主上的 `aarch64-linux-musl` 产物 —— 使用同一个 `[target.].runner`,以 `qemu-aarch64-static` 这类用户态模拟器代替 系统模拟器;在这类目标上,缺少 runner 在内核拒绝产物之前不是错误。hosted 目标的规则、 -`--no-runner` 出口与 `mcpp test` 的未运行报告见 [5 —— mcpp.toml](05-mcpp-toml.md) +`--no-runner` 出口与 `mcpp test` 的未运行报告见 [02 —— mcpp.toml](02-mcpp-toml.md) §2.7.3。 ## 编写板级支持包 @@ -591,7 +591,7 @@ if (const char* dir = mcpp::xpkg_dir("xim", "qemu-riscv"); dir && *dir) { ``` 没有那一行,构建会成功、不配置 runner,而 `mcpp run` 报告缺少 runner 并建议写一个 -`runner` 键 —— 这句话一般情况下对,在这里不对。见 [07 —— build.mcpp](07-build-mcpp.md) +`runner` 键 —— 这句话一般情况下对,在这里不对。见 [05 —— build.mcpp](05-build-mcpp.md) 的 `mcpp:warning=`。 ### 板级支持包发出的指令 diff --git a/docs/zh/18-devices.md b/docs/zh/31-devices.md similarity index 96% rename from docs/zh/18-devices.md rename to docs/zh/31-devices.md index a4cff365..144fb4b2 100644 --- a/docs/zh/18-devices.md +++ b/docs/zh/31-devices.md @@ -1,10 +1,10 @@ -# 18 - 抵达一台设备 +# 31 —— 抵达一台设备 本文规定 mcpp 如何执行一个运行在构建机器之外的产物、包如何提供抵达它的其他方式, 以及工程如何在模拟器与真实硬件之间选择。 -相关文档:[13 - 裸机与 freestanding 目标](13-baremetal.md) · [07 - build.mcpp](07-build-mcpp.md) -· [11 - 机器输出](11-machine-output.md)。 +相关文档:[30 - 裸机与 freestanding 目标](30-baremetal.md) · [05 - build.mcpp](05-build-mcpp.md) +· [50 - 机器输出](50-machine-output.md)。 ## 一条命令,加具名的例外 diff --git a/docs/zh/20-heterogeneous-builds.md b/docs/zh/32-heterogeneous-builds.md similarity index 99% rename from docs/zh/20-heterogeneous-builds.md rename to docs/zh/32-heterogeneous-builds.md index 0073fa55..906108b9 100644 --- a/docs/zh/20-heterogeneous-builds.md +++ b/docs/zh/32-heterogeneous-builds.md @@ -1,4 +1,4 @@ -# 20 — 异构硬件构建 +# 32 —— 异构硬件构建 GPU 与 AI 加速器目标,以及宿主/设备混合编译:mcpp 如何构建设备代码,以及一个预建产物如何 声明它能在哪些设备上运行。 @@ -87,7 +87,7 @@ C++20 modules 的编译器。 上表是 mcpp **不需要被告知**就知道的那些:在「包可以自己声明」之前就已经支持的语言。 规则包通过 `[features]..device_extensions`(见 -[05 — mcpp.toml](05-mcpp-toml.md) §2.8)向它增补,而这正是**一门新设备语言到达的方式** +[02 — mcpp.toml](02-mcpp-toml.md) §2.8)向它增补,而这正是**一门新设备语言到达的方式** —— 不动引擎,也不需要发一版引擎。Slang 是第一个:`.slang` 不在上表里,由 `mcpp:plugins` 的 `rules-slang` 声明。 @@ -196,7 +196,7 @@ mcpp 2026.9.7.1 之前,那条 lane 是唯一的例外:生成的头文件**就是 调用设备编译器的那条命令不内置在 mcpp 里,而是由**构建规则包**提供 —— 以 `host-module = true` 消费,emit 输出汇入链接的构建边。机制见 -[07 — build.mcpp](07-build-mcpp.md),可用的 CUDA 规则见 `examples/09-heterogeneous/cuda`。 +[05 — build.mcpp](05-build-mcpp.md),可用的 CUDA 规则见 `examples/09-heterogeneous/cuda`。 这个划分是刻意的。mcpp 拥有构建图、产物身份与架构集合;厂商的 flag 拼法、 架构语法与宿主编译器要求属于规则包。 @@ -239,7 +239,7 @@ plugins = { version = "0.3.0", features = ["rules-cuda"], host-module = true } ``` 更近的声明赢,装一个版本,并且 mcpp 说出用了哪条。不满足规则下界的钉会被拒绝并点出 -两侧,而不是与它并排装下来。完整规则见 [05 — mcpp.toml](05-mcpp-toml.md) 的 +两侧,而不是与它并排装下来。完整规则见 [02 — mcpp.toml](02-mcpp-toml.md) 的 「一个包一个版本」;`examples/09-heterogeneous/multi-backend` 是本仓库里唯一走覆盖 路径的示例,其余每一个都只写那条边。 @@ -267,7 +267,7 @@ mcpp 里长出第二份拷贝)。 干净、链接干净,到第一次分配才以 *"CUDA driver version is insufficient for CUDA runtime version"* 失败。规则经驱动自己的库(经由 sentinel 包够到,绝不经 `/usr/lib`)读出驱动 版本并陈述为事实;陈述它的运行时需要的下界;引擎在编译任何东西之前比较两者 —— 见 -[07 — build.mcpp](07-build-mcpp.md) 的探针通道。引擎读到的是一个名字、一个关系、一个 +[05 — build.mcpp](05-build-mcpp.md) 的探针通道。引擎读到的是一个名字、一个关系、一个 版本;`cuda.driver` 是流过引擎的数据。 凡是错答比不答更贵的地方都只报告不强制:工程里没有规则包的机器没有任何厂商相关的话 diff --git a/docs/zh/23-authoring-a-rule-package.md b/docs/zh/40-authoring-a-rule-package.md similarity index 96% rename from docs/zh/23-authoring-a-rule-package.md rename to docs/zh/40-authoring-a-rule-package.md index cb58b680..c4cdcf2d 100644 --- a/docs/zh/23-authoring-a-rule-package.md +++ b/docs/zh/40-authoring-a-rule-package.md @@ -1,11 +1,11 @@ -# 23 —— 编写规则包 +# 40 —— 编写规则包 规则包教会 mcpp 构建引擎本身没有规则的东西:一种设备语言、一份着色器、一个生成的 接口、一项检查。它是一个普通的包,它的模块被消费者的构建程序 import。 -相关文档:[07 —— build.mcpp](07-build-mcpp.md) 是本章所用指令协议与类型化 API 的 -参考;[22 —— Feature 与能力](22-features-and-capabilities.md) 覆盖 feature 相关键; -[20 —— 异构硬件构建](20-heterogeneous-builds.md) 列出已发布的规则。两个示例: +相关文档:[05 —— build.mcpp](05-build-mcpp.md) 是本章所用指令协议与类型化 API 的 +参考;[04 —— Feature 与能力](04-features-and-capabilities.md) 覆盖 feature 相关键; +[32 —— 异构硬件构建](32-heterogeneous-builds.md) 列出已发布的规则。两个示例: [`08-build-rules`](../../examples/08-build-rules/) 是做检查与嵌入的规则, [`12-a-new-device-language`](../../examples/12-a-new-device-language/) 是新增一门 语言的规则。 @@ -152,7 +152,7 @@ device link 在引擎侧的全部内容:N 个 `artifact` action,其输出不进 都不开。 裸版本是项目可以覆盖的**选择**;`>=` 是项目不得低于的**要求**。见 -[05 —— mcpp.toml](05-mcpp-toml.md) 的*一个包,一个版本*。 +[02 —— mcpp.toml](02-mcpp-toml.md) 的*一个包,一个版本*。 ## 生成岛的边界 @@ -225,4 +225,4 @@ mcpp::floor("cuda.driver", runtime_needs); - `mcpp emit xpkg` 把 `manifest = "mcpp.toml"` 写进 `mcpp` 段,而 `mcpp xpkg parse` 把该键报为未知并以 1 退出。`mcpp-index` 里没有任何描述符使用 它(218 个里 0 个);自带 `mcpp.toml` 的包整个省略 `mcpp` 字段。见 - [21 —— 按场景选命令](21-commands-by-scenario.md)的*当前边界*。 + [06 —— 按场景选命令](06-commands-by-scenario.md)的*当前边界*。 diff --git a/docs/zh/11-machine-output.md b/docs/zh/50-machine-output.md similarity index 99% rename from docs/zh/11-machine-output.md rename to docs/zh/50-machine-output.md index 45b6a4ba..e2a8b5a0 100644 --- a/docs/zh/11-machine-output.md +++ b/docs/zh/50-machine-output.md @@ -1,4 +1,4 @@ -# 11 — 机器可读输出 +# 50 —— 机器可读输出 mcpp 面向两类读者。本章是对第二类 —— **程序** —— 的契约。编辑器扩展、CI 脚本,以及任何解析 mcpp 输出的程序, 可依赖的部分在此列出。 @@ -310,7 +310,7 @@ mcpp why toolchain [--target ] [--toolchain ] --format json 从 `gnu` 变为 `glibc`,Windows 上从 `gnu` 变为 `ucrt`。字段的**含义**没变(它仍然是 「哪个实现」),所以 §7 仍然成立;变的是它不再报三元组的 env 段 —— 那是一次请求而 不是一个实现,也不是任何一个 C 库的名字。现在的取值就是 -[14 —— 目标侧](14-target-side.md)一直列着的那些,并且包可以在 +[22 —— 目标侧](22-target-side.md)一直列着的那些,并且包可以在 `cfg(c-abi = …)` 谓词里与它们比较。按字面量 `gnu` 取值的客户端需要更新; `musl`、`picolibc`、`libSystem` 不受影响。 diff --git a/docs/zh/19-supported-versions.md b/docs/zh/51-supported-versions.md similarity index 97% rename from docs/zh/19-supported-versions.md rename to docs/zh/51-supported-versions.md index 0556fccb..8336b598 100644 --- a/docs/zh/19-supported-versions.md +++ b/docs/zh/51-supported-versions.md @@ -1,4 +1,4 @@ -# 19 - 受支持的版本与兼容性 +# 51 —— 受支持的版本与兼容性 本文陈述哪些发布受支持、支持多久、以及版本之间什么可以变化。它之所以存在,是因为 一个采用 mcpp 的工程会被它自己的评审流程问到这些问题,而只活在维护者脑子里的答案 @@ -29,7 +29,7 @@ |---|---| | `mcpp.toml` 的键 | 只增。既有键保持其含义;无法识别的键会被报告,绝不静默忽略 | | CLI 命令与旗标 | 只增。被取代的拼写保留为别名 | -| 机器输出(`--message-format json`) | 由 `schemaVersion` 版本化,见 [11](11-machine-output.md) | +| 机器输出(`--message-format json`) | 由 `schemaVersion` 版本化,见 [50](50-machine-output.md) | | `build.mcpp` 指令协议 | 版本化,见 `kProtocolVersion`。引擎遇到声明**更高**版本的程序会拒绝,而不是猜测 | | `mcpp.lock` 格式 | 由 `schemaVersion` 版本化;更旧的文件在读取时迁移 | | 目标表的行 | 只增。一行的档位可以上升;只要还有已发布的包以它为目标,该行不会被移除 | diff --git a/docs/zh/04-build-from-source.md b/docs/zh/90-build-from-source.md similarity index 98% rename from docs/zh/04-build-from-source.md rename to docs/zh/90-build-from-source.md index 8ab0125f..c2823a78 100644 --- a/docs/zh/04-build-from-source.md +++ b/docs/zh/90-build-from-source.md @@ -1,4 +1,4 @@ -# 04 — 从源码构建 & 参与贡献 +# 90 —— 从源码构建与参与贡献 > mcpp 采用自托管模式 —— 通过 mcpp 自身从源码构建 mcpp。 > 任何已具备可运行 mcpp 二进制的环境均可完成源码构建。 @@ -24,7 +24,7 @@ mcpp test # 构建并运行 tests/**/*.cpp 中发现的 C++ 测试 `mcpp test` 不执行 `tests/e2e/` 下的 shell 端到端套件;应单独让它使用刚构建的二进制。 首次构建会自动拉取默认工具链,详见 -[03 — 工具链管理](03-toolchains.md)。 +[20 — 工具链管理](20-toolchains.md)。 如需生成与 release 一致的全静态二进制(对应 `release.yml` 走的路径): diff --git a/docs/zh/08-toolchain-internals.md b/docs/zh/91-toolchain-internals.md similarity index 99% rename from docs/zh/08-toolchain-internals.md rename to docs/zh/91-toolchain-internals.md index e42a56d8..e0b38b91 100644 --- a/docs/zh/08-toolchain-internals.md +++ b/docs/zh/91-toolchain-internals.md @@ -1,7 +1,7 @@ -# 08 — 工具链机制内幕 +# 91 —— 工具链机制内幕 > 本文详细描述 mcpp 工具链机制的内部工作原理,以及如何扩充新工具链、新架构乃至 -> 嵌入式目标的支持。与面向用户的 [03 — 工具链管理](03-toolchains.md)(CLI 用法) +> 嵌入式目标的支持。与面向用户的 [20 — 工具链管理](20-toolchains.md)(CLI 用法) > 互补,本文面向贡献者与维护者。 ## 1. 一张图看全模型 @@ -432,7 +432,7 @@ loader;`/bin/sh` 有事:它的 `PT_INTERP` 指向**宿主** loader,而且任何 ### 7.3 嵌入式与裸机工具链 `riscv64-none-elf` 与 `riscv32-none-elf` 已实现,面向用户的说明见 -[13 — 裸机与 freestanding 目标](13-baremetal.md)。本节记录由此得到的形态与 +[30 — 裸机与 freestanding 目标](30-baremetal.md)。本节记录由此得到的形态与 上文 hosted 模型之间的关系。 本节早先的三条预测成立: diff --git a/docs/zh/09-release.md b/docs/zh/92-release.md similarity index 99% rename from docs/zh/09-release.md rename to docs/zh/92-release.md index 947f5976..8f85fce3 100644 --- a/docs/zh/09-release.md +++ b/docs/zh/92-release.md @@ -1,7 +1,7 @@ -# 09 — 发布 mcpp +# 92 —— 发布 mcpp **mcpp 自身**的发布如何到达用户手上。本章面向维护者;打包**自有项目**见 -[02 — 打包发布](02-pack-and-release.md)。 +[10 — 打包发布](10-pack-and-release.md)。 在此之前这套流程只活在 commit message 和 workflow 注释里,其中一条 commit message 里的诊断是错的,已在 §5 更正。 diff --git a/docs/zh/README.md b/docs/zh/README.md index a3a4272e..e3491e70 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -22,14 +22,14 @@ | 目标 | 阅读 | 运行 | |---|---|---| -| 写一个程序 | [00](00-getting-started.md)、[05](05-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | -| 写一个供他人 import 的库 | [10](10-publishing-a-library.md)、[22](22-features-and-capabilities.md)、[05](05-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | -| 发布它 | [02](02-pack-and-release.md)、[10](10-publishing-a-library.md)、[12](12-binary-distribution.md) | [`03-pack-static`](../../examples/03-pack-static/)、[`05-lib-distribution`](../../examples/05-lib-distribution/) | -| 为另一台机器构建 | [16](16-the-target-triple.md)、[15](15-openkal-cross.md)、[13](13-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | -| 使用 GPU 或加速器 | [20](20-heterogeneous-builds.md)、[18](18-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | -| 增加一条规则、一种语言或一个生成器 | [23](23-authoring-a-rule-package.md)、[07](07-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`12-a-new-device-language`](../../examples/12-a-new-device-language/) | -| 向索引添加一个包 | [10](10-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [21](21-commands-by-scenario.md) —— 发布相关场景 | -| 修改 mcpp 本身 | [04](04-build-from-source.md)、[09](09-release.md)、[19](19-supported-versions.md) | — | +| 写一个程序 | [00](00-getting-started.md)、[02](02-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | +| 写一个供他人 import 的库 | [11](11-publishing-a-library.md)、[04](04-features-and-capabilities.md)、[02](02-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | +| 发布它 | [10](10-pack-and-release.md)、[11](11-publishing-a-library.md)、[12](12-binary-distribution.md) | [`03-pack-static`](../../examples/03-pack-static/)、[`05-lib-distribution`](../../examples/05-lib-distribution/) | +| 为另一台机器构建 | [21](21-the-target-triple.md)、[24](24-openkal-cross.md)、[30](30-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | +| 使用 GPU 或加速器 | [32](32-heterogeneous-builds.md)、[31](31-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | +| 增加一条规则、一种语言或一个生成器 | [40](40-authoring-a-rule-package.md)、[05](05-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`12-a-new-device-language`](../../examples/12-a-new-device-language/) | +| 向索引添加一个包 | [11](11-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [06](06-commands-by-scenario.md) —— 发布相关场景 | +| 修改 mcpp 本身 | [90](90-build-from-source.md)、[92](92-release.md)、[51](51-supported-versions.md) | — | 课程也可以以**项目模板**的形式到达:模板由包提供,`mcpp new --template` 实例化 它。今天有文档的两个是 `riscv-virt-rt`(裸机)与 `ocornut.imgui`(图形应用), @@ -37,50 +37,54 @@ ## 章节 -### 第一部分 —— 使用 mcpp +编号说明这一章属于哪一部分:`0x` 使用 mcpp,`1x` 发布构建产物,`2x` 工具链与目标, +`3x` 裸机与设备,`4x` 扩展 mcpp,`5x` 面向机器的契约,`9x` mcpp 自身。同一部分内部 +的排列是阅读顺序。 -- [00 - 快速开始](00-getting-started.md) -- [01 - 示例项目](01-examples.md) -- [05 - mcpp.toml 工程文件指南](05-mcpp-toml.md) -- [06 - 工作空间](06-workspace.md) -- [07 - build.mcpp 构建程序](07-build-mcpp.md) -- [21 - 按场景选命令](21-commands-by-scenario.md) -- [22 - Feature 与能力](22-features-and-capabilities.md) +### 0x —— 使用 mcpp -### 第二部分 —— 发布构建产物 +- [00 —— 快速开始](00-getting-started.md) +- [01 —— 示例项目](01-examples.md) +- [02 —— mcpp.toml 工程文件指南](02-mcpp-toml.md) +- [03 —— 工作空间](03-workspace.md) +- [04 —— Feature 与能力](04-features-and-capabilities.md) +- [05 —— 构建程序:`build.mcpp`](05-build-mcpp.md) +- [06 —— 按场景选命令](06-commands-by-scenario.md) -- [02 - 发布打包](02-pack-and-release.md) -- [10 - 发布一个库到 mcpp-index](10-publishing-a-library.md) -- [12 - 分发预编译库](12-binary-distribution.md) +### 1x —— 发布构建产物 -### 第三部分 —— 工具链与目标 +- [10 —— 发布打包](10-pack-and-release.md) +- [11 —— 发布一个库到 mcpp-index](11-publishing-a-library.md) +- [12 —— 分发预编译库](12-binary-distribution.md) -- [03 - 工具链管理](03-toolchains.md) -- [13 - 裸机与 freestanding 目标](13-baremetal.md) -- [14 - 目标侧](14-target-side.md) -- [15 - 基于 openkal 的交叉构建](15-openkal-cross.md) -- [16 - 目标三元组](16-the-target-triple.md) -- [17 - 项目环境](17-the-project-environment.md) +### 2x —— 工具链与目标 -### 第四部分 —— 设备与加速器 +- [20 —— 工具链管理](20-toolchains.md) +- [21 —— 目标三元组](21-the-target-triple.md) +- [22 —— 目标侧](22-target-side.md) +- [23 —— 项目环境](23-the-project-environment.md) +- [24 —— 基于 openkal 的交叉构建](24-openkal-cross.md) -- [18 - 抵达一台设备](18-devices.md) -- [20 - 异构硬件构建](20-heterogeneous-builds.md) +### 3x —— 裸机、设备与加速器 -### 第五部分 —— 从外部扩展 mcpp +- [30 —— 裸机与 freestanding 目标](30-baremetal.md) +- [31 —— 抵达一台设备](31-devices.md) +- [32 —— 异构硬件构建](32-heterogeneous-builds.md) -- [23 - 编写规则包](23-authoring-a-rule-package.md) +### 4x —— 从外部扩展 mcpp -### 第六部分 —— 机器接口与兼容性 +- [40 —— 编写规则包](40-authoring-a-rule-package.md) -- [11 - 机器可读输出](11-machine-output.md) -- [19 - 受支持的版本与兼容性](19-supported-versions.md) +### 5x —— 机器接口与兼容性 -### 第七部分 —— 为 mcpp 本身做贡献 +- [50 —— 机器可读输出](50-machine-output.md) +- [51 —— 受支持的版本与兼容性](51-supported-versions.md) -- [04 - 从源码构建 & 参与贡献](04-build-from-source.md) -- [08 - 工具链机制内幕](08-toolchain-internals.md) -- [09 - 发布 mcpp](09-release.md) +### 9x —— 为 mcpp 本身做贡献 + +- [90 —— 从源码构建与参与贡献](90-build-from-source.md) +- [91 —— 工具链机制内幕](91-toolchain-internals.md) +- [92 —— 发布 mcpp](92-release.md) ## 规范文档 diff --git a/examples/06-openkal-cross/README.md b/examples/06-openkal-cross/README.md index fbf1616f..dc433e0f 100644 --- a/examples/06-openkal-cross/README.md +++ b/examples/06-openkal-cross/README.md @@ -150,10 +150,10 @@ There is no platform interface beneath it to depend on; what such a program builds on is `mcpplibs/openarch`, the architecture-mechanism layer. Both routes are described in -[docs/15 — Cross-Compilation Over openkal](../../docs/15-openkal-cross.md). +[docs/15 — Cross-Compilation Over openkal](../../docs/24-openkal-cross.md). ## Reference -[docs/15 — Cross-Compilation Over openkal](../../docs/15-openkal-cross.md) for -the model, and [docs/14 — The Target Side](../../docs/14-target-side.md) for the +[docs/15 — Cross-Compilation Over openkal](../../docs/24-openkal-cross.md) for +the model, and [docs/14 — The Target Side](../../docs/22-target-side.md) for the five layers and the rules that govern them. diff --git a/examples/09-heterogeneous/README.md b/examples/09-heterogeneous/README.md index 3b6e0896..713982e6 100644 --- a/examples/09-heterogeneous/README.md +++ b/examples/09-heterogeneous/README.md @@ -105,7 +105,7 @@ plugins = { version = "0.4.0", features = ["rules-cuda", "tools-island"], host-m `multi-backend/` is the one example here that also pins a version, and it does so to demonstrate the override: the rule owns "which package, and no older than what", the project owns "and exactly this one". One version is installed either -way — see *One package, one version* in `docs/05-mcpp-toml.md`. +way — see *One package, one version* in `docs/02-mcpp-toml.md`. ## The layers underneath @@ -152,6 +152,6 @@ The examples show the shape mcpp implements: device code in separate translation units, compiled by a separate compiler, linked into an ordinary program. Models that compile a whole target with an offloading compiler — OpenMP `target`, C++ parallel algorithms with a device backend — have no -separable island and are not reached this way. `docs/20-heterogeneous-builds.md` +separable island and are not reached this way. `docs/32-heterogeneous-builds.md` states the distinction and why it is a property of the model rather than a gap in the tool. diff --git a/examples/09-heterogeneous/cuda/app/mcpp.toml b/examples/09-heterogeneous/cuda/app/mcpp.toml index 7823492b..5a2680c9 100644 --- a/examples/09-heterogeneous/cuda/app/mcpp.toml +++ b/examples/09-heterogeneous/cuda/app/mcpp.toml @@ -38,7 +38,7 @@ cuda-driver = "2026.09.05" # `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.7.1 and mcpp:plugins 0.3.0. +# docs/02-mcpp-toml.md. Needs mcpp 2026.9.7.1 and mcpp:plugins 0.3.0. # # 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, diff --git a/mcpp.toml b/mcpp.toml index 272c8f67..cd52df87 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -13,7 +13,7 @@ repo = "https://github.com/mcpp-community/mcpp" # binary would be -O0. A `--profile`/`--dev`/`--release` flag still overrides. default-profile = "release" # Split the module edge: importers start when the BMI is published rather than -# when the compiler exits. See docs/05-mcpp-toml.md. +# when the compiler exits. See docs/02-mcpp-toml.md. # # ON HERE, `auto` (= off) EVERYWHERE ELSE. The key stays opt-in until it has been # through CI on every platform, and nothing was exercising it, so it could never diff --git a/modules/manifest/src/glob.cppm b/modules/manifest/src/glob.cppm index ca50bae0..036c396a 100644 --- a/modules/manifest/src/glob.cppm +++ b/modules/manifest/src/glob.cppm @@ -86,7 +86,7 @@ std::optional try_narrow(const std::filesystem::path& p) { // The stored spelling is GENERIC (`/`), because that is what try_narrow // produces and there is no second narrowing here to disagree with it. On // Windows the reported path therefore reads `C:/pkg/test/www`, not -// `C:\pkg\test\www`; docs/05-mcpp-toml.md shows it that way too. +// `C:\pkg\test\www`; docs/02-mcpp-toml.md shows it that way too. void note_unnarrowable_path(const std::filesystem::path& p); // Take and clear this run's records. @@ -96,7 +96,7 @@ void note_unnarrowable_path(const std::filesystem::path& p); // CLI reports. Drained in exactly one place (`cli::run`'s scope guard), which // is what keeps "recorded but never shown" from becoming the next silent // failure. The rule is written up in .agents/skills/mcpp-contributing/SKILL.md -// ("路径窄化不变式") and the user-facing behaviour in docs/05-mcpp-toml.md. +// ("路径窄化不变式") and the user-facing behaviour in docs/02-mcpp-toml.md. std::vector take_unnarrowable_paths(); // Does `candidate` match `glob`, interpreted relative to `root`? diff --git a/modules/manifest/src/toml.cppm b/modules/manifest/src/toml.cppm index 3085dae4..9e66574d 100644 --- a/modules/manifest/src/toml.cppm +++ b/modules/manifest/src/toml.cppm @@ -2207,7 +2207,7 @@ std::expected parse_string(std::string_view content, // [hooks] — project build lifecycle commands (#496). Parsed HERE rather // than by the module that runs them, for the reason Appendix A of - // docs/05-mcpp-toml.md states: mcpp.toml has one grammar and one parser. + // docs/02-mcpp-toml.md states: mcpp.toml has one grammar and one parser. // A second reader of the same file would report ITS syntax errors in its // own vocabulary — a typo in [package] arriving as "invalid hook // configuration" — and would sit outside the warning/--strict policy every @@ -2376,7 +2376,7 @@ std::expected parse_string(std::string_view content, m.lib.path = *v; } - // [pack] — `mcpp pack` configuration. See docs/02-pack-and-release.md. + // [pack] — `mcpp pack` configuration. See docs/10-pack-and-release.md. if (auto v = doc->get_string("pack.default_mode")) { const auto& s = *v; if (s != "static" && s != "bundle-project" && s != "bundle-all") { diff --git a/modules/manifest/src/types.cppm b/modules/manifest/src/types.cppm index 5f570e09..116d6170 100644 --- a/modules/manifest/src/types.cppm +++ b/modules/manifest/src/types.cppm @@ -154,7 +154,7 @@ struct Target { // Aliases at the top of this file keep `mcpp::manifest::DependencySpec` // resolvable for unchanged call sites. -// `[toolchain]` section per docs/03-toolchains.md +// `[toolchain]` section per docs/20-toolchains.md // linux = "gcc@15.1.0" // macos = "llvm@20" // windows = "msvc@system" @@ -1149,7 +1149,7 @@ struct LibConfig { std::filesystem::path path; // explicit override; empty = use convention }; -// `[pack]` — `mcpp pack` configuration. See docs/02-pack-and-release.md. +// `[pack]` — `mcpp pack` configuration. See docs/10-pack-and-release.md. // // `default_mode` picks the bundling strategy when the user runs bare // `mcpp pack` (no `--mode` flag): @@ -1263,7 +1263,7 @@ struct WorkspaceConfig { // `[hooks]` — project build lifecycle commands (#496). // // The commands are host-shell strings written by the project author, run by -// `mcpp build` around the build it performs. See docs/05-mcpp-toml.md §2.16. +// `mcpp build` around the build it performs. See docs/02-mcpp-toml.md §2.16. // // ONLY THE ROOT PROJECT'S HOOKS ARE EVER RUN. Every manifest mcpp parses // carries this field, including a DEPENDENCY's — and `mcpp build` reaches the @@ -1577,7 +1577,7 @@ struct Manifest { // [target.] tables — empty if user didn't declare any. std::map targetOverrides; - // [pack] — `mcpp pack` config (see docs/02-pack-and-release.md). + // [pack] — `mcpp pack` config (see docs/10-pack-and-release.md). PackConfig packConfig; // [lib] — library root interface convention (M5.x+). diff --git a/modules/toolchain-model/src/fingerprint.cppm b/modules/toolchain-model/src/fingerprint.cppm index 61dbf437..2403f861 100644 --- a/modules/toolchain-model/src/fingerprint.cppm +++ b/modules/toolchain-model/src/fingerprint.cppm @@ -1,6 +1,6 @@ // mcpp.toolchain.fingerprint — 11-field fingerprint that gates BMI cache safety. // -// Per docs/08-toolchain-internals.md, the fingerprint MUST cover: +// Per docs/91-toolchain-internals.md, the fingerprint MUST cover: // 1. compiler id 2. compiler version // 3. compiler driver identity 4. target triple // 5. stdlib id+version 6. C++ standard diff --git a/modules/toolchain-model/src/linkmodel.cppm b/modules/toolchain-model/src/linkmodel.cppm index f625c9d1..44eec313 100644 --- a/modules/toolchain-model/src/linkmodel.cppm +++ b/modules/toolchain-model/src/linkmodel.cppm @@ -223,7 +223,7 @@ struct ClangDriverModel { // A third, declared-metadata source (a persisted `.xpkg-exports.json` // written by the installer) was evaluated and removed: its only consumer // would have been this resolver, while the two sources above already cover -// every real payload — see docs/08-toolchain-internals.md for the record. +// every real payload — see docs/91-toolchain-internals.md for the record. // // Returns the loader's absolute path, or empty when none was found (callers // then omit --dynamic-linker and the hermeticity check reports the gap). diff --git a/modules/toolchain-model/src/triple.cppm b/modules/toolchain-model/src/triple.cppm index cf098f54..8b01aa08 100644 --- a/modules/toolchain-model/src/triple.cppm +++ b/modules/toolchain-model/src/triple.cppm @@ -571,7 +571,7 @@ inline Triple host_triple() { // ── Version pins (single site; §4.6 of the design doc) ─────────────────────── // Every default/convention toolchain version literal lives here. Help and // error strings format these — never inline a pinned version elsewhere. -// Changing a pin: update this block, then sync docs/03-toolchains.md and the +// Changing a pin: update this block, then sync docs/20-toolchains.md and the // README platform table (drawn from kKnownTargets above). namespace pins { // First-run auto-install defaults (prepare.cppm), per host platform/arch. diff --git a/modules/versioning/src/version.cppm b/modules/versioning/src/version.cppm index 9558c133..8444cd22 100644 --- a/modules/versioning/src/version.cppm +++ b/modules/versioning/src/version.cppm @@ -23,7 +23,7 @@ // SINGLE SOURCE OF TRUTH. `.github/tools/check_version_pins.sh` reads the // literal below and cross-checks it against `mcpp.toml`'s `[package].version`; // tests/e2e/01_help_and_version.sh checks it against `mcpp --version` at -// runtime. Both must be updated together — see docs/09-release.md. +// runtime. Both must be updated together — see docs/92-release.md. export module mcpp.version; diff --git a/src/build/ninja_backend.cppm b/src/build/ninja_backend.cppm index 0702743c..15c57ae9 100644 --- a/src/build/ninja_backend.cppm +++ b/src/build/ninja_backend.cppm @@ -1656,7 +1656,7 @@ std::string emit_ninja_string(const BuildPlan& plan) { // same result — not a race, never run. // // PER PACKAGE, not per build. `include_dir` colours only the declaring - // package's own TUs (docs/07-build-mcpp.md), so a generated header is + // package's own TUs (docs/05-build-mcpp.md), so a generated header is // visible to exactly one package and a build-wide phony would encode a // dependency that does not exist. It would also land on the critical path // of a build whose wall clock IS its critical path. diff --git a/src/build/prepare.cppm b/src/build/prepare.cppm index 2524d422..acb45128 100644 --- a/src/build/prepare.cppm +++ b/src/build/prepare.cppm @@ -1004,7 +1004,7 @@ export struct BuildOverrides { // Is this git remote reachable without a network round-trip? // -// `--offline` means "never touch the network" (docs/05-mcpp-toml.md), and its +// `--offline` means "never touch the network" (docs/02-mcpp-toml.md), and its // standing promise is that anything already on disk still builds. A remote that // names a local directory — or a file:// URL — is served by plain filesystem // reads, so refusing it would break that promise without buying any isolation. diff --git a/src/cli.cppm b/src/cli.cppm index 3cebb232..a3e4db36 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -525,7 +525,7 @@ int run(int argc, char** argv) { .option(cl::Option("toolchain").takes_value() .help("Ask about this toolchain, e.g. llvm@22.1.8")) .option(cl::Option("format").takes_value().value_name("json") - .help("Machine-readable output (enveloped; see docs/11-machine-output.md)")) + .help("Machine-readable output (enveloped; see docs/50-machine-output.md)")) .action(wrap_rc(cmd_why))) .subcommand(cl::App("resolve") .description("Re-resolve the build plan and explain it") @@ -630,7 +630,7 @@ int run(int argc, char** argv) { .option(cl::Option("json") .help("Emit machine-readable JSON (legacy payload, kept for ever)")) .option(cl::Option("format").takes_value().value_name("json") - .help("Machine-readable output (enveloped; see docs/11-machine-output.md)")) + .help("Machine-readable output (enveloped; see docs/50-machine-output.md)")) .option(cl::Option("allow-unknown") .help("Downgrade unknown mcpp-segment keys from error to warning")) .option(cl::Option("all-os") @@ -650,7 +650,7 @@ int run(int argc, char** argv) { .description("Install / list / select / remove C++ toolchains") .subcommand(cl::App("list").description("List installed toolchains") .option(cl::Option("format").takes_value().value_name("json") - .help("Machine-readable output (enveloped; see docs/11-machine-output.md)"))) + .help("Machine-readable output (enveloped; see docs/50-machine-output.md)"))) .subcommand(cl::App("install") .description("Install a toolchain via mcpp's xlings") // Both `mcpp toolchain install gcc 16.1.0` and `mcpp toolchain @@ -686,7 +686,7 @@ int run(int argc, char** argv) { .option(cl::Option("json") .help("Emit machine-readable JSON (legacy payload, kept for ever)")) .option(cl::Option("format").takes_value().value_name("json") - .help("Machine-readable output (enveloped; see docs/11-machine-output.md)"))) + .help("Machine-readable output (enveloped; see docs/50-machine-output.md)"))) .subcommand(cl::App("info") .description("Show details (incl. key inputs) for a cached package") .arg(cl::Arg("pkg").help("@").required())) @@ -777,7 +777,7 @@ int run(int argc, char** argv) { .subcommand(cl::App("env") .description("Print mcpp paths and configuration") .option(cl::Option("format").takes_value().value_name("json") - .help("Machine-readable output (enveloped; see docs/11-machine-output.md)"))) + .help("Machine-readable output (enveloped; see docs/50-machine-output.md)"))) .subcommand(cl::App("config") .description("Show or modify mcpp's private xlings configuration") .option(cl::Option("mirror").takes_value().value_name("CN|GLOBAL") diff --git a/src/hooks.cppm b/src/hooks.cppm index dc1314e2..346f785e 100644 --- a/src/hooks.cppm +++ b/src/hooks.cppm @@ -3,7 +3,7 @@ // EXPERIMENTAL. A hook cannot currently change whether a build succeeded: // every failure is a warning, and `side_effect = true` is refused by the // manifest parser. Only the ROOT project's hooks are ever run — a dependency's -// `[hooks]` is inert. See docs/05-mcpp-toml.md §2.16. +// `[hooks]` is inert. See docs/02-mcpp-toml.md §2.16. // // The CONFIGURATION is not parsed here: `[hooks]` is a section of mcpp.toml // and mcpp.toml has one parser (mcpp.manifest). What lives here is the part diff --git a/src/pack/pack.cppm b/src/pack/pack.cppm index 4f81c19f..413800c5 100644 --- a/src/pack/pack.cppm +++ b/src/pack/pack.cppm @@ -15,7 +15,7 @@ // mcpp.pack.binfmt reads the import table instead, so a Linux box packaging a // Windows build is simply what happens when nothing has to be executed. // -// See docs/02-pack-and-release.md for the full design. Three modes: +// See docs/10-pack-and-release.md for the full design. Three modes: // Static full musl static, no PT_INTERP / RUNPATH // BundleProject bundle only the project's third-party .so (default) // BundleAll bundle every dynamic dep incl. libc / libstdc++ / ld diff --git a/src/pm/publisher.cppm b/src/pm/publisher.cppm index 303d4fa9..79cb671d 100644 --- a/src/pm/publisher.cppm +++ b/src/pm/publisher.cppm @@ -1,6 +1,6 @@ // mcpp.pm.publisher — generate xpkg Lua entry from mcpp.toml + scanner. // -// See docs/10-publishing-a-library.md for the produced layout. +// See docs/11-publishing-a-library.md for the produced layout. module; diff --git a/src/publish/pipeline.cppm b/src/publish/pipeline.cppm index b30d48d2..d1f7ca46 100644 --- a/src/publish/pipeline.cppm +++ b/src/publish/pipeline.cppm @@ -180,7 +180,7 @@ export int publish_package(bool dry_run, bool allow_dirty) { // `mcpp publish --auto` to: // - gh release create v // - fork mcpp-index, add pkg lua, gh pr create - // See docs/10-publishing-a-library.md. + // See docs/11-publishing-a-library.md. std::println("Tip: future versions of mcpp may automate steps 2-3 via the gh CLI."); return 0; } diff --git a/src/toolchain/stdmod.cppm b/src/toolchain/stdmod.cppm index ff2224e1..a8b26377 100644 --- a/src/toolchain/stdmod.cppm +++ b/src/toolchain/stdmod.cppm @@ -3,7 +3,7 @@ module; // mcpp.toolchain.stdmod — pre-build the `import std` BMI and cache it. // -// GCC 15 flow (see docs/08-toolchain-internals.md): +// GCC 15 flow (see docs/91-toolchain-internals.md): // g++ -std=c++23 -fmodules -Og -c -o std.o // ⇒ produces gcm.cache/std.gcm + std.o // diff --git a/tests/e2e/150_clang_module_operator_template.sh b/tests/e2e/150_clang_module_operator_template.sh index b1a1368b..2b6cd53b 100755 --- a/tests/e2e/150_clang_module_operator_template.sh +++ b/tests/e2e/150_clang_module_operator_template.sh @@ -117,7 +117,7 @@ if [[ "$ACTUAL" != "$EXPECTED" ]]; then if [[ "$ACTUAL" == "ok" ]]; then echo " The importer now compiles — the Clang regression appears FIXED." echo " Update the expectation table here and the hazard section in" - echo " docs/03-toolchains.md, and tell mcpp-community/mcpp#256." + echo " docs/20-toolchains.md, and tell mcpp-community/mcpp#256." else echo " The importer now crashes on a version previously known good." echo " A toolchain bump has re-broken module operator templates." diff --git a/tests/e2e/315_blocking_check_gates_compilation.sh b/tests/e2e/315_blocking_check_gates_compilation.sh index be91eb69..c638957f 100755 --- a/tests/e2e/315_blocking_check_gates_compilation.sh +++ b/tests/e2e/315_blocking_check_gates_compilation.sh @@ -5,7 +5,7 @@ # # WHAT THIS DEFENDS. `blocking` was typed (`BuildAction::blocking`), emitted # over the build-program protocol (`hostprogram.cppm`), parsed -# (`directives.cppm`), documented in both languages (`docs/07-build-mcpp.md` +# (`directives.cppm`), documented in both languages (`docs/05-build-mcpp.md` # and its Chinese counterpart) and demonstrated in a shipped example # (`examples/08-build-rules/rules-tidy`) — and read by nothing. The only # order-only edge the ninja backend emitted was the staged-BMI one. So a check diff --git a/tests/unit/test_wire.cpp b/tests/unit/test_wire.cpp index 9d7765a3..3c4811dd 100644 --- a/tests/unit/test_wire.cpp +++ b/tests/unit/test_wire.cpp @@ -160,7 +160,7 @@ TEST(WireFormat, LegacyJsonIsNotTheSameFormatAsJson) { // ── Golden shapes, one per kind ──────────────────────────────────────────── // -// docs/11-machine-output.md promises that within a kindVersion fields are +// docs/50-machine-output.md promises that within a kindVersion fields are // added and never removed or renamed. A promise nobody can break is the thing // this whole module was written against — `xlings interface --list` declares // 20 capabilities whose outputSchema is, for all 20, only @@ -179,7 +179,7 @@ void expect_has_keys(const nlohmann::json& obj, << what << " lost published key '" << k << "'. Removing or renaming one is a breaking change: bump the " "kind's version in mcpp.wire and say so in " - "docs/11-machine-output.md."; + "docs/50-machine-output.md."; } } // namespace From 65be3f12a7bccf83de775a86cb8d6bcba9887eae Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:40:12 +0800 Subject: [PATCH 16/36] docs: design the book, write the three chapters no topic owned, and record the rules in the skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commits reorganised. This designs. MEASURED, and it is why renumbering was not enough: testing is a section in seven chapters, dependency resolution in seven, C++ modules in seven, and the model -- what mcpp's moving parts are -- in none. Five of 24 chapters state their reader and their question; six have a limits section. A tree indexed by mechanism gives a TASK no home, and no numbering gives it one. Three chapters, each for a topic the measurement showed unowned: 00 How mcpp Works the five nouns every other chapter uses -- package, graph, toolchain, target, index -- the three seams a first build crosses, where state lives, and a table from a message's shape to the noun it is about 04 Dependencies and Resolution 07 Testing including a target this machine cannot execute `00` is the one that changes how the rest reads: mcpp is a build system, a package manager and a toolchain provisioner in one program, and a reader who does not know that reads every chapter as an unrelated feature. Renumbered a second time, into the bands the design specifies rather than the ones the reorganisation guessed: build programs move beside rule authoring because they are one subject at two scales, and bare metal moves in front of devices because that is the arc. The design states that a second renumbering is the cost of having reorganised before designing. The design document caught its own defect: its chapter list dropped Workspaces entirely, and a filename collision surfaced it. Six rules are abstracted into `.agents/skills/mcpp-docs-style` so they outlive this batch: one owner per topic; a chapter exists for a reader with a task, not a mechanism with a name; reader, question and EXCLUSIONS in the first fifteen lines; a part is a reader's arc; one recommended path in the body with every other spelling folded into `
`; and an increment marked beside itself. The skill also gains the seven-cell chapter spec that must be fillable before a chapter is touched, and the review rubric grows to eleven dimensions. Chapter 04 is the first section written to the recommended-path rule: the dotted selector in the body, the namespace sub-table folded away. Rule 10 caught that the 简体中文 half had not been changed with it. --- .../2026-09-08-the-documentation-as-a-book.md | 18 ++ .agents/skills/mcpp-docs-style/SKILL.md | 144 +++++++-- .github/tools/check_docs_style.sh | 2 +- CHANGELOG.md | 24 +- README.md | 14 +- README.zh-CN.md | 12 +- docs/00-how-mcpp-works.md | 136 ++++++++ ...tting-started.md => 01-getting-started.md} | 6 +- docs/{01-examples.md => 02-examples.md} | 12 +- docs/{02-mcpp-toml.md => 03-mcpp-toml.md} | 292 +---------------- docs/04-dependencies.md | 295 ++++++++++++++++++ ...ies.md => 05-features-and-capabilities.md} | 10 +- docs/{03-workspace.md => 06-workspace.md} | 2 +- docs/07-testing.md | 124 ++++++++ ...scenario.md => 08-commands-by-scenario.md} | 6 +- docs/10-pack-and-release.md | 4 +- docs/20-toolchains.md | 2 +- docs/21-the-target-triple.md | 2 +- docs/22-target-side.md | 6 +- docs/23-the-project-environment.md | 8 +- docs/24-openkal-cross.md | 2 +- docs/{05-build-mcpp.md => 30-build-mcpp.md} | 18 +- ...kage.md => 31-authoring-a-rule-package.md} | 14 +- docs/{30-baremetal.md => 40-baremetal.md} | 12 +- docs/{31-devices.md => 41-devices.md} | 6 +- ...s-builds.md => 42-heterogeneous-builds.md} | 10 +- docs/90-build-from-source.md | 2 +- docs/91-toolchain-internals.md | 2 +- docs/README.md | 58 ++-- docs/specs/manifest-semantics.md | 2 +- docs/zh/00-how-mcpp-works.md | 120 +++++++ ...tting-started.md => 01-getting-started.md} | 6 +- docs/zh/{01-examples.md => 02-examples.md} | 12 +- docs/zh/{02-mcpp-toml.md => 03-mcpp-toml.md} | 260 +-------------- docs/zh/04-dependencies.md | 261 ++++++++++++++++ ...ies.md => 05-features-and-capabilities.md} | 10 +- docs/zh/{03-workspace.md => 06-workspace.md} | 2 +- docs/zh/07-testing.md | 115 +++++++ ...scenario.md => 08-commands-by-scenario.md} | 6 +- docs/zh/10-pack-and-release.md | 4 +- docs/zh/20-toolchains.md | 4 +- docs/zh/21-the-target-triple.md | 2 +- docs/zh/22-target-side.md | 6 +- docs/zh/23-the-project-environment.md | 8 +- docs/zh/24-openkal-cross.md | 2 +- .../zh/{05-build-mcpp.md => 30-build-mcpp.md} | 18 +- ...kage.md => 31-authoring-a-rule-package.md} | 12 +- docs/zh/{30-baremetal.md => 40-baremetal.md} | 10 +- docs/zh/{31-devices.md => 41-devices.md} | 4 +- ...s-builds.md => 42-heterogeneous-builds.md} | 10 +- docs/zh/90-build-from-source.md | 2 +- docs/zh/91-toolchain-internals.md | 2 +- docs/zh/README.md | 55 ++-- examples/09-heterogeneous/README.md | 4 +- examples/09-heterogeneous/cuda/app/mcpp.toml | 2 +- mcpp.toml | 2 +- modules/buildmcpp/src/directives.cppm | 2 +- modules/buildmcpp/src/program_protocol.cppm | 2 +- modules/manifest/src/glob.cppm | 4 +- modules/manifest/src/toml.cppm | 2 +- modules/manifest/src/types.cppm | 4 +- src/build/ninja_backend.cppm | 2 +- src/build/prepare.cppm | 2 +- src/hooks.cppm | 2 +- .../e2e/186_build_mcpp_protocol_and_bound.sh | 2 +- tests/e2e/188_build_actions.sh | 2 +- .../315_blocking_check_gates_compilation.sh | 2 +- .../e2e/616_examples_and_their_index_agree.sh | 2 +- 68 files changed, 1444 insertions(+), 768 deletions(-) create mode 100644 docs/00-how-mcpp-works.md rename docs/{00-getting-started.md => 01-getting-started.md} (98%) rename docs/{01-examples.md => 02-examples.md} (94%) rename docs/{02-mcpp-toml.md => 03-mcpp-toml.md} (83%) create mode 100644 docs/04-dependencies.md rename docs/{04-features-and-capabilities.md => 05-features-and-capabilities.md} (98%) rename docs/{03-workspace.md => 06-workspace.md} (99%) create mode 100644 docs/07-testing.md rename docs/{06-commands-by-scenario.md => 08-commands-by-scenario.md} (99%) rename docs/{05-build-mcpp.md => 30-build-mcpp.md} (99%) rename docs/{40-authoring-a-rule-package.md => 31-authoring-a-rule-package.md} (96%) rename docs/{30-baremetal.md => 40-baremetal.md} (99%) rename docs/{31-devices.md => 41-devices.md} (97%) rename docs/{32-heterogeneous-builds.md => 42-heterogeneous-builds.md} (99%) create mode 100644 docs/zh/00-how-mcpp-works.md rename docs/zh/{00-getting-started.md => 01-getting-started.md} (98%) rename docs/zh/{01-examples.md => 02-examples.md} (93%) rename docs/zh/{02-mcpp-toml.md => 03-mcpp-toml.md} (83%) create mode 100644 docs/zh/04-dependencies.md rename docs/zh/{04-features-and-capabilities.md => 05-features-and-capabilities.md} (98%) rename docs/zh/{03-workspace.md => 06-workspace.md} (99%) create mode 100644 docs/zh/07-testing.md rename docs/zh/{06-commands-by-scenario.md => 08-commands-by-scenario.md} (99%) rename docs/zh/{05-build-mcpp.md => 30-build-mcpp.md} (99%) rename docs/zh/{40-authoring-a-rule-package.md => 31-authoring-a-rule-package.md} (96%) rename docs/zh/{30-baremetal.md => 40-baremetal.md} (99%) rename docs/zh/{31-devices.md => 41-devices.md} (97%) rename docs/zh/{32-heterogeneous-builds.md => 42-heterogeneous-builds.md} (99%) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 76276a19..62572fea 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -91,6 +91,24 @@ A fourth rule governs the sequence rather than a chapter: **R4 — A part is a reader's arc, and its order is the order that reader needs it in.** Not alphabetical, not chronological by when the feature shipped. +Two more govern how a section is written, and both are about not handing the +reader a decision that the design already made: + +**R5 — One recommended path in the body; every other spelling in a +`
`.** mcpp supports more than one way to say many things, and it has a +style and a semantics of its own, so there is always a default. The body carries +that one. Legacy spellings, escape hatches and platform-specific forms fold +away. The criterion: a reader who reads only the body and opens nothing can do +the thing correctly without choosing. Folding a form away is not deprecation; +deprecation is said in words. + +**R6 — An increment is marked beside itself.** A key, flag or behaviour added +later carries its version floor on its own line (`2026.9.6.5+`), not at the top +of the chapter and never as "it used to be X". + +All six are recorded in `.agents/skills/mcpp-docs-style` so they outlive this +batch. + --- ## 3. The model the book teaches, stated once diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index 7992c9c5..ea276244 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -5,9 +5,9 @@ description: Use when writing or editing anything under docs/ (English or 简体 # mcpp 文档规范 -本规范回答五个问题:**这份文档的归属**(第一节)、**怎么写**(第二至六节)、 -**它必须对得上什么**(第七节)、**它欠多少覆盖**(第九节)、 -**怎么评审它**(第十三节)。 +本规范回答六个问题:**这份文档的归属**(第一节)、**为什么要有这一章**(第二节)、 +**怎么写**(第三至七节)、**它必须对得上什么**(第八节)、 +**它欠多少覆盖**(第十节)、**怎么评审它**(第十四节)。 最核心的一条在第一节:**用户文档是已实现功能的使用手册**,不是设计说明。 @@ -46,7 +46,7 @@ description: Use when writing or editing anything under docs/ (English or 简体 - 能 → 删掉,或移进设计记录。 - 不能 → 它不是设计论证,是**使用信息**;改写成事实陈述,去掉论证语气。 -**边界写成事实,不写成论证。** 「当前边界」一节(第九节要求它必须存在)是一份 +**边界写成事实,不写成论证。** 「当前边界」一节(第十节要求它必须存在)是一份 清单,不是一段说理: | 不采用 | 采用 | @@ -85,7 +85,46 @@ docs/specs/ ──▶ docs/ 允许 它,只说明两件事之一 —— 章节不完整,或那份记录里的内容已经变成规范性的。两者 的修法都不是加链接,而是**把内容提升上来**(是操作就进章节,是保证就进规范)。 -## 二、总原则 +## 二、重构的定义,以及每章的设计规格 + +**重构不是在既有文档上修补,是重新设计。** 分组、重编号、把段落搬到别的章,这些 +是**重组**;它们改的是索引,不是书。重构要回答的是:每一章**为什么存在**、给谁看、 +放在哪里、按什么顺序、包含什么、传递什么信息 —— 并且把**为什么**写下来。 + +判据:拿掉某一章,读者少了哪个问题的答案?答不上来,这一章就没有被设计过。 + +### 四条产生每个决定的规则 + +| | 规则 | 它排除了什么 | +|---|---|---| +| **R1** | **一个主题一个拥有者。** 恰好一章拥有一个主题;其余每一章只写一句话并链接过去 | 同一件事被解释两遍,然后各自过期 | +| **R2** | **一章为**有任务的读者**而存在,不为**有名字的机制**而存在** | 按机制建目录,于是「测试」「依赖解析」这类**任务**没有家 | +| **R3** | 每章在**前 15 行**内写明读者、它回答的那一个问题、以及它**排除**什么 | 排除是承重的:它是防止这一章重新吸收 R1 已经分配出去的主题的闸 | +| **R4** | 一个部分是**某类读者的一段弧**,其内部顺序是那类读者需要它的顺序 | 字母序、按特性发布时间排序 | + +### 每章的设计规格 + +动一章之前,先把这张表填出来。填不出来的那一格,就是还没设计的地方。 + +| 项 | 要回答什么 | +|---|---| +| 读者 | 谁在读它。一句话说不出来就是没定位 | +| 那一个问题 | 它存在的理由,一个问句 | +| 包含 | 哪些内容归它拥有(R1) | +| **排除** | 哪些内容**不**归它,以及归谁 | +| 位置与理由 | 在哪个部分、第几位,**为什么在这里而不是别处** | +| 前后 | 上一章与下一章,以及为什么是这两章 | +| 判据 | 读完这一章的人能做到什么 | + +### 出版级的含义 + +这套规格施加于**每一章**,而不是只施加于新写的章。一份文档集合的质量由它最差的 +那一章决定,因为读者不知道哪一章是被设计过的。 + +**先设计再动手。** 先重组、再设计,会得到两次重编号和一份没有被设计过的书 —— +第二次重编号的成本,就是没有先设计的代价。 + +## 三、总原则 文档是**参考资料**,不是博客,也不是聊天记录。判据只有一条: @@ -94,10 +133,10 @@ docs/specs/ ──▶ docs/ 允许 由此得到四条可执行的规则:**学术、陈述、精确、克制**。 -「学术」在这里是具体的三件事,不是气质:每个断言的强度与它的证据相符(第六节)、 -每个可粘贴的东西都可复现(第七节)、每个枚举都有分母(第九节)。 +「学术」在这里是具体的三件事,不是气质:每个断言的强度与它的证据相符(第七节)、 +每个可粘贴的东西都可复现(第八节)、每个枚举都有分母(第十节)。 -## 三、标题 +## 四、标题 **标题一律是名词短语或陈述句,不使用疑问句、不使用口语片段。** @@ -122,7 +161,7 @@ docs/specs/ ──▶ docs/ 允许 「…的原因」「…的依据」「…的范围」是把 why 型标题转成名词短语的常用形。 **保留 why 本身,去掉疑问语气。** -## 四、词汇 +## 五、词汇 ### 不采用的类别 @@ -158,7 +197,7 @@ docs/specs/ ──▶ docs/ 允许 `your toolchain : …` 是程序打印的原文,**逐字复现是要求,不是文风问题**。 检查脚本因此会先剔除行内代码段再判定。 -## 五、句式 +## 六、句式 - **陈述句优先。** 命令式仅用于操作步骤(「运行 `mcpp build`」)。 - **一句话一个事实。** 从句套从句的长句拆开。 @@ -167,7 +206,7 @@ docs/specs/ ──▶ docs/ 允许 - **破折号克制使用**:插入语用逗号或括号;破折号留给「随后是对前半句的 重述或收束」这一种用法。 -## 六、断言的强度必须与证据相符 +## 七、断言的强度必须与证据相符 这是本规范里最实质的一条,也是最容易违反的一条。 @@ -195,7 +234,7 @@ docs/specs/ ──▶ docs/ 允许 | 已安装并编译 | 组件装得上、代码编得过,没有跑过 | | 已声明 | 描述符里有,没有装过 | -## 七、文档必须对应当前实现 +## 八、文档必须对应当前实现 一份与实现脱节的文档比没有文档更坏:读者按它写出来的东西编不过,而错的是 文档,他不知道。 @@ -216,7 +255,7 @@ docs/specs/ ──▶ docs/ 允许 措辞时静默变空转。要判断一份文档是否覆盖某个能力,读**结构化的东西** —— 示例的 `mcpp.toml`、源码里的键表、`print_usage()` 的正文。 -## 八、梯度:一个主题的五级台阶,以及只链接相邻级 +## 九、梯度:一个主题的五级台阶,以及只链接相邻级 文档目录要有梯度,读者才能按自己的深度进入。同一个主题从浅到深有五级: @@ -239,7 +278,7 @@ docs/specs/ ──▶ docs/ 允许 一个能力的文档化按这个顺序推进,不跳级:先有一个能跑的最小形态,再有字段参考, 再在「两个实现会不一致」时抽出规范。**倒过来做会得到一份没有人验证过的规范。** -## 九、覆盖度 +## 十、覆盖度 「写了」不等于「覆盖了」。覆盖度要有分母,而**分母取自代码树,不取自文档**—— 用文档自己的列表当分母,只能证明这份文档自洽。 @@ -266,7 +305,7 @@ docs/specs/ ──▶ docs/ 允许 读者只能靠踩到才知道。缺口写在**它所属的那棵树**里:用户文档写「不支持 X」,设计记录写为什么以及打算怎么办。 -## 十、双语对照 +## 十一、双语对照 `docs/X.md` 与 `docs/zh/X.md` 是**同一份文档的两个版本**,不是两篇文章。 @@ -277,15 +316,62 @@ docs/specs/ ──▶ docs/ 允许 - 改动一侧时**同时改另一侧**。只改一侧会让两份文档随时间分叉, 而读者无从知道哪一份是新的。 -## 十一、结构 +## 十二、结构 + +### 每份文档的开头三行 + +用户章节开头必须回答三件事,各一行,在前 15 行之内: + +``` +**读者:** … 谁在读它 +**本章回答的那一个问题:** … 它存在的理由 +**不在这里:** … 它刻意排除什么,以及那些内容归谁 +``` + +第三行是承重的:**排除**是防止这一章重新吸收别处已经拥有的主题的那道闸(第二节 +的 R1:一个主题一个拥有者)。再加一行「在此之前 / 在此之后」,指出相邻级(第九节)。 + +规范另有两项硬性要求:开头一张元数据表(编号、标题、状态、版本、最后修改、 +对应实现、相关设计文档),结尾一份变更记录。 + +### 一条推荐路径写在正文,其余收进 `
` + +mcpp 在很多场景支持不止一种做法,但它有自己的设计风格与语义,因此**总有一条 +默认推荐**。正文只写那一条;其余的形态 —— 遗留拼法、逃生舱、只在某个平台成立的 +写法、为兼容保留的别名 —— 收进折叠块: + +```markdown +推荐写法。<正文,一条路径> + +
+其它形态:子表形式、旧拼法 + +… + +
+``` + +**判据:一个只读正文、不展开任何折叠块的读者,能不能不做选择就把事情做对?** +能 → 对。需要在 N 个并列的做法里自己挑一个 → 错,那是把设计决定推给了读者。 + +把一种做法降进折叠块**不表示它被弃用**。弃用要明说,并写清替代与从哪个版本起。 + +### 增量标在它自己旁边 + +一个后来才加进来的键、旗标或行为,版本下界写在**它那一行或那一段**旁边 +(`2026.9.6.5+`),不写在章节顶部,也不写成「从前是 X,后来变成 Y」的叙述 —— +后者是设计记录的句子(第一节)。 + +### 两个视角都要设计 + +- **全局**:入口的角色索引、部分的划分、编号所在的段位、章节之间的顺序。 +- **局部**:每章的开头三行、每一节推荐哪一条路径、表格用于枚举而散文用于因果、 + 段落不超过约六行、代码块前有一句说明它演示什么。 -- 顶部一段引言说明**这份文档回答什么问题**、它处在第八节的哪一级,以及相关 - 文档的链接(用「相关文档:」,不用「姊妹篇」)。 -- 表格用于枚举与对照,散文用于因果。**不要用散文列举**。 -- 规范另有两项硬性要求:开头一张元数据表(编号、标题、状态、版本、最后修改、 - 对应实现、相关设计文档),结尾一份变更记录。 +一份全局清楚而局部混乱的文档,读者找得到却读不懂;反过来则读者读得懂却找不到。 +两者都要过第十四节的评审。 -## 十二、机器检查 +## 十三、机器检查 规则里可判定的那一部分由 `.github/tools/check_docs_style.sh` 执行: @@ -308,7 +394,7 @@ bash .github/tools/check_docs_style.sh 覆盖是否有分母,都需要读者判断,而那三条是本规范里最重要的。 **脚本能做的事不等于规范的全部。** -## 十三、评审判据 +## 十四、评审判据 文档改动**至少评审一次**,而且**不由写它的那一遍来评审** —— 刚写完就自审,读到的 是自己的意图而不是文本。判据:评审时只读渲染后的成文,不读 diff。 @@ -318,13 +404,16 @@ bash .github/tools/check_docs_style.sh | 维度 | 判据 | |---|---| | **面向人群** | 一句话说出这份文档的读者是谁。说不出,就是没定位。二次判据:从入口的角色索引能不能指到它 | -| **梯度** | 它是第八节五级里的哪一级?它链接的是不是相邻级?跨级链接一律是缺陷 | +| **梯度** | 它是第九节五级里的哪一级?它链接的是不是相邻级?跨级链接一律是缺陷 | | **渐进性** | 一个从零开始的读者,能不能不跳级地到达这里 —— 前置的最小可跑形态存在吗 | | **直观** | 只读前 15 行,能不能答出「这章讲什么、我要不要读」 | -| **覆盖度** | 分母是什么(第九节)?「当前边界」一节在不在,且是事实清单不是说理 | +| **覆盖度** | 分母是什么(第十节)?「当前边界」一节在不在,且是事实清单不是说理 | | **陈述方式** | 陈述句;无第二人称(教程除外);无 emoji;每条断言的强度与证据相符;「支持」分三档 | | **信息密度** | 随机抽三段,逐段问「删掉它读者少知道什么」。答不上来就是低密度 —— 典型是复述上一段、为强调而重复、以及把一个事实拆成三句 | | **易读** | 表格用于枚举、散文用于因果;一句一个事实;段落不超过约六行;代码块前有一句说明它演示什么 | +| **一条推荐路径** | 只读正文、不展开任何 `
`,读者能不能不做选择就把事情做对(第十二节) | +| **增量标注** | 每个后加的键/旗标/行为,版本下界写在它自己旁边,而不是章节顶部或历史叙述 | +| **章节规格** | 第二节那张表能不能填满 —— 读者、那一个问题、包含、排除、位置与理由、前后、判据 | **用户文档额外一条,优先级高于以上八条**:逐段问「删掉它读者还能不能正确地用」 (第一节)。设计理由、被否掉的替代、路线图,一律不在用户文档里。 @@ -332,7 +421,7 @@ bash .github/tools/check_docs_style.sh 评审的产出是**一份逐条的结论**,不是「看起来不错」。每个维度给出:通过 / 不通过 + 具体位置。 -## 十四、自检清单 +## 十五、自检清单 提交文档改动前: @@ -342,6 +431,9 @@ bash .github/tools/check_docs_style.sh 路线图都不在里面 [ ] 用户文档:一个能力只写「能用(带版本下界)」或「不支持(一句话)」 [ ] 「当前边界」是事实清单,不是说理 +[ ] 有多种做法时,正文只写推荐那一条,其余在 `
` 里 +[ ] 后加的键/旗标带版本下界,且标在它自己旁边 +[ ] 第二节的章节规格七格都能填出来 [ ] 没有 docs/** → .agents/** 的引用 [ ] 开头点明了它在梯度里的哪一级,且只链接相邻级 [ ] 标题没有疑问句、没有口语片段 diff --git a/.github/tools/check_docs_style.sh b/.github/tools/check_docs_style.sh index 482de98d..4a8173c9 100755 --- a/.github/tools/check_docs_style.sh +++ b/.github/tools/check_docs_style.sh @@ -20,7 +20,7 @@ fail=0 bad() { echo "FAIL: $*"; fail=1; } # Tutorials address the reader on purpose: the reader is following along. -TUTORIALS="00-getting-started.md 01-examples.md 90-build-from-source.md" +TUTORIALS="01-getting-started.md 02-examples.md 90-build-from-source.md" # Headings outside fenced code blocks. `# …` inside a ```sh block is a shell # comment, and counting it made the first version of this script report a diff --git a/CHANGELOG.md b/CHANGELOG.md index abc0dd3e..2570460c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -596,7 +596,7 @@ e2e 616 是双向的,而单向的那一半不值得跑:「列出的路径都存 通过,「盘上的示例都被列出」在一份把一半链接指错地方的文档上通过。两条合起来才是那条性质。 它当场抓到一个既有缺口:`examples/04-workspace`、`05-lib-distribution`、 -`06-openkal-cross`、`07-project-subos` 都在盘上,而中英两份 `docs/01-examples.md` +`06-openkal-cross`、`07-project-subos` 都在盘上,而中英两份 `docs/02-examples.md` 一个都没列。现在列上了。 这是结构判据,它自己也这么说:它不构建任何东西,所以回答不了「这个示例还能不能跑」。 @@ -677,7 +677,7 @@ C++20 modules 的第二个编译器(icpx,或带 SYCL 前端的 clang)。 配套的规则包在 `mcpp:plugins` 0.2.0:`mcpp.rules.sycl` 驱动 `xim:dpcpp` 载荷, `mcpp.rules.hip` 在 NVIDIA 平台上把 HIP 当作 CUDA 运行时之上的一层头文件, `mcpp.rules.spirv` 增加 `glslc` 路线(`xim:shaderc` 使它从一句声明变成一条路线)。 -文档见 `docs/32-heterogeneous-builds.md` 的「lanes」一节。 +文档见 `docs/42-heterogeneous-builds.md` 的「lanes」一节。 ### `mcpp clean --stale`:只清 target/ 里已无构建使用的指纹目录 (#565) @@ -779,7 +779,7 @@ e2e 610 分别断言这五条(含 `mcpp` 命名空间零告警、其他命名空 文件的相反的话。 表因此扩到「由另一个编译器消费的语言」:CUDA 与 HIP,GLSL 的各个 stage 与无 stage 的 -`.glsl`,HLSL,OpenCL C,Metal。共 18 个扩展名,完整清单见 `docs/32-heterogeneous-builds.md`。 +`.glsl`,HLSL,OpenCL C,Metal。共 18 个扩展名,完整清单见 `docs/42-heterogeneous-builds.md`。 这次扩表不能改变任何今天可用的构建,理由有两条且互相独立:设备扩展名**本来就不在** 默认 source glob 里,所以没有 glob 变宽;而这些扩展名今天在 `sources` 里是**硬错误**, @@ -814,7 +814,7 @@ e2e 610 分别断言这五条(含 `mcpp` 命名空间零告警、其他命名空 ### 文档 -- 第 20 章由「加速器」更名为「异构硬件构建」(`docs/32-heterogeneous-builds.md`),副题指明 +- 第 20 章由「加速器」更名为「异构硬件构建」(`docs/42-heterogeneous-builds.md`),副题指明 GPU 与 AI 加速器目标以及宿主/设备混合编译;`accel` 键不变。 - 第 5 章与第 7 章(中英)补入 feature 选择的规则集合与 `mcpp.rules.*` / `mcpp.tools.*` 命名;第 7 章中文版此前缺少命名一节,本次补齐。 @@ -957,7 +957,7 @@ generic target 取得同样的覆盖,所以空的下界不放宽任何东西。 设计与调研:`.agents/docs/2026-09-05-accelerator-support-design.md`、 `.agents/docs/2026-09-04-ai-accelerator-toolchain-ecosystem-survey.md`。 -新增手册章节 `docs/32-heterogeneous-builds.md`(中英双份)。 +新增手册章节 `docs/42-heterogeneous-builds.md`(中英双份)。 ## [2026.9.4.3] — 2026-09-04 @@ -1159,7 +1159,7 @@ M-profile:MPU 按基址与上限描述区域,没有页表项。A-profile 有真 `mcpp why runners` 列出本工程提供的 runner,与其余解析结果并列; `mcpp run --list-runners` 是同一份读取,单独报告。 -新增 `docs/31-devices.md`、`docs/51-supported-versions.md`(中英双份)。 +新增 `docs/41-devices.md`、`docs/51-supported-versions.md`(中英双份)。 指令协议版本 6。 ## [2026.9.4.1] — 2026-09-04 @@ -4317,11 +4317,11 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 文档 -- `docs/02-mcpp-toml.md` 的示例此前写的是 `import mcpp.rules.protobuf;`,**做不到**:mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一原子段。随之澄清一条会咬人的约束:规则包的名字必须是**合法 C++ 模块名**(`grpcgen` 可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示你名字有问题。 +- `docs/03-mcpp-toml.md` 的示例此前写的是 `import mcpp.rules.protobuf;`,**做不到**:mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一原子段。随之澄清一条会咬人的约束:规则包的名字必须是**合法 C++ 模块名**(`grpcgen` 可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示你名字有问题。 ## [2026.8.5.1] — 2026-08-05 -`build.mcpp` 机制的**架构地基**:把「一条指令是什么」收敛成一张表,并补上三个今天就存在的稳定性缺口。架构分析见 `.agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md`(本次实现其中的步 0 与步 1)。 +`build.mcpp` 机制的**架构地基**:把「一条指令是什么」收敛成一张表,并补上三个今天就存在的稳定性缺口。架构分析见 `.agents/docs/2026-08-30-build-mcpp-extensibility-architecture.md`(本次实现其中的步 0 与步 1)。 ### 改进 @@ -4516,7 +4516,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 修复 -- **`[build] static_stdlib = false` 对测试二进制静默失效(#336)。** #124 在 0.0.52 明确写下这个 opt-out,`docs/02-mcpp-toml.md` 至今也还这么写;但 #202(0.0.86)把测试二进制改成与分发目标相同的静态 `-load_hidden` libc++ 时,新增的那条推导**没有带上 `staticStdlib` 门**。结果是约一年时间里 macOS 上的 `mcpp test` 没有任何办法回到动态 libc++,而文档一直在承诺它可以。 +- **`[build] static_stdlib = false` 对测试二进制静默失效(#336)。** #124 在 0.0.52 明确写下这个 opt-out,`docs/03-mcpp-toml.md` 至今也还这么写;但 #202(0.0.86)把测试二进制改成与分发目标相同的静态 `-load_hidden` libc++ 时,新增的那条推导**没有带上 `staticStdlib` 门**。结果是约一年时间里 macOS 上的 `mcpp test` 没有任何办法回到动态 libc++,而文档一直在承诺它可以。 - **macOS 上全局对象在静态初始化期访问 `std::cout` 必崩(#336)。** Mach-O 没有按优先级排序的初始化段(`init_priority` 只在单个 TU 内有效),归档成员的初始化器按链接顺序排在最后;而 libc++ 的 `` 不像 libstdc++ / MSVC STL 那样自带 `ios_base::Init` 守卫,流的构造只存在于库内对象里。两件事叠起来的后果是:默认配置下,任何在构造函数里碰 `std::cout` 的全局对象都会读到 vptr 为零的流,进程启动即 SIGSEGV —— 而且**包侧无法修复**,因为 `std::ios_base::Init` 在 libc++ 的头文件里只有前向声明(`ios:70`),标准为静态初始化次序提供的官方解药在 libc++ 上用户根本写不出来。 @@ -4550,7 +4550,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 修复 -- **`--offline` 不再拒绝本地 git 远端。** `docs/02-mcpp-toml.md` 写明 `--offline` 的语义是「完全不碰网络……已安装的东西照常构建」,`prepare.cppm` 里依赖下载闸的注释也把线画在同一处:「这条线以上全是本地操作,一个依赖齐备的离线构建必须成功」。但 `git = "../sibling-repo"` 这种指向本地目录的远端,`ls-remote`/`clone` 都只是文件系统读取,拒绝它买不到任何隔离性。现在按远端形态判定——`file://`、以及不带 scheme 也不是 `git@host:path` 的存在路径,算本地(Windows 盘符 `C:\repo` 含冒号但不含 `@`,因此仍归本地)。 +- **`--offline` 不再拒绝本地 git 远端。** `docs/03-mcpp-toml.md` 写明 `--offline` 的语义是「完全不碰网络……已安装的东西照常构建」,`prepare.cppm` 里依赖下载闸的注释也把线画在同一处:「这条线以上全是本地操作,一个依赖齐备的离线构建必须成功」。但 `git = "../sibling-repo"` 这种指向本地目录的远端,`ls-remote`/`clone` 都只是文件系统读取,拒绝它买不到任何隔离性。现在按远端形态判定——`file://`、以及不带 scheme 也不是 `git@host:path` 的存在路径,算本地(Windows 盘符 `C:\repo` 含冒号但不含 `@`,因此仍归本地)。 - **克隆被中途杀掉后不再永久提供错误的 commit。** 缓存目录以 commit 命名,但内容是 `git clone` 之后再 `git checkout` 两步做出来的;进程死在两步之间,目录名和 HEAD 就对不上了,而后续构建只检查目录存不存在。现在分支依赖会比对 `git rev-parse HEAD`,不符即删除重克隆(tag/rev 以 ref 名为身份,无可比之物)。 @@ -4633,7 +4633,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 **最危险的一条判据**(`SuppressedInconclusive`):「本地查不到」单独不能推出「需要刷新」。xim 描述符不写 `namespace`,`(xim, x)` 永远匹配不上身份门 —— 把这种 miss 当真,任何带 xim 依赖的工程会**每次构建都刷**,比被删掉的 TTL 更糟。判据复用 `IndexRoute::authoritative_for`(#307),单测 + e2e 双闸锁住。 - 语义变化:`^1.2` 对**本地索引已知的版本**求解。上游新发的 1.3.0 需要 `mcpp index update` 或 `mcpp update` 才可见 —— 这正是那两个命令存在的意义,已写进 `docs/02-mcpp-toml.md`。 + 语义变化:`^1.2` 对**本地索引已知的版本**求解。上游新发的 1.3.0 需要 `mcpp index update` 或 `mcpp update` 才可见 —— 这正是那两个命令存在的意义,已写进 `docs/03-mcpp-toml.md`。 - **`mcpp update` 不再是空操作。** 它此前只删 mcpp.lock 条目、然后叫用户去跑 `mcpp build` —— 而构建路径**从不读 mcpp.lock**(`prepare` 只写不读),所以删了等于没删,行为影响为零。它现在先强制刷新索引(显式意图 ⇒ 不看 TTL、不看去抖),并报告索引 rev 的变化;工程里没有任何走共享 registry 的依赖时跳过(刷了也没用)。 @@ -5629,7 +5629,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 - `[targets.]` 下的不支持键不再被静默丢弃,而是产生 warning(`--strict` 下为 error), 并指引到正确的机制(workspace / features / profile)。 -- 文档 `docs/02-mcpp-toml.md`(及 `docs/zh`)新增"构建配置该放哪"的决策指引。 +- 文档 `docs/03-mcpp-toml.md`(及 `docs/zh`)新增"构建配置该放哪"的决策指引。 设计记录见 `.agents/docs/2026-06-18-per-target-build-config-design.md`。 ## [0.0.54] — 2026-06-10 diff --git a/README.md b/README.md index 6529a50a..f89d7fd0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Module](https://img.shields.io/badge/module-ok-green.svg)](https://en.cppreference.com/w/cpp/language/modules) [![License](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](LICENSE) -| [Documentation](docs/) · [Getting Started](docs/00-getting-started.md) · [mcpp.toml Guide](docs/02-mcpp-toml.md) · [Examples](docs/01-examples.md) · [Toolchains](docs/20-toolchains.md) | +| [Documentation](docs/) · [Getting Started](docs/01-getting-started.md) · [mcpp.toml Guide](docs/03-mcpp-toml.md) · [Examples](docs/02-examples.md) · [Toolchains](docs/20-toolchains.md) | |:---:| | [Package index mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [Module libraries mcpplibs](https://github.com/mcpplibs) · [Community Forum](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) | | [![ci-linux](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml) [![ci-macos](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml) [![ci-windows](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml) | @@ -224,7 +224,7 @@ Then `import` it directly in your code: import mcpplibs.cmdline; ``` -> For more dependency options (version constraints, namespaces, Git references, local paths, etc.), see the [mcpp.toml guide — dependency management](docs/02-mcpp-toml.md). +> For more dependency options (version constraints, namespaces, Git references, local paths, etc.), see the [mcpp.toml guide — dependency management](docs/03-mcpp-toml.md). ## Feature Overview @@ -413,17 +413,17 @@ verified — CI builds **and executes** the artifact end-to-end (qemu/wine inclu > cross-compilers by construction, so any host that can install the LLVM > payload produces these targets. The C library, startup code, memory layout > and emulator travel with a board-support package rather than with mcpp — see -> [docs/13 — Bare-Metal and Freestanding Targets](docs/30-baremetal.md). +> [docs/13 — Bare-Metal and Freestanding Targets](docs/40-baremetal.md). ## Documentation -- [Getting Started](docs/00-getting-started.md) — install → new → build → run in 5 minutes -- [Examples](docs/01-examples.md) +- [Getting Started](docs/01-getting-started.md) — install → new → build → run in 5 minutes +- [Examples](docs/02-examples.md) - [Packaging & Release](docs/10-pack-and-release.md) - [Toolchain Management](docs/20-toolchains.md) - [Building from Source](docs/90-build-from-source.md) -- [mcpp.toml Guide](docs/02-mcpp-toml.md) -- [Workspaces](docs/03-workspace.md) +- [mcpp.toml Guide](docs/03-mcpp-toml.md) +- [Workspaces](docs/06-workspace.md) Full options for any command are available via `mcpp --help`. diff --git a/README.zh-CN.md b/README.zh-CN.md index e2403db9..3d3a0132 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,7 +9,7 @@ [![Module](https://img.shields.io/badge/module-ok-green.svg)](https://en.cppreference.com/w/cpp/language/modules) [![License](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](LICENSE) -| [文档](docs/zh/) · [快速开始](docs/zh/00-getting-started.md) · [mcpp.toml 指南](docs/zh/02-mcpp-toml.md) · [示例项目](docs/zh/01-examples.md) · [工具链管理](docs/zh/20-toolchains.md) | +| [文档](docs/zh/) · [快速开始](docs/zh/01-getting-started.md) · [mcpp.toml 指南](docs/zh/03-mcpp-toml.md) · [示例项目](docs/zh/02-examples.md) · [工具链管理](docs/zh/20-toolchains.md) | |:---:| | [包索引 mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [模块化库 mcpplibs](https://github.com/mcpplibs) · [社区论坛](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) | | [![ci-linux](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml) [![ci-macos](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml) [![ci-windows](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml) | @@ -221,7 +221,7 @@ cmdline = "0.0.2" import mcpplibs.cmdline; ``` -> 更多依赖配置方式(版本约束、命名空间、Git 引用、本地路径等)参见 [mcpp.toml 指南 — 依赖管理](docs/zh/02-mcpp-toml.md)。 +> 更多依赖配置方式(版本约束、命名空间、Git 引用、本地路径等)参见 [mcpp.toml 指南 — 依赖管理](docs/zh/03-mcpp-toml.md)。 ## 功能概览 @@ -387,13 +387,13 @@ mcpp 的身份模型是两条正交轴:**工具链** = `family@version`(family ## 文档 -- [快速开始](docs/zh/00-getting-started.md) — 5 分钟完成 install → new → build → run -- [示例项目](docs/zh/01-examples.md) +- [快速开始](docs/zh/01-getting-started.md) — 5 分钟完成 install → new → build → run +- [示例项目](docs/zh/02-examples.md) - [发布打包](docs/zh/10-pack-and-release.md) - [工具链管理](docs/zh/20-toolchains.md) - [从源码构建](docs/zh/90-build-from-source.md) -- [mcpp.toml 指南](docs/zh/02-mcpp-toml.md) -- [工作空间](docs/zh/03-workspace.md) +- [mcpp.toml 指南](docs/zh/03-mcpp-toml.md) +- [工作空间](docs/zh/06-workspace.md) 任意命令的完整选项可通过 `mcpp --help` 查阅。 diff --git a/docs/00-how-mcpp-works.md b/docs/00-how-mcpp-works.md new file mode 100644 index 00000000..ae3c02e1 --- /dev/null +++ b/docs/00-how-mcpp-works.md @@ -0,0 +1,136 @@ +# 00 — How mcpp Works + +**Reader:** anyone, before anything else. Every other chapter assumes this one. + +**The question this chapter answers:** what are the moving parts, and which one +is failing when something fails. + +mcpp is three tools in one program — a build system, a package manager, and a +toolchain provisioner. That is unusual, and it is the reason a reader who starts +with a command reference finds a list of verbs with nothing to attach them to. +This chapter is the model. It names no field and no flag; those are the +reference chapters, which this one makes readable. + +Next: [01 — Getting Started](01-getting-started.md) puts a program on the screen. + +## The five nouns + +Everything in this documentation is about five things and the seams between +them. + +### package + +A directory with an `mcpp.toml`. It states a name, a version, what it is built +from and what it depends on. Its identity is the pair `(namespace, name)` — not +its directory, and not its file name — which is why two packages called +`cmdline` from different namespaces can coexist. + +A package is also the unit of everything else: the unit that is published, that +is cached, that declares features, and that a build program belongs to. + +Reference: [03 — The mcpp.toml Manifest](03-mcpp-toml.md). + +### graph + +A build is a graph. Sources become objects, objects become an artifact, and +C++20 module interfaces add edges between sources because one unit must be +compiled before another that imports it. mcpp computes that ordering by scanning +the sources; nothing in the manifest states it. + +Two things extend the graph rather than sitting outside it: a **build program** +(`build.mcpp`) declares additional edges, and a **rule package** supplies those +declarations for a whole class of projects. Both add nodes to the same graph; +neither is a pre-build script. + +Reference: [30 — Build Programs](30-build-mcpp.md). + +### toolchain + +The compiler mcpp uses is a **payload it installs and pins**, not a program +found on the machine. This is the property most other build systems do not have, +and it decides the shape of much else: a build is reproducible because the +compiler is part of what was resolved, and a project can state which compiler it +needs rather than documenting it in a README. + +Reference: [20 — Toolchain Management](20-toolchains.md). + +### target + +The machine the artifact runs on. It is named by a triple and it is not the +host: a build on Linux can produce a Windows executable, a bare-metal image, or +an object for a GPU. Everything conditioned on "where this runs" hangs off the +target, and the manifest can say "only there" without a second manifest. + +Reference: [21 — The Target Triple](21-the-target-triple.md). + +### index + +Where packages come from. An index holds **descriptors** — a package's identity, +its versions, and where each version's source or artifact is fetched from. A +descriptor is data; mcpp is the program that reads it. + +Reference: [11 — Publishing a Library to mcpp-index](11-publishing-a-library.md). + +## What a build does, end to end + +``` + mcpp.toml ──▶ resolve ──▶ provision ──▶ scan ──▶ compile ──▶ link + │ │ │ │ │ │ + package index + toolchain graph toolchain artifact + versions + tools edges + + ╰── three seams a first build crosses ──╯ +``` + +**Seam 1 — manifest to resolution.** What the manifest names becomes a set of +exact versions. A failure here is about the index or a version constraint, and +nothing has been compiled. + +**Seam 2 — resolution to environment.** What was resolved becomes payloads on +disk: the toolchain, and any tool a package declared. A failure here is about a +download, a platform that has no such payload, or a version floor. + +**Seam 3 — sources to graph.** The sources are scanned for `import` and the +edges are computed. A failure here names a module, not a file. + +Compilation and linking come after all three, which is why "it did not compile" +is one of four quite different situations. + +## Where the state lives + +Nothing mcpp writes is hidden, and each store answers a different question. + +| store | scope | holds | emptied by | +|---|---|---|---| +| `target///` | one project | objects, module interfaces, the artifact | `mcpp clean` | +| the build cache | the machine | compiled dependencies and `std` | `mcpp cache gc` | +| the package store | the machine | toolchain payloads and declared tools | the package manager | +| `mcpp.lock` | one project, checked in | what a resolution produced | rewritten by `mcpp update` | + +`mcpp self env` prints where each of these is on this machine. + +The fingerprint in the build directory is why two configurations do not fight: +a debug build, a release build and a cross build occupy different directories +and none invalidates the others. + +## Which noun a failure is about + +The single most useful thing this model buys. A message names a noun, and the +noun names the chapter. + +| a message about | the noun | where to look | +|---|---|---| +| a package name, a version, or "no candidate" | index | [11](11-publishing-a-library.md), [04](04-dependencies.md) | +| a download, a payload, or a version floor | toolchain | [20](20-toolchains.md), [23](23-the-project-environment.md) | +| a triple, or "unsupported target" | target | [21](21-the-target-triple.md) | +| a module that cannot be read or is not provided | graph | [30](30-build-mcpp.md) | +| a compile or link error in a file of the project | none of them | the compiler's own message | + +The last row is the useful one: when a compiler error is about the code, none of +mcpp's parts is involved, and reading mcpp's documentation will not help. + +## What this chapter leaves out + +Everything operational. No field of the manifest, no flag, and no command other +than `mcpp build` appears here on purpose — a model that also tries to be a +reference is neither. Each noun's section names the chapter that owns it. diff --git a/docs/00-getting-started.md b/docs/01-getting-started.md similarity index 98% rename from docs/00-getting-started.md rename to docs/01-getting-started.md index 7d086007..3c0c7f83 100644 --- a/docs/00-getting-started.md +++ b/docs/01-getting-started.md @@ -1,4 +1,4 @@ -# 00 — Getting Started +# 01 — Getting Started > Go from install → new → build → run → pack in 5 minutes. @@ -168,7 +168,7 @@ Declare dependencies in `mcpp.toml`: `mcpp build` automatically resolves SemVer constraints against the [mcpp-index](https://github.com/mcpplibs/mcpp-index), fetches the source, and adds it to the build graph. For a complete example, see `02-with-deps` in -[01 — Examples](01-examples.md). +[02 — Examples](02-examples.md). ## Producing a Release Package @@ -185,7 +185,7 @@ For the differences between the four modes and their artifact layouts, see [10 ## Further Reading -- [01 — Examples](01-examples.md) — a collection of ready-to-run minimal projects +- [02 — Examples](02-examples.md) — a collection of ready-to-run minimal projects - [10 — Packaging and Release](10-pack-and-release.md) — building distributable artifacts - [20 — Toolchain Management](20-toolchains.md) — switching compilers and managing multiple versions - The full set of options for any command is available via `mcpp --help` diff --git a/docs/01-examples.md b/docs/02-examples.md similarity index 94% rename from docs/01-examples.md rename to docs/02-examples.md index 4af8f595..d3cf3e3c 100644 --- a/docs/01-examples.md +++ b/docs/02-examples.md @@ -1,4 +1,4 @@ -# 01 — Examples +# 02 — Examples The [`examples/`](../examples) directory is a curriculum. Each project is runnable on its own, and each one teaches **one thing no earlier example @@ -14,7 +14,7 @@ mcpp build && mcpp run ``` Every example ships a README that explains only what it adds. Installation and -toolchain setup live in [00 — Getting Started](00-getting-started.md) and are +toolchain setup live in [01 — Getting Started](01-getting-started.md) and are not repeated. ## The curriculum @@ -73,7 +73,7 @@ the map; the table below is what each sub-example adds. | [`08-build-rules`](../examples/08-build-rules/) | two rule packages and a project using both; `host-module = true`, `mcpp::action` with `role = "check"` | | [`12-a-new-device-language`](../examples/12-a-new-device-language/) | `device_extensions` and `rule_module`: a rule package teaching mcpp a language the engine has never heard of | -[40 — Authoring a Rule Package](40-authoring-a-rule-package.md) is the reference +[31 — Authoring a Rule Package](31-authoring-a-rule-package.md) is the reference these two illustrate. ## Lessons that arrive as templates @@ -85,8 +85,8 @@ rather than by mcpp. | template | lesson | chapter | |---|---|---| -| `riscv-virt-rt` | a bare-metal project, its board support and its runner | [30](30-baremetal.md) | -| `riscv-virt-rt:nolibc` | the same with no C library | [30](30-baremetal.md) | +| `riscv-virt-rt` | a bare-metal project, its board support and its runner | [40](40-baremetal.md) | +| `riscv-virt-rt:nolibc` | the same with no C library | [40](40-baremetal.md) | | `ocornut.imgui` | a graphical application with its window and rendering stack | [20](20-toolchains.md) | ```bash @@ -100,7 +100,7 @@ last one. A new example is warranted when a capability **changes the shape of a project** — the files it contains, the manifest it declares, or the commands its author types. A capability that is one line inside a project an example already contains belongs in that chapter as a code block; one reached only through a -command belongs in [06 — Commands by Scenario](06-commands-by-scenario.md). +command belongs in [08 — Commands by Scenario](08-commands-by-scenario.md). The README states what the example is the first to teach and the criterion by which it is judged to work. For contribution mechanics see diff --git a/docs/02-mcpp-toml.md b/docs/03-mcpp-toml.md similarity index 83% rename from docs/02-mcpp-toml.md rename to docs/03-mcpp-toml.md index 9347c8c4..dd4bcabe 100644 --- a/docs/02-mcpp-toml.md +++ b/docs/03-mcpp-toml.md @@ -1,4 +1,4 @@ -# 02 — The mcpp.toml Manifest +# 03 — The mcpp.toml Manifest `mcpp.toml` is the project configuration file for the mcpp build tool, analogous to Cargo's `Cargo.toml` or Node's `package.json`. Place it in the project root; `mcpp build` discovers and reads it automatically. @@ -76,7 +76,7 @@ Both spellings of the value are accepted: `standard = "c++26"` and `standard = 2 When a **dependency declares a level above the graph's**, mcpp says so before compiling rather than letting it fail somewhere inside that dependency's sources. See -[workspace §4.2](03-workspace.md). +[workspace §4.2](06-workspace.md). #### Dialect flags and the `import std` BMI @@ -220,7 +220,7 @@ required_features = ["gui"] # only built when feature `gui` is > per-test contract evaluation semantic (`-fcontract-evaluation-semantic=observe`) for a test whose > `main` exercises the violation, a feature macro the entry alone reads, or a local warning > suppression. If a flag must reach **shared** code, it does not belong here — split into a -> [workspace](03-workspace.md) member or use `[features]`, or for a whole-build mode use a +> [workspace](06-workspace.md) member or use `[features]`, or for a whole-build mode use a > `[profile.*]` (`mcpp test --profile ` builds the whole test image, code-under-test > included, under that profile). > @@ -231,7 +231,7 @@ required_features = ["gui"] # only built when feature `gui` is | You want | Use | |---|---| | Different macros/flags on a binary's **own entry** | per-target `defines` / `cxxflags` (above) | -| Two products that differ in code they **share** | split into [workspace](03-workspace.md) members, each with its own `[build]` flags over a shared `lib` | +| Two products that differ in code they **share** | split into [workspace](06-workspace.md) members, each with its own `[build]` flags over a shared `lib` | | To **select a variant** of a shared library (e.g. a backend) | `[features]` on that library (§2.8) — additive, reaches the library's own compile | | A **whole-build mode** (sanitizers, contract semantics, opt level) | `[profile.]` (§2.9) + `--profile`; also honored by `mcpp test --profile ` | @@ -266,7 +266,7 @@ the package/feature boundary, not on an individual target. > CPU-only variant. Under an `--accel` that does not cover the constraint the > build is refused naming both (`accel-mismatch`). Device-kind files the > effective set matches — CUDA and HIP, the GLSL stages, HLSL, OpenCL C and -> Metal, listed in full in [32 — Heterogeneous Builds](32-heterogeneous-builds.md) — are never +> Metal, listed in full in [42 — Heterogeneous Builds](42-heterogeneous-builds.md) — are never > compiled by the engine; they reach the build program as > `MCPP_DEVICE_SOURCES`, where the rule package the project imports turns each > into an `mcpp::action`. @@ -575,7 +575,7 @@ rebuilt the whole project, which is the opposite of what someone raising a timeout wants. The **compile** phase is not bounded, only the build *program*. See -[05-build-mcpp.md](05-build-mcpp.md) for why that asymmetry is deliberate. +[30-build-mcpp.md](30-build-mcpp.md) for why that asymmetry is deliberate. ### The C++ runtime contract (`cxx_runtime`) Moved to [20 — Toolchain Management](20-toolchains.md). @@ -623,7 +623,7 @@ package that also publishes device builds is selected. The value is compared against the `accel` field of any prebuilt artifact the build consumes, and a build asking for none is satisfied by every artifact. See -[32 — Heterogeneous Builds](32-heterogeneous-builds.md). +[42 — Heterogeneous Builds](42-heterogeneous-builds.md). ### 2.4 `[lib]` — Library Root Module Convention @@ -633,271 +633,9 @@ path = "src/capi/lua.cppm" # Override the default lib-root location ``` Default convention: `src/.cppm` (e.g. package name `mcpplibs.cmdline` → `src/cmdline.cppm`). +### 2.5 `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]` -### 2.5 `[dependencies]` — Runtime Dependencies - -```toml -# Packages under the default package namespace (mcpplibs) -[dependencies] -cmdline = "0.0.2" # Exact version -templates = "0.0.1" - -# Dotted selector: one exact identity. Everything before the final dot is the -# namespace; the final segment is the package name. -compat.gtest = "1.15.2" -imgui.core = "0.0.1" -imgui.backend.glfw_opengl3 = "0.0.1" -mcpplibs.capi.lua = "0.0.3" -``` - -```toml -# Namespace sub-table form -[dependencies.mcpplibs] -cmdline = "0.0.2" -tinyhttps = "0.2.2" -llmapi = "0.2.5" - -[dependencies.compat] -glfw = "3.4" # Explicit namespace; no fallback search -``` - -```toml -# Path dependency (local development) -[dependencies] -mylib = { path = "../mylib" } -``` - -```toml -# Git dependency — pick exactly one of tag / branch / rev -[dependencies] -mylib = { git = "https://github.com/user/mylib.git", tag = "v1.0.0" } -applib = { git = "https://github.com/user/applib.git", branch = "develop" } -``` - -```toml -# Long-form dep spec: features and backend knobs -[dependencies] -imgui = { version = "0.0.3", features = ["docking"] } # Request a feature of this dependency -widget = { version = "1.0", backend = "glfw_opengl3" } # Sugar for: features=["backend-glfw_opengl3"] -``` - -`backend = ""` is **general-purpose convention sugar**: it desugars 1:1 into -requesting the dependency's `backend-` feature (a library that supports this -knob should declare a `backend-*` family in its own `[features]`). If the target -package declares `[features]` but does not include the requested feature (including -the result of backend desugaring), a warning is issued by default, and an error -under `mcpp build --strict`. - -**Git dependencies and `mcpp.lock`**: a `tag` or `rev` already names a fixed point -in history, but a `branch` moves. The first build resolves the branch to a commit -and records it in `mcpp.lock`, and every later build rebuilds **that** commit — the -lock is authoritative, not a cache hint, so deleting `~/.mcpp/git` or moving to -another machine cannot silently move the build to a newer tip. A newer tip must be requested -explicitly: - -```bash -mcpp update mylib # drop the recorded commit; the next build re-resolves it -mcpp update # same, for every dependency -``` - -Because the recorded commit is enough to decide what to build, a rebuild with the -clone already in `~/.mcpp/git` makes no network request at all and works under -`--offline`. Only two things need the network: resolving a branch that has no -commit in the lock, and cloning a commit that is not cached yet. A `git =` value -that names a local directory (or a `file://` URL) needs neither, so it is never -refused offline. - -**SemVer constraints**: - -```toml -[dependencies] -foo = "^1.2.3" # >= 1.2.3, < 2.0.0 (caret, default) -bar = "~1.2.3" # >= 1.2.3, < 1.3.0 (tilde) -baz = "=1.2.3" # Exact match -qux = ">=1.0, <2.0" # Range combination -``` - -#### Namespace resolution rules - -Every package has a two-part identity: a **namespace** and a **name**. Every -selector normalizes to exactly one identity: - -- `cmdline` → `(mcpplibs, cmdline)`; omitting the namespace means the - `mcpplibs` default, and nothing else. -- `compat.gtest` → `(compat, gtest)`. -- `mcpplibs.capi.lua` → `(mcpplibs.capi, lua)`. - -There is no ordered fallback or fuzzy, index-wide search by short name: - -```toml -# Correct — dotted selector -[dependencies] -chriskohlhoff.asio = "1.38.1" - -# Correct — namespace sub-table (preferred for several packages from one org) -[dependencies.chriskohlhoff] -asio = "1.38.1" - -# Wrong — a bare name never reaches the `chriskohlhoff` namespace -[dependencies] -asio = "1.38.1" -``` - -The third form fails with an error that names the exact `(mcpplibs, asio)` -identity that was tried and, when the short name exists elsewhere, gives a -copyable explicit selector. - -##### Migration window for bare names (`2026.8.10.1` → removed in `2026.9`) - -Every published `compat.*` package and every manifest written before exact -identity spells its dependency bare — `gtest = "1.15.2"`. Failing those -outright on upgrade would break builds against data that is already published -and cannot be edited retroactively, so for one release a bare name that misses -`mcpplibs` still reaches `compat.`, and a descriptor that declares no -namespace at all still answers to its bare name. - -It is not quiet about it: - -``` -warning: dependency 'gtest' resolved to 'compat.gtest' through the deprecated -bare-name search; namespace omission means `mcpplibs` only. Write the exact -package: - [dependencies.compat] - gtest = "1.15.2" - (or run `mcpp add compat.gtest@1.15.2`). This fallback is removed in 2026.9. -``` - -What reaches `mcpp.lock`, the install layer and the cache is the canonical -identity, so the ambiguous spelling lives in exactly one place — the manifest — -until it is rewritten. `mcpp add gtest@1.15.2` performs that rewrite. - -The window does **not** apply to a selector that states a namespace -(`mcpplibs.gtest` misses and stays missed), and a bare name still never reaches -a third-party namespace. - -**Why one identity?** Dependency resolution has to be reproducible. Candidate -search would let two namespaces with the same short name be settled by index -state, and adding an index could silently retarget an existing dependency. - -**For xpkg authors:** in an index descriptor, identity is the pair -`(package.namespace, package.name)`. The namespace is the dotted path; **`name` is -a single atomic segment**: - -```lua -package = { - namespace = "chriskohlhoff", - name = "asio", -- one segment; NOT "chriskohlhoff.asio" -} - -package = { - namespace = "mcpplibs.capi", -- depth belongs here - name = "lua", -} -``` - -The filename is only a hint — a descriptor is found by its declared identity, so -`pkgs/c/chriskohlhoff.asio.lua` and `pkgs/z/anything.lua` resolve identically. -`.lua` or `..lua` are recommended (they hit mcpp's fast -path) but not required. - -The older fully-qualified spelling (`name = "chriskohlhoff.asio"`) is still -accepted, so already-published descriptors keep working. `mcpp xpkg parse` -enforces the descriptor rule; run it in index CI. Descriptor identity -requires mcpp >= 0.0.106; exact selectors require mcpp >= 2026.8.10.1; both use -xlings >= 0.4.69. Full normative text is in `docs/specs/package-identity.md`. - -`mcpp new --template` deliberately reuses this identity model instead of -creating another package grammar: `[ns.]name[@version][:tname]`. A bare name -there also means only `mcpplibs`; version and template may be omitted -independently. Omitted `tname` selects the sole explicit default, or the only -template when no `default = true` is present. Multiple unmarked templates are -an error, never a directory-order choice. See the normative template rows in -`docs/specs/package-identity.md` §4.4. - -#### When mcpp refreshes the package index - -`mcpp build` / `run` / `test` refresh the package index **only when a dependency -cannot be resolved from the local copy** — never merely because time has passed. -Concretely, a refresh happens when there is no local index at all, when a -dependency's descriptor is missing from it, or when a SemVer constraint matches -none of the versions it knows. A build whose dependencies all resolve locally -makes no network request, however old the local index is. - -The consequence worth knowing: a constraint like `^1.2` resolves against the -**the versions the local index knows**. A `1.3.0` published upstream after the last refresh -is invisible until it is fetched: - -```bash -mcpp index update # sync the index -mcpp update # sync, then re-resolve dependencies -mcpp index status # local state: state, age and revision -``` - -Controls, in order of precedence: - -| Control | Effect | -|---|---| -| `--offline` (any command) | Never touch the network — no index refresh, no downloads, no toolchain auto-install, no `git ls-remote`/`clone`. Anything already installed still builds, including git deps whose commit is in `mcpp.lock` and whose clone is cached | -| `MCPP_OFFLINE=1` | Same, for a whole shell session or CI job | -| `[index] auto_refresh = false` in `~/.mcpp/config.toml` | Never refresh the index automatically; downloads still work | - -`MCPP_NO_AUTO_INSTALL=1` remains accepted as the older, narrower spelling of -`--offline` (it gates only toolchain auto-install). - -Run any command with `-v` to see the decision for each dependency and why. - -### 2.6 `[dev-dependencies]` — Test Dependencies - -```toml -[dev-dependencies.compat] -gtest = "1.15.2" -``` - -`mcpp build` ignores these; `mcpp test` resolves and uses them. `mcpp test` automatically discovers `tests/**/*.cpp` and compiles them into test binaries. The runner is framework-agnostic: each file is an independent binary judged by exit code — a bare `main`, gtest (via `[dev-dependencies]` + `gtest_main`), or any other framework all work identically, and `-- args` are forwarded to every test binary (e.g. `-- --gtest_filter=...`). Note: synthesized test target names may contain `/` (`tests/00-a/0.cpp` → `00-a/0`), unlike `[targets.*]` names — the two namespaces are intentionally separate (test targets never enter the manifest or publishing). Tests are named by their `tests/`-relative path (`tests/00-a/0.cpp` → `00-a/0`), each test compiles in isolation (a broken test fails alone; package/dep breakage is reported as a build error instead), and `mcpp test ` / `--message-format json` filter and machine-format the run. - -### 2.6.1 `[build-dependencies]` — Build-Time Dependencies (mcpp 2026.8.29.1+) - -```toml -[build-dependencies] -protobuf = { version = "35.1", tools = ["protoc"] } -``` - -The section and the per-edge request answer **different questions**, and -conflating them is a modelling error rather than a matter of spelling. - -- The **section** says whether the package itself reaches the target. - `[dependencies]` means it does; `[build-dependencies]` means it never does, - and neither does anything reachable only through it. -- The **request on the edge** says which build-time product is wanted. - `tools = [...]` asks for a host executable; `host-module = true` asks for a - module the build program can import. - -A package takes a value on both axes at once, and protobuf is the case that -proves the axes must stay separate: a project links `libprotobuf` *and* needs -`protoc` during the build. It is written once, in `[dependencies]`: - -```toml -[dependencies] -protobuf = { version = "35.1", tools = ["protoc"] } -``` - -`[build-dependencies]` is for the combination the first axis cannot otherwise -express — a package whose library must not reach the target while its tool or -its rule is still wanted. Naming one package in both tables is not an error: -the ordinary declaration wins, because a `[build-dependencies]` line must not -quietly drop a library the target needs. - -Unlike `[dev-dependencies]`, these **are** walked transitively: a build -dependency's own dependencies are what make it work, and they inherit its -build-only nature. - -A feature scopes build-time requests without a second declaration site — -`[feature-deps.]` may add `tools` to a dependency already declared -unconditionally, so "only when needed" needs no separate table. - -> The section has been parsed since early versions and, until 2026.8.29.1, read -> by nothing that made a decision: writing it produced a manifest that loaded, -> no diagnostic, and no effect. +Moved to [04 — Dependencies and Resolution](04-dependencies.md). ### 2.7 `[toolchain]` — Toolchain Configuration @@ -925,7 +663,7 @@ can produce them. This section is the manifest reference. The worked examples — scaffolding, running, testing on the target, the freestanding standard-library subset and writing a board-support package — are in -[30 — Bare-Metal and Freestanding Targets](30-baremetal.md). +[40 — Bare-Metal and Freestanding Targets](40-baremetal.md). ```bash mcpp build --target riscv64-none-elf @@ -1062,7 +800,7 @@ ran and passed. `--message-format json` carries `"status":"not_run"` and a record (see [50 — Machine-Readable Output](50-machine-output.md)). ### 2.8 `[features]` — Features -Moved to [04 — Features and Capabilities](04-features-and-capabilities.md), +Moved to [05 — Features and Capabilities](05-features-and-capabilities.md), with `provides` / `requires` and `[feature-deps.]`. @@ -1334,14 +1072,14 @@ statement of intent and a CI-matrix hint, shown by `mcpp why`, never a gate. Distinct from an artifact's `accel` field on purpose. A declaration is written by hand and may be aspirational; `accel` is measured from the build that produced a binary and is what a consumer is refused against. See -[32 — Heterogeneous Builds](32-heterogeneous-builds.md). +[42 — Heterogeneous Builds](42-heterogeneous-builds.md). ### 2.13 `[xlings]` — the project's environment Moved to [23 — The Project Environment](23-the-project-environment.md). ### 2.14 Host tools from a dependency -Moved to [05 — build.mcpp](05-build-mcpp.md). +Moved to [30 — build.mcpp](30-build-mcpp.md). ### 2.15 `[resources]` — Metadata and Assets Embedded in the Artifact (2026.8.7.1+) @@ -1438,13 +1176,13 @@ o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o") .submit(); ``` -See [05 — build.mcpp](05-build-mcpp.md). Naming such a file in +See [30 — build.mcpp](30-build-mcpp.md). Naming such a file in `[build].ldflags` also "works", but ldflags is a flat string in the link command: nothing tracks it, and editing the file produces `ninja: no work to do`. ### 2.16 `[hooks]` — Project Build Lifecycle Commands -Moved to [06 — Commands by Scenario](06-commands-by-scenario.md). +Moved to [08 — Commands by Scenario](08-commands-by-scenario.md). ## Appendix A. Schema Ownership Principle (admission criteria for new fields) diff --git a/docs/04-dependencies.md b/docs/04-dependencies.md new file mode 100644 index 00000000..3d698e6e --- /dev/null +++ b/docs/04-dependencies.md @@ -0,0 +1,295 @@ +# 04 — Dependencies and Resolution + +**Reader:** an author whose build now contains more than their own code. + +**The question this chapter answers:** where does a dependency come from, what +does a version constraint mean, and what happens when two of them disagree. + +**Not here:** what makes two packages the same package — that is +[SPEC-001](specs/package-identity.md), which this chapter applies rather than +restates — and how to publish one, which is +[11 — Publishing a Library](11-publishing-a-library.md). + +Before: [03 — The mcpp.toml Manifest](03-mcpp-toml.md) is where these tables +live among the others. After: [05 — Features and Capabilities](05-features-and-capabilities.md) +is how a dependency becomes optional. + +## `[dependencies]` — Runtime Dependencies + +**The recommended form is a dotted selector with an exact version.** The dotted +form names one identity — everything before the final dot is the namespace, the +final segment is the package name — so what resolves does not depend on which +namespaces happen to be configured. + +```toml +[dependencies] +compat.gtest = "1.15.2" +imgui.core = "0.0.1" +imgui.backend.glfw_opengl3 = "0.0.1" +mcpplibs.capi.lua = "0.0.3" +``` + +A bare name is accepted and resolves through the default namespace +(`mcpplibs`), which is convenient in a project that uses only that namespace and +ambiguous in one that does not: + +```toml +[dependencies] +cmdline = "0.0.2" # resolves to mcpplibs.cmdline +``` + +
+Equivalent spelling: the namespace sub-table + +Groups entries under one namespace. It resolves to exactly the same identities +as the dotted form and is worth using when many dependencies share a namespace. + +```toml +[dependencies.mcpplibs] +cmdline = "0.0.2" +tinyhttps = "0.2.2" +llmapi = "0.2.5" + +[dependencies.compat] +glfw = "3.4" # Explicit namespace; no fallback search +``` + +
+ +```toml +# Path dependency (local development) +[dependencies] +mylib = { path = "../mylib" } +``` + +```toml +# Git dependency — pick exactly one of tag / branch / rev +[dependencies] +mylib = { git = "https://github.com/user/mylib.git", tag = "v1.0.0" } +applib = { git = "https://github.com/user/applib.git", branch = "develop" } +``` + +```toml +# Long-form dep spec: features and backend knobs +[dependencies] +imgui = { version = "0.0.3", features = ["docking"] } # Request a feature of this dependency +widget = { version = "1.0", backend = "glfw_opengl3" } # Sugar for: features=["backend-glfw_opengl3"] +``` + +`backend = ""` is **general-purpose convention sugar**: it desugars 1:1 into +requesting the dependency's `backend-` feature (a library that supports this +knob should declare a `backend-*` family in its own `[features]`). If the target +package declares `[features]` but does not include the requested feature (including +the result of backend desugaring), a warning is issued by default, and an error +under `mcpp build --strict`. + +**Git dependencies and `mcpp.lock`**: a `tag` or `rev` already names a fixed point +in history, but a `branch` moves. The first build resolves the branch to a commit +and records it in `mcpp.lock`, and every later build rebuilds **that** commit — the +lock is authoritative, not a cache hint, so deleting `~/.mcpp/git` or moving to +another machine cannot silently move the build to a newer tip. A newer tip must be requested +explicitly: + +```bash +mcpp update mylib # drop the recorded commit; the next build re-resolves it +mcpp update # same, for every dependency +``` + +Because the recorded commit is enough to decide what to build, a rebuild with the +clone already in `~/.mcpp/git` makes no network request at all and works under +`--offline`. Only two things need the network: resolving a branch that has no +commit in the lock, and cloning a commit that is not cached yet. A `git =` value +that names a local directory (or a `file://` URL) needs neither, so it is never +refused offline. + +**SemVer constraints**: + +```toml +[dependencies] +foo = "^1.2.3" # >= 1.2.3, < 2.0.0 (caret, default) +bar = "~1.2.3" # >= 1.2.3, < 1.3.0 (tilde) +baz = "=1.2.3" # Exact match +qux = ">=1.0, <2.0" # Range combination +``` + +### Namespace resolution rules + +Every package has a two-part identity: a **namespace** and a **name**. Every +selector normalizes to exactly one identity: + +- `cmdline` → `(mcpplibs, cmdline)`; omitting the namespace means the + `mcpplibs` default, and nothing else. +- `compat.gtest` → `(compat, gtest)`. +- `mcpplibs.capi.lua` → `(mcpplibs.capi, lua)`. + +There is no ordered fallback or fuzzy, index-wide search by short name: + +```toml +# Correct — dotted selector +[dependencies] +chriskohlhoff.asio = "1.38.1" + +# Correct — namespace sub-table (preferred for several packages from one org) +[dependencies.chriskohlhoff] +asio = "1.38.1" + +# Wrong — a bare name never reaches the `chriskohlhoff` namespace +[dependencies] +asio = "1.38.1" +``` + +The third form fails with an error that names the exact `(mcpplibs, asio)` +identity that was tried and, when the short name exists elsewhere, gives a +copyable explicit selector. + +#### Migration window for bare names (`2026.8.10.1` → removed in `2026.9`) + +Every published `compat.*` package and every manifest written before exact +identity spells its dependency bare — `gtest = "1.15.2"`. Failing those +outright on upgrade would break builds against data that is already published +and cannot be edited retroactively, so for one release a bare name that misses +`mcpplibs` still reaches `compat.`, and a descriptor that declares no +namespace at all still answers to its bare name. + +It is not quiet about it: + +``` +warning: dependency 'gtest' resolved to 'compat.gtest' through the deprecated +bare-name search; namespace omission means `mcpplibs` only. Write the exact +package: + [dependencies.compat] + gtest = "1.15.2" + (or run `mcpp add compat.gtest@1.15.2`). This fallback is removed in 2026.9. +``` + +What reaches `mcpp.lock`, the install layer and the cache is the canonical +identity, so the ambiguous spelling lives in exactly one place — the manifest — +until it is rewritten. `mcpp add gtest@1.15.2` performs that rewrite. + +The window does **not** apply to a selector that states a namespace +(`mcpplibs.gtest` misses and stays missed), and a bare name still never reaches +a third-party namespace. + +**Why one identity?** Dependency resolution has to be reproducible. Candidate +search would let two namespaces with the same short name be settled by index +state, and adding an index could silently retarget an existing dependency. + +**For xpkg authors:** in an index descriptor, identity is the pair +`(package.namespace, package.name)`. The namespace is the dotted path; **`name` is +a single atomic segment**: + +```lua +package = { + namespace = "chriskohlhoff", + name = "asio", -- one segment; NOT "chriskohlhoff.asio" +} + +package = { + namespace = "mcpplibs.capi", -- depth belongs here + name = "lua", +} +``` + +The filename is only a hint — a descriptor is found by its declared identity, so +`pkgs/c/chriskohlhoff.asio.lua` and `pkgs/z/anything.lua` resolve identically. +`.lua` or `..lua` are recommended (they hit mcpp's fast +path) but not required. + +The older fully-qualified spelling (`name = "chriskohlhoff.asio"`) is still +accepted, so already-published descriptors keep working. `mcpp xpkg parse` +enforces the descriptor rule; run it in index CI. Descriptor identity +requires mcpp >= 0.0.106; exact selectors require mcpp >= 2026.8.10.1; both use +xlings >= 0.4.69. Full normative text is in `docs/specs/package-identity.md`. + +`mcpp new --template` deliberately reuses this identity model instead of +creating another package grammar: `[ns.]name[@version][:tname]`. A bare name +there also means only `mcpplibs`; version and template may be omitted +independently. Omitted `tname` selects the sole explicit default, or the only +template when no `default = true` is present. Multiple unmarked templates are +an error, never a directory-order choice. See the normative template rows in +`docs/specs/package-identity.md` §4.4. + +### When mcpp refreshes the package index + +`mcpp build` / `run` / `test` refresh the package index **only when a dependency +cannot be resolved from the local copy** — never merely because time has passed. +Concretely, a refresh happens when there is no local index at all, when a +dependency's descriptor is missing from it, or when a SemVer constraint matches +none of the versions it knows. A build whose dependencies all resolve locally +makes no network request, however old the local index is. + +The consequence worth knowing: a constraint like `^1.2` resolves against the +**the versions the local index knows**. A `1.3.0` published upstream after the last refresh +is invisible until it is fetched: + +```bash +mcpp index update # sync the index +mcpp update # sync, then re-resolve dependencies +mcpp index status # local state: state, age and revision +``` + +Controls, in order of precedence: + +| Control | Effect | +|---|---| +| `--offline` (any command) | Never touch the network — no index refresh, no downloads, no toolchain auto-install, no `git ls-remote`/`clone`. Anything already installed still builds, including git deps whose commit is in `mcpp.lock` and whose clone is cached | +| `MCPP_OFFLINE=1` | Same, for a whole shell session or CI job | +| `[index] auto_refresh = false` in `~/.mcpp/config.toml` | Never refresh the index automatically; downloads still work | + +`MCPP_NO_AUTO_INSTALL=1` remains accepted as the older, narrower spelling of +`--offline` (it gates only toolchain auto-install). + +Run any command with `-v` to see the decision for each dependency and why. + +## `[dev-dependencies]` — Test Dependencies + +```toml +[dev-dependencies.compat] +gtest = "1.15.2" +``` + +`mcpp build` ignores these; `mcpp test` resolves and uses them. `mcpp test` automatically discovers `tests/**/*.cpp` and compiles them into test binaries. The runner is framework-agnostic: each file is an independent binary judged by exit code — a bare `main`, gtest (via `[dev-dependencies]` + `gtest_main`), or any other framework all work identically, and `-- args` are forwarded to every test binary (e.g. `-- --gtest_filter=...`). Note: synthesized test target names may contain `/` (`tests/00-a/0.cpp` → `00-a/0`), unlike `[targets.*]` names — the two namespaces are intentionally separate (test targets never enter the manifest or publishing). Tests are named by their `tests/`-relative path (`tests/00-a/0.cpp` → `00-a/0`), each test compiles in isolation (a broken test fails alone; package/dep breakage is reported as a build error instead), and `mcpp test ` / `--message-format json` filter and machine-format the run. + +## `[build-dependencies]` — Build-Time Dependencies (mcpp 2026.8.29.1+) + +```toml +[build-dependencies] +protobuf = { version = "35.1", tools = ["protoc"] } +``` + +The section and the per-edge request answer **different questions**, and +conflating them is a modelling error rather than a matter of spelling. + +- The **section** says whether the package itself reaches the target. + `[dependencies]` means it does; `[build-dependencies]` means it never does, + and neither does anything reachable only through it. +- The **request on the edge** says which build-time product is wanted. + `tools = [...]` asks for a host executable; `host-module = true` asks for a + module the build program can import. + +A package takes a value on both axes at once, and protobuf is the case that +proves the axes must stay separate: a project links `libprotobuf` *and* needs +`protoc` during the build. It is written once, in `[dependencies]`: + +```toml +[dependencies] +protobuf = { version = "35.1", tools = ["protoc"] } +``` + +`[build-dependencies]` is for the combination the first axis cannot otherwise +express — a package whose library must not reach the target while its tool or +its rule is still wanted. Naming one package in both tables is not an error: +the ordinary declaration wins, because a `[build-dependencies]` line must not +quietly drop a library the target needs. + +Unlike `[dev-dependencies]`, these **are** walked transitively: a build +dependency's own dependencies are what make it work, and they inherit its +build-only nature. + +A feature scopes build-time requests without a second declaration site — +`[feature-deps.]` may add `tools` to a dependency already declared +unconditionally, so "only when needed" needs no separate table. + +> The section has been parsed since early versions and, until 2026.8.29.1, read +> by nothing that made a decision: writing it produced a manifest that loaded, +> no diagnostic, and no effect. diff --git a/docs/04-features-and-capabilities.md b/docs/05-features-and-capabilities.md similarity index 98% rename from docs/04-features-and-capabilities.md rename to docs/05-features-and-capabilities.md index 34b0458a..17a00e24 100644 --- a/docs/04-features-and-capabilities.md +++ b/docs/05-features-and-capabilities.md @@ -1,13 +1,13 @@ -# 04 — Features and Capabilities +# 05 — Features and Capabilities Features are how a package offers something optional: a compile macro, an extra source, an extra dependency, or a choice between backends. This chapter is the reference for declaring them and for consuming them. -Related documents: [02 — mcpp.toml](02-mcpp-toml.md) is the field reference for +Related documents: [03 — mcpp.toml](03-mcpp-toml.md) is the field reference for the rest of the manifest; [`examples/11-features`](../examples/11-features/) is a package that declares all three shapes and a test that uses a -dev-dependency; [32 — Heterogeneous Builds](32-heterogeneous-builds.md) is the +dev-dependency; [42 — Heterogeneous Builds](42-heterogeneous-builds.md) is the largest consumer of the mechanism, because every accelerator lane is a feature. ## `[features]` — Features (Cargo-style, additive) @@ -71,7 +71,7 @@ simd = { sources = ["src/simd/**"], flags = [ - The automatic `-DMCPP_FEATURE_` is still defined for every active feature, so `defines` are additive to it. - `flags` (mcpp 0.0.101+) is the same ordered array-of-inline-tables grammar as - `[build].flags` ([02 §2.3](02-mcpp-toml.md): `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ + `[build].flags` ([03 §2.3](03-mcpp-toml.md): `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ `defines`; the `[[features..flags]]` array-of-tables spelling is accepted too, like `[[build.flags]]`). When the feature is active the entries are appended **after** the base `[build].flags`, features in name order, so a @@ -102,7 +102,7 @@ sources -- never scanned for imports, never a BMI, compiled by something mcpp does not drive. This is the same shape as `[build] module_extensions`: mcpp knows what a device source *is* and does not know that `.cu` is CUDA, so a NEW device language costs no engine change. It is what makes -[32 — Heterogeneous Builds](32-heterogeneous-builds.md)' claim that "a sixth +[42 — Heterogeneous Builds](42-heterogeneous-builds.md)' claim that "a sixth backend is a package rather than an engine change" true rather than aspirational; `.slang` was removed from mcpp's built-in table and now arrives this way. diff --git a/docs/03-workspace.md b/docs/06-workspace.md similarity index 99% rename from docs/03-workspace.md rename to docs/06-workspace.md index e6f86b64..cf142dd5 100644 --- a/docs/03-workspace.md +++ b/docs/06-workspace.md @@ -1,4 +1,4 @@ -# 03 — Workspaces +# 06 — Workspaces A workspace organizes multiple related mcpp packages (libraries or applications) within a single repository. Member packages share a unified set of dependency versions and toolchain settings while each keeping its own `mcpp.toml` project file. diff --git a/docs/07-testing.md b/docs/07-testing.md new file mode 100644 index 00000000..38e827e5 --- /dev/null +++ b/docs/07-testing.md @@ -0,0 +1,124 @@ +# 07 — Testing + +**Reader:** anyone with code that has to keep working. + +**The question this chapter answers:** how tests are written and run, what mcpp +considers a test, and how to test something that does not run on this machine. + +**Not here:** how a runner reaches a device — that is +[41 — Reaching a Device](41-devices.md) — and the schema of the machine-readable +stream, which is [50 — Machine-Readable Output](50-machine-output.md). This +chapter states which flag produces it and stops there. + +Before: [04 — Dependencies and Resolution](04-dependencies.md) covers +`[dev-dependencies]`, which is how a test reaches a package the artifact does +not. After: [08 — Commands by Scenario](08-commands-by-scenario.md) is the +lookup for everything else. + +## What mcpp considers a test + +Every `tests/**/*.cpp` is a test: mcpp compiles each one into its own program +and runs it. A test passes when its program exits zero. + +``` +myproject/ + mcpp.toml + src/… + tests/ + test_parse.cpp one program + unit/test_span.cpp another +``` + +There is no framework and no registration. A test may use one — `[dev-dependencies]` +is how it reaches it — but the contract mcpp holds is the exit code, which is +also why a test written for another framework needs no adapter. + +`mcpp new` scaffolds `tests/test_smoke.cpp` so a project starts with the +directory in place. + +## Running them + +```bash +mcpp test # build and run every test +mcpp test parse # only those whose name matches +mcpp test --list # what would run, without building or running it +mcpp test -- --verbose # everything after `--` goes to each test binary +``` + +Tests build with the same axes as `mcpp build`, so a test runs against the +configuration it is meant to check rather than against the default one: + +| flag | what it selects | +|---|---| +| `--profile ` | `dev` (default), `release`, `dist`, or a `[profile.*]` the manifest declares | +| `--features ` | the feature set for the test build | +| `--target ` | a target other than the host | +| `--accel ` / `--no-accel` | the device backends the build targets | +| `--cap ` | pin a capability provider | + +`--timeout ` kills a test still running (default 300; `0` disables it) and +`--build-timeout ` bounds the compile. A test that hangs is reported as a +failure with its own name, not as a job that stopped. + +## Tests that reach packages the artifact does not + +```toml +[dev-dependencies] +counters = { path = "../counters" } +``` + +A `[dev-dependencies]` entry is resolved for the test build and for nothing +else: it is not in the artifact, and a consumer of the package never sees it. +That is the difference between a test's dependency and the package's own, and it +is why a test framework does not become part of what a library ships. + +[`examples/11-features`](../examples/11-features/) declares one and uses it. + +## Testing on a target this machine cannot run + +A test for a cross target or a bare-metal board is compiled for that target and +executed through a **runner** — an argv a board-support package supplies, which +mcpp performs with the test binary appended. + +```bash +mcpp test --target thumbv7em-none-eabihf # built for the board, run through its runner +mcpp test --no-runner # ignore the runner and execute directly +``` + +Nothing about the test changes. The same `tests/**/*.cpp` compiles for the +device, and the verdict is still the exit code — which is why a semihosting exit +or a QEMU exit code is what a bare-metal runner is chosen to produce. + +`--no-runner` exists for a host that can execute the binaries natively and +should not pay for an emulator. + +The runner itself, named runners, and what a board declares are +[41 — Reaching a Device](41-devices.md). + +## Tests that cannot run beside each other + +`mcpp test` runs test programs on a worker pool. One board on one probe, one +GPU, one serial port or a single-seat licence admits one user at a time, and two +workers reaching for it interleave rather than fail. + +The package that owns the resource states this about itself, and `mcpp test` +then serialises those tests. A project never has to remember `-j1`. + +## Reporting to a program + +```bash +mcpp test --message-format json +``` + +One NDJSON record per test, for a CI job or an editor. The schema and its +version are [50 — Machine-Readable Output](50-machine-output.md); what belongs +here is only that the flag exists and that the human format is the default. + +## Current limitations + +- A test is one `.cpp` producing one program. mcpp does not discover cases + inside a file, so a framework's per-case selection happens inside the program, + through arguments after `--`. +- `--build-timeout` is POSIX-only. +- `--workspace-timeout` bounds a `--workspace` fan-out and reports what did run; + it does not attribute the timeout to a member. diff --git a/docs/06-commands-by-scenario.md b/docs/08-commands-by-scenario.md similarity index 99% rename from docs/06-commands-by-scenario.md rename to docs/08-commands-by-scenario.md index bbd9b676..6de8075e 100644 --- a/docs/06-commands-by-scenario.md +++ b/docs/08-commands-by-scenario.md @@ -1,4 +1,4 @@ -# 06 — Commands by Scenario +# 08 — Commands by Scenario The command reference is `mcpp --help`, and each subcommand carries its own `--help`. This chapter answers a different question: which command applies to a @@ -7,7 +7,7 @@ resolution nobody expected, a descriptor about to be published, an index that may be stale. The commands collected here are the ones whose name does not announce the situation they belong to. -Related documents: [00 — Getting Started](00-getting-started.md) for the +Related documents: [01 — Getting Started](01-getting-started.md) for the everyday build/test loop, [20 — Toolchain Management](20-toolchains.md), [11 — Publishing a Library](11-publishing-a-library.md), [50 — Machine-Readable Output](50-machine-output.md). @@ -334,7 +334,7 @@ outside 1–86400, a key offered to the wrong interval — is a manifest error. > **A hook is code, and `mcpp.toml` is part of the repository.** Building a > freshly cloned project runs whatever its `[hooks]` say, with the privileges > of whoever invoked `mcpp build`. This is the same trust `build.mcpp` already -> asks for ([05 — build.mcpp](05-build-mcpp.md)); `[hooks]` widens its reach +> asks for ([30 — build.mcpp](30-build-mcpp.md)); `[hooks]` widens its reach > rather than introducing it. Hook programs can be installed as ordinary xlings dependencies. For example, diff --git a/docs/10-pack-and-release.md b/docs/10-pack-and-release.md index bb8588e1..4b729dd2 100644 --- a/docs/10-pack-and-release.md +++ b/docs/10-pack-and-release.md @@ -85,7 +85,7 @@ ELF in the bundle for anything under `$MCPP_HOME` and fails on a hit. machine: a graphics driver's user-space half is version-locked to the running kernel module, and for the proprietary stacks redistribution is not permitted. Declare those as run-phase capability requirements (§2.11 of -`docs/02-mcpp-toml.md`), and the mode table gains a column: +`docs/03-mcpp-toml.md`), and the mode table gains a column: | Mode | Program needing a host-provided capability | |---|---| @@ -325,7 +325,7 @@ force_bundle` still overrides this, as it does the ELF skip list. `vcruntime140.dll` and `msvcp140.dll` are **not** Windows' own: they belong to the MSVC toolset, exactly as `libstdc++.so` belongs to gcc. Whether they -travel is decided by `cxx_runtime` (see `docs/02-mcpp-toml.md`), not by this +travel is decided by `cxx_runtime` (see `docs/03-mcpp-toml.md`), not by this list — and `mcpp pack` refuses a combination that cannot deliver what the contract promised: diff --git a/docs/20-toolchains.md b/docs/20-toolchains.md index 2b6ee965..190fbd9c 100644 --- a/docs/20-toolchains.md +++ b/docs/20-toolchains.md @@ -212,7 +212,7 @@ It uses the regular GCC module pipeline (`gcm.cache`, `import std` via libstdc++'s `bits/std.cc`). The target's default linkage is **static** — the produced `.exe` is fully self-contained (no `libstdc++-6.dll` to ship, runs directly under wine). To opt out, set it on the target section — -`linkage` is exact-triple only (§2.7 of [mcpp.toml](02-mcpp-toml.md)), and a +`linkage` is exact-triple only (§2.7 of [mcpp.toml](03-mcpp-toml.md)), and a `[build] linkage` key does not exist and is silently ignored: ```toml diff --git a/docs/21-the-target-triple.md b/docs/21-the-target-triple.md index 70a07a51..c84a25ba 100644 --- a/docs/21-the-target-triple.md +++ b/docs/21-the-target-triple.md @@ -500,7 +500,7 @@ runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic", `sysroot = ""` selects the zero-libc tier: no C library on the compile line and none on the link. An absent `sysroot` key is a different answer — it inherits -the target row's own default. See [chapter 13](30-baremetal.md). +the target row's own default. See [chapter 13](40-baremetal.md). ## Reference diff --git a/docs/22-target-side.md b/docs/22-target-side.md index 5fdccc93..0ba6acb7 100644 --- a/docs/22-target-side.md +++ b/docs/22-target-side.md @@ -342,7 +342,7 @@ The selector `` has three forms: |---|---|---| | **bare OS alias** | a single OS / family — the concise, common form | `[target.windows]`, `[target.unix]` | | **`cfg(...)` predicate** | a compound condition (arch / env / combinators) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see [02 §2.7.3](02-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | +| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see [03 §2.7.3](03-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | A selector may carry platform-conditional **dependencies** and **build flags**: @@ -365,7 +365,7 @@ there is no ambiguity. Use the bare form for a single OS/family; use `cfg(...)` for arch/env conditions and combinators. - **Keys**: `dependencies` / `dev-dependencies` / `build-dependencies` / - `feature-deps.` (mcpp 2026.8.6.2+ — see [05 — build.mcpp](05-build-mcpp.md); the feature is + `feature-deps.` (mcpp 2026.8.6.2+ — see [30 — build.mcpp](30-build-mcpp.md); the feature is registered unconditionally, only its dependency set is scoped), and `build` with `cflags` / `cxxflags` / `ldflags` / `sources` (mcpp 0.0.95+ — conditional source globs, e.g. gating `src/x86/**/*.asm` behind @@ -460,7 +460,7 @@ A build program can ask which C library **payload** supplies the sysroot: `mcpp::target_libc()` returns that package's name and `mcpp::target_libc_profile()` the sub-directory for the target's ISA profile. Both are empty on the zero-libc tier. See -[30 — Bare-Metal and Freestanding Targets](30-baremetal.md). +[40 — Bare-Metal and Freestanding Targets](40-baremetal.md). **That is not the same question as "which C library did the target side resolve to".** `target_libc()` names the payload mcpp installed, and that value diff --git a/docs/23-the-project-environment.md b/docs/23-the-project-environment.md index 51013f26..840df350 100644 --- a/docs/23-the-project-environment.md +++ b/docs/23-the-project-environment.md @@ -110,7 +110,7 @@ Where a project and a dependency name **one package**, one version of it is installed: identity is `(namespace, name)` and the version is a constraint on it. The declaration nearer the artifact wins and the override is reported; a pin that fails a requirement the other side stated is refused naming both. -See *One package, one version* in [02 — mcpp.toml](02-mcpp-toml.md). +See *One package, one version* in [03 — mcpp.toml](03-mcpp-toml.md). ## 4. Reading an environment, never creating one @@ -175,7 +175,7 @@ downloaded: this chapter. Omitting the tier is the historical behaviour. **The runner.** A program under `[xlings.workspace]` is also where `[target.].runner` looks first for its first element, before `PATH` -([02 §2.7.3](02-mcpp-toml.md)). The two keys together provision a user-mode emulator on a +([03 §2.7.3](03-mcpp-toml.md)). The two keys together provision a user-mode emulator on a CI host and execute a cross-built artifact through it, without the manifest naming the payload's path. @@ -509,8 +509,8 @@ used it. ## 8. Related chapters -- [05 - build.mcpp](05-build-mcpp.md) — the contract a build program receives, +- [30 - build.mcpp](30-build-mcpp.md) — the contract a build program receives, including the `PATH` it runs with. - [91 - Toolchain Internals](91-toolchain-internals.md) — runtime selection, the `RuntimeBinding` snapshot, and the degradation rules. -- [02 - mcpp.toml](02-mcpp-toml.md) — the rest of the manifest. +- [03 - mcpp.toml](03-mcpp-toml.md) — the rest of the manifest. diff --git a/docs/24-openkal-cross.md b/docs/24-openkal-cross.md index 3abe188f..2b35a216 100644 --- a/docs/24-openkal-cross.md +++ b/docs/24-openkal-cross.md @@ -207,7 +207,7 @@ runner = ["qemu-aarch64-static"] Without the key, `mcpp run` reports the kernel's refusal (`Exec format error`) and the key to write, and `mcpp test` reports every test as not run and exits 2. A host that executes the artifact natively passes `--no-runner`. The rules -are in [02 — mcpp.toml](02-mcpp-toml.md), §2.7.3. +are in [03 — mcpp.toml](03-mcpp-toml.md), §2.7.3. ### The Source Is The Same, The Program Is Not diff --git a/docs/05-build-mcpp.md b/docs/30-build-mcpp.md similarity index 99% rename from docs/05-build-mcpp.md rename to docs/30-build-mcpp.md index fb5f6064..a1e966c2 100644 --- a/docs/05-build-mcpp.md +++ b/docs/30-build-mcpp.md @@ -1,6 +1,6 @@ -# 05 — Build Programs: `build.mcpp` +# 30 — Build Programs: `build.mcpp` -**English** | [简体中文](zh/05-build-mcpp.md) +**English** | [简体中文](zh/30-build-mcpp.md) Most projects need nothing more than `mcpp.toml`. When build-time logic is required — probe the host, generate a source, decide a flag from the environment — put a @@ -345,7 +345,7 @@ could not state a floor and every project repeated its rule's package list. version this build actually installed rather than the one the local manifest wrote. One package means one version: where a project and a rule both name it, the declaration nearer the artifact wins and both sides are told the same -answer. See *One package, one version* in [02 — mcpp.toml](02-mcpp-toml.md). +answer. See *One package, one version* in [03 — mcpp.toml](03-mcpp-toml.md). **`[feature-xlings.]` is answered too, while `` is active** *(2026.9.6.2+)*. That table has provisioned its packages since it existed -- @@ -524,7 +524,7 @@ link the command would never run and the build would say nothing. > should not be used for anything the build produces: ldflags is a flat string > in the link command, not a file in the graph, so nothing tracks it and editing > it reports `ninja: no work to do`. For Windows resources specifically, use -> [`[resources]`](02-mcpp-toml.md) — +> [`[resources]`](03-mcpp-toml.md) — > `object` is the escape hatch for everything else. **You must name the output files.** mcpp fixes the source set, the fingerprint @@ -712,7 +712,7 @@ and when to want one; `examples/07-project-subos/` is a working project. A rule — "run protoc over these `.proto` files", "run clang-tidy over these sources" — belongs in a package, not copy-pasted into every consumer's `build.mcpp`. The mechanism is -[`host-module = true`](02-mcpp-toml.md); this section is about the shape of +[`host-module = true`](03-mcpp-toml.md); this section is about the shape of what goes inside. The guidance below generalises from `mcpplibs.grpcgen`, the first such package, @@ -764,7 +764,7 @@ under the package name, so `export module mcpp.rules.spirv;` is what a consumer then imports. Official plugins live in one package, `mcpp:plugins` (repository `mcpp-community/mcpp-plugins`): rule packages are named `mcpp.rules.`, build-time utilities `mcpp.tools.`, and each member is selected by a feature -of that package (see [`host-module = true`](02-mcpp-toml.md)). `mcpp.build.*` +of that package (see [`host-module = true`](03-mcpp-toml.md)). `mcpp.build.*` is the engine's own module family and is not used for plugins. The engine cannot tell who is official, so it keys the check on the package *namespace* and warns when the two disagree — @@ -892,7 +892,7 @@ When nothing changed the output is `build.mcpp up to date (cached)`; otherwise `mcpp build --target ` the program is compiled with a host-resolved toolchain, runs on the host, and sees `MCPP_TARGET` = the cross triple. For purely declarative target gating, `[target.'cfg(...)']` tables remain - the first choice — see [02 - mcpp.toml Manifest Guide](02-mcpp-toml.md). + the first choice — see [03 - mcpp.toml Manifest Guide](03-mcpp-toml.md). - **CWD is the project root**, so relative paths (`src/generated.cpp`) land where expected. - A non-zero exit from `build.mcpp` aborts the build and prints its output. @@ -957,7 +957,7 @@ grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } Each name must be a `kind = "bin"` target of that package. mcpp builds it **for the build machine** and hands `build.mcpp` its absolute path as `MCPP_DEP__BIN_` — read it with `mcpp::dep_bin("protobuf", "protoc")` -(see [05 — build.mcpp](05-build-mcpp.md)). +(see [30 — build.mcpp](30-build-mcpp.md)). Four properties worth knowing: @@ -1079,7 +1079,7 @@ plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } ``` **`[build-dependencies]`, not `[dependencies]`** — a rule package is the case -[02 §2.6.1](02-mcpp-toml.md) describes exactly: its library must never reach the target while its +[03 §2.6.1](03-mcpp-toml.md) describes exactly: its library must never reach the target while its rule is still wanted. The two axes are separate, so `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" on the second axis, and the diff --git a/docs/40-authoring-a-rule-package.md b/docs/31-authoring-a-rule-package.md similarity index 96% rename from docs/40-authoring-a-rule-package.md rename to docs/31-authoring-a-rule-package.md index 0eebbf5d..2f87cc30 100644 --- a/docs/40-authoring-a-rule-package.md +++ b/docs/31-authoring-a-rule-package.md @@ -1,13 +1,13 @@ -# 40 — Authoring a Rule Package +# 31 — Authoring a Rule Package A rule package teaches mcpp to build something the engine has no rule for: a device language, a shader, a generated interface, a check. It is an ordinary package whose module a consumer's build program imports. -Related documents: [05 — build.mcpp](05-build-mcpp.md) is the reference for the -directive protocol and the typed API this chapter uses; [04 — Features and -Capabilities](04-features-and-capabilities.md) covers the feature keys; -[32 — Heterogeneous Builds](32-heterogeneous-builds.md) is where the shipped +Related documents: [30 — build.mcpp](30-build-mcpp.md) is the reference for the +directive protocol and the typed API this chapter uses; [05 — Features and +Capabilities](05-features-and-capabilities.md) covers the feature keys; +[42 — Heterogeneous Builds](42-heterogeneous-builds.md) is where the shipped rules are listed. Two examples: [`08-build-rules`](../examples/08-build-rules/) for a rule that checks and embeds, [`12-a-new-device-language`](../examples/12-a-new-device-language/) for @@ -166,7 +166,7 @@ neither. A bare version is a **choice** a project may override; `>=` is a **requirement** a project may not go below. See *One package, one version* in -[02 — mcpp.toml](02-mcpp-toml.md). +[03 — mcpp.toml](03-mcpp-toml.md). ## Generating an island's boundary @@ -247,4 +247,4 @@ path. `mcpp xpkg parse` reports that key as unknown and exits 1. No descriptor in `mcpp-index` uses it (0 of 218); a package keeping its own `mcpp.toml` omits the `mcpp` field entirely. See *Current limitations* in - [06 — Commands by Scenario](06-commands-by-scenario.md). + [08 — Commands by Scenario](08-commands-by-scenario.md). diff --git a/docs/30-baremetal.md b/docs/40-baremetal.md similarity index 99% rename from docs/30-baremetal.md rename to docs/40-baremetal.md index 57f252ad..c1aaf375 100644 --- a/docs/30-baremetal.md +++ b/docs/40-baremetal.md @@ -1,12 +1,12 @@ -# 30 — Bare-Metal and Freestanding Targets +# 40 — Bare-Metal and Freestanding Targets This document describes how mcpp builds, runs and tests software for targets with no operating system underneath, and how a board-support package supplies the parts of such a target that the engine deliberately does not know. -Related documents: [02 — mcpp.toml Manifest Guide](02-mcpp-toml.md) §2.7.2 is -the reference for the `[target.]` keys used here; [05 — build.mcpp -Build Program](05-build-mcpp.md) is the reference for the directive protocol a +Related documents: [03 — mcpp.toml Manifest Guide](03-mcpp-toml.md) §2.7.2 is +the reference for the `[target.]` keys used here; [30 — build.mcpp +Build Program](30-build-mcpp.md) is the reference for the directive protocol a board-support package speaks; [91 — Toolchain Internals](91-toolchain-internals.md) covers the hosted link model this chapter departs from. @@ -632,7 +632,7 @@ The key is not specific to bare metal. A hosted cross target — an `qemu-aarch64-static` in place of the system emulator; on such a target an absent runner is not an error until the kernel refuses the artifact. The rules for hosted targets, the `--no-runner` escape and the not-run reporting of -`mcpp test` are in [02 — mcpp.toml](02-mcpp-toml.md), §2.7.3. +`mcpp test` are in [03 — mcpp.toml](03-mcpp-toml.md), §2.7.3. ## Writing a board-support package @@ -671,7 +671,7 @@ if (const char* dir = mcpp::xpkg_dir("xim", "qemu-riscv"); dir && *dir) { Without that line the build succeeds, configures no runner, and `mcpp run` reports a missing runner with advice about writing a `runner` key — true in general, and not the cause here. See `mcpp:warning=` in -[05 — build.mcpp](05-build-mcpp.md). +[30 — build.mcpp](30-build-mcpp.md). ### The directives a board-support package emits diff --git a/docs/31-devices.md b/docs/41-devices.md similarity index 97% rename from docs/31-devices.md rename to docs/41-devices.md index fbf38239..cd77e55b 100644 --- a/docs/31-devices.md +++ b/docs/41-devices.md @@ -1,12 +1,12 @@ -# 31 — Reaching a Device +# 41 — Reaching a Device This document specifies how mcpp executes an artifact that runs somewhere other than the machine that built it, how a package supplies additional ways of reaching it, and how a project selects between an emulator and physical hardware. -Related documents: [30 — Bare-Metal and Freestanding Targets](30-baremetal.md) -covers the targets this most often applies to; [05 — build.mcpp](05-build-mcpp.md) +Related documents: [40 — Bare-Metal and Freestanding Targets](40-baremetal.md) +covers the targets this most often applies to; [30 — build.mcpp](30-build-mcpp.md) is the reference for the directive protocol a package speaks; [50 — Machine Output](50-machine-output.md) is the interface a debugger client or IDE uses. diff --git a/docs/32-heterogeneous-builds.md b/docs/42-heterogeneous-builds.md similarity index 99% rename from docs/32-heterogeneous-builds.md rename to docs/42-heterogeneous-builds.md index e785d9f3..e55c1b69 100644 --- a/docs/32-heterogeneous-builds.md +++ b/docs/42-heterogeneous-builds.md @@ -1,4 +1,4 @@ -# 32 — Heterogeneous Builds +# 42 — Heterogeneous Builds GPU and AI accelerator targets, and mixed host/device compilation: how mcpp builds device code, and how a prebuilt artifact states which devices it can @@ -107,7 +107,7 @@ and less clearly. The table above is what mcpp knows **without being told**: the languages whose support shipped before a package could declare one. A rule package adds to it, through `[features]..device_extensions` (see -[02 — mcpp.toml](02-mcpp-toml.md) §2.8), and that is how a NEW device language +[03 — mcpp.toml](03-mcpp-toml.md) §2.8), and that is how a NEW device language arrives -- with no engine change and no engine release. Slang is the first: `.slang` is not in the list above, and `mcpp:plugins`' `rules-slang` declares it. @@ -240,7 +240,7 @@ object rather than in an array, and there is then nothing to take the size of. The command that invokes a device compiler is not built into mcpp. It is supplied by a **build-rule package**, consumed with `host-module = true`, which emits build-graph edges whose outputs join the link. See -[05 — build.mcpp](05-build-mcpp.md) for the mechanism and +[30 — build.mcpp](30-build-mcpp.md) for the mechanism and `examples/09-heterogeneous/cuda` for a working CUDA rule. The division is deliberate. mcpp owns the graph, the artifact's identity and @@ -293,7 +293,7 @@ the machines it will run on: The nearer declaration wins, one version is installed, and mcpp says which. A pin that does not satisfy the rule's floor is refused naming both sides rather than installed alongside it. See *One package, one version* in -[02 — mcpp.toml](02-mcpp-toml.md) for the full rule; +[03 — mcpp.toml](03-mcpp-toml.md) for the full rule; `examples/09-heterogeneous/multi-backend` is the one example in this repository that takes the override path, and every other one writes only the edge. @@ -331,7 +331,7 @@ version is insufficient for CUDA runtime version"*. The rule reads the driver's version through the driver's own library (reached through the sentinel package, never through `/usr/lib`) and states it as a fact; it states the floor its runtime needs; and the engine compares the two before anything -is compiled -- see the probe channel in [05 — build.mcpp](05-build-mcpp.md). +is compiled -- see the probe channel in [30 — build.mcpp](30-build-mcpp.md). The engine reads a name, a relation and a version; `cuda.driver` is data flowing through. diff --git a/docs/90-build-from-source.md b/docs/90-build-from-source.md index ef5d8e99..b32b0318 100644 --- a/docs/90-build-from-source.md +++ b/docs/90-build-from-source.md @@ -5,7 +5,7 @@ ## Prerequisites -Follow [00 — Getting Started](00-getting-started.md) to install a working copy of mcpp, then clone the repository: +Follow [01 — Getting Started](01-getting-started.md) to install a working copy of mcpp, then clone the repository: ```bash git clone https://github.com/mcpp-community/mcpp diff --git a/docs/91-toolchain-internals.md b/docs/91-toolchain-internals.md index 520c9afa..eaddd84a 100644 --- a/docs/91-toolchain-internals.md +++ b/docs/91-toolchain-internals.md @@ -530,7 +530,7 @@ The machinery is already arch-parameterized; the work is data: ### 7.3 Embedded and bare-metal toolchains `riscv64-none-elf` and `riscv32-none-elf` are implemented, and the user-facing -account is [30 — Bare-Metal and Freestanding Targets](30-baremetal.md). This +account is [40 — Bare-Metal and Freestanding Targets](40-baremetal.md). This section records how the resulting shape relates to the hosted model above. Three of this section's earlier predictions held: diff --git a/docs/README.md b/docs/README.md index 8dd3abd1..4adbe028 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,13 +25,13 @@ anything a reader needs is written here or in a specification instead. | To | Read | Run | |---|---|---| -| write a program | [00](00-getting-started.md), [02](02-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | -| write a library others import | [11](11-publishing-a-library.md), [04](04-features-and-capabilities.md), [02](02-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | +| write a program | [01](01-getting-started.md), [03](03-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | +| write a library others import | [11](11-publishing-a-library.md), [05](05-features-and-capabilities.md), [03](03-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | | publish it | [10](10-pack-and-release.md), [11](11-publishing-a-library.md), [12](12-binary-distribution.md) | [`03-pack-static`](../examples/03-pack-static/), [`05-lib-distribution`](../examples/05-lib-distribution/) | -| build for another machine | [21](21-the-target-triple.md), [24](24-openkal-cross.md), [30](30-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | -| use a GPU or an accelerator | [32](32-heterogeneous-builds.md), [31](31-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | -| add a rule, a language or a generator | [40](40-authoring-a-rule-package.md), [05](05-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`12-a-new-device-language`](../examples/12-a-new-device-language/) | -| add a package to the index | [11](11-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [06](06-commands-by-scenario.md) — the publishing scenarios | +| build for another machine | [21](21-the-target-triple.md), [24](24-openkal-cross.md), [40](40-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | +| use a GPU or an accelerator | [42](42-heterogeneous-builds.md), [41](41-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | +| add a rule, a language or a generator | [31](31-authoring-a-rule-package.md), [30](30-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`12-a-new-device-language`](../examples/12-a-new-device-language/) | +| add a package to the index | [11](11-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [08](08-commands-by-scenario.md) — the publishing scenarios | | change mcpp itself | [90](90-build-from-source.md), [92](92-release.md), [51](51-supported-versions.md) | — | Lessons also arrive as project templates, which a package ships and `mcpp new @@ -41,22 +41,25 @@ one names it. ## Chapters -The number says which part a chapter is in: `0x` uses mcpp, `1x` ships what was -built, `2x` is toolchains and targets, `3x` is bare metal and devices, `4x` -extends mcpp, `5x` is the machine-facing contracts, `9x` is mcpp itself. Within -a part the order is a reading order. +The first digit is the part, so a number says where a chapter belongs: `0x` is +what everyone needs, `1x` publishes, `2x` is toolchains and targets, `3x` +extends the build graph, `4x` is devices and accelerators, `5x` is what a +program may parse, `9x` is mcpp itself. Within a part the order is a reading +order, not an alphabet. -### 0x — Using mcpp +### 0x — Everyone -- [00 — Getting Started](00-getting-started.md) -- [01 — Examples](01-examples.md) -- [02 — The mcpp.toml Manifest](02-mcpp-toml.md) -- [03 — Workspaces](03-workspace.md) -- [04 — Features and Capabilities](04-features-and-capabilities.md) -- [05 — Build Programs: `build.mcpp`](05-build-mcpp.md) -- [06 — Commands by Scenario](06-commands-by-scenario.md) +- [00 — How mcpp Works](00-how-mcpp-works.md) — the model every other chapter assumes +- [01 — Getting Started](01-getting-started.md) — install, create, build, run +- [02 — Examples](02-examples.md) — which example teaches what +- [03 — The mcpp.toml Manifest](03-mcpp-toml.md) — what a manifest may say +- [04 — Dependencies and Resolution](04-dependencies.md) — where a dependency comes from, and which version wins +- [05 — Features and Capabilities](05-features-and-capabilities.md) — making part of a package optional +- [06 — Workspaces](06-workspace.md) — several packages, one build +- [07 — Testing](07-testing.md) — including what does not run on this machine +- [08 — Commands by Scenario](08-commands-by-scenario.md) — the lookup, once the nouns are known -### 1x — Shipping what was built +### 1x — Publishing - [10 — Packaging an Application for Release](10-pack-and-release.md) - [11 — Publishing a Library to mcpp-index](11-publishing-a-library.md) @@ -70,22 +73,23 @@ a part the order is a reading order. - [23 — The Project Environment](23-the-project-environment.md) - [24 — Cross-Compilation Over openkal](24-openkal-cross.md) -### 3x — Bare metal, devices and accelerators +### 3x — Extending the build graph -- [30 — Bare-Metal and Freestanding Targets](30-baremetal.md) -- [31 — Reaching a Device](31-devices.md) -- [32 — Heterogeneous Builds](32-heterogeneous-builds.md) +- [30 — Build Programs: `build.mcpp`](30-build-mcpp.md) — a project that needs a step mcpp has no rule for +- [31 — Authoring a Rule Package](31-authoring-a-rule-package.md) — packaging that step for other projects -### 4x — Extending mcpp from outside +### 4x — Devices and accelerators -- [40 — Authoring a Rule Package](40-authoring-a-rule-package.md) +- [40 — Bare-Metal and Freestanding Targets](40-baremetal.md) +- [41 — Reaching a Device](41-devices.md) +- [42 — Heterogeneous Builds](42-heterogeneous-builds.md) -### 5x — Machine interfaces and compatibility +### 5x — Contracts for programs - [50 — Machine-Readable Output](50-machine-output.md) - [51 — Supported Versions and Compatibility](51-supported-versions.md) -### 9x — Contributing to mcpp itself +### 9x — mcpp itself - [90 — Building from Source and Contributing](90-build-from-source.md) - [91 — Toolchain Internals](91-toolchain-internals.md) diff --git a/docs/specs/manifest-semantics.md b/docs/specs/manifest-semantics.md index f32be0af..aa21861f 100644 --- a/docs/specs/manifest-semantics.md +++ b/docs/specs/manifest-semantics.md @@ -10,7 +10,7 @@ | **最低实现版本** | 条件化形状:mcpp **2026.8.29.1**(`[target..build-dependencies]` 起齐备);目标轴:mcpp **2026.9.6.4** | | **作者/维护** | mcpp-community | | **相关设计文档** | `.agents/docs/2026-09-07-mcpp-toml-unified-semantics-design.md`
`.agents/docs/2026-06-04-manifest-schema-ownership.md`
`.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md` | -| **相关使用文档** | [docs/05 —— mcpp.toml 字段参考](../02-mcpp-toml.md) | +| **相关使用文档** | [docs/05 —— mcpp.toml 字段参考](../03-mcpp-toml.md) | ## 规范用语 diff --git a/docs/zh/00-how-mcpp-works.md b/docs/zh/00-how-mcpp-works.md new file mode 100644 index 00000000..c07813c1 --- /dev/null +++ b/docs/zh/00-how-mcpp-works.md @@ -0,0 +1,120 @@ +# 00 —— mcpp 的运转方式 + +**读者:**任何人,在读别的之前。其余每一章都假定读过这一章。 + +**本章回答的那一个问题:**有哪些活动部件,以及出问题时是哪一个在出问题。 + +mcpp 是三样东西合在一个程序里 —— 构建系统、包管理器、工具链供给者。这不常见, +也正是「从命令参考读起的人只看到一串动词、无处安放」的原因。本章是模型。它不写 +任何字段、任何旗标;那些在参考章节里,而本章让它们可读。 + +下一章:[01 —— 快速开始](01-getting-started.md) 把一个程序跑到屏幕上。 + +## 五个名词 + +本套文档讲的全部内容,就是五样东西以及它们之间的缝。 + +### 包(package) + +一个带 `mcpp.toml` 的目录。它声明名字、版本、由什么构建、依赖什么。它的身份是 +`(namespace, name)` 这一对 —— 不是它的目录,也不是它的文件名 —— 所以来自不同 +namespace 的两个 `cmdline` 可以共存。 + +包同时是其余一切的单位:被发布的单位、被缓存的单位、声明 feature 的单位,以及 +构建程序所归属的单位。 + +参考:[03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md)。 + +### 图(graph) + +一次构建是一张图。源产出目标文件,目标文件产出产物,而 C++20 模块接口会在源与源 +之间加边 —— 一个单元必须先于 import 它的那个单元被编译。这个次序由 mcpp 扫描源 +算出;manifest 里不写它。 + +有两样东西是**扩展这张图**而不是待在图外面的:**构建程序**(`build.mcpp`)声明 +额外的边,**规则包**为一整类工程供给这些声明。两者都往同一张图里加节点;都不是 +构建前脚本。 + +参考:[30 —— 构建程序](30-build-mcpp.md)。 + +### 工具链(toolchain) + +mcpp 使用的编译器是**它自己安装并钉住的载荷**,不是在机器上找到的程序。这是大多数 +其它构建系统没有的性质,而它决定了很多别的形状:构建可复现,因为编译器本身是被 +解析出来的一部分;工程可以**声明**它需要哪个编译器,而不是写在 README 里让人照做。 + +参考:[20 —— 工具链管理](20-toolchains.md)。 + +### 目标(target) + +产物运行的那台机器。它由三元组命名,而且它不是宿主:在 Linux 上的一次构建可以产出 +Windows 可执行文件、裸机镜像,或者给 GPU 的一个目标文件。一切以「它在哪里运行」为 +条件的东西都挂在目标上,而 manifest 说「只在那里」不需要第二份 manifest。 + +参考:[21 —— 目标三元组](21-the-target-triple.md)。 + +### 索引(index) + +包从哪里来。索引里存的是**描述符** —— 一个包的身份、它有哪些版本、每个版本的源码 +或产物从哪里取。描述符是数据;mcpp 是读它的那个程序。 + +参考:[11 —— 发布一个库到 mcpp-index](11-publishing-a-library.md)。 + +## 一次构建从头到尾做了什么 + +``` + mcpp.toml ──▶ 解析 ──▶ 供给 ──▶ 扫描 ──▶ 编译 ──▶ 链接 + │ │ │ │ │ │ + 包 索引 + 工具链 图的边 工具链 产物 + 版本 + 工具 + + ╰──── 第一次构建跨过的三道缝 ────╯ +``` + +**第一道缝 —— 从 manifest 到解析结果。** manifest 点名的东西变成一组确切的版本。 +这里失败是索引或版本约束的事,而且还没有编译过任何东西。 + +**第二道缝 —— 从解析结果到环境。** 解析出来的东西变成磁盘上的载荷:工具链,以及 +任何包声明过的工具。这里失败是下载、某平台没有这样的载荷、或者版本下界的事。 + +**第三道缝 —— 从源到图。** 源被扫描 `import`,边被算出。这里失败点名的是一个模块, +不是一个文件。 + +编译与链接在这三道缝之后,所以「它编不过」其实是四种相当不同的处境。 + +## 状态落在磁盘的哪里 + +mcpp 写的东西没有一样是隐藏的,而每个存储回答的是不同的问题。 + +| 存储 | 作用域 | 存什么 | 由什么清空 | +|---|---|---|---| +| `target///` | 单个工程 | 目标文件、模块接口、产物 | `mcpp clean` | +| 构建缓存 | 整台机器 | 编译过的依赖与 `std` | `mcpp cache gc` | +| 包存储 | 整台机器 | 工具链载荷与被声明的工具 | 包管理器 | +| `mcpp.lock` | 单个工程,进版本库 | 一次解析产生的结果 | `mcpp update` 重写 | + +`mcpp self env` 打印这几样在本机的位置。 + +构建目录名里的那个指纹,正是两种配置互不打架的原因:debug 构建、release 构建与 +交叉构建各占一个目录,谁也不让谁失效。 + +## 一次失败是关于哪个名词的 + +这个模型买到的最有用的一件事。一条消息点名一个名词,而名词点名章节。 + +| 消息里出现 | 名词 | 去哪里看 | +|---|---|---| +| 包名、版本,或「没有候选」 | 索引 | [11](11-publishing-a-library.md)、[04](04-dependencies.md) | +| 下载、载荷,或版本下界 | 工具链 | [20](20-toolchains.md)、[23](23-the-project-environment.md) | +| 三元组,或「不支持的目标」 | 目标 | [21](21-the-target-triple.md) | +| 某个模块读不到或没有人提供它 | 图 | [30](30-build-mcpp.md) | +| 工程自己某个文件里的编译或链接错误 | 一个都不是 | 编译器自己的消息 | + +最后一行是有用的那一行:当编译器报的是代码本身的错误时,mcpp 的任何部件都没有 +参与,读 mcpp 的文档不会有帮助。 + +## 本章有意不写什么 + +一切操作性的东西。manifest 的字段、旗标,以及 `mcpp build` 之外的命令,都刻意不在 +这里出现 —— 一份既想当模型又想当参考的文档,两样都不是。每个名词那一节点名了拥有 +它的章节。 diff --git a/docs/zh/00-getting-started.md b/docs/zh/01-getting-started.md similarity index 98% rename from docs/zh/00-getting-started.md rename to docs/zh/01-getting-started.md index 3a2feed1..0e08e381 100644 --- a/docs/zh/00-getting-started.md +++ b/docs/zh/01-getting-started.md @@ -1,4 +1,4 @@ -# 00 —— 快速开始 +# 01 —— 快速开始 > 5 分钟完成 install → new → build → run → pack 全流程。 @@ -154,7 +154,7 @@ linux 1019s、windows 1289s)。给它一个默认上限会把「慢但正确」 `mcpp build` 将自动从 [mcpp-index](https://github.com/mcpplibs/mcpp-index) 解析 SemVer 约束、拉取源码并加入编译图。完整示例参见 -[01 — 示例项目](01-examples.md) 中的 `02-with-deps`。 +[02 — 示例项目](02-examples.md) 中的 `02-with-deps`。 ## 生成发布包 @@ -171,7 +171,7 @@ mcpp pack --mode self-contained # 打包 loader、libc 与依赖 ## 后续阅读 -- [01 — 示例项目](01-examples.md) — 可直接运行的最小工程集合 +- [02 — 示例项目](02-examples.md) — 可直接运行的最小工程集合 - [10 — 发布打包](10-pack-and-release.md) — 构建可分发产物 - [20 — 工具链管理](20-toolchains.md) — 切换编译器与多版本管理 - 任意命令的完整选项可通过 `mcpp --help` 查阅 diff --git a/docs/zh/01-examples.md b/docs/zh/02-examples.md similarity index 93% rename from docs/zh/01-examples.md rename to docs/zh/02-examples.md index 5e823957..c94272b4 100644 --- a/docs/zh/01-examples.md +++ b/docs/zh/02-examples.md @@ -1,4 +1,4 @@ -# 01 —— 示例项目 +# 02 —— 示例项目 [`examples/`](../../examples) 目录是一套课程。每个工程都可以单独跑起来,而且每个 工程都教**一件更早的示例没有教过的事**。本章说明那件事是什么,于是你可以按自己 @@ -13,7 +13,7 @@ mcpp build && mcpp run ``` 每个示例自带 README,只解释它新增的部分。安装与工具链初始化在 -[00 —— 快速开始](00-getting-started.md),不在示例里重复。 +[01 —— 快速开始](01-getting-started.md),不在示例里重复。 ## 课程 @@ -70,7 +70,7 @@ mcpp build && mcpp run | [`08-build-rules`](../../examples/08-build-rules/) | 两个规则包与同时使用它们的工程;`host-module = true`、`role = "check"` 的 `mcpp::action` | | [`12-a-new-device-language`](../../examples/12-a-new-device-language/) | `device_extensions` 与 `rule_module`:规则包教会 mcpp 一门引擎从未听说过的语言 | -[40 —— 编写规则包](40-authoring-a-rule-package.md) 是这两个示例所演示内容的参考。 +[31 —— 编写规则包](31-authoring-a-rule-package.md) 是这两个示例所演示内容的参考。 ## 以模板形式到达的课程 @@ -79,8 +79,8 @@ mcpp build && mcpp run | 模板 | 课程 | 章节 | |---|---|---| -| `riscv-virt-rt` | 一个裸机工程、它的板级支持与它的 runner | [30](30-baremetal.md) | -| `riscv-virt-rt:nolibc` | 同上,但没有 C 库 | [30](30-baremetal.md) | +| `riscv-virt-rt` | 一个裸机工程、它的板级支持与它的 runner | [40](40-baremetal.md) | +| `riscv-virt-rt:nolibc` | 同上,但没有 C 库 | [40](40-baremetal.md) | | `ocornut.imgui` | 一个带窗口与渲染栈的图形应用 | [20](20-toolchains.md) | ```bash @@ -93,7 +93,7 @@ mcpp new blinky --template riscv-virt-rt 值得新增一个示例:当一个能力**改变工程的形状** —— 它包含的文件、它声明的 manifest、 或者作者敲的命令。如果一个能力只是既有示例工程里的一行,它属于对应章节里的一个 代码块;如果它只经由命令到达,它属于 -[06 —— 按场景选命令](06-commands-by-scenario.md)。 +[08 —— 按场景选命令](08-commands-by-scenario.md)。 README 要写明这个示例第一个教什么,以及判断它是否成立的判据。贡献流程见 [90 —— 从源码构建 & 参与贡献](90-build-from-source.md)。 diff --git a/docs/zh/02-mcpp-toml.md b/docs/zh/03-mcpp-toml.md similarity index 83% rename from docs/zh/02-mcpp-toml.md rename to docs/zh/03-mcpp-toml.md index 92e59f41..daf8d3e5 100644 --- a/docs/zh/02-mcpp-toml.md +++ b/docs/zh/03-mcpp-toml.md @@ -1,4 +1,4 @@ -# 02 —— mcpp.toml 工程文件指南 +# 03 —— mcpp.toml 工程文件指南 `mcpp.toml` 是 mcpp 构建工具的项目配置文件,类似 Cargo 的 `Cargo.toml` 或 Node 的 `package.json`。放在项目根目录下,`mcpp build` 会自动发现并读取它。 @@ -73,7 +73,7 @@ mcpp 会在编译前失败,并同时报出工具链与工程档位。 值的两种拼法都接受:`standard = "c++26"` 与 `standard = 26`。 当**依赖声明的档位高于当前图**时,mcpp 会在编译前说出来,而不是让它在那个依赖的源码里 -某处失败。见 [workspace §4.2](03-workspace.md)。 +某处失败。见 [workspace §4.2](06-workspace.md)。 #### 方言标志与 `import std` BMI @@ -204,7 +204,7 @@ required_features = ["gui"] # 仅当 feature `gui` 激活时 > compile-once 模型)。当标志只需影响某个二进制(或测试)**自己的入口**时,这正是合适的工具 —— > 例如某个测试的 `main` 里触发契约违规、需要按测试设置契约求值语义 > (`-fcontract-evaluation-semantic=observe`),或入口独享的 feature 宏、局部告警抑制。 -> 若标志必须穿透**共享**代码,就不该放在这里 —— 改用 [workspace](03-workspace.md) member 或 +> 若标志必须穿透**共享**代码,就不该放在这里 —— 改用 [workspace](06-workspace.md) member 或 > `[features]`;若是整次构建的模式,用 `[profile.*]`(`mcpp test --profile ` 会让包括被测 > 代码在内的整个测试镜像都在该 profile 下编译)。 > @@ -215,7 +215,7 @@ required_features = ["gui"] # 仅当 feature `gui` 激活时 | 目标 | 使用 | |---|---| | 某二进制**自己入口**上的不同宏/标志 | per-target `defines` / `cxxflags`(见上) | -| 两个产品差异在它们**共享**的代码里 | 拆成 [workspace](03-workspace.md) member,各自 `[build]` 标志,共享一个 `lib` | +| 两个产品差异在它们**共享**的代码里 | 拆成 [workspace](06-workspace.md) member,各自 `[build]` 标志,共享一个 `lib` | | **选择**某共享库的变体(如某后端) | 在该库上用 `[features]`(§2.8)——additive,作用到库自己的编译 | | **整次构建的模式**(sanitizer、契约语义、优化档) | `[profile.]`(§2.9)+ `--profile`;`mcpp test --profile ` 同样支持 | @@ -243,7 +243,7 @@ mcpp 刻意不在一次构建里把同一个共享源编译成两份:一个源 > 那会让这个设备无东西可编,而只在链接时才说话)。`--no-accel` 下该 glob 被排除, > 一个工程由此产出它的 CPU-only 变体。`--accel` 未覆盖该约束时构建被拒并给出两侧 > (`accel-mismatch`)。有效集合匹配到的设备类源文件 —— CUDA 与 HIP、GLSL 各 stage、 -> HLSL、OpenCL C 与 Metal,完整清单见 [32 — 异构硬件构建](32-heterogeneous-builds.md) —— 引擎 +> HLSL、OpenCL C 与 Metal,完整清单见 [42 — 异构硬件构建](42-heterogeneous-builds.md) —— 引擎 > 从不编译;它们以 `MCPP_DEVICE_SOURCES` 到达构建程序,由工程引入的规则包把每一个 > 变成一条 `mcpp::action`。 @@ -495,7 +495,7 @@ MCPP_BUILD_PROGRAM_TIMEOUT=<秒> 本次调用(最高) 「抬高超时」触发全量重建,这恰好与抬高超时的人想要的相反。 只有构建**程序**受限,**编译**不受限。原因见 -[05-build-mcpp.md](05-build-mcpp.md)。 +[30-build-mcpp.md](30-build-mcpp.md)。 ### C++ 运行时契约(`cxx_runtime`) 已移入 [20 —— 工具链管理](20-toolchains.md)。 @@ -537,7 +537,7 @@ accel = "cuda12.8+{sm_80,sm_90f} ptx>=90" 也就是在一个同时发布了设备构建的包中选中 CPU-only 变体的方式。 该取值会与构建所消费的任何预建产物的 `accel` 字段比较,而请求为空的构建被任何产物满足。 -见 [32 — 异构硬件构建](32-heterogeneous-builds.md)。 +见 [42 — 异构硬件构建](42-heterogeneous-builds.md)。 ### 2.4 `[lib]` — 库根模块约定 @@ -547,239 +547,9 @@ path = "src/capi/lua.cppm" # 覆盖默认的 lib-root 位置 ``` 默认约定:`src/<包名最后一段>.cppm`(如包名 `mcpplibs.cmdline` → `src/cmdline.cppm`)。 +### 2.5 `[dependencies]`、`[dev-dependencies]`、`[build-dependencies]` -### 2.5 `[dependencies]` — 运行时依赖 - -```toml -# 默认包空间(mcpplibs)下的包 -[dependencies] -cmdline = "0.0.2" # 精确版本 -templates = "0.0.1" - -# dotted selector 是单一精确身份:最后一段是包名,之前所有段都是 namespace。 -compat.gtest = "1.15.2" -imgui.core = "0.0.1" -imgui.backend.glfw_opengl3 = "0.0.1" -mcpplibs.capi.lua = "0.0.3" -``` - -```toml -# 命名空间子表写法 -[dependencies.mcpplibs] -cmdline = "0.0.2" -tinyhttps = "0.2.2" -llmapi = "0.2.5" - -[dependencies.compat] -glfw = "3.4" # 显式 namespace,无回退搜索 -``` - -```toml -# 路径依赖(本地开发) -[dependencies] -mylib = { path = "../mylib" } -``` - -```toml -# Git 依赖 —— tag / branch / rev 三选一 -[dependencies] -mylib = { git = "https://github.com/user/mylib.git", tag = "v1.0.0" } -applib = { git = "https://github.com/user/applib.git", branch = "develop" } -``` - -```toml -# 长式 dep spec:features 与 backend 旋钮 -[dependencies] -imgui = { version = "0.0.3", features = ["docking"] } # 请求该依赖的 feature -widget = { version = "1.0", backend = "glfw_opengl3" } # 糖:= features=["backend-glfw_opengl3"] -``` - -`backend = ""` 是**通用约定糖**:1:1 脱糖为请求该依赖的 `backend-` -feature(库若支持该旋钮,应在自己的 `[features]` 中声明 `backend-*` 系列)。 -若目标包声明了 `[features]` 但不含所请求的 feature(含 backend 脱糖结果), -默认给出 warning,`mcpp build --strict` 下报错。 - -**Git 依赖与 `mcpp.lock`**:`tag` 和 `rev` 本身就指向历史中的固定点,而 `branch` -是会动的。首次构建把分支解析成一个 commit 并写进 `mcpp.lock`,此后每次构建都重建 -**那个** commit —— lock 是权威而不是缓存提示,所以删掉 `~/.mcpp/git` 或换一台机器 -都不会静默切换到更新的分支头。需要新的分支头时,必须显式指定: - -```bash -mcpp update mylib # 丢掉记录的 commit,下次构建重新解析 -mcpp update # 同上,对所有依赖 -``` - -既然记录的 commit 已经足以决定构建什么,那么在 `~/.mcpp/git` 里已有克隆的情况下, -重新构建完全不发网络请求,`--offline` 下照常工作。只有两件事需要网络:解析一个在 -lock 里没有 commit 的分支,以及克隆一个尚未缓存的 commit。`git =` 若指向本地目录 -(或 `file://` URL),这两件事都不需要网络,因此离线下也绝不会被拒绝。 - -**SemVer 约束**: - -```toml -[dependencies] -foo = "^1.2.3" # >= 1.2.3, < 2.0.0 (caret,默认) -bar = "~1.2.3" # >= 1.2.3, < 1.3.0 (tilde) -baz = "=1.2.3" # 精确匹配 -qux = ">=1.0, <2.0" # 范围组合 -``` - -#### 命名空间解析规则 - -每个包的身份是**命名空间 + 名字**二元组。每个 selector 都只规范化成一个身份: - -- `cmdline` → `(mcpplibs, cmdline)`;省略 namespace 只表示默认 `mcpplibs`。 -- `compat.gtest` → `(compat, gtest)`。 -- `mcpplibs.capi.lua` → `(mcpplibs.capi, lua)`。 - -不存在有序回退或按短名的全索引模糊搜索: - -```toml -# 正确 —— 点式选择器 -[dependencies] -chriskohlhoff.asio = "1.38.1" - -# 正确 —— 命名空间子表(同一组织有多个包时更推荐) -[dependencies.chriskohlhoff] -asio = "1.38.1" - -# 错误 —— 裸名永远到不了 chriskohlhoff 命名空间 -[dependencies] -asio = "1.38.1" -``` - -第三种写法会明确报错,指出实际尝试的 `(mcpplibs, asio)`;若该短名存在于别处,错误信息会给出可直接复制的显式 selector。 - -##### 裸名过渡期(`2026.8.10.1` 起,`2026.9` 移除) - -索引里已发布的 `compat.*` 包与既有 manifest **全部**写成裸名(`gtest = "1.15.2"`)。 -升级后直接失败,等于让一次程序发布把**已经发布、且无法追溯修改**的数据作废, -所以有一个版本的过渡期:裸名在 `mcpplibs` 未命中时仍可到达 `compat.`, -不声明 namespace 的 descriptor 也仍可被裸名解析。 - -但它不再静默: - -``` -warning: dependency 'gtest' resolved to 'compat.gtest' through the deprecated -bare-name search; namespace omission means `mcpplibs` only. Write the exact -package: - [dependencies.compat] - gtest = "1.15.2" - (or run `mcpp add compat.gtest@1.15.2`). This fallback is removed in 2026.9. -``` - -写进 `mcpp.lock`、install 与 cache 的是**规范身份**;歧义拼写只存在于工程的 -`mcpp.toml` 中,直到被改写 —— `mcpp add gtest@1.15.2` 会完成这次改写。 - -过渡期**不适用于**写明 namespace 的 selector(`mcpplibs.gtest` 未命中就是未命中), -裸名也**仍然**到不了第三方 namespace。 - -**为什么只允许一个身份?** 因为依赖解析必须可复现。候选搜索会让同短名包受索引状态影响,新增索引还可能悄悄重定向既有依赖。 - -**给 xpkg 作者:** 索引描述符里,身份是 `(package.namespace, package.name)` 二元组。命名空间是点分路径,**`name` 是单一原子段**: - -```lua -package = { - namespace = "chriskohlhoff", - name = "asio", -- 单一段;不是 "chriskohlhoff.asio" -} - -package = { - namespace = "mcpplibs.capi", -- 层级放这里 - name = "lua", -} -``` - -文件名只是提示 —— 描述符按声明的身份被发现,所以 `pkgs/c/chriskohlhoff.asio.lua` 与 `pkgs/z/anything.lua` 解析结果完全相同。推荐 `.lua` 或 `..lua`(命中 mcpp 的快路径),但不强制。 - -旧的完全限定拼写(`name = "chriskohlhoff.asio"`)仍被接受,已发布的描述符无需改动。`mcpp xpkg parse` 会校验该规则,请在索引 CI 里跑它。描述符身份需要 mcpp >= 0.0.106,精确 selector 需要 mcpp >= 2026.8.10.1,两者使用 xlings >= 0.4.69;规范全文见 `docs/specs/package-identity.md`。 - -`mcpp new --template` 刻意复用同一身份模型,而不是另造包文法: -`[ns.]name[@version][:tname]`。其中裸名同样只表示 `mcpplibs`,version 与模板名可分别 -省略。省略 `tname` 时选择唯一显式 default;若未写 `default = true` 且只有一个模板, -该单模板自动成为默认。多个未标默认的模板会报错,绝不按目录顺序选择。规范表见 -`docs/specs/package-identity.md` §4.4。 -#### mcpp 何时刷新包索引 - -`mcpp build` / `run` / `test` **只在依赖无法用本地索引解析时**刷新包索引,绝不会 -因为"时间到了"就刷。具体地说,只有三种情况会触发:本地根本没有索引、依赖的描述符 -不在其中、或 SemVer 约束在本地已知版本里无解。只要所有依赖都能在本地解析出来, -无论本地索引多旧,构建都不会发起任何网络请求。 - -由此带来的一个需要知道的语义:`^1.2` 这类约束是对**本地索引已知的版本**求解的。 -若上游在上次刷新之后发布了 `1.3.0`,需要主动获取: - -```bash -mcpp index update # 同步索引 -mcpp update # 同步索引,并重新解析依赖 -mcpp index status # 看本地现状:状态、年龄、修订号 -``` - -三个开关,优先级从高到低: - -| 开关 | 作用 | -|---|---| -| `--offline`(任意命令) | 完全不碰网络——不刷索引、不下载、不自动装工具链,也不发 `git ls-remote`/`clone`。已安装的东西照常构建,包括 commit 已在 `mcpp.lock`、克隆已在缓存里的 git 依赖 | -| `MCPP_OFFLINE=1` | 同上,作用于整个 shell 会话或 CI job | -| `~/.mcpp/config.toml` 里 `[index] auto_refresh = false` | 永不自动刷新索引,但下载仍然可用 | - -`MCPP_NO_AUTO_INSTALL=1` 作为 `--offline` 的旧式窄化拼写仍然有效(它只管工具链的 -自动安装)。 - -任意命令加 `-v` 可以看到每个依赖的判定结果与原因。 - -### 2.6 `[dev-dependencies]` —— 测试依赖 - -```toml -[dev-dependencies.compat] -gtest = "1.15.2" -``` - -`mcpp build` 忽略这些依赖;`mcpp test` 解析并使用它们。`mcpp test` 自动发现 -`tests/**/*.cpp` 并把它们编译为测试二进制。运行器与框架无关:每个文件是一个独立的 -二进制,以退出码判定 —— 裸 `main`、gtest(经 `[dev-dependencies]` + `gtest_main`) -或任何其他框架的行为完全一致,`-- args` 会转发给每个测试二进制 -(例如 `-- --gtest_filter=...`)。注意:合成的测试目标名可能包含 `/` -(`tests/00-a/0.cpp` → `00-a/0`),这与 `[targets.*]` 名不同 —— 两个命名空间是 -刻意分开的(测试目标从不进入 manifest,也不参与发布)。测试按其相对 `tests/` 的 -路径命名(`tests/00-a/0.cpp` → `00-a/0`),每个测试独立编译(一个测试写坏只让它 -自己失败;包或依赖损坏则报告为构建错误),`mcpp test ` 与 -`--message-format json` 分别提供过滤与机器可读输出。 - -### 2.6.1 `[build-dependencies]` —— 构建期依赖(mcpp 2026.8.29.1+) - -```toml -[build-dependencies] -protobuf = { version = "35.1", tools = ["protoc"] } -``` - -段与边上的请求回答的是**两个不同的问题**,把它们混为一谈是建模上的错误,不是写法之争。 - -- **段**回答:这个包本身进不进目标。`[dependencies]` 进;`[build-dependencies]` 永不进, - 只能经由它到达的东西也一样。 -- **边上的请求**回答:要它的哪一种构建期产物。`tools = [...]` 要一个宿主可执行文件, - `host-module = true` 要一个构建程序可以 import 的模块。 - -一个包可以同时在两个轴上取值,而 protobuf 正是证明这两个轴必须分开的例子:工程既链接 -`libprotobuf`,构建期又需要 `protoc`。它只写一次,写在 `[dependencies]` 里: - -```toml -[dependencies] -protobuf = { version = "35.1", tools = ["protoc"] } -``` - -`[build-dependencies]` 用于第一个轴无法表达的那种组合 —— 某个包的库不得进入目标, -而它的工具或规则仍然需要。同一个包同时出现在两张表里不是错误:普通声明胜出,因为 -一行 `[build-dependencies]` 不应该悄悄拿掉目标真正需要的库。 - -与 `[dev-dependencies]` 不同,这些依赖**会**被传递遍历:一个构建期依赖自己的依赖正是 -让它能工作的东西,并且继承它「只服务构建」的性质。 - -feature 可以为构建期请求划定范围而无需第二个声明处 —— `[feature-deps.]` 可以给 -一条已经无条件声明的依赖追加 `tools`,所以「按需才要」不需要另开一张表。 - -> 这个段很早就能被解析,而直到 2026.8.29.1 之前没有任何做决定的代码读它:写下它得到的是 -> 一份能加载的清单、零诊断、零效果。 +已移入 [04 —— 依赖与解析](04-dependencies.md)。 ### 2.7 `[toolchain]` —— 工具链配置 @@ -805,7 +575,7 @@ linkage = "static" 本节是清单参考。示例部分 —— 生成工程、运行、在目标上测试、freestanding 标准库 子集,以及编写板级支持包 —— 在 -[30 — 裸机与 freestanding 目标](30-baremetal.md)。 +[40 — 裸机与 freestanding 目标](40-baremetal.md)。 ```bash mcpp build --target riscv64-none-elf @@ -925,7 +695,7 @@ error: test result: NOT RUN. 0 passed; 0 failed; 1 not run (this host cannot exe 带 `not_run` / `not_run_reason`(见 [50 —— 机器可读输出](50-machine-output.md))。 ### 2.8 `[features]` —— Feature -已移入 [04 —— Feature 与能力](04-features-and-capabilities.md), +已移入 [05 —— Feature 与能力](05-features-and-capabilities.md), 连同 `provides` / `requires` 与 `[feature-deps.]`。 @@ -1157,14 +927,14 @@ accelerators = ["cuda", "rocm"] 与产物的 `accel` 字段刻意不同。声明由人手写、可以是期望值;`accel` 是从产生该二进制的 那次构建测量出来的,并且是消费者被拒绝时所依据的东西。见 -[32 — 异构硬件构建](32-heterogeneous-builds.md)。 +[42 — 异构硬件构建](42-heterogeneous-builds.md)。 ### 2.13 `[xlings]` —— 工程的环境 已移入 [23 —— 项目环境](23-the-project-environment.md)。 ### 2.14 依赖产出的 host 工具 -已移入 [05 —— build.mcpp](05-build-mcpp.md)。 +已移入 [30 —— build.mcpp](30-build-mcpp.md)。 ### 2.15 `[resources]` —— 编译进产物的元数据与资产(2026.8.7.1+) @@ -1250,12 +1020,12 @@ o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o") .submit(); ``` -见 [05 — build.mcpp](05-build-mcpp.md)。把这类文件写进 `[build].ldflags` 也「能用」, +见 [30 — build.mcpp](30-build-mcpp.md)。把这类文件写进 `[build].ldflags` 也「能用」, 但 ldflags 是链接命令里的一串字符:没有任何东西跟踪它,改了它得到的是 `ninja: no work to do`。 ### 2.16 `[hooks]` —— 项目构建生命周期命令 -已移入 [06 —— 按场景选命令](06-commands-by-scenario.md)。 +已移入 [08 —— 按场景选命令](08-commands-by-scenario.md)。 ## 附录 A. Schema 所有权原则(新字段准入标准) diff --git a/docs/zh/04-dependencies.md b/docs/zh/04-dependencies.md new file mode 100644 index 00000000..52b44022 --- /dev/null +++ b/docs/zh/04-dependencies.md @@ -0,0 +1,261 @@ +# 04 —— 依赖与解析 + +**读者:**构建里已经不只有自己代码的作者。 + +**本章回答的那一个问题:**一个依赖从哪里来,版本约束是什么意思,以及两个约束 +不一致时会发生什么。 + +**不在这里:**什么使两个包成为同一个包 —— 那是 +[SPEC-001](../specs/package-identity.md),本章施用它而不复述它;以及怎么发布一个包, +那是 [11 —— 发布一个库](11-publishing-a-library.md)。 + +在此之前:[03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md) 是这些表与其余表同处的 +地方。在此之后:[05 —— Feature 与能力](05-features-and-capabilities.md) 讲一个依赖 +怎么变成可选的。 + +## `[dependencies]` — 运行时依赖 + +**推荐写法是带精确版本的 dotted selector。** dotted 形式点名的是**一个身份** —— +最后一段是包名,之前所有段都是 namespace —— 因此解析到什么,不取决于当前配置了 +哪些 namespace。 + +```toml +[dependencies] +compat.gtest = "1.15.2" +imgui.core = "0.0.1" +imgui.backend.glfw_opengl3 = "0.0.1" +mcpplibs.capi.lua = "0.0.3" +``` + +裸名也被接受,它经由默认 namespace(`mcpplibs`)解析 —— 在只用那一个 namespace 的 +工程里方便,在不是的工程里有歧义: + +```toml +[dependencies] +cmdline = "0.0.2" # 解析为 mcpplibs.cmdline +``` + +
+等价拼法:namespace 子表 + +把同一 namespace 下的条目归组。它解析到的身份与 dotted 形式完全相同,在许多依赖 +共享一个 namespace 时值得用。 + +```toml +[dependencies.mcpplibs] +cmdline = "0.0.2" +tinyhttps = "0.2.2" +llmapi = "0.2.5" + +[dependencies.compat] +glfw = "3.4" # 显式 namespace,不做回退搜索 +``` + +
+ +```toml +# 路径依赖(本地开发) +[dependencies] +mylib = { path = "../mylib" } +``` + +```toml +# Git 依赖 —— tag / branch / rev 三选一 +[dependencies] +mylib = { git = "https://github.com/user/mylib.git", tag = "v1.0.0" } +applib = { git = "https://github.com/user/applib.git", branch = "develop" } +``` + +```toml +# 长式 dep spec:features 与 backend 旋钮 +[dependencies] +imgui = { version = "0.0.3", features = ["docking"] } # 请求该依赖的 feature +widget = { version = "1.0", backend = "glfw_opengl3" } # 糖:= features=["backend-glfw_opengl3"] +``` + +`backend = ""` 是**通用约定糖**:1:1 脱糖为请求该依赖的 `backend-` +feature(库若支持该旋钮,应在自己的 `[features]` 中声明 `backend-*` 系列)。 +若目标包声明了 `[features]` 但不含所请求的 feature(含 backend 脱糖结果), +默认给出 warning,`mcpp build --strict` 下报错。 + +**Git 依赖与 `mcpp.lock`**:`tag` 和 `rev` 本身就指向历史中的固定点,而 `branch` +是会动的。首次构建把分支解析成一个 commit 并写进 `mcpp.lock`,此后每次构建都重建 +**那个** commit —— lock 是权威而不是缓存提示,所以删掉 `~/.mcpp/git` 或换一台机器 +都不会静默切换到更新的分支头。需要新的分支头时,必须显式指定: + +```bash +mcpp update mylib # 丢掉记录的 commit,下次构建重新解析 +mcpp update # 同上,对所有依赖 +``` + +既然记录的 commit 已经足以决定构建什么,那么在 `~/.mcpp/git` 里已有克隆的情况下, +重新构建完全不发网络请求,`--offline` 下照常工作。只有两件事需要网络:解析一个在 +lock 里没有 commit 的分支,以及克隆一个尚未缓存的 commit。`git =` 若指向本地目录 +(或 `file://` URL),这两件事都不需要网络,因此离线下也绝不会被拒绝。 + +**SemVer 约束**: + +```toml +[dependencies] +foo = "^1.2.3" # >= 1.2.3, < 2.0.0 (caret,默认) +bar = "~1.2.3" # >= 1.2.3, < 1.3.0 (tilde) +baz = "=1.2.3" # 精确匹配 +qux = ">=1.0, <2.0" # 范围组合 +``` + +### 命名空间解析规则 + +每个包的身份是**命名空间 + 名字**二元组。每个 selector 都只规范化成一个身份: + +- `cmdline` → `(mcpplibs, cmdline)`;省略 namespace 只表示默认 `mcpplibs`。 +- `compat.gtest` → `(compat, gtest)`。 +- `mcpplibs.capi.lua` → `(mcpplibs.capi, lua)`。 + +不存在有序回退或按短名的全索引模糊搜索: + +```toml +# 正确 —— 点式选择器 +[dependencies] +chriskohlhoff.asio = "1.38.1" + +# 正确 —— 命名空间子表(同一组织有多个包时更推荐) +[dependencies.chriskohlhoff] +asio = "1.38.1" + +# 错误 —— 裸名永远到不了 chriskohlhoff 命名空间 +[dependencies] +asio = "1.38.1" +``` + +第三种写法会明确报错,指出实际尝试的 `(mcpplibs, asio)`;若该短名存在于别处,错误信息会给出可直接复制的显式 selector。 + +#### 裸名过渡期(`2026.8.10.1` 起,`2026.9` 移除) + +索引里已发布的 `compat.*` 包与既有 manifest **全部**写成裸名(`gtest = "1.15.2"`)。 +升级后直接失败,等于让一次程序发布把**已经发布、且无法追溯修改**的数据作废, +所以有一个版本的过渡期:裸名在 `mcpplibs` 未命中时仍可到达 `compat.`, +不声明 namespace 的 descriptor 也仍可被裸名解析。 + +但它不再静默: + +``` +warning: dependency 'gtest' resolved to 'compat.gtest' through the deprecated +bare-name search; namespace omission means `mcpplibs` only. Write the exact +package: + [dependencies.compat] + gtest = "1.15.2" + (or run `mcpp add compat.gtest@1.15.2`). This fallback is removed in 2026.9. +``` + +写进 `mcpp.lock`、install 与 cache 的是**规范身份**;歧义拼写只存在于工程的 +`mcpp.toml` 中,直到被改写 —— `mcpp add gtest@1.15.2` 会完成这次改写。 + +过渡期**不适用于**写明 namespace 的 selector(`mcpplibs.gtest` 未命中就是未命中), +裸名也**仍然**到不了第三方 namespace。 + +**为什么只允许一个身份?** 因为依赖解析必须可复现。候选搜索会让同短名包受索引状态影响,新增索引还可能悄悄重定向既有依赖。 + +**给 xpkg 作者:** 索引描述符里,身份是 `(package.namespace, package.name)` 二元组。命名空间是点分路径,**`name` 是单一原子段**: + +```lua +package = { + namespace = "chriskohlhoff", + name = "asio", -- 单一段;不是 "chriskohlhoff.asio" +} + +package = { + namespace = "mcpplibs.capi", -- 层级放这里 + name = "lua", +} +``` + +文件名只是提示 —— 描述符按声明的身份被发现,所以 `pkgs/c/chriskohlhoff.asio.lua` 与 `pkgs/z/anything.lua` 解析结果完全相同。推荐 `.lua` 或 `..lua`(命中 mcpp 的快路径),但不强制。 + +旧的完全限定拼写(`name = "chriskohlhoff.asio"`)仍被接受,已发布的描述符无需改动。`mcpp xpkg parse` 会校验该规则,请在索引 CI 里跑它。描述符身份需要 mcpp >= 0.0.106,精确 selector 需要 mcpp >= 2026.8.10.1,两者使用 xlings >= 0.4.69;规范全文见 `docs/specs/package-identity.md`。 + +`mcpp new --template` 刻意复用同一身份模型,而不是另造包文法: +`[ns.]name[@version][:tname]`。其中裸名同样只表示 `mcpplibs`,version 与模板名可分别 +省略。省略 `tname` 时选择唯一显式 default;若未写 `default = true` 且只有一个模板, +该单模板自动成为默认。多个未标默认的模板会报错,绝不按目录顺序选择。规范表见 +`docs/specs/package-identity.md` §4.4。 +### mcpp 何时刷新包索引 + +`mcpp build` / `run` / `test` **只在依赖无法用本地索引解析时**刷新包索引,绝不会 +因为"时间到了"就刷。具体地说,只有三种情况会触发:本地根本没有索引、依赖的描述符 +不在其中、或 SemVer 约束在本地已知版本里无解。只要所有依赖都能在本地解析出来, +无论本地索引多旧,构建都不会发起任何网络请求。 + +由此带来的一个需要知道的语义:`^1.2` 这类约束是对**本地索引已知的版本**求解的。 +若上游在上次刷新之后发布了 `1.3.0`,需要主动获取: + +```bash +mcpp index update # 同步索引 +mcpp update # 同步索引,并重新解析依赖 +mcpp index status # 看本地现状:状态、年龄、修订号 +``` + +三个开关,优先级从高到低: + +| 开关 | 作用 | +|---|---| +| `--offline`(任意命令) | 完全不碰网络——不刷索引、不下载、不自动装工具链,也不发 `git ls-remote`/`clone`。已安装的东西照常构建,包括 commit 已在 `mcpp.lock`、克隆已在缓存里的 git 依赖 | +| `MCPP_OFFLINE=1` | 同上,作用于整个 shell 会话或 CI job | +| `~/.mcpp/config.toml` 里 `[index] auto_refresh = false` | 永不自动刷新索引,但下载仍然可用 | + +`MCPP_NO_AUTO_INSTALL=1` 作为 `--offline` 的旧式窄化拼写仍然有效(它只管工具链的 +自动安装)。 + +任意命令加 `-v` 可以看到每个依赖的判定结果与原因。 + +## `[dev-dependencies]` —— 测试依赖 + +```toml +[dev-dependencies.compat] +gtest = "1.15.2" +``` + +`mcpp build` 忽略这些依赖;`mcpp test` 解析并使用它们。`mcpp test` 自动发现 +`tests/**/*.cpp` 并把它们编译为测试二进制。运行器与框架无关:每个文件是一个独立的 +二进制,以退出码判定 —— 裸 `main`、gtest(经 `[dev-dependencies]` + `gtest_main`) +或任何其他框架的行为完全一致,`-- args` 会转发给每个测试二进制 +(例如 `-- --gtest_filter=...`)。注意:合成的测试目标名可能包含 `/` +(`tests/00-a/0.cpp` → `00-a/0`),这与 `[targets.*]` 名不同 —— 两个命名空间是 +刻意分开的(测试目标从不进入 manifest,也不参与发布)。测试按其相对 `tests/` 的 +路径命名(`tests/00-a/0.cpp` → `00-a/0`),每个测试独立编译(一个测试写坏只让它 +自己失败;包或依赖损坏则报告为构建错误),`mcpp test ` 与 +`--message-format json` 分别提供过滤与机器可读输出。 + +## `[build-dependencies]` —— 构建期依赖(mcpp 2026.8.29.1+) + +```toml +[build-dependencies] +protobuf = { version = "35.1", tools = ["protoc"] } +``` + +段与边上的请求回答的是**两个不同的问题**,把它们混为一谈是建模上的错误,不是写法之争。 + +- **段**回答:这个包本身进不进目标。`[dependencies]` 进;`[build-dependencies]` 永不进, + 只能经由它到达的东西也一样。 +- **边上的请求**回答:要它的哪一种构建期产物。`tools = [...]` 要一个宿主可执行文件, + `host-module = true` 要一个构建程序可以 import 的模块。 + +一个包可以同时在两个轴上取值,而 protobuf 正是证明这两个轴必须分开的例子:工程既链接 +`libprotobuf`,构建期又需要 `protoc`。它只写一次,写在 `[dependencies]` 里: + +```toml +[dependencies] +protobuf = { version = "35.1", tools = ["protoc"] } +``` + +`[build-dependencies]` 用于第一个轴无法表达的那种组合 —— 某个包的库不得进入目标, +而它的工具或规则仍然需要。同一个包同时出现在两张表里不是错误:普通声明胜出,因为 +一行 `[build-dependencies]` 不应该悄悄拿掉目标真正需要的库。 + +与 `[dev-dependencies]` 不同,这些依赖**会**被传递遍历:一个构建期依赖自己的依赖正是 +让它能工作的东西,并且继承它「只服务构建」的性质。 + +feature 可以为构建期请求划定范围而无需第二个声明处 —— `[feature-deps.]` 可以给 +一条已经无条件声明的依赖追加 `tools`,所以「按需才要」不需要另开一张表。 + +> 这个段很早就能被解析,而直到 2026.8.29.1 之前没有任何做决定的代码读它:写下它得到的是 +> 一份能加载的清单、零诊断、零效果。 diff --git a/docs/zh/04-features-and-capabilities.md b/docs/zh/05-features-and-capabilities.md similarity index 98% rename from docs/zh/04-features-and-capabilities.md rename to docs/zh/05-features-and-capabilities.md index 59596b26..e6d0a2d2 100644 --- a/docs/zh/04-features-and-capabilities.md +++ b/docs/zh/05-features-and-capabilities.md @@ -1,11 +1,11 @@ -# 04 —— Feature 与能力 +# 05 —— Feature 与能力 Feature 是一个包提供可选内容的方式:一个编译宏、一份额外的源文件、一个额外的 依赖,或者在多个后端之间做选择。本章是声明与消费 feature 的参考。 -相关文档:[02 —— mcpp.toml](02-mcpp-toml.md) 是 manifest 其余部分的字段参考; +相关文档:[03 —— mcpp.toml](03-mcpp-toml.md) 是 manifest 其余部分的字段参考; [`examples/11-features`](../../examples/11-features/) 是一个把三种形态都声明了 -一遍、并且用 dev-dependency 写测试的包;[32 —— 异构硬件构建](32-heterogeneous-builds.md) +一遍、并且用 dev-dependency 写测试的包;[42 —— 异构硬件构建](42-heterogeneous-builds.md) 是这套机制最大的消费者,因为每条加速器 lane 都是一个 feature。 ## `[features]` —— Feature(Cargo 风格,可加性) @@ -60,7 +60,7 @@ simd = { sources = ["src/simd/**"], flags = [ 空间宏:feature **不**注入自由的包级 `cflags`/`ldflags`,否则会破坏加性的 feature 并集模型。链接旗标来自 provider 依赖(见下文*`provides` / `requires`*),而非 feature。 - 每个激活的 feature 仍会得到自动的 `-DMCPP_FEATURE_`,`defines` 与之叠加。 -- `flags`(mcpp 0.0.101+)与 `[build].flags`([02 §2.3](02-mcpp-toml.md))共用同一有序 inline-table 数组 +- `flags`(mcpp 0.0.101+)与 `[build].flags`([03 §2.3](03-mcpp-toml.md))共用同一有序 inline-table 数组 文法(`glob` 必填,加 `cflags`/`cxxflags`/`asmflags`/`defines`;与 `[[build.flags]]` 一样也接受 `[[features..flags]]` 拼写)。feature 激活时 条目追加在 base `[build].flags` **之后**(feature 按名 @@ -87,7 +87,7 @@ device_extensions = [".comp", ".vert", ".frag", ".glsl"] 会把它们分类为设备源 —— 不扫描 import、不产 BMI、由 mcpp 不驱动的编译器编译。这与 `[build] module_extensions` 是同一个形状:mcpp 知道设备源*是什么*,不知道 `.cu` 是 CUDA,所以**一门新设备语言不需要引擎改动**。 -[32 — 异构硬件构建](32-heterogeneous-builds.md) 里那句「第六个后端是一个包而不是一次 +[42 — 异构硬件构建](42-heterogeneous-builds.md) 里那句「第六个后端是一个包而不是一次 引擎改动」由此才成立;`.slang` 已从 mcpp 的内置表中移除,现在正是经由这条路到达的。 `rule_module` 给出消费者的构建程序为够到这条规则而 import 的模块,以及它调用的 diff --git a/docs/zh/03-workspace.md b/docs/zh/06-workspace.md similarity index 99% rename from docs/zh/03-workspace.md rename to docs/zh/06-workspace.md index 3a7f12a0..ce38370d 100644 --- a/docs/zh/03-workspace.md +++ b/docs/zh/06-workspace.md @@ -1,4 +1,4 @@ -# 03 —— 工作空间 +# 06 —— 工作空间 工作空间允许在同一个仓库中组织和管理多个相关的 mcpp 包(库或应用程序)。各成员包共享统一的依赖版本配置和工具链设置,同时保持独立的 `mcpp.toml` 工程文件。 diff --git a/docs/zh/07-testing.md b/docs/zh/07-testing.md new file mode 100644 index 00000000..522a90a5 --- /dev/null +++ b/docs/zh/07-testing.md @@ -0,0 +1,115 @@ +# 07 —— 测试 + +**读者:**任何有代码需要持续可用的人。 + +**本章回答的那一个问题:**测试怎么写、怎么跑,mcpp 认为什么是一个测试,以及 +在本机跑不了的东西怎么测。 + +**不在这里:**runner 怎么抵达一台设备 —— 那是 +[41 —— 抵达一台设备](41-devices.md);以及机器可读流的 schema,那是 +[50 —— 机器可读输出](50-machine-output.md)。本章只说明哪个旗标产生它,到此为止。 + +在此之前:[04 —— 依赖与解析](04-dependencies.md) 覆盖 `[dev-dependencies]`, +那是测试如何取到产物取不到的包。在此之后: +[08 —— 按场景选命令](08-commands-by-scenario.md) 是其余一切的查阅入口。 + +## mcpp 认为什么是一个测试 + +每一个 `tests/**/*.cpp` 都是一个测试:mcpp 把每个文件编译成它自己的程序并运行它。 +测试通过的判据是它的程序以 0 退出。 + +``` +myproject/ + mcpp.toml + src/… + tests/ + test_parse.cpp 一个程序 + unit/test_span.cpp 另一个 +``` + +没有框架,也不需要注册。测试**可以**用一个框架 —— `[dev-dependencies]` 是它取到 +框架的方式 —— 但 mcpp 持有的契约是退出码,这也是为什么为别的框架写的测试不需要 +适配层。 + +`mcpp new` 会生成 `tests/test_smoke.cpp`,让工程一开始就有这个目录。 + +## 运行它们 + +```bash +mcpp test # 构建并运行每个测试 +mcpp test parse # 只运行名字匹配的那些 +mcpp test --list # 列出会跑哪些,不构建也不运行 +mcpp test -- --verbose # `--` 之后的一切传给每个测试程序 +``` + +测试的构建轴与 `mcpp build` 相同,因此测试跑在它要检查的那个配置上,而不是默认 +配置上: + +| 旗标 | 选择什么 | +|---|---| +| `--profile ` | `dev`(默认)、`release`、`dist`,或 manifest 声明的某个 `[profile.*]` | +| `--features ` | 这次测试构建的 feature 集合 | +| `--target ` | 宿主以外的目标 | +| `--accel ` / `--no-accel` | 本次构建面向的设备后端 | +| `--cap ` | 钉住某个能力的 provider | + +`--timeout ` 杀掉仍在运行的测试(默认 300;`0` 关闭),`--build-timeout ` +限制编译。一个挂住的测试被报为**以它自己的名字失败**,而不是一个停下来的任务。 + +## 取到产物取不到的包的测试 + +```toml +[dev-dependencies] +counters = { path = "../counters" } +``` + +`[dev-dependencies]` 的条目只为测试构建解析,别处一概不用:它不在产物里,包的 +消费者也永远看不见它。这就是「测试的依赖」与「包自己的依赖」之间的区别,也是 +测试框架不会变成一个库所发布内容的一部分的原因。 + +[`examples/11-features`](../../examples/11-features/) 声明了一个并使用它。 + +## 在本机跑不了的目标上测试 + +面向交叉目标或裸机板子的测试,会被编译到那个目标,并经由一个 **runner** 执行 —— +runner 是板级支持包提供的一串 argv,mcpp 把测试二进制附加在其后执行它。 + +```bash +mcpp test --target thumbv7em-none-eabihf # 为板子构建,经它的 runner 运行 +mcpp test --no-runner # 忽略 runner,直接执行 +``` + +测试本身一个字都不用改。同样的 `tests/**/*.cpp` 为设备编译,判据仍然是退出码 —— +这正是裸机 runner 被选成「能产生 semihosting 退出码或 QEMU 退出码」的原因。 + +`--no-runner` 是给「本机就能原生执行这些二进制、不该为模拟器付代价」的宿主准备的。 + +runner 本身、具名 runner,以及一块板子声明什么,见 +[41 —— 抵达一台设备](41-devices.md)。 + +## 不能彼此并排运行的测试 + +`mcpp test` 在一个工作池上运行测试程序。一块板子接一个探针、一块 GPU、一个串口, +或者一份单座许可证,同一时刻只容一个使用者;两个 worker 同时去拿的结果是交错, +而不是干净地失败。 + +拥有该资源的那个包**自己声明**这一点,`mcpp test` 随后把这些测试串行化。工程方 +永远不需要记得加 `-j1`。 + +## 报告给程序 + +```bash +mcpp test --message-format json +``` + +每个测试一条 NDJSON 记录,供 CI 任务或编辑器消费。schema 及其版本见 +[50 —— 机器可读输出](50-machine-output.md);属于本章的只有「这个旗标存在」以及 +「人类可读格式是默认」。 + +## 当前边界 + +- 一个测试是一个 `.cpp` 产出一个程序。mcpp 不发现文件内部的用例,因此框架的 + 逐用例选择发生在程序内部,经由 `--` 之后的参数。 +- `--build-timeout` 只在 POSIX 上有效。 +- `--workspace-timeout` 限制 `--workspace` 的扇出并报告跑到了哪些;它不把超时 + 归因到某个成员。 diff --git a/docs/zh/06-commands-by-scenario.md b/docs/zh/08-commands-by-scenario.md similarity index 99% rename from docs/zh/06-commands-by-scenario.md rename to docs/zh/08-commands-by-scenario.md index 20006c43..2a34c0ad 100644 --- a/docs/zh/06-commands-by-scenario.md +++ b/docs/zh/08-commands-by-scenario.md @@ -1,10 +1,10 @@ -# 06 —— 按场景选命令 +# 08 —— 按场景选命令 命令清单是 `mcpp --help`,每个子命令还有自己的 `--help`。本章回答的是另一个问题: 某个情形已经发生时该用哪条命令 —— 构建目录一直变大、解析结果出乎意料、描述符即将 发布、索引可能陈旧。这里收的都是名字本身没有说出它所属场景的命令。 -相关文档:[00 — 快速开始](00-getting-started.md)(日常构建与测试循环)、 +相关文档:[01 — 快速开始](01-getting-started.md)(日常构建与测试循环)、 [20 — 工具链管理](20-toolchains.md)、 [11 — 发布库到 mcpp-index](11-publishing-a-library.md)、 [50 — 机器可读输出](50-machine-output.md)。 @@ -282,7 +282,7 @@ manifest 在这一版仍能加载;不认识的**值**——`cmd` 缺失或不是 > **Hook 是代码,而 `mcpp.toml` 是仓库的一部分。** 构建一个刚克隆下来的项目,会以 > 执行 `mcpp build` 的那个账户的权限,运行它 `[hooks]` 里写的任何东西。这与 -> `build.mcpp`([05 — build.mcpp](05-build-mcpp.md))已经要求的信任是同一份; +> `build.mcpp`([30 — build.mcpp](30-build-mcpp.md))已经要求的信任是同一份; > `[hooks]` 扩大的是它的范围,而不是引入了一份新的信任。 Hook 程序可以作为普通 xlings 依赖安装。例如,音频通知程序可以把音频内置进自己的 diff --git a/docs/zh/10-pack-and-release.md b/docs/zh/10-pack-and-release.md index 0cec42f1..1b23db38 100644 --- a/docs/zh/10-pack-and-release.md +++ b/docs/zh/10-pack-and-release.md @@ -55,7 +55,7 @@ mcpp 之所以针对私有 glibc 构建,正是为了让产物的行为不取决 「自包含」有一个下限。有些库只能来自目标机器:图形驱动的用户态部分与正在运行的 内核模块版本绑定,而对专有栈而言,再分发是不被允许的。这类依赖应声明为运行期能力 -需求(`docs/zh/02-mcpp-toml.md` §2.11),模式表随之多出一列: +需求(`docs/zh/03-mcpp-toml.md` §2.11),模式表随之多出一列: | Mode | 需要宿主提供能力的程序 | |---|---| @@ -270,7 +270,7 @@ Win32 loader 解析 DLL 的第一顺位就是**可执行文件所在目录**,而 `vcruntime140.dll` / `msvcp140.dll` **不是** Windows 自己的:它们属于 MSVC toolset,就像 `libstdc++.so` 属于 gcc。它们要不要跟着产物走,由 `cxx_runtime` -决定(见 `docs/zh/02-mcpp-toml.md`),不由这张表决定 —— 而 `mcpp pack` 会拒绝 +决定(见 `docs/zh/03-mcpp-toml.md`),不由这张表决定 —— 而 `mcpp pack` 会拒绝 那些无法兑现契约的组合: ``` diff --git a/docs/zh/20-toolchains.md b/docs/zh/20-toolchains.md index 0e2ba371..3f5464a7 100644 --- a/docs/zh/20-toolchains.md +++ b/docs/zh/20-toolchains.md @@ -200,7 +200,7 @@ mcpp toolchain default gcc@16 --target x86_64-windows-gnu `import std`)。该 target 默认 linkage 为 **static**——产出的 `.exe` 完全自包含(无需随包分发 `libstdc++-6.dll`,可直接在 wine 下运行); 要退出请写在 target 段上——`linkage` 只认精确 triple(见 -[mcpp.toml](02-mcpp-toml.md) §2.7),`[build] linkage` 这个键并不存在,写了会被静默忽略: +[mcpp.toml](03-mcpp-toml.md) §2.7),`[build] linkage` 这个键并不存在,写了会被静默忽略: ```toml [target.x86_64-windows-gnu] @@ -437,7 +437,7 @@ cxx_runtime = "self-contained" # C++ 运行时那根轴 CRT。 toolset 自带的那份可再分发 CRT(`vcruntime140.dll` / `msvcp140.dll`)可以跟着 -产物走 —— 见 `docs/zh/02-mcpp-toml.md` 的 `cxx_runtime = "toolchain-coupled"`。 +产物走 —— 见 `docs/zh/03-mcpp-toml.md` 的 `cxx_runtime = "toolchain-coupled"`。 ## 项目级版本锁定 diff --git a/docs/zh/21-the-target-triple.md b/docs/zh/21-the-target-triple.md index 48e2d4f3..76cf935f 100644 --- a/docs/zh/21-the-target-triple.md +++ b/docs/zh/21-the-target-triple.md @@ -450,7 +450,7 @@ runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic", `sysroot = ""` 选定零 libc 档:编译行上没有 C 库,链接上也没有。 **缺席 `sysroot` 键是另一个答案** —— 它继承该目标行自己的默认值。 -见[第 13 章](30-baremetal.md)。 +见[第 13 章](40-baremetal.md)。 ## 参考 diff --git a/docs/zh/22-target-side.md b/docs/zh/22-target-side.md index 2de9a985..666b2e39 100644 --- a/docs/zh/22-target-side.md +++ b/docs/zh/22-target-side.md @@ -290,7 +290,7 @@ mcpp 不认识的键 —— 打错的字,或来自更新版本 mcpp 的谓词 |---|---|---| | **裸 OS 别名** | 单个 OS / 族 —— 简洁且常用的形式 | `[target.windows]`、`[target.unix]` | | **`cfg(...)` 谓词** | 复合条件(arch / env / 组合子) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 [02 §2.7.3](02-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | +| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 [03 §2.7.3](03-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | 一个选择器可以承载平台条件的**依赖**与**构建 flag**: @@ -312,7 +312,7 @@ cxxflags = ["-march=x86-64-v2"] 单个 OS/族用裸形式,arch/env 条件与组合子用 `cfg(...)`。 - **可用键**:`dependencies` / `dev-dependencies` / `build-dependencies` / - `feature-deps.`(mcpp 2026.8.6.2+ —— 见 [05 —— build.mcpp](05-build-mcpp.md);feature 本身无条件注册, + `feature-deps.`(mcpp 2026.8.6.2+ —— 见 [30 —— build.mcpp](30-build-mcpp.md);feature 本身无条件注册, 只有它的依赖集合受限定),以及带 `cflags` / `cxxflags` / `ldflags` / `sources` 的 `build`(mcpp 0.0.95+ —— 条件源码 glob,例如把 `src/x86/**/*.asm` 收在 `cfg(arch = "x86_64")` 之后;`!` 排除 glob 在此同样有效),再加 `flags` 与 @@ -393,4 +393,4 @@ mcpp 装上的那个载荷,而这个值是目标侧解析的一项**输入** — C 库,那时解析出的 `c-abi` 就不是这里返回的东西。要按已解析的层分支,请用层谓词: `[target.'cfg(c-abi = "musl")'.build]`(见[22 —— 目标侧](22-target-side.md))。 这一段在 2026.9.1.1 之前写的是「解析到的是哪份 C 库」,那是两者里错的那一个。 -参见[30 —— 裸机与 freestanding 目标](30-baremetal.md)。 +参见[40 —— 裸机与 freestanding 目标](40-baremetal.md)。 diff --git a/docs/zh/23-the-project-environment.md b/docs/zh/23-the-project-environment.md index 641298e1..7f537516 100644 --- a/docs/zh/23-the-project-environment.md +++ b/docs/zh/23-the-project-environment.md @@ -89,7 +89,7 @@ SubOS 里。前置让被声明的环境成为默认答案;其余的仍在它后 工程与依赖命名**同一个包**时,只装它的一个版本:身份是 `(namespace, name)`,版本是这个 包上的约束。离产物更近的声明赢,并且覆盖会被报出来;不满足对方所陈述之要求的钉会被拒绝 -并点出两侧。见 [02 — mcpp.toml](02-mcpp-toml.md) 的「一个包一个版本」。 +并点出两侧。见 [03 — mcpp.toml](03-mcpp-toml.md) 的「一个包一个版本」。 ## 4. 只读取环境,从不创建环境 @@ -138,7 +138,7 @@ create/bootstrap that environment instead of falling back to active/default 见本章。不写档位就是从前的行为。 **runner。** `[xlings.workspace]` 下的程序也是 `[target.].runner` 查找其第一个元素 -的首选位置,在 `PATH` 之前([02 §2.7.3](02-mcpp-toml.md))。两个键合起来,在 CI 宿主上供给用户态模拟器, +的首选位置,在 `PATH` 之前([03 §2.7.3](03-mcpp-toml.md))。两个键合起来,在 CI 宿主上供给用户态模拟器, 并通过它执行交叉构建的产物,而清单不必写出载荷的路径。 ## 6. 什么该写在别处 @@ -423,8 +423,8 @@ error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins ## 8. 相关章节 -- [05 - build.mcpp](05-build-mcpp.md) —— 构建程序收到的契约,含它运行时的 +- [30 - build.mcpp](30-build-mcpp.md) —— 构建程序收到的契约,含它运行时的 `PATH`。 - [91 - 工具链内部](91-toolchain-internals.md) —— 运行时选择、`RuntimeBinding` 快照与降级规则。 -- [02 - mcpp.toml](02-mcpp-toml.md) —— manifest 的其余部分。 +- [03 - mcpp.toml](03-mcpp-toml.md) —— manifest 的其余部分。 diff --git a/docs/zh/24-openkal-cross.md b/docs/zh/24-openkal-cross.md index 8f4bbe74..9f9bc2bf 100644 --- a/docs/zh/24-openkal-cross.md +++ b/docs/zh/24-openkal-cross.md @@ -178,7 +178,7 @@ runner = ["qemu-aarch64-static"] 没有这个键时,`mcpp run` 报告内核的拒绝(`Exec format error`)与应当写的键,`mcpp test` 把每个测试报告为未运行并以 2 退出。能原生执行该产物的宿主传 `--no-runner`。规则见 -[02 —— mcpp.toml](02-mcpp-toml.md) §2.7.3。 +[03 —— mcpp.toml](03-mcpp-toml.md) §2.7.3。 ### 源码是同一份,程序不是 diff --git a/docs/zh/05-build-mcpp.md b/docs/zh/30-build-mcpp.md similarity index 99% rename from docs/zh/05-build-mcpp.md rename to docs/zh/30-build-mcpp.md index 785b0414..354f2198 100644 --- a/docs/zh/05-build-mcpp.md +++ b/docs/zh/30-build-mcpp.md @@ -1,6 +1,6 @@ -# 05 —— 构建程序:`build.mcpp` +# 30 —— 构建程序:`build.mcpp` -[English](../05-build-mcpp.md) | **简体中文** +[English](../30-build-mcpp.md) | **简体中文** 绝大多数工程只需要 `mcpp.toml`。需要构建期逻辑时 —— 探测主机、生成源码、依据环境 决定某个编译开关——就在工程根目录放一个 `build.mcpp`。它是 mcpp 版的 Zig `build.zig` @@ -295,7 +295,7 @@ store 内部结构 —— 与 `dep_dir` 存在的理由相同。 **依赖声明的包同样被作答**(2026.9.6.6+),而且答的是这次构建**真正装上**的版本,不是 本地 manifest 写下的那个。一个包只有一个版本:工程与规则都命名它时,离产物更近的声明赢, -而两侧被告知同一个答案。见 [02 — mcpp.toml](02-mcpp-toml.md) 的「一个包一个版本」。 +而两侧被告知同一个答案。见 [03 — mcpp.toml](03-mcpp-toml.md) 的「一个包一个版本」。 **`[feature-xlings.]` 在 `` 生效时同样被作答**(2026.9.6.2+)。这张表从诞生 起就参与供给 —— 在那里写下一个包,它就会被下载并安装 —— 但构建程序的环境只由 @@ -443,7 +443,7 @@ mcpp 为那条边写出 `depfile =` 与 `deps = gcc`,ninja 读取该文件并把 > 把预编译对象写进 `[build].ldflags` 同样能到达链接器,但**不要**用它承载构建产物: > ldflags 是链接命令里的一串字符、不是图里的文件,没有任何东西跟踪它,改了它得到的是 -> `ninja: no work to do`。Windows 资源请用 [`[resources]`](02-mcpp-toml.md); +> `ninja: no work to do`。Windows 资源请用 [`[resources]`](03-mcpp-toml.md); > `object` 是其余一切的出口。 **必须写出输出文件名。** mcpp 在 prepare 期就定死源码集、fingerprint 与模块图, @@ -609,7 +609,7 @@ shim,而可用的那份就在项目自己的环境里,根本不在 `PATH` 上。 一条规则 ——「对这些 `.proto` 跑 protoc」「对这些源码跑 clang-tidy」—— 属于一个包, 而不该被复制到每个消费者的 `build.mcpp` 里。机制是 -[`host-module = true`](../02-mcpp-toml.md);本节讲的是它里面应该长什么样。 +[`host-module = true`](../03-mcpp-toml.md);本节讲的是它里面应该长什么样。 下面这些从第一个规则包 `mcpplibs.grpcgen` 归纳而来,每一条特征都单独判过是必然还是偶然。 它们是指引而非规则,因为其中没有一条能给出引擎可以检查的判据。 @@ -649,7 +649,7 @@ shim,而可用的那份就在项目自己的环境里,根本不在 `PATH` 上。 而不是以包名注册,所以 `export module mcpp.rules.spirv;` 就是消费者 import 的那个名字。 官方插件集中在一个包里,`mcpp:plugins`(仓库 `mcpp-community/mcpp-plugins`):规则包命名为 `mcpp.rules.`,构建期工具命名为 `mcpp.tools.`,每个成员由该包的一个 feature 选择 -(见 [`host-module = true`](../02-mcpp-toml.md))。`mcpp.build.*` 是引擎自己的模块族, +(见 [`host-module = true`](../03-mcpp-toml.md))。`mcpp.build.*` 是引擎自己的模块族, 不用于插件。引擎判定不了谁是官方,所以检查以包的**命名空间**为键,两者不一致时告警 —— warning: build rule 'mcpplibs.plugins' declares the module @@ -750,7 +750,7 @@ mcpp **不会**每次构建都重跑 `build.mcpp`。它会缓存程序产出的 - **在主机上运行——交叉构建下也是**(mcpp 0.0.95+)。`mcpp build --target ` 下,程序用宿主解析的工具链编译、在宿主运行,并看到 `MCPP_TARGET` = 交叉三元组。 纯声明式的目标门控仍首选 `[target.'cfg(...)']` 表——参见 - [02 - mcpp.toml 工程文件指南](02-mcpp-toml.md)。 + [03 - mcpp.toml 工程文件指南](03-mcpp-toml.md)。 - **当前工作目录是工程根目录**,因此相对路径(`src/generated.cpp`)会落在预期位置。 - `build.mcpp` 非零退出会中止构建并打印其输出。 - **运行有时间上限**(mcpp 2026.8.5.1+):构建程序默认有 **600 秒**,超时后 mcpp @@ -804,7 +804,7 @@ grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } 每个名字必须是该包的一个 `kind = "bin"` target。mcpp 会**为构建机器**构建它, 并把绝对路径以 `MCPP_DEP__BIN_` 交给 `build.mcpp` —— 用 -`mcpp::dep_bin("protobuf", "protoc")` 读取(见 [05 — build.mcpp](05-build-mcpp.md))。 +`mcpp::dep_bin("protobuf", "protoc")` 读取(见 [30 — build.mcpp](30-build-mcpp.md))。 四条值得知道的性质: @@ -907,7 +907,7 @@ rules-spirv = { sources = ["rules/spirv.cppm"] } # export module mcpp.rules.spir plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } ``` -**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 [02 §2.6.1](02-mcpp-toml.md) 描述的那种 +**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 [03 §2.6.1](03-mcpp-toml.md) 描述的那种 情形:它的库绝不该到达目标,而它的规则仍然被需要。两条轴是分开的: `host-module = true` 说的是**要哪一种构建期产物**,而 section 说的是**这个包是否到达 目标**;规则包在第二条轴上的答案是"否",而 section 就是说这件事的地方。写在 diff --git a/docs/zh/40-authoring-a-rule-package.md b/docs/zh/31-authoring-a-rule-package.md similarity index 96% rename from docs/zh/40-authoring-a-rule-package.md rename to docs/zh/31-authoring-a-rule-package.md index c4cdcf2d..f8946505 100644 --- a/docs/zh/40-authoring-a-rule-package.md +++ b/docs/zh/31-authoring-a-rule-package.md @@ -1,11 +1,11 @@ -# 40 —— 编写规则包 +# 31 —— 编写规则包 规则包教会 mcpp 构建引擎本身没有规则的东西:一种设备语言、一份着色器、一个生成的 接口、一项检查。它是一个普通的包,它的模块被消费者的构建程序 import。 -相关文档:[05 —— build.mcpp](05-build-mcpp.md) 是本章所用指令协议与类型化 API 的 -参考;[04 —— Feature 与能力](04-features-and-capabilities.md) 覆盖 feature 相关键; -[32 —— 异构硬件构建](32-heterogeneous-builds.md) 列出已发布的规则。两个示例: +相关文档:[30 —— build.mcpp](30-build-mcpp.md) 是本章所用指令协议与类型化 API 的 +参考;[05 —— Feature 与能力](05-features-and-capabilities.md) 覆盖 feature 相关键; +[42 —— 异构硬件构建](42-heterogeneous-builds.md) 列出已发布的规则。两个示例: [`08-build-rules`](../../examples/08-build-rules/) 是做检查与嵌入的规则, [`12-a-new-device-language`](../../examples/12-a-new-device-language/) 是新增一门 语言的规则。 @@ -152,7 +152,7 @@ device link 在引擎侧的全部内容:N 个 `artifact` action,其输出不进 都不开。 裸版本是项目可以覆盖的**选择**;`>=` 是项目不得低于的**要求**。见 -[02 —— mcpp.toml](02-mcpp-toml.md) 的*一个包,一个版本*。 +[03 —— mcpp.toml](03-mcpp-toml.md) 的*一个包,一个版本*。 ## 生成岛的边界 @@ -225,4 +225,4 @@ mcpp::floor("cuda.driver", runtime_needs); - `mcpp emit xpkg` 把 `manifest = "mcpp.toml"` 写进 `mcpp` 段,而 `mcpp xpkg parse` 把该键报为未知并以 1 退出。`mcpp-index` 里没有任何描述符使用 它(218 个里 0 个);自带 `mcpp.toml` 的包整个省略 `mcpp` 字段。见 - [06 —— 按场景选命令](06-commands-by-scenario.md)的*当前边界*。 + [08 —— 按场景选命令](08-commands-by-scenario.md)的*当前边界*。 diff --git a/docs/zh/30-baremetal.md b/docs/zh/40-baremetal.md similarity index 99% rename from docs/zh/30-baremetal.md rename to docs/zh/40-baremetal.md index f807b213..be996532 100644 --- a/docs/zh/30-baremetal.md +++ b/docs/zh/40-baremetal.md @@ -1,10 +1,10 @@ -# 30 —— 裸机与 freestanding 目标 +# 40 —— 裸机与 freestanding 目标 本文说明 mcpp 如何为没有操作系统的目标构建、运行与测试软件,以及板级支持包如何 提供引擎刻意不去掌握的那部分目标事实。 -相关文档:[02 — mcpp.toml 清单指南](02-mcpp-toml.md) §2.7.2 是本文使用的 -`[target.]` 各键的参考;[05 — build.mcpp 构建程序](05-build-mcpp.md) +相关文档:[03 — mcpp.toml 清单指南](03-mcpp-toml.md) §2.7.2 是本文使用的 +`[target.]` 各键的参考;[30 — build.mcpp 构建程序](30-build-mcpp.md) 是板级支持包所用指令协议的参考;[91 — 工具链内部机制](91-toolchain-internals.md) 描述了本文所偏离的宿主链接模型。 @@ -558,7 +558,7 @@ error: no runner is configured for 'riscv64-none-elf' — a freestanding artifac 这个键不限于裸机。hosted 交叉目标 —— x86_64 宿主上的 `aarch64-linux-musl` 产物 —— 使用同一个 `[target.].runner`,以 `qemu-aarch64-static` 这类用户态模拟器代替 系统模拟器;在这类目标上,缺少 runner 在内核拒绝产物之前不是错误。hosted 目标的规则、 -`--no-runner` 出口与 `mcpp test` 的未运行报告见 [02 —— mcpp.toml](02-mcpp-toml.md) +`--no-runner` 出口与 `mcpp test` 的未运行报告见 [03 —— mcpp.toml](03-mcpp-toml.md) §2.7.3。 ## 编写板级支持包 @@ -591,7 +591,7 @@ if (const char* dir = mcpp::xpkg_dir("xim", "qemu-riscv"); dir && *dir) { ``` 没有那一行,构建会成功、不配置 runner,而 `mcpp run` 报告缺少 runner 并建议写一个 -`runner` 键 —— 这句话一般情况下对,在这里不对。见 [05 —— build.mcpp](05-build-mcpp.md) +`runner` 键 —— 这句话一般情况下对,在这里不对。见 [30 —— build.mcpp](30-build-mcpp.md) 的 `mcpp:warning=`。 ### 板级支持包发出的指令 diff --git a/docs/zh/31-devices.md b/docs/zh/41-devices.md similarity index 97% rename from docs/zh/31-devices.md rename to docs/zh/41-devices.md index 144fb4b2..acf1f435 100644 --- a/docs/zh/31-devices.md +++ b/docs/zh/41-devices.md @@ -1,9 +1,9 @@ -# 31 —— 抵达一台设备 +# 41 —— 抵达一台设备 本文规定 mcpp 如何执行一个运行在构建机器之外的产物、包如何提供抵达它的其他方式, 以及工程如何在模拟器与真实硬件之间选择。 -相关文档:[30 - 裸机与 freestanding 目标](30-baremetal.md) · [05 - build.mcpp](05-build-mcpp.md) +相关文档:[40 - 裸机与 freestanding 目标](40-baremetal.md) · [30 - build.mcpp](30-build-mcpp.md) · [50 - 机器输出](50-machine-output.md)。 ## 一条命令,加具名的例外 diff --git a/docs/zh/32-heterogeneous-builds.md b/docs/zh/42-heterogeneous-builds.md similarity index 99% rename from docs/zh/32-heterogeneous-builds.md rename to docs/zh/42-heterogeneous-builds.md index 906108b9..33ef464d 100644 --- a/docs/zh/32-heterogeneous-builds.md +++ b/docs/zh/42-heterogeneous-builds.md @@ -1,4 +1,4 @@ -# 32 —— 异构硬件构建 +# 42 —— 异构硬件构建 GPU 与 AI 加速器目标,以及宿主/设备混合编译:mcpp 如何构建设备代码,以及一个预建产物如何 声明它能在哪些设备上运行。 @@ -87,7 +87,7 @@ C++20 modules 的编译器。 上表是 mcpp **不需要被告知**就知道的那些:在「包可以自己声明」之前就已经支持的语言。 规则包通过 `[features]..device_extensions`(见 -[02 — mcpp.toml](02-mcpp-toml.md) §2.8)向它增补,而这正是**一门新设备语言到达的方式** +[03 — mcpp.toml](03-mcpp-toml.md) §2.8)向它增补,而这正是**一门新设备语言到达的方式** —— 不动引擎,也不需要发一版引擎。Slang 是第一个:`.slang` 不在上表里,由 `mcpp:plugins` 的 `rules-slang` 声明。 @@ -196,7 +196,7 @@ mcpp 2026.9.7.1 之前,那条 lane 是唯一的例外:生成的头文件**就是 调用设备编译器的那条命令不内置在 mcpp 里,而是由**构建规则包**提供 —— 以 `host-module = true` 消费,emit 输出汇入链接的构建边。机制见 -[05 — build.mcpp](05-build-mcpp.md),可用的 CUDA 规则见 `examples/09-heterogeneous/cuda`。 +[30 — build.mcpp](30-build-mcpp.md),可用的 CUDA 规则见 `examples/09-heterogeneous/cuda`。 这个划分是刻意的。mcpp 拥有构建图、产物身份与架构集合;厂商的 flag 拼法、 架构语法与宿主编译器要求属于规则包。 @@ -239,7 +239,7 @@ plugins = { version = "0.3.0", features = ["rules-cuda"], host-module = true } ``` 更近的声明赢,装一个版本,并且 mcpp 说出用了哪条。不满足规则下界的钉会被拒绝并点出 -两侧,而不是与它并排装下来。完整规则见 [02 — mcpp.toml](02-mcpp-toml.md) 的 +两侧,而不是与它并排装下来。完整规则见 [03 — mcpp.toml](03-mcpp-toml.md) 的 「一个包一个版本」;`examples/09-heterogeneous/multi-backend` 是本仓库里唯一走覆盖 路径的示例,其余每一个都只写那条边。 @@ -267,7 +267,7 @@ mcpp 里长出第二份拷贝)。 干净、链接干净,到第一次分配才以 *"CUDA driver version is insufficient for CUDA runtime version"* 失败。规则经驱动自己的库(经由 sentinel 包够到,绝不经 `/usr/lib`)读出驱动 版本并陈述为事实;陈述它的运行时需要的下界;引擎在编译任何东西之前比较两者 —— 见 -[05 — build.mcpp](05-build-mcpp.md) 的探针通道。引擎读到的是一个名字、一个关系、一个 +[30 — build.mcpp](30-build-mcpp.md) 的探针通道。引擎读到的是一个名字、一个关系、一个 版本;`cuda.driver` 是流过引擎的数据。 凡是错答比不答更贵的地方都只报告不强制:工程里没有规则包的机器没有任何厂商相关的话 diff --git a/docs/zh/90-build-from-source.md b/docs/zh/90-build-from-source.md index c2823a78..7895bf4d 100644 --- a/docs/zh/90-build-from-source.md +++ b/docs/zh/90-build-from-source.md @@ -5,7 +5,7 @@ ## 准备 -参照 [00 — 快速开始](00-getting-started.md) 安装一份现成的 mcpp, +参照 [01 — 快速开始](01-getting-started.md) 安装一份现成的 mcpp, 然后克隆仓库: ```bash diff --git a/docs/zh/91-toolchain-internals.md b/docs/zh/91-toolchain-internals.md index e0b38b91..820e9d6c 100644 --- a/docs/zh/91-toolchain-internals.md +++ b/docs/zh/91-toolchain-internals.md @@ -432,7 +432,7 @@ loader;`/bin/sh` 有事:它的 `PT_INTERP` 指向**宿主** loader,而且任何 ### 7.3 嵌入式与裸机工具链 `riscv64-none-elf` 与 `riscv32-none-elf` 已实现,面向用户的说明见 -[30 — 裸机与 freestanding 目标](30-baremetal.md)。本节记录由此得到的形态与 +[40 — 裸机与 freestanding 目标](40-baremetal.md)。本节记录由此得到的形态与 上文 hosted 模型之间的关系。 本节早先的三条预测成立: diff --git a/docs/zh/README.md b/docs/zh/README.md index e3491e70..5051e62e 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -22,13 +22,13 @@ | 目标 | 阅读 | 运行 | |---|---|---| -| 写一个程序 | [00](00-getting-started.md)、[02](02-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | -| 写一个供他人 import 的库 | [11](11-publishing-a-library.md)、[04](04-features-and-capabilities.md)、[02](02-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | +| 写一个程序 | [01](01-getting-started.md)、[03](03-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | +| 写一个供他人 import 的库 | [11](11-publishing-a-library.md)、[05](05-features-and-capabilities.md)、[03](03-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | | 发布它 | [10](10-pack-and-release.md)、[11](11-publishing-a-library.md)、[12](12-binary-distribution.md) | [`03-pack-static`](../../examples/03-pack-static/)、[`05-lib-distribution`](../../examples/05-lib-distribution/) | -| 为另一台机器构建 | [21](21-the-target-triple.md)、[24](24-openkal-cross.md)、[30](30-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | -| 使用 GPU 或加速器 | [32](32-heterogeneous-builds.md)、[31](31-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | -| 增加一条规则、一种语言或一个生成器 | [40](40-authoring-a-rule-package.md)、[05](05-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`12-a-new-device-language`](../../examples/12-a-new-device-language/) | -| 向索引添加一个包 | [11](11-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [06](06-commands-by-scenario.md) —— 发布相关场景 | +| 为另一台机器构建 | [21](21-the-target-triple.md)、[24](24-openkal-cross.md)、[40](40-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | +| 使用 GPU 或加速器 | [42](42-heterogeneous-builds.md)、[41](41-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | +| 增加一条规则、一种语言或一个生成器 | [31](31-authoring-a-rule-package.md)、[30](30-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`12-a-new-device-language`](../../examples/12-a-new-device-language/) | +| 向索引添加一个包 | [11](11-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [08](08-commands-by-scenario.md) —— 发布相关场景 | | 修改 mcpp 本身 | [90](90-build-from-source.md)、[92](92-release.md)、[51](51-supported-versions.md) | — | 课程也可以以**项目模板**的形式到达:模板由包提供,`mcpp new --template` 实例化 @@ -37,21 +37,23 @@ ## 章节 -编号说明这一章属于哪一部分:`0x` 使用 mcpp,`1x` 发布构建产物,`2x` 工具链与目标, -`3x` 裸机与设备,`4x` 扩展 mcpp,`5x` 面向机器的契约,`9x` mcpp 自身。同一部分内部 -的排列是阅读顺序。 +首位数字就是部分,所以编号本身说明一章属于哪里:`0x` 是人人都需要的,`1x` 发布, +`2x` 工具链与目标,`3x` 扩展构建图,`4x` 设备与加速器,`5x` 是程序可以解析的东西, +`9x` 是 mcpp 自身。同一部分内部的排列是阅读顺序,不是字母序。 -### 0x —— 使用 mcpp +### 0x —— 人人都需要 -- [00 —— 快速开始](00-getting-started.md) -- [01 —— 示例项目](01-examples.md) -- [02 —— mcpp.toml 工程文件指南](02-mcpp-toml.md) -- [03 —— 工作空间](03-workspace.md) -- [04 —— Feature 与能力](04-features-and-capabilities.md) -- [05 —— 构建程序:`build.mcpp`](05-build-mcpp.md) -- [06 —— 按场景选命令](06-commands-by-scenario.md) +- [00 —— mcpp 的运转方式](00-how-mcpp-works.md) —— 其余每章都假定的模型 +- [01 —— 快速开始](01-getting-started.md) —— 安装、创建、构建、运行 +- [02 —— 示例项目](02-examples.md) —— 哪个示例教什么 +- [03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md) —— manifest 可以说什么 +- [04 —— 依赖与解析](04-dependencies.md) —— 依赖从哪里来,以及哪个版本胜出 +- [05 —— Feature 与能力](05-features-and-capabilities.md) —— 让包的一部分成为可选 +- [06 —— 工作空间](06-workspace.md) —— 多个包,一次构建 +- [07 —— 测试](07-testing.md) —— 包括在本机跑不了的那些 +- [08 —— 按场景选命令](08-commands-by-scenario.md) —— 认识名词之后的查阅入口 -### 1x —— 发布构建产物 +### 1x —— 发布 - [10 —— 发布打包](10-pack-and-release.md) - [11 —— 发布一个库到 mcpp-index](11-publishing-a-library.md) @@ -65,22 +67,23 @@ - [23 —— 项目环境](23-the-project-environment.md) - [24 —— 基于 openkal 的交叉构建](24-openkal-cross.md) -### 3x —— 裸机、设备与加速器 +### 3x —— 扩展构建图 -- [30 —— 裸机与 freestanding 目标](30-baremetal.md) -- [31 —— 抵达一台设备](31-devices.md) -- [32 —— 异构硬件构建](32-heterogeneous-builds.md) +- [30 —— 构建程序:`build.mcpp`](30-build-mcpp.md) —— 工程需要一步 mcpp 没有规则的工作 +- [31 —— 编写规则包](31-authoring-a-rule-package.md) —— 把那一步打包给别的工程用 -### 4x —— 从外部扩展 mcpp +### 4x —— 设备与加速器 -- [40 —— 编写规则包](40-authoring-a-rule-package.md) +- [40 —— 裸机与 freestanding 目标](40-baremetal.md) +- [41 —— 抵达一台设备](41-devices.md) +- [42 —— 异构硬件构建](42-heterogeneous-builds.md) -### 5x —— 机器接口与兼容性 +### 5x —— 面向程序的契约 - [50 —— 机器可读输出](50-machine-output.md) - [51 —— 受支持的版本与兼容性](51-supported-versions.md) -### 9x —— 为 mcpp 本身做贡献 +### 9x —— mcpp 自身 - [90 —— 从源码构建与参与贡献](90-build-from-source.md) - [91 —— 工具链机制内幕](91-toolchain-internals.md) diff --git a/examples/09-heterogeneous/README.md b/examples/09-heterogeneous/README.md index 713982e6..547723b0 100644 --- a/examples/09-heterogeneous/README.md +++ b/examples/09-heterogeneous/README.md @@ -105,7 +105,7 @@ plugins = { version = "0.4.0", features = ["rules-cuda", "tools-island"], host-m `multi-backend/` is the one example here that also pins a version, and it does so to demonstrate the override: the rule owns "which package, and no older than what", the project owns "and exactly this one". One version is installed either -way — see *One package, one version* in `docs/02-mcpp-toml.md`. +way — see *One package, one version* in `docs/03-mcpp-toml.md`. ## The layers underneath @@ -152,6 +152,6 @@ The examples show the shape mcpp implements: device code in separate translation units, compiled by a separate compiler, linked into an ordinary program. Models that compile a whole target with an offloading compiler — OpenMP `target`, C++ parallel algorithms with a device backend — have no -separable island and are not reached this way. `docs/32-heterogeneous-builds.md` +separable island and are not reached this way. `docs/42-heterogeneous-builds.md` states the distinction and why it is a property of the model rather than a gap in the tool. diff --git a/examples/09-heterogeneous/cuda/app/mcpp.toml b/examples/09-heterogeneous/cuda/app/mcpp.toml index 5a2680c9..a2e650f5 100644 --- a/examples/09-heterogeneous/cuda/app/mcpp.toml +++ b/examples/09-heterogeneous/cuda/app/mcpp.toml @@ -38,7 +38,7 @@ cuda-driver = "2026.09.05" # `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/02-mcpp-toml.md. Needs mcpp 2026.9.7.1 and mcpp:plugins 0.3.0. +# docs/03-mcpp-toml.md. Needs mcpp 2026.9.7.1 and mcpp:plugins 0.3.0. # # 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, diff --git a/mcpp.toml b/mcpp.toml index cd52df87..fd56852a 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -13,7 +13,7 @@ repo = "https://github.com/mcpp-community/mcpp" # binary would be -O0. A `--profile`/`--dev`/`--release` flag still overrides. default-profile = "release" # Split the module edge: importers start when the BMI is published rather than -# when the compiler exits. See docs/02-mcpp-toml.md. +# when the compiler exits. See docs/03-mcpp-toml.md. # # ON HERE, `auto` (= off) EVERYWHERE ELSE. The key stays opt-in until it has been # through CI on every platform, and nothing was exercising it, so it could never diff --git a/modules/buildmcpp/src/directives.cppm b/modules/buildmcpp/src/directives.cppm index 349c837e..d0557d39 100644 --- a/modules/buildmcpp/src/directives.cppm +++ b/modules/buildmcpp/src/directives.cppm @@ -33,7 +33,7 @@ // Scope a field means the next directive cannot be added without someone // answering that question. // -// See .agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md §4 (S5). +// See .agents/docs/2026-08-30-build-mcpp-extensibility-architecture.md §4 (S5). export module mcpp.build.directives; diff --git a/modules/buildmcpp/src/program_protocol.cppm b/modules/buildmcpp/src/program_protocol.cppm index fab31c4d..7301dcfa 100644 --- a/modules/buildmcpp/src/program_protocol.cppm +++ b/modules/buildmcpp/src/program_protocol.cppm @@ -23,7 +23,7 @@ // the manifest, the toolchain or the filesystem to be stated would not be a // protocol term. // -// See .agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md §4 and +// See .agents/docs/2026-08-30-build-mcpp-extensibility-architecture.md §4 and // .agents/docs/2026-08-11-source-kind-table-and-build-program-timeout.md §4. export module mcpp.build.program_protocol; diff --git a/modules/manifest/src/glob.cppm b/modules/manifest/src/glob.cppm index 036c396a..75c2eb04 100644 --- a/modules/manifest/src/glob.cppm +++ b/modules/manifest/src/glob.cppm @@ -86,7 +86,7 @@ std::optional try_narrow(const std::filesystem::path& p) { // The stored spelling is GENERIC (`/`), because that is what try_narrow // produces and there is no second narrowing here to disagree with it. On // Windows the reported path therefore reads `C:/pkg/test/www`, not -// `C:\pkg\test\www`; docs/02-mcpp-toml.md shows it that way too. +// `C:\pkg\test\www`; docs/03-mcpp-toml.md shows it that way too. void note_unnarrowable_path(const std::filesystem::path& p); // Take and clear this run's records. @@ -96,7 +96,7 @@ void note_unnarrowable_path(const std::filesystem::path& p); // CLI reports. Drained in exactly one place (`cli::run`'s scope guard), which // is what keeps "recorded but never shown" from becoming the next silent // failure. The rule is written up in .agents/skills/mcpp-contributing/SKILL.md -// ("路径窄化不变式") and the user-facing behaviour in docs/02-mcpp-toml.md. +// ("路径窄化不变式") and the user-facing behaviour in docs/03-mcpp-toml.md. std::vector take_unnarrowable_paths(); // Does `candidate` match `glob`, interpreted relative to `root`? diff --git a/modules/manifest/src/toml.cppm b/modules/manifest/src/toml.cppm index 9e66574d..6285f6b0 100644 --- a/modules/manifest/src/toml.cppm +++ b/modules/manifest/src/toml.cppm @@ -2207,7 +2207,7 @@ std::expected parse_string(std::string_view content, // [hooks] — project build lifecycle commands (#496). Parsed HERE rather // than by the module that runs them, for the reason Appendix A of - // docs/02-mcpp-toml.md states: mcpp.toml has one grammar and one parser. + // docs/03-mcpp-toml.md states: mcpp.toml has one grammar and one parser. // A second reader of the same file would report ITS syntax errors in its // own vocabulary — a typo in [package] arriving as "invalid hook // configuration" — and would sit outside the warning/--strict policy every diff --git a/modules/manifest/src/types.cppm b/modules/manifest/src/types.cppm index 116d6170..99aa6380 100644 --- a/modules/manifest/src/types.cppm +++ b/modules/manifest/src/types.cppm @@ -314,7 +314,7 @@ inline void append(BuildInputs& dst, const BuildInputs& src) { // A build-graph node declared by a build program (`mcpp:action=`). // // The architectural point (see -// .agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md §3.1): +// .agents/docs/2026-08-30-build-mcpp-extensibility-architecture.md §3.1): // build.mcpp answers "what does this build look like" — CONFIGURATION — and is // a bad place to do WORK. Generating sources, linting, signing and packaging // are work: they want to be incremental, parallel and attributable, which a @@ -1263,7 +1263,7 @@ struct WorkspaceConfig { // `[hooks]` — project build lifecycle commands (#496). // // The commands are host-shell strings written by the project author, run by -// `mcpp build` around the build it performs. See docs/02-mcpp-toml.md §2.16. +// `mcpp build` around the build it performs. See docs/03-mcpp-toml.md §2.16. // // ONLY THE ROOT PROJECT'S HOOKS ARE EVER RUN. Every manifest mcpp parses // carries this field, including a DEPENDENCY's — and `mcpp build` reaches the diff --git a/src/build/ninja_backend.cppm b/src/build/ninja_backend.cppm index 15c57ae9..128ea6b4 100644 --- a/src/build/ninja_backend.cppm +++ b/src/build/ninja_backend.cppm @@ -1656,7 +1656,7 @@ std::string emit_ninja_string(const BuildPlan& plan) { // same result — not a race, never run. // // PER PACKAGE, not per build. `include_dir` colours only the declaring - // package's own TUs (docs/05-build-mcpp.md), so a generated header is + // package's own TUs (docs/30-build-mcpp.md), so a generated header is // visible to exactly one package and a build-wide phony would encode a // dependency that does not exist. It would also land on the critical path // of a build whose wall clock IS its critical path. diff --git a/src/build/prepare.cppm b/src/build/prepare.cppm index acb45128..47349090 100644 --- a/src/build/prepare.cppm +++ b/src/build/prepare.cppm @@ -1004,7 +1004,7 @@ export struct BuildOverrides { // Is this git remote reachable without a network round-trip? // -// `--offline` means "never touch the network" (docs/02-mcpp-toml.md), and its +// `--offline` means "never touch the network" (docs/03-mcpp-toml.md), and its // standing promise is that anything already on disk still builds. A remote that // names a local directory — or a file:// URL — is served by plain filesystem // reads, so refusing it would break that promise without buying any isolation. diff --git a/src/hooks.cppm b/src/hooks.cppm index 346f785e..8866b4e6 100644 --- a/src/hooks.cppm +++ b/src/hooks.cppm @@ -3,7 +3,7 @@ // EXPERIMENTAL. A hook cannot currently change whether a build succeeded: // every failure is a warning, and `side_effect = true` is refused by the // manifest parser. Only the ROOT project's hooks are ever run — a dependency's -// `[hooks]` is inert. See docs/02-mcpp-toml.md §2.16. +// `[hooks]` is inert. See docs/03-mcpp-toml.md §2.16. // // The CONFIGURATION is not parsed here: `[hooks]` is a section of mcpp.toml // and mcpp.toml has one parser (mcpp.manifest). What lives here is the part diff --git a/tests/e2e/186_build_mcpp_protocol_and_bound.sh b/tests/e2e/186_build_mcpp_protocol_and_bound.sh index 9317f886..c3371a99 100755 --- a/tests/e2e/186_build_mcpp_protocol_and_bound.sh +++ b/tests/e2e/186_build_mcpp_protocol_and_bound.sh @@ -16,7 +16,7 @@ # * run bound — a build program that hangs used to hang the whole build with # no diagnostic at all. # -# See .agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md §4. +# See .agents/docs/2026-08-30-build-mcpp-extensibility-architecture.md §4. set -e TMP=$(mktemp -d) diff --git a/tests/e2e/188_build_actions.sh b/tests/e2e/188_build_actions.sh index ddace435..ecf1f8a3 100755 --- a/tests/e2e/188_build_actions.sh +++ b/tests/e2e/188_build_actions.sh @@ -22,7 +22,7 @@ # # Also: a malformed action is refused rather than silently skipped. # -# See .agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md §3.1. +# See .agents/docs/2026-08-30-build-mcpp-extensibility-architecture.md §3.1. set -e TMP=$(mktemp -d) diff --git a/tests/e2e/315_blocking_check_gates_compilation.sh b/tests/e2e/315_blocking_check_gates_compilation.sh index c638957f..1e9b5ac9 100755 --- a/tests/e2e/315_blocking_check_gates_compilation.sh +++ b/tests/e2e/315_blocking_check_gates_compilation.sh @@ -5,7 +5,7 @@ # # WHAT THIS DEFENDS. `blocking` was typed (`BuildAction::blocking`), emitted # over the build-program protocol (`hostprogram.cppm`), parsed -# (`directives.cppm`), documented in both languages (`docs/05-build-mcpp.md` +# (`directives.cppm`), documented in both languages (`docs/30-build-mcpp.md` # and its Chinese counterpart) and demonstrated in a shipped example # (`examples/08-build-rules/rules-tidy`) — and read by nothing. The only # order-only edge the ninja backend emitted was the staged-BMI one. So a check diff --git a/tests/e2e/616_examples_and_their_index_agree.sh b/tests/e2e/616_examples_and_their_index_agree.sh index 44c5c010..2672851d 100755 --- a/tests/e2e/616_examples_and_their_index_agree.sh +++ b/tests/e2e/616_examples_and_their_index_agree.sh @@ -22,7 +22,7 @@ ROOT="${ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}" fails=0 fail() { printf 'ASSERT-FAIL: %s\n' "$1"; fails=$((fails + 1)); } -for doc in "$ROOT/docs/01-examples.md" "$ROOT/docs/zh/01-examples.md"; do +for doc in "$ROOT/docs/02-examples.md" "$ROOT/docs/zh/02-examples.md"; do [ -f "$doc" ] || { fail "missing $doc"; continue; } printf -- '--- %s ---\n' "${doc#"$ROOT"/}" From db5f396202f333d9e1f8a2c73f1974865b4b6b81 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:44:26 +0800 Subject: [PATCH 17/36] docs: every chapter states its reader, its question and what it excludes R3 of the book design, applied to all 27 chapters in both languages, and then made a check so it does not decay back. Five of 24 chapters had an opening that named their reader. The other 22 began with a summary of their own subject, which tells a reader what the chapter is about and not whether it is the chapter they want. Each now opens with three statements in its first fifteen lines: who reads it, the one question it answers, and what it deliberately does NOT contain. The exclusions are the half that does the work. They name the chapter that owns each neighbouring topic, which is what stops a chapter from re-absorbing a subject another one owns -- the failure the measurement found seven times over for testing and for dependency resolution. Rule 11 checks all three. It immediately caught chapter 00, which stated its exclusions in a closing section rather than in its opening; the closing section was written first and reads as a summary, which is exactly the shape the rule exists to reject. --- .github/tools/check_docs_structure.sh | 18 +++++++++++++++++ docs/00-how-mcpp-works.md | 3 +++ docs/01-getting-started.md | 11 ++++++++++ docs/02-examples.md | 11 ++++++++++ docs/03-mcpp-toml.md | 12 +++++++++++ docs/05-features-and-capabilities.md | 12 +++++++++++ docs/06-workspace.md | 11 ++++++++++ docs/08-commands-by-scenario.md | 10 +++++++++ docs/10-pack-and-release.md | 10 +++++++++ docs/11-publishing-a-library.md | 12 +++++++++++ docs/12-binary-distribution.md | 11 ++++++++++ docs/20-toolchains.md | 11 ++++++++++ docs/21-the-target-triple.md | 10 +++++++++ docs/22-target-side.md | 11 ++++++++++ docs/23-the-project-environment.md | 10 +++++++++ docs/24-openkal-cross.md | 10 +++++++++ docs/30-build-mcpp.md | 11 ++++++++++ docs/31-authoring-a-rule-package.md | 27 ++++++++++++++----------- docs/40-baremetal.md | 11 ++++++++++ docs/41-devices.md | 11 ++++++++++ docs/42-heterogeneous-builds.md | 11 ++++++++++ docs/50-machine-output.md | 10 +++++++++ docs/90-build-from-source.md | 10 +++++++++ docs/91-toolchain-internals.md | 11 ++++++++++ docs/92-release.md | 10 +++++++++ docs/zh/00-how-mcpp-works.md | 3 +++ docs/zh/01-getting-started.md | 9 +++++++++ docs/zh/02-examples.md | 9 +++++++++ docs/zh/03-mcpp-toml.md | 10 +++++++++ docs/zh/05-features-and-capabilities.md | 10 +++++++++ docs/zh/06-workspace.md | 9 +++++++++ docs/zh/08-commands-by-scenario.md | 9 +++++++++ docs/zh/10-pack-and-release.md | 10 +++++++++ docs/zh/11-publishing-a-library.md | 10 +++++++++ docs/zh/12-binary-distribution.md | 8 ++++++++ docs/zh/20-toolchains.md | 10 +++++++++ docs/zh/21-the-target-triple.md | 9 +++++++++ docs/zh/22-target-side.md | 10 +++++++++ docs/zh/23-the-project-environment.md | 8 ++++++++ docs/zh/24-openkal-cross.md | 9 +++++++++ docs/zh/30-build-mcpp.md | 11 ++++++++++ docs/zh/31-authoring-a-rule-package.md | 20 +++++++++--------- docs/zh/40-baremetal.md | 9 +++++++++ docs/zh/41-devices.md | 10 +++++++++ docs/zh/42-heterogeneous-builds.md | 10 +++++++++ docs/zh/50-machine-output.md | 9 +++++++++ docs/zh/51-supported-versions.md | 8 ++++++++ docs/zh/90-build-from-source.md | 9 +++++++++ docs/zh/91-toolchain-internals.md | 9 +++++++++ docs/zh/92-release.md | 9 +++++++++ 50 files changed, 501 insertions(+), 21 deletions(-) diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh index 40f7e2cf..6945bf9d 100755 --- a/.github/tools/check_docs_structure.sh +++ b/.github/tools/check_docs_structure.sh @@ -14,6 +14,7 @@ # 8. a new design record declares its subject and status # 9. every relative link in docs/ and examples/ resolves # 10. a translation carries the same tables and code blocks +# 11. every chapter states its reader, its question and its exclusions # # What it deliberately does NOT check: whether a chapter documents what is # implemented, whether an assertion's strength matches its evidence, or whether @@ -201,6 +202,23 @@ for en in sorted(pathlib.Path("docs").glob("*.md")): sys.exit(1 if bad else 0) PYPARITY +# ── 11. every chapter states its reader and its question ───────────────── +# +# `.agents/skills/mcpp-docs-style` R3: reader, the one question, and the +# EXCLUSIONS, in the first fifteen lines. The exclusions are the load-bearing +# half -- they are the gate that stops a chapter re-absorbing a topic another +# chapter owns. Five of 24 chapters had this before the design; a rule nothing +# checks is a rule that decays back to five. +for f in docs/[0-9]*.md docs/zh/[0-9]*.md; do + [ -f "$f" ] || continue + head -18 "$f" | grep -qE '^\*\*(Reader|读者)' \ + || bad "$f: no designed opening — the first lines must name the reader" + head -18 "$f" | grep -qE '(question this chapter answers|本章回答的那一个问题)' \ + || bad "$f: the opening names no question" + head -22 "$f" | grep -qE '^\*\*(Not here|不在这里)' \ + || bad "$f: the opening states no exclusions" +done + if [[ "$fail" -eq 0 ]]; then echo "OK: docs structure checks pass" fi diff --git a/docs/00-how-mcpp-works.md b/docs/00-how-mcpp-works.md index ae3c02e1..57b7db7d 100644 --- a/docs/00-how-mcpp-works.md +++ b/docs/00-how-mcpp-works.md @@ -11,6 +11,9 @@ with a command reference finds a list of verbs with nothing to attach them to. This chapter is the model. It names no field and no flag; those are the reference chapters, which this one makes readable. +**Not here:** every field, flag and command. A model that also tries to be a +reference is neither, so each noun below names the chapter that owns it. + Next: [01 — Getting Started](01-getting-started.md) puts a program on the screen. ## The five nouns diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md index 3c0c7f83..9e887834 100644 --- a/docs/01-getting-started.md +++ b/docs/01-getting-started.md @@ -1,5 +1,15 @@ # 01 — Getting Started +**Reader:** a newcomer with nothing installed yet. + +**The question this chapter answers:** how do I get a program compiling and +running, from an empty machine. + +**Not here:** what mcpp's parts are — that is +[00 — How mcpp Works](00-how-mcpp-works.md), which this chapter assumes rather +than repeats — and every field a manifest may hold, which is +[03 — The mcpp.toml Manifest](03-mcpp-toml.md). After: [02 — Examples](02-examples.md). + > Go from install → new → build → run → pack in 5 minutes. ## Installation @@ -199,3 +209,4 @@ For the differences between the four modes and their artifact layouts, see [10 - Explaining default decisions: `mcpp why [toolchain|runtime|deps]`; host capability checkup: `mcpp self doctor`; machine-readable resolution manifest: the build artifact `target///resolution.json`. - Offline operation: `mcpp --offline` or `MCPP_OFFLINE=1` prevents index refreshes, downloads, and toolchain installation. In a home that has never been used it also skips the first-use sandbox bootstrap (index clone, ninja, patchelf), announces the skip once, and leaves the home un-bootstrapped; commands that need those tools report it. + diff --git a/docs/02-examples.md b/docs/02-examples.md index d3cf3e3c..5206cd1a 100644 --- a/docs/02-examples.md +++ b/docs/02-examples.md @@ -1,5 +1,15 @@ # 02 — Examples +**Reader:** someone choosing where to start, or looking for a project shaped +like theirs. + +**The question this chapter answers:** which example teaches what, and in which +order they build on each other. + +**Not here:** the content of any example — each has its own README, which +explains only what it adds. Before: [01 — Getting Started](01-getting-started.md). +After: [03 — The mcpp.toml Manifest](03-mcpp-toml.md). + The [`examples/`](../examples) directory is a curriculum. Each project is runnable on its own, and each one teaches **one thing no earlier example teaches**. This chapter says what that thing is, so you can enter at the level @@ -105,3 +115,4 @@ command belongs in [08 — Commands by Scenario](08-commands-by-scenario.md). The README states what the example is the first to teach and the criterion by which it is judged to work. For contribution mechanics see [90 — Building from Source & Contributing](90-build-from-source.md). + diff --git a/docs/03-mcpp-toml.md b/docs/03-mcpp-toml.md index dd4bcabe..bfd521ec 100644 --- a/docs/03-mcpp-toml.md +++ b/docs/03-mcpp-toml.md @@ -1,5 +1,16 @@ # 03 — The mcpp.toml Manifest +**Reader:** an author writing or reading a manifest. + +**The question this chapter answers:** what may an `mcpp.toml` say, field by +field. + +**Not here:** four topics this file's tables belong to but this chapter does not +own — dependencies are [04](04-dependencies.md), features are +[05](05-features-and-capabilities.md), conditioning on a target is +[22](22-target-side.md), and the project's environment is +[23](23-the-project-environment.md). Each is named where its table would be. + `mcpp.toml` is the project configuration file for the mcpp build tool, analogous to Cargo's `Cargo.toml` or Node's `package.json`. Place it in the project root; `mcpp build` discovers and reads it automatically. ## 1. Minimal Examples @@ -1362,3 +1373,4 @@ standard = "c++26" ``` New projects should use `[package].standard`. If both locations are present, `[package].standard` is authoritative. + diff --git a/docs/05-features-and-capabilities.md b/docs/05-features-and-capabilities.md index 17a00e24..27f6092d 100644 --- a/docs/05-features-and-capabilities.md +++ b/docs/05-features-and-capabilities.md @@ -1,5 +1,16 @@ # 05 — Features and Capabilities +**Reader:** an author with something optional — an extra source, an extra +dependency, or a choice between backends. + +**The question this chapter answers:** how does a package offer something +optional, and how does a consumer ask for it. + +**Not here:** which device backends a build targets, which looks like a feature +and is not — that is [42 — Heterogeneous Builds](42-heterogeneous-builds.md). +Before: [04 — Dependencies and Resolution](04-dependencies.md). After: +[06 — Workspaces](06-workspace.md). + Features are how a package offers something optional: a compile macro, an extra source, an extra dependency, or a choice between backends. This chapter is the reference for declaring them and for consuming them. @@ -416,3 +427,4 @@ resolved from the dependency graph, so a dependency chosen by it would decide the answer it is asking for. mcpp reports the predicate and ignores it. Packages are unconditional or conditioned on the platform; `[build] sources` is what the accelerator selects. + diff --git a/docs/06-workspace.md b/docs/06-workspace.md index cf142dd5..3bd393bf 100644 --- a/docs/06-workspace.md +++ b/docs/06-workspace.md @@ -1,5 +1,15 @@ # 06 — Workspaces +**Reader:** an author whose repository holds more than one package. + +**The question this chapter answers:** how do several packages become one build, +and what does a member share with the others. + +**Not here:** publishing those packages, which is +[11 — Publishing a Library](11-publishing-a-library.md). Before: +[05 — Features and Capabilities](05-features-and-capabilities.md). After: +[07 — Testing](07-testing.md). + A workspace organizes multiple related mcpp packages (libraries or applications) within a single repository. Member packages share a unified set of dependency versions and toolchain settings while each keeping its own `mcpp.toml` project file. ## 1. Overview @@ -363,3 +373,4 @@ Workspaces work in concert with the C++23 module mechanism: ## 8. Complete Example See [`examples/04-workspace/`](../examples/04-workspace/) for a complete, runnable example of a three-member workspace. + diff --git a/docs/08-commands-by-scenario.md b/docs/08-commands-by-scenario.md index 6de8075e..36f46eb5 100644 --- a/docs/08-commands-by-scenario.md +++ b/docs/08-commands-by-scenario.md @@ -1,5 +1,14 @@ # 08 — Commands by Scenario +**Reader:** someone who knows the nouns and wants the verb. + +**The question this chapter answers:** which command does the thing at hand — +reclaiming disk, explaining a resolution, validating a descriptor, diagnosing an +environment. + +**Not here:** what each command means in depth. A scenario names the command and +links to the chapter that owns it. Before: [07 — Testing](07-testing.md). + The command reference is `mcpp --help`, and each subcommand carries its own `--help`. This chapter answers a different question: which command applies to a situation that has already arisen — a build directory that keeps growing, a @@ -389,3 +398,4 @@ second and not the first, and `mcpp xpkg parse` still exits 1. No descriptor in `mcpp-index` uses that key — 0 of 218. A package that keeps its own `mcpp.toml` omits the `mcpp` field entirely, and mcpp looks the manifest up under the version directory. Measured on 2026.9.8.1. + diff --git a/docs/10-pack-and-release.md b/docs/10-pack-and-release.md index 4b729dd2..aa8e917c 100644 --- a/docs/10-pack-and-release.md +++ b/docs/10-pack-and-release.md @@ -1,5 +1,14 @@ # 10 — Packaging an Application for Release +**Reader:** someone shipping a program to a machine that does not have mcpp. + +**The question this chapter answers:** how do I turn a build into something +another machine can run, and what does each packaging mode carry. + +**Not here:** shipping a **library** for other packages to build against, which +is [12 — Distributing a Prebuilt Library](12-binary-distribution.md). After: +[11 — Publishing a Library to mcpp-index](11-publishing-a-library.md). + > This page is about bundling a **program**. To ship a *library* as interface + > prebuilt binaries, see [12 - Distributing a Prebuilt Library](12-binary-distribution.md). > @@ -416,3 +425,4 @@ the current `.zip`, and distribution formats such as `.deb` / `.rpm` / AppImage, are also on the roadmap. This document evolves alongside the `mcpp pack` implementation; for the latest options, refer to `mcpp pack --help`. + diff --git a/docs/11-publishing-a-library.md b/docs/11-publishing-a-library.md index 2f0cc5da..1205ac39 100644 --- a/docs/11-publishing-a-library.md +++ b/docs/11-publishing-a-library.md @@ -2,6 +2,17 @@ **English** | [简体中文](zh/11-publishing-a-library.md) +**Reader:** a library author whose package should become one others can name in +their `[dependencies]`. + +**The question this chapter answers:** what steps take a tagged release to a +resolvable package, and in which order they must happen. + +**Not here:** what makes two packages the same package — +[SPEC-001](specs/package-identity.md) — and shipping compiled artifacts, which +is [12](12-binary-distribution.md). Before: +[10 — Packaging an Application](10-pack-and-release.md). + How a library becomes something `[dependencies]` can name. This is the *library author's* chain; [92 - Releasing mcpp](92-release.md) is about releasing mcpp itself, and [10 - Packaging & Release](10-pack-and-release.md) @@ -158,3 +169,4 @@ re-refreshing the index looking for it. - [ ] `publish-artifact.yml` succeeded - [ ] cold resolve (seeded copy deleted) downloads and compiles it - [ ] consumers bumped + diff --git a/docs/12-binary-distribution.md b/docs/12-binary-distribution.md index fcb82a2f..27b3dd76 100644 --- a/docs/12-binary-distribution.md +++ b/docs/12-binary-distribution.md @@ -2,6 +2,16 @@ **English** | [简体中文](zh/12-binary-distribution.md) +**Reader:** a publisher who ships compiled artifacts rather than source. + +**The question this chapter answers:** how do I ship binaries, and how does a +consumer's build decide whether one of them fits. + +**Not here:** publishing source, which is +[11 — Publishing a Library](11-publishing-a-library.md), and the accelerator +field of a compatibility tag, which is +[42 — Heterogeneous Builds](42-heterogeneous-builds.md). + > Ship a library as **interface + prebuilt binaries** instead of as source. > It applies to closed-source distribution, offline environments, and builds whose > artifacts a build farm has already produced. @@ -490,3 +500,4 @@ answers "not installed". So the static half of the old-client check (the generat manifest uses no section a previous mcpp cannot read) runs everywhere, and the real half — build against the package with the previous release — has been run by hand, not by CI. + diff --git a/docs/20-toolchains.md b/docs/20-toolchains.md index 190fbd9c..3c6d778d 100644 --- a/docs/20-toolchains.md +++ b/docs/20-toolchains.md @@ -1,5 +1,15 @@ # 20 — Toolchain Management +**Reader:** anyone for whom which compiler runs matters — a version floor, a +second toolchain, a machine that must not use its own. + +**The question this chapter answers:** which compiler will build this project, +how is it installed, and how do I choose another. + +**Not here:** how the resolution is implemented, which is +[91 — Toolchain Internals](91-toolchain-internals.md), and how a target is +named, which is [21 — The Target Triple](21-the-target-triple.md). + > mcpp maintains an independent toolchain sandbox, fully isolated from the system PATH. ## Motivation @@ -892,3 +902,4 @@ yields nasm ≥ 2.16 the build **fails hard** (assembly is never silently skipped). Limits: `.asm` targets x86 only (hard error elsewhere — gate the files off other targets), `.S` is unavailable on the MSVC toolchain, and `.asm` means NASM syntax (MASM sources should be `!`-excluded). + diff --git a/docs/21-the-target-triple.md b/docs/21-the-target-triple.md index c84a25ba..fafaea8b 100644 --- a/docs/21-the-target-triple.md +++ b/docs/21-the-target-triple.md @@ -1,5 +1,14 @@ # 21 — The Target Triple +**Reader:** someone building for a machine that is not this one. + +**The question this chapter answers:** how is a target named, which targets are +supported and at what tier, and which build hosts can serve each. + +**Not here:** how a manifest conditions on a target, which is +[22 — The Target Side](22-target-side.md), and targets with no operating +system, which are [40 — Bare-Metal](40-baremetal.md). + A target triple is written `-` or `--`. This chapter states what each segment means, when the third may be declined, and why the answer differs between two systems that mcpp supports at the same time. @@ -508,3 +517,4 @@ the target row's own default. See [chapter 13](40-baremetal.md). [chapter 15](24-openkal-cross.md) for the build-time system in full. [chapter 03](20-toolchains.md) for the toolchain axis, which is separate: a target does not determine a compiler. + diff --git a/docs/22-target-side.md b/docs/22-target-side.md index 0ba6acb7..f83a32db 100644 --- a/docs/22-target-side.md +++ b/docs/22-target-side.md @@ -1,5 +1,15 @@ # 22 — The Target Side +**Reader:** an author whose one manifest must serve several targets. + +**The question this chapter answers:** how does a manifest say "only there", and +what may be conditioned that way. + +**Not here:** the vocabulary of target names, which is +[21 — The Target Triple](21-the-target-triple.md), and the accelerator axis, +which resolves after the graph and is +[42 — Heterogeneous Builds](42-heterogeneous-builds.md). + A build must answer one question before it can emit a command line: where the target's compiler runtime, platform interface, C library and C++ runtime come from. mcpp resolves that question once, after the dependency graph is known, @@ -470,3 +480,4 @@ this returns. To branch on the resolved layer, use a layer predicate: `[target.'cfg(c-abi = "musl")'.build]` ([22 — The Target Side](22-target-side.md)). This paragraph said "which C library was resolved" until 2026.9.1.1, which was the wrong one of the two. + diff --git a/docs/23-the-project-environment.md b/docs/23-the-project-environment.md index 840df350..71020599 100644 --- a/docs/23-the-project-environment.md +++ b/docs/23-the-project-environment.md @@ -1,5 +1,14 @@ # 23 — The Project Environment +**Reader:** an author whose build needs tools that are not the compiler. + +**The question this chapter answers:** how does a project declare the +environment its build runs in, and what does that declaration decide. + +**Not here:** what a build program does with those tools, which is +[30 — Build Programs](30-build-mcpp.md), and choosing a compiler, which is +[20 — Toolchain Management](20-toolchains.md). + A project can declare the environment it builds in. That one declaration decides which C library the project links against and which tools its build programs find — so a `mcpp.toml` means the same build on a developer's laptop @@ -514,3 +523,4 @@ used it. - [91 - Toolchain Internals](91-toolchain-internals.md) — runtime selection, the `RuntimeBinding` snapshot, and the degradation rules. - [03 - mcpp.toml](03-mcpp-toml.md) — the rest of the manifest. + diff --git a/docs/24-openkal-cross.md b/docs/24-openkal-cross.md index 2b35a216..28da38c9 100644 --- a/docs/24-openkal-cross.md +++ b/docs/24-openkal-cross.md @@ -1,5 +1,14 @@ # 24 — Cross-Compilation Over openkal +**Reader:** someone cross-building for another operating system from this one. + +**The question this chapter answers:** how does one source tree build for +several hosted targets without the machine having their toolchains. + +**Not here:** targets with no operating system, which are +[40 — Bare-Metal](40-baremetal.md), and how targets are named, which is +[21 — The Target Triple](21-the-target-triple.md). + Conventional cross-compilation is served by a payload. A toolchain is built for one target, its driver has exactly one answer, and reaching a second target means obtaining a second toolchain. The number of payloads a distribution must @@ -307,3 +316,4 @@ the paths where a project overrides the contract explicitly. [docs/14 — The Target Side](22-target-side.md) for the five layers, the four origins and the rules. [SPEC-002](specs/target-side.md) for the normative statement of the capability grammar. + diff --git a/docs/30-build-mcpp.md b/docs/30-build-mcpp.md index a1e966c2..e3da52c2 100644 --- a/docs/30-build-mcpp.md +++ b/docs/30-build-mcpp.md @@ -2,6 +2,16 @@ **English** | [简体中文](zh/30-build-mcpp.md) +**Reader:** an author whose build needs a step mcpp has no rule for — code +generation, an embedded asset, a check, a second compiler. + +**The question this chapter answers:** how do I add work to the build graph, so +that it is ordered, fingerprinted and incremental like everything else. + +**Not here:** packaging that step so other projects can use it, which is +[31 — Authoring a Rule Package](31-authoring-a-rule-package.md), and the tools +the step runs, which are [23 — The Project Environment](23-the-project-environment.md). + Most projects need nothing more than `mcpp.toml`. When build-time logic is required — probe the host, generate a source, decide a flag from the environment — put a `build.mcpp` in the project root. It is the mcpp analog of Zig's `build.zig` and @@ -1216,3 +1226,4 @@ unsupported platform into an error its user cannot edit away. Scope it: the other conditional dependency tables ([22 — The Target Side](22-target-side.md)). The **feature itself is registered on every platform** — only what it pulls in is conditional — so requesting it where no predicate matches is not an unknown-feature error. + diff --git a/docs/31-authoring-a-rule-package.md b/docs/31-authoring-a-rule-package.md index 2f87cc30..58d29c83 100644 --- a/docs/31-authoring-a-rule-package.md +++ b/docs/31-authoring-a-rule-package.md @@ -1,17 +1,20 @@ # 31 — Authoring a Rule Package -A rule package teaches mcpp to build something the engine has no rule for: a -device language, a shader, a generated interface, a check. It is an ordinary -package whose module a consumer's build program imports. - -Related documents: [30 — build.mcpp](30-build-mcpp.md) is the reference for the -directive protocol and the typed API this chapter uses; [05 — Features and -Capabilities](05-features-and-capabilities.md) covers the feature keys; -[42 — Heterogeneous Builds](42-heterogeneous-builds.md) is where the shipped -rules are listed. Two examples: -[`08-build-rules`](../examples/08-build-rules/) for a rule that checks and -embeds, [`12-a-new-device-language`](../examples/12-a-new-device-language/) for -one that adds a language. +**Reader:** an ecosystem author packaging a build step so that other projects +can use it — a device language, a shader compiler, a generated interface, a +check. + +**The question this chapter answers:** how does a package supply a rule, and +what does a consumer have to write to use it. + +**Not here:** adding a step to one project's own build, which is +[30 — Build Programs](30-build-mcpp.md) and is the same primitives at a smaller +scale; the feature keys themselves, which are +[05](05-features-and-capabilities.md); and the spellings of the shipped rules, +which belong to `mcpp:plugins`. Examples: +[`08-build-rules`](../examples/08-build-rules/) checks and embeds; +[`12-a-new-device-language`](../examples/12-a-new-device-language/) adds a +language. ## What a rule package is diff --git a/docs/40-baremetal.md b/docs/40-baremetal.md index c1aaf375..e087545f 100644 --- a/docs/40-baremetal.md +++ b/docs/40-baremetal.md @@ -1,5 +1,15 @@ # 40 — Bare-Metal and Freestanding Targets +**Reader:** an embedded developer targeting a machine with no operating system. + +**The question this chapter answers:** how does a project build for a +freestanding target, what supplies the target's world, and what of the standard +library survives. + +**Not here:** running and testing the image, which is +[41 — Reaching a Device](41-devices.md), and hosted cross-compilation, which is +[24 — Cross-Compilation Over openkal](24-openkal-cross.md). + This document describes how mcpp builds, runs and tests software for targets with no operating system underneath, and how a board-support package supplies the parts of such a target that the engine deliberately does not know. @@ -798,3 +808,4 @@ targets, but that expectation is **not** covered by a test. | C library substitution | Expressible since 2026.8.20.2 through `[target.].sysroot`, and **verified only for the empty value** (the zero-libc tier). Pointing it at a different C library is accepted and installed through the same channel, but no second bare-metal C library is published, so that path is untested. | | `qemu-riscv` on `win32-arm64` | The upstream package publishes no asset for that host, so installation fails on it. The failure is correct rather than silent, but the host cannot run a bare-metal image. | | Ecosystem CI breadth | The two ecosystem packages run their own CI on `ubuntu-24.04` only. mcpp-index's `tests/examples/` workspace members run unconditionally on three platforms with no capability gate, so a package requiring an emulator and a target sysroot cannot be added there. This is a known coverage gap. | + diff --git a/docs/41-devices.md b/docs/41-devices.md index cd77e55b..3cf7f3d6 100644 --- a/docs/41-devices.md +++ b/docs/41-devices.md @@ -1,5 +1,15 @@ # 41 — Reaching a Device +**Reader:** anyone whose artifact does not run on the machine that built it — +a board, an emulator, a remote host. + +**The question this chapter answers:** how is such an artifact run and tested, +and what does a package supply so that one command reaches it. + +**Not here:** how the target itself is built, which is +[40 — Bare-Metal](40-baremetal.md), and what mcpp considers a test, which is +[07 — Testing](07-testing.md). + This document specifies how mcpp executes an artifact that runs somewhere other than the machine that built it, how a package supplies additional ways of reaching it, and how a project selects between an emulator and physical @@ -162,3 +172,4 @@ This required no engine mechanism. The engine reads runners and knows nothing about emulators or probes; `mcpp::has_feature` already existed. That the question is answerable without adding anything is the layering working as specified. + diff --git a/docs/42-heterogeneous-builds.md b/docs/42-heterogeneous-builds.md index e55c1b69..258fca91 100644 --- a/docs/42-heterogeneous-builds.md +++ b/docs/42-heterogeneous-builds.md @@ -1,5 +1,15 @@ # 42 — Heterogeneous Builds +**Reader:** someone compiling part of a program for a GPU or an AI accelerator. + +**The question this chapter answers:** how does device code get compiled and +linked into an ordinary program, and how does a prebuilt artifact state which +devices it can run on. + +**Not here:** writing the rule that drives a device compiler, which is +[31 — Authoring a Rule Package](31-authoring-a-rule-package.md), and reaching +the device to run on it, which is [41 — Reaching a Device](41-devices.md). + GPU and AI accelerator targets, and mixed host/device compilation: how mcpp builds device code, and how a prebuilt artifact states which devices it can run on. @@ -733,3 +743,4 @@ past it is the framework's own selector rather than a change to the packaging. Per-platform limits for each lane are in the table under *Which platforms each lane reaches*. + diff --git a/docs/50-machine-output.md b/docs/50-machine-output.md index 3a04334e..79ddb039 100644 --- a/docs/50-machine-output.md +++ b/docs/50-machine-output.md @@ -1,5 +1,14 @@ # 50 — Machine-Readable Output +**Reader:** the author of a tool, an editor extension or a CI job that parses +mcpp's output. + +**The question this chapter answers:** what may a program rely on, how is it +versioned, and how is the protocol detected. + +**Not here:** the human-facing output, which carries no compatibility promise, +and the exit-code contract, which is [SPEC-003](specs/exit-codes.md). + mcpp writes for two audiences. This chapter is the contract for the second one: programs. Editor extensions, CI scripts, and anything else that parses mcpp's output may rely on what is stated here. @@ -444,3 +453,4 @@ must also read `not_run`. `unrunnable_members` (members all of whose tests were `not_run`), alongside the existing `not_run` list, which continues to name members the `--workspace-timeout` stopped before they started. + diff --git a/docs/90-build-from-source.md b/docs/90-build-from-source.md index b32b0318..c3aa25dc 100644 --- a/docs/90-build-from-source.md +++ b/docs/90-build-from-source.md @@ -1,5 +1,14 @@ # 90 — Building from Source and Contributing +**Reader:** a contributor who will build and change mcpp itself. + +**The question this chapter answers:** how is mcpp built from source, how are +its own tests organised, and what does a contribution have to satisfy. + +**Not here:** how a user builds their own project, which is +[01 — Getting Started](01-getting-started.md), and how a release is cut, which +is [92 — Releasing mcpp](92-release.md). + > mcpp is self-hosting — mcpp builds mcpp from source using mcpp itself. > Any environment that already has a working mcpp binary can build from source. @@ -114,3 +123,4 @@ mcpp is in early iteration and its interfaces may change. Before submitting a PR - Chat group QQ: 1067245099 - [mcpp-index](https://github.com/mcpplibs/mcpp-index) — the default package index - [mcpplibs](https://github.com/mcpplibs) — the companion collection of modular C++ libraries + diff --git a/docs/91-toolchain-internals.md b/docs/91-toolchain-internals.md index eaddd84a..845202ea 100644 --- a/docs/91-toolchain-internals.md +++ b/docs/91-toolchain-internals.md @@ -1,5 +1,15 @@ # 91 — Toolchain Internals +**Reader:** a contributor, or a user debugging why a toolchain resolved the way +it did. + +**The question this chapter answers:** how does mcpp actually resolve, install +and assemble a toolchain, and which layer decides each flag. + +**Not here:** how to *choose* a toolchain, which is +[20 — Toolchain Management](20-toolchains.md). This chapter is the mechanism +under it, and nothing here is a stable interface. + > How mcpp's toolchain machinery works under the hood, and how to extend it > with new toolchains, new architectures, and (eventually) embedded targets. > Companion to [20 — Toolchain Management](20-toolchains.md), which covers the @@ -646,3 +656,4 @@ answer them. | build.mcpp host flags | `src/build/build_program.cppm` | | hermetic link check | `src/build/hermetic.cppm` | | regression fences | `tests/e2e/86_llvm_hermetic_link.sh`, unit `test_linkmodel.cpp`, `test_post_install.cpp`; the no-host-toolchain CI job in `ci-linux-e2e.yml` | + diff --git a/docs/92-release.md b/docs/92-release.md index 7c1517a0..dc2b7c03 100644 --- a/docs/92-release.md +++ b/docs/92-release.md @@ -1,5 +1,14 @@ # 92 — Releasing mcpp +**Reader:** a maintainer cutting a release of mcpp itself. + +**The question this chapter answers:** what steps take a commit to a released +version users can install, and how is each one verified. + +**Not here:** publishing a package to the index, which is +[11 — Publishing a Library](11-publishing-a-library.md), and what may change +between releases, which is [51](51-supported-versions.md). + How a release of **mcpp itself** reaches users. This is maintainer-facing; for packaging an *ordinary* project see [10 — Packaging for Release](10-pack-and-release.md). @@ -277,3 +286,4 @@ would again let the index guard and the installed version drift apart. [ ] clean-room XLINGS_HOME: xlings install mcpp@ succeeds [ ] (optional) bump .xlings.json — only now, never earlier ``` + diff --git a/docs/zh/00-how-mcpp-works.md b/docs/zh/00-how-mcpp-works.md index c07813c1..90a235c2 100644 --- a/docs/zh/00-how-mcpp-works.md +++ b/docs/zh/00-how-mcpp-works.md @@ -8,6 +8,9 @@ mcpp 是三样东西合在一个程序里 —— 构建系统、包管理器、 也正是「从命令参考读起的人只看到一串动词、无处安放」的原因。本章是模型。它不写 任何字段、任何旗标;那些在参考章节里,而本章让它们可读。 +**不在这里:**每一个字段、旗标与命令。一份既想当模型又想当参考的文档两样都不是, +所以下面每个名词都点名了拥有它的章节。 + 下一章:[01 —— 快速开始](01-getting-started.md) 把一个程序跑到屏幕上。 ## 五个名词 diff --git a/docs/zh/01-getting-started.md b/docs/zh/01-getting-started.md index 0e08e381..63777306 100644 --- a/docs/zh/01-getting-started.md +++ b/docs/zh/01-getting-started.md @@ -1,5 +1,13 @@ # 01 —— 快速开始 +**读者:**还什么都没装的新用户。 + +**本章回答的那一个问题:**从一台空机器开始,怎样把一个程序编译并运行起来。 + +**不在这里:**mcpp 有哪些部件 —— 那是 [00 —— mcpp 的运转方式](00-how-mcpp-works.md), +本章假定它而不重复它;以及 manifest 可以写的每一个字段,那是 +[03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md)。下一章:[02 —— 示例项目](02-examples.md)。 + > 5 分钟完成 install → new → build → run → pack 全流程。 ## 安装 @@ -185,3 +193,4 @@ mcpp pack --mode self-contained # 打包 loader、libc 与依赖 - 解释默认决策:`mcpp why [toolchain|runtime|deps]`;主机能力体检:`mcpp self doctor`; 机器可读解析清单:构建产物 `target///resolution.json`。 - 离线运行:`mcpp --offline` 或 `MCPP_OFFLINE=1` 可阻止索引刷新、下载和工具链安装。在从未使用过的 home 中,它同时跳过首次使用时的沙箱引导(索引克隆、ninja、patchelf),只提示一次,并让该 home 保持未引导状态;需要这些工具的命令会自行报告。 + diff --git a/docs/zh/02-examples.md b/docs/zh/02-examples.md index c94272b4..f62c06b0 100644 --- a/docs/zh/02-examples.md +++ b/docs/zh/02-examples.md @@ -1,5 +1,13 @@ # 02 —— 示例项目 +**读者:**在挑一个起点,或者在找一个与自己形状相近的工程的人。 + +**本章回答的那一个问题:**哪个示例教什么,以及它们以什么顺序相互叠加。 + +**不在这里:**任何一个示例的内容 —— 每个示例自带 README,只解释它新增的部分。 +在此之前:[01 —— 快速开始](01-getting-started.md)。在此之后: +[03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md)。 + [`examples/`](../../examples) 目录是一套课程。每个工程都可以单独跑起来,而且每个 工程都教**一件更早的示例没有教过的事**。本章说明那件事是什么,于是你可以按自己 需要的深度进入,而不必从头读起。 @@ -97,3 +105,4 @@ mcpp new blinky --template riscv-virt-rt README 要写明这个示例第一个教什么,以及判断它是否成立的判据。贡献流程见 [90 —— 从源码构建 & 参与贡献](90-build-from-source.md)。 + diff --git a/docs/zh/03-mcpp-toml.md b/docs/zh/03-mcpp-toml.md index daf8d3e5..abf1538d 100644 --- a/docs/zh/03-mcpp-toml.md +++ b/docs/zh/03-mcpp-toml.md @@ -1,5 +1,14 @@ # 03 —— mcpp.toml 工程文件指南 +**读者:**正在写或正在读一份 manifest 的作者。 + +**本章回答的那一个问题:**一份 `mcpp.toml` 可以说什么,逐字段地。 + +**不在这里:**四个主题的表虽然写在这个文件里,但本章不拥有它们 —— 依赖是 +[04](04-dependencies.md),feature 是 [05](05-features-and-capabilities.md), +以目标为条件是 [22](22-target-side.md),工程的环境是 +[23](23-the-project-environment.md)。每一处都在它的表本该出现的位置点名。 + `mcpp.toml` 是 mcpp 构建工具的项目配置文件,类似 Cargo 的 `Cargo.toml` 或 Node 的 `package.json`。放在项目根目录下,`mcpp build` 会自动发现并读取它。 ## 1. 最小化示例 @@ -1190,3 +1199,4 @@ standard = "c++26" ``` 新项目请使用 `[package].standard`。如果两个位置都出现,`[package].standard` 是权威配置。 + diff --git a/docs/zh/05-features-and-capabilities.md b/docs/zh/05-features-and-capabilities.md index e6d0a2d2..4dbadca2 100644 --- a/docs/zh/05-features-and-capabilities.md +++ b/docs/zh/05-features-and-capabilities.md @@ -1,5 +1,14 @@ # 05 —— Feature 与能力 +**读者:**手上有可选内容的作者 —— 一份额外的源、一个额外的依赖,或者在多个后端 +之间做选择。 + +**本章回答的那一个问题:**一个包怎样提供可选内容,消费者又怎样要它。 + +**不在这里:**一次构建面向哪些设备后端 —— 那看起来像 feature 而不是 feature, +它是 [42 —— 异构硬件构建](42-heterogeneous-builds.md)。在此之前: +[04 —— 依赖与解析](04-dependencies.md)。在此之后:[06 —— 工作空间](06-workspace.md)。 + Feature 是一个包提供可选内容的方式:一个编译宏、一份额外的源文件、一个额外的 依赖,或者在多个后端之间做选择。本章是声明与消费 feature 的参考。 @@ -358,3 +367,4 @@ std-freestanding-alloc-kal = "0.1.x" **依赖不能以加速器为条件。** `accelerator` 是从依赖图解析出来的,因此由它选择的 依赖会决定它自己在问的那个答案。mcpp 会报告该谓词并忽略它。包要么无条件,要么以 平台为条件;由加速器选择的是 `[build] sources`。 + diff --git a/docs/zh/06-workspace.md b/docs/zh/06-workspace.md index ce38370d..72eef9c7 100644 --- a/docs/zh/06-workspace.md +++ b/docs/zh/06-workspace.md @@ -1,5 +1,13 @@ # 06 —— 工作空间 +**读者:**仓库里不止一个包的作者。 + +**本章回答的那一个问题:**多个包怎样成为一次构建,以及一个成员与其余成员共享什么。 + +**不在这里:**把这些包发布出去,那是 [11 —— 发布一个库](11-publishing-a-library.md)。 +在此之前:[05 —— Feature 与能力](05-features-and-capabilities.md)。在此之后: +[07 —— 测试](07-testing.md)。 + 工作空间允许在同一个仓库中组织和管理多个相关的 mcpp 包(库或应用程序)。各成员包共享统一的依赖版本配置和工具链设置,同时保持独立的 `mcpp.toml` 工程文件。 ## 1. 概述 @@ -343,3 +351,4 @@ myproject/ ## 8. 完整示例 参见 [`examples/04-workspace/`](../../examples/04-workspace/),包含一个三成员工作空间的完整可运行示例。 + diff --git a/docs/zh/08-commands-by-scenario.md b/docs/zh/08-commands-by-scenario.md index 2a34c0ad..bdeb17fc 100644 --- a/docs/zh/08-commands-by-scenario.md +++ b/docs/zh/08-commands-by-scenario.md @@ -1,5 +1,13 @@ # 08 —— 按场景选命令 +**读者:**已经认识那些名词、现在想找动词的人。 + +**本章回答的那一个问题:**手上这件事该用哪个命令 —— 回收磁盘、解释一次解析、 +校验一个描述符、诊断环境。 + +**不在这里:**每个命令的含义细节。一个场景点名命令,并链接到拥有它的那一章。 +在此之前:[07 —— 测试](07-testing.md)。 + 命令清单是 `mcpp --help`,每个子命令还有自己的 `--help`。本章回答的是另一个问题: 某个情形已经发生时该用哪条命令 —— 构建目录一直变大、解析结果出乎意料、描述符即将 发布、索引可能陈旧。这里收的都是名字本身没有说出它所属场景的命令。 @@ -332,3 +340,4 @@ error: synthesised manifest missing sources (mcpp segment must declare `mcpp-index` 里没有任何描述符使用那个键 —— 218 个里 0 个。自带 `mcpp.toml` 的包 **整个省略 `mcpp` 字段**,由 mcpp 在版本目录下查找那份 manifest。实测于 2026.9.8.1。 + diff --git a/docs/zh/10-pack-and-release.md b/docs/zh/10-pack-and-release.md index 1b23db38..16837e10 100644 --- a/docs/zh/10-pack-and-release.md +++ b/docs/zh/10-pack-and-release.md @@ -1,5 +1,14 @@ # 10 —— 发布打包 +**读者:**要把一个程序交付到没有 mcpp 的机器上的人。 + +**本章回答的那一个问题:**怎样把一次构建变成另一台机器能运行的东西,以及每种打包 +模式各自携带什么。 + +**不在这里:**交付一个供其它包构建时使用的**库**,那是 +[12 —— 分发预编译库](12-binary-distribution.md)。在此之后: +[11 —— 发布一个库到 mcpp-index](11-publishing-a-library.md)。 + > 默认的动态链接 `mcpp build` 产物会把 loader 与 RUNPATH 指向构建沙盒。它是 > 开发产物,不是交付物。有三条路把它变成交付物 —— **没有一条使用宿主的 C 库**。 @@ -348,3 +357,4 @@ macOS **程序** bundling(Mach-O 依赖闭包,走 `otool -L` / `LC_LOAD_DYLIB`, 会在该格式上拒绝,而不是产出一个只是看起来像 bundle 的东西。当前 `.zip` 之外的 Windows DLL 分发,以及 `.deb` / `.rpm` / AppImage 等格式,同样在规划中。本文档随 `mcpp pack` 实现演进,最新选项以 `mcpp pack --help` 为准。 + diff --git a/docs/zh/11-publishing-a-library.md b/docs/zh/11-publishing-a-library.md index 5eef21f5..7c5428f6 100644 --- a/docs/zh/11-publishing-a-library.md +++ b/docs/zh/11-publishing-a-library.md @@ -2,6 +2,15 @@ [English](../11-publishing-a-library.md) | **简体中文** +**读者:**希望自己的包能被别人写进 `[dependencies]` 的库作者。 + +**本章回答的那一个问题:**从打好 tag 到成为一个可解析的包,要走哪些步骤,以及它们 +必须按什么顺序发生。 + +**不在这里:**什么使两个包成为同一个包 —— [SPEC-001](../specs/package-identity.md); +以及交付编译产物,那是 [12](12-binary-distribution.md)。在此之前: +[10 —— 发布打包](10-pack-and-release.md)。 + 一个库如何变成 `[dependencies]` 可以写出来的东西。这是**库作者**的链路; [92 - 发布 mcpp](92-release.md) 讲的是发布 mcpp 自身, [10 - 发布打包](10-pack-and-release.md) 讲的是 `mcpp pack` 打包应用。 @@ -143,3 +152,4 @@ mcpp 的构建沙箱是网络隔离的,`file://` 和 `http://127.0.0.1` 形式 - [ ] `publish-artifact.yml` 成功 - [ ] 冷解析(删掉播种拷贝后)能下载并编译 - [ ] 消费方已升版本 + diff --git a/docs/zh/12-binary-distribution.md b/docs/zh/12-binary-distribution.md index e66f14a0..502cfb90 100644 --- a/docs/zh/12-binary-distribution.md +++ b/docs/zh/12-binary-distribution.md @@ -2,6 +2,13 @@ [English](../12-binary-distribution.md) | **简体中文** +**读者:**交付编译产物而不是源码的发布方。 + +**本章回答的那一个问题:**怎样交付二进制,以及消费方的构建如何判断其中哪一个合用。 + +**不在这里:**发布源码,那是 [11 —— 发布一个库](11-publishing-a-library.md);以及 +兼容性 tag 的加速器字段,那是 [42 —— 异构硬件构建](42-heterogeneous-builds.md)。 + > 把一个库以**接口 + 预编译二进制**的形式分发,而不是发源码。 > 适用于闭源分发、离线环境,以及构建产物已在构建农场生成的场景。 > @@ -428,3 +435,4 @@ e2e 套件按宿主能力给每条测试开门,所以「套件是绿的」和「 xvm 的 **shim**,在 e2e 套件改过的环境里它回答「未安装」。所以老客户端检查的 **静态那半**(生成的 manifest 不含任何旧 mcpp 读不了的段)到处都跑,而**真实那半** —— 用上一版发布的 mcpp 去构建这个包 —— 是**手工跑的,不是 CI 跑的**。 + diff --git a/docs/zh/20-toolchains.md b/docs/zh/20-toolchains.md index 3f5464a7..c5105ae7 100644 --- a/docs/zh/20-toolchains.md +++ b/docs/zh/20-toolchains.md @@ -1,5 +1,14 @@ # 20 —— 工具链管理 +**读者:**在意「哪个编译器在跑」的人 —— 版本下界、第二套工具链,或者一台不许用 +自带编译器的机器。 + +**本章回答的那一个问题:**这个工程会被哪个编译器构建、它怎样被安装、以及怎样换成 +另一个。 + +**不在这里:**这套解析是怎么实现的,那是 [91 —— 工具链机制内幕](91-toolchain-internals.md); +以及目标怎么命名,那是 [21 —— 目标三元组](21-the-target-triple.md)。 + > mcpp 维护一个独立的工具链沙盒,与系统 PATH 完全隔离。 ## 设计动机 @@ -818,3 +827,4 @@ AT&T 语法 x86)和 `.asm`(NASM——Intel 语法 x86)是一等源文件:默认 nasm 则**硬失败**(汇编绝不静默跳过)。限制:`.asm` 仅限 x86 目标(其他目标硬 报错——用条件 sources 门控)、MSVC 工具链不支持 `.S`、`.asm` 即 NASM 语法 (MASM 源请用 `!` 排除)。 + diff --git a/docs/zh/21-the-target-triple.md b/docs/zh/21-the-target-triple.md index 76cf935f..dc35528f 100644 --- a/docs/zh/21-the-target-triple.md +++ b/docs/zh/21-the-target-triple.md @@ -1,5 +1,13 @@ # 21 —— 目标三元组 +**读者:**为另一台机器构建的人。 + +**本章回答的那一个问题:**目标怎样被命名、支持哪些目标、各在什么档位,以及哪些 +构建机能服务它们。 + +**不在这里:**manifest 怎样以目标为条件,那是 [22 —— 目标侧](22-target-side.md); +以及没有操作系统的目标,那是 [40 —— 裸机](40-baremetal.md)。 + 目标三元组写作 `-` 或 `--`。本章说明每一段的含义、 第三段何时可以省略,以及为何这个答案在 mcpp 同时支持的两种体系下并不相同。 @@ -457,3 +465,4 @@ runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic", [第 14 章](22-target-side.md)讲五层以及每一层由谁供给。 [第 15 章](24-openkal-cross.md)完整讲构建期体系。 [第 03 章](20-toolchains.md)讲工具链轴,它是分开的:目标不决定编译器。 + diff --git a/docs/zh/22-target-side.md b/docs/zh/22-target-side.md index 666b2e39..80e7d538 100644 --- a/docs/zh/22-target-side.md +++ b/docs/zh/22-target-side.md @@ -1,5 +1,14 @@ # 22 —— 目标侧 +**读者:**要用同一份 manifest 服务多个目标的作者。 + +**本章回答的那一个问题:**manifest 怎样说「只在那里」,以及哪些东西可以这样被 +条件化。 + +**不在这里:**目标名的词汇表,那是 [21 —— 目标三元组](21-the-target-triple.md); +以及加速器这条轴 —— 它在图之后才解析,属于 +[42 —— 异构硬件构建](42-heterogeneous-builds.md)。 + 一次构建在发出任何命令行之前必须回答一个问题:目标的编译器运行时、平台接口、 C 库与 C++ 运行时从哪里来。mcpp 在依赖图解析完成之后解析该问题一次, 其后的每一个阶段读取同一个结果。 @@ -394,3 +403,4 @@ C 库,那时解析出的 `c-abi` 就不是这里返回的东西。要按已解 `[target.'cfg(c-abi = "musl")'.build]`(见[22 —— 目标侧](22-target-side.md))。 这一段在 2026.9.1.1 之前写的是「解析到的是哪份 C 库」,那是两者里错的那一个。 参见[40 —— 裸机与 freestanding 目标](40-baremetal.md)。 + diff --git a/docs/zh/23-the-project-environment.md b/docs/zh/23-the-project-environment.md index 7f537516..cb570e5c 100644 --- a/docs/zh/23-the-project-environment.md +++ b/docs/zh/23-the-project-environment.md @@ -1,5 +1,12 @@ # 23 —— 项目环境 +**读者:**构建需要「编译器之外的工具」的作者。 + +**本章回答的那一个问题:**工程怎样声明它的构建所处的环境,以及这条声明决定什么。 + +**不在这里:**构建程序拿这些工具做什么,那是 [30 —— 构建程序](30-build-mcpp.md); +以及选择编译器,那是 [20 —— 工具链管理](20-toolchains.md)。 + 项目可以声明自己在哪个环境里构建。这一条声明决定项目链接哪个 C 库、以及它的 构建程序找到哪些工具——于是同一份 `mcpp.toml` 在开发机和 CI 上是同一个构建, 不论这两台机器上还装了别的什么。 @@ -428,3 +435,4 @@ error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins - [91 - 工具链内部](91-toolchain-internals.md) —— 运行时选择、`RuntimeBinding` 快照与降级规则。 - [03 - mcpp.toml](03-mcpp-toml.md) —— manifest 的其余部分。 + diff --git a/docs/zh/24-openkal-cross.md b/docs/zh/24-openkal-cross.md index 9f9bc2bf..0ce3fc49 100644 --- a/docs/zh/24-openkal-cross.md +++ b/docs/zh/24-openkal-cross.md @@ -1,5 +1,13 @@ # 24 —— 基于 openkal 的交叉构建 +**读者:**从本机为另一个操作系统做交叉构建的人。 + +**本章回答的那一个问题:**一份源码树怎样为多个 hosted 目标构建,而本机并没有它们 +的工具链。 + +**不在这里:**没有操作系统的目标,那是 [40 —— 裸机](40-baremetal.md);以及目标 +怎样命名,那是 [21 —— 目标三元组](21-the-target-triple.md)。 + 传统的交叉构建由载荷承担。一份工具链为一个目标而构建,它的驱动只有一个答案, 到达第二个目标意味着获取第二份工具链。因此一个发行方必须发布的载荷数, 等于它支持的宿主-目标对数。 @@ -259,3 +267,4 @@ g++: error: unrecognized command-line option '-fuse-ld=…/ld.lld' [docs/14 — 目标侧](22-target-side.md) 给出五个层、四种来源与规则。 [SPEC-002](../specs/target-side.md) 给出能力语法的规范性陈述。 + diff --git a/docs/zh/30-build-mcpp.md b/docs/zh/30-build-mcpp.md index 354f2198..774725d6 100644 --- a/docs/zh/30-build-mcpp.md +++ b/docs/zh/30-build-mcpp.md @@ -2,6 +2,16 @@ [English](../30-build-mcpp.md) | **简体中文** +**读者:**构建里需要一步 mcpp 没有规则的工作的作者 —— 代码生成、嵌入资源、 +一项检查,或者第二个编译器。 + +**本章回答的那一个问题:**怎样把这份工作加进构建图,使它像其余部分一样被定序、 +进指纹、可增量。 + +**不在这里:**把这一步打包给别的工程用,那是 +[31 —— 编写规则包](31-authoring-a-rule-package.md);以及这一步要跑的工具,那是 +[23 —— 项目环境](23-the-project-environment.md)。 + 绝大多数工程只需要 `mcpp.toml`。需要构建期逻辑时 —— 探测主机、生成源码、依据环境 决定某个编译开关——就在工程根目录放一个 `build.mcpp`。它是 mcpp 版的 Zig `build.zig` / Cargo `build.rs`,但用 **C++** 编写:不引入第二种语言,而且 mcpp 自己吃自己的狗粮。 @@ -1024,3 +1034,4 @@ mcpp 写的包仍然能加载,这个读取器认识的部分照常生效。在 一套谓词规则,针对**解析后的 target** 求值。**feature 本身在所有平台都注册** —— 只有它拉进来的东西是条件性的 —— 因此在没有任何谓词匹配的平台上请求它,不是 「未知 feature」错误。 + diff --git a/docs/zh/31-authoring-a-rule-package.md b/docs/zh/31-authoring-a-rule-package.md index f8946505..461e7bf5 100644 --- a/docs/zh/31-authoring-a-rule-package.md +++ b/docs/zh/31-authoring-a-rule-package.md @@ -1,14 +1,16 @@ # 31 —— 编写规则包 -规则包教会 mcpp 构建引擎本身没有规则的东西:一种设备语言、一份着色器、一个生成的 -接口、一项检查。它是一个普通的包,它的模块被消费者的构建程序 import。 - -相关文档:[30 —— build.mcpp](30-build-mcpp.md) 是本章所用指令协议与类型化 API 的 -参考;[05 —— Feature 与能力](05-features-and-capabilities.md) 覆盖 feature 相关键; -[42 —— 异构硬件构建](42-heterogeneous-builds.md) 列出已发布的规则。两个示例: -[`08-build-rules`](../../examples/08-build-rules/) 是做检查与嵌入的规则, -[`12-a-new-device-language`](../../examples/12-a-new-device-language/) 是新增一门 -语言的规则。 +**读者:**要把一步构建工作打包给别的工程使用的生态作者 —— 一种设备语言、一个 +着色器编译器、一个生成的接口,或者一项检查。 + +**本章回答的那一个问题:**一个包怎样供给一条规则,以及消费者要写什么才能用上它。 + +**不在这里:**给单个工程自己的构建加一步,那是 +[30 —— 构建程序](30-build-mcpp.md) —— 同一批原语,规模更小;feature 相关键本身, +那是 [05](05-features-and-capabilities.md);以及已发布规则各自的拼法,那属于 +`mcpp:plugins`。示例:[`08-build-rules`](../../examples/08-build-rules/) 做检查与 +嵌入,[`12-a-new-device-language`](../../examples/12-a-new-device-language/) 新增 +一门语言。 ## 规则包是什么 diff --git a/docs/zh/40-baremetal.md b/docs/zh/40-baremetal.md index be996532..c3c42eb6 100644 --- a/docs/zh/40-baremetal.md +++ b/docs/zh/40-baremetal.md @@ -1,5 +1,13 @@ # 40 —— 裸机与 freestanding 目标 +**读者:**面向没有操作系统的机器的嵌入式开发者。 + +**本章回答的那一个问题:**一个工程怎样为 freestanding 目标构建、谁供给这个目标的 +整个世界,以及标准库还剩下什么。 + +**不在这里:**运行与测试这个镜像,那是 [41 —— 抵达一台设备](41-devices.md);以及 +hosted 的交叉构建,那是 [24 —— 基于 openkal 的交叉构建](24-openkal-cross.md)。 + 本文说明 mcpp 如何为没有操作系统的目标构建、运行与测试软件,以及板级支持包如何 提供引擎刻意不去掌握的那部分目标事实。 @@ -711,3 +719,4 @@ int main() { | 替换 C 库 | 自 2026.8.20.2 起可经 `[target.].sysroot` 表达,而**仅空值一侧经过验证**(零 libc 档)。指向另一份 C 库同样被接受并经同一通道安装,但生态中没有第二份裸机 C 库,该路径未经测试。 | | `win32-arm64` 上的 `qemu-riscv` | 上游包未为该宿主发布资产,因此在其上安装会失败。该失败是正确的而非静默的,但该宿主无法运行裸机镜像。 | | 生态侧 CI 广度 | 两个生态包各自的 CI 只跑 `ubuntu-24.04`。mcpp-index 的 `tests/examples/` workspace 成员在三个平台上无条件运行且没有能力门,因此需要模拟器与目标 sysroot 的包无法加入其中。这是一个已知的覆盖缺口。 | + diff --git a/docs/zh/41-devices.md b/docs/zh/41-devices.md index acf1f435..2b5cdfe6 100644 --- a/docs/zh/41-devices.md +++ b/docs/zh/41-devices.md @@ -1,5 +1,14 @@ # 41 —— 抵达一台设备 +**读者:**产物不在构建它的那台机器上运行的人 —— 一块板子、一个模拟器,或者一台 +远程主机。 + +**本章回答的那一个问题:**这样的产物怎样被运行与测试,以及一个包供给什么,才能让 +一条命令抵达它。 + +**不在这里:**这个目标本身怎么构建,那是 [40 —— 裸机](40-baremetal.md);以及 mcpp +认为什么是一个测试,那是 [07 —— 测试](07-testing.md)。 + 本文规定 mcpp 如何执行一个运行在构建机器之外的产物、包如何提供抵达它的其他方式, 以及工程如何在模拟器与真实硬件之间选择。 @@ -132,3 +141,4 @@ cortex-m-rt = { version = "0.1.0", features = ["hardware"] } **这不需要任何引擎机制。** 引擎读 runner,对模拟器与探针一无所知; `mcpp::has_feature` 本来就在。**一个问题不必新增任何东西就能回答,是分层按规定在 起作用。** + diff --git a/docs/zh/42-heterogeneous-builds.md b/docs/zh/42-heterogeneous-builds.md index 33ef464d..134d8f68 100644 --- a/docs/zh/42-heterogeneous-builds.md +++ b/docs/zh/42-heterogeneous-builds.md @@ -1,5 +1,14 @@ # 42 —— 异构硬件构建 +**读者:**把程序的一部分编译到 GPU 或 AI 加速器上的人。 + +**本章回答的那一个问题:**设备代码怎样被编译并链接进一个普通程序,以及一个预建 +产物怎样声明它能在哪些设备上运行。 + +**不在这里:**编写驱动设备编译器的那条规则,那是 +[31 —— 编写规则包](31-authoring-a-rule-package.md);以及抵达设备把它跑起来,那是 +[41 —— 抵达一台设备](41-devices.md)。 + GPU 与 AI 加速器目标,以及宿主/设备混合编译:mcpp 如何构建设备代码,以及一个预建产物如何 声明它能在哪些设备上运行。 @@ -599,3 +608,4 @@ Mesa 的 lavapipe 仅因类型就被排除 —— 尽管它声明了后端要求 - `mcpp pack` 不产出 `accel` 字段。需要它的发布方写进描述符。 每条 lane 的按平台边界见*每条 lane 到达哪些平台*一节的表格。 + diff --git a/docs/zh/50-machine-output.md b/docs/zh/50-machine-output.md index e2a8b5a0..53fe3a50 100644 --- a/docs/zh/50-machine-output.md +++ b/docs/zh/50-machine-output.md @@ -1,5 +1,13 @@ # 50 —— 机器可读输出 +**读者:**要解析 mcpp 输出的工具、编辑器扩展或 CI 任务的作者。 + +**本章回答的那一个问题:**一个程序可以依赖什么、它怎样被版本化、以及协议怎样被 +识别。 + +**不在这里:**面向人的输出 —— 它不带任何兼容性承诺;以及退出码契约,那是 +[SPEC-003](../specs/exit-codes.md)。 + mcpp 面向两类读者。本章是对第二类 —— **程序** —— 的契约。编辑器扩展、CI 脚本,以及任何解析 mcpp 输出的程序, 可依赖的部分在此列出。 @@ -390,3 +398,4 @@ mcpp test [pattern] [--workspace] --message-format json `workspace_summary` 增加 `tests_not_run`(各成员之和)与 `unrunnable_members`(所有 测试都 `not_run` 的成员),与既有的 `not_run` 列表并列;后者仍然指 `--workspace-timeout` 到达时尚未开始的成员。 + diff --git a/docs/zh/51-supported-versions.md b/docs/zh/51-supported-versions.md index 8336b598..ed1983a1 100644 --- a/docs/zh/51-supported-versions.md +++ b/docs/zh/51-supported-versions.md @@ -1,5 +1,12 @@ # 51 —— 受支持的版本与兼容性 +**读者:**带着政策问题来的人 —— 一次评审流程、一次隔网构建、一个被钉住的版本。 + +**本章回答的那一个问题:**哪些发布受支持、版本之间什么可以变、什么不可以。 + +**不在这里:**退出码表,那是 [SPEC-003](../specs/exit-codes.md);以及机器输出的 +schema,那是 [50 —— 机器可读输出](50-machine-output.md)。 + 本文陈述哪些发布受支持、支持多久、以及版本之间什么可以变化。它之所以存在,是因为 一个采用 mcpp 的工程会被它自己的评审流程问到这些问题,而只活在维护者脑子里的答案 无法被引用。 @@ -76,3 +83,4 @@ mcpp emit sbom -o sbom.json 缺陷与安全报告提交到拥有该组件的仓库的 issue tracker —— 引擎、包索引,或者那个包 自身。一份点明了版本、宿主、目标与命令的报告是可执行的;没有这四样的报告通常换来 一次索要它们的回复。 + diff --git a/docs/zh/90-build-from-source.md b/docs/zh/90-build-from-source.md index 7895bf4d..e92514a3 100644 --- a/docs/zh/90-build-from-source.md +++ b/docs/zh/90-build-from-source.md @@ -1,5 +1,13 @@ # 90 —— 从源码构建与参与贡献 +**读者:**要构建并修改 mcpp 本身的贡献者。 + +**本章回答的那一个问题:**mcpp 怎样从源码构建、它自己的测试怎样组织,以及一次 +贡献要满足什么。 + +**不在这里:**用户怎样构建自己的工程,那是 [01 —— 快速开始](01-getting-started.md); +以及一次发布怎样切出来,那是 [92 —— 发布 mcpp](92-release.md)。 + > mcpp 采用自托管模式 —— 通过 mcpp 自身从源码构建 mcpp。 > 任何已具备可运行 mcpp 二进制的环境均可完成源码构建。 @@ -115,3 +123,4 @@ mcpp 处于早期迭代阶段,接口可能调整,提交 PR 前请注意: - 交流群 QQ: 1067245099 - [mcpp-index](https://github.com/mcpplibs/mcpp-index) — 默认包索引 - [mcpplibs](https://github.com/mcpplibs) — 配套的模块化 C++ 库集合 + diff --git a/docs/zh/91-toolchain-internals.md b/docs/zh/91-toolchain-internals.md index 820e9d6c..77534230 100644 --- a/docs/zh/91-toolchain-internals.md +++ b/docs/zh/91-toolchain-internals.md @@ -1,5 +1,13 @@ # 91 —— 工具链机制内幕 +**读者:**贡献者,或者正在排查「工具链为什么解析成这样」的用户。 + +**本章回答的那一个问题:**mcpp 实际上怎样解析、安装并拼装一条工具链,以及每个 +flag 由哪一层决定。 + +**不在这里:**怎样**选择**一条工具链,那是 [20 —— 工具链管理](20-toolchains.md)。 +本章是它底下的机制,而这里没有任何东西是稳定接口。 + > 本文详细描述 mcpp 工具链机制的内部工作原理,以及如何扩充新工具链、新架构乃至 > 嵌入式目标的支持。与面向用户的 [20 — 工具链管理](20-toolchains.md)(CLI 用法) > 互补,本文面向贡献者与维护者。 @@ -531,3 +539,4 @@ flag 的拼法 —— 它是产物记录下的关于它自己的东西: | build.mcpp 宿主 flags | `src/build/build_program.cppm` | | hermetic 链接校验 | `src/build/hermetic.cppm` | | 回归fence | `tests/e2e/86_llvm_hermetic_link.sh`、单测 `test_linkmodel.cpp`、`test_post_install.cpp`;`ci-linux-e2e.yml` 的无宿主工具链 CI job | + diff --git a/docs/zh/92-release.md b/docs/zh/92-release.md index 8f85fce3..a0fdc282 100644 --- a/docs/zh/92-release.md +++ b/docs/zh/92-release.md @@ -1,5 +1,13 @@ # 92 —— 发布 mcpp +**读者:**正在切一次 mcpp 自身发布的维护者。 + +**本章回答的那一个问题:**从一个提交到用户可安装的发布版本要走哪些步骤,以及每一步 +怎样被核验。 + +**不在这里:**把一个包发布到索引,那是 [11 —— 发布一个库](11-publishing-a-library.md); +以及版本之间什么可以变,那是 [51](51-supported-versions.md)。 + **mcpp 自身**的发布如何到达用户手上。本章面向维护者;打包**自有项目**见 [10 — 打包发布](10-pack-and-release.md)。 @@ -232,3 +240,4 @@ curl -fsSL https://github.com/xlings-res/xim-index/releases/download/latest/xim- [ ] clean-room XLINGS_HOME:xlings install mcpp@ 成功 [ ] (可选)bump .xlings.json —— 只在此刻,绝不提前 ``` + From 6624d006b285fad2b0ff174c008e49ffa2ec9441 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:44:53 +0800 Subject: [PATCH 18/36] docs: restore chapter 51's opening, which a falsification test reverted The check for rule 11 was falsified by breaking chapter 51 and then running `git checkout -- docs/51-supported-versions.md` -- which restored the file to HEAD, and the opening had not been committed yet. The rule reported it correctly and the commit went out red. A falsification test must copy the file aside, not revert it, whenever the change under test is uncommitted. --- docs/51-supported-versions.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/51-supported-versions.md b/docs/51-supported-versions.md index e4ecd055..f8aa7de5 100644 --- a/docs/51-supported-versions.md +++ b/docs/51-supported-versions.md @@ -1,5 +1,15 @@ # 51 — Supported Versions and Compatibility +**Reader:** anyone with a policy question — a review process, an air-gapped +build, a pinned release. + +**The question this chapter answers:** which releases are supported, what may +change between them, and what may not. + +**Not here:** the exit-code table, which is [SPEC-003](specs/exit-codes.md), and +the machine-output schema, which is +[50 — Machine-Readable Output](50-machine-output.md). + This document states which releases are supported, for how long, and what may change between them. It exists because a project adopting mcpp is asked these questions by its own review process, and an answer that lives only in @@ -88,3 +98,4 @@ Defects and security reports go to the issue tracker of the repository that owns the component — the engine, the package index, or the package itself. A report that names the version, the host, the target and the command is actionable; one that does not usually results in a request for those four. + From 5aa14a6844652b31b1bb319c8d8c846a4c61f7cb Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:46:51 +0800 Subject: [PATCH 19/36] docs: apply R1 where it actually applies, and correct the measurement that said seven MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The design's §1.1 reported testing as a section in seven chapters and concluded that seven chapters explain it. The criterion was a substring search over heading text -- the exact shape this repository's style skill forbids, used in the document that states the rule. Read properly, the seven are: two about mcpp's OWN test suite (a contributor's subject), one that owns the JSON schema, one that owns the `[dev-dependencies]` table, two legitimate mentions inside a tutorial and a workspace section, one heading that says "testing" over a section about path overrides, and one worked example. Exactly ONE is a second explanation of how a test runs: 40-baremetal. So R1 is applied where it applies. 40-baremetal now defers the model to 07 and keeps what is specific to a board -- one image per test file, and semihosting carrying `main`'s return value into the emulator's exit code, which is why the model is identical to a hosted run rather than merely similar. 01 and 06 gain a line naming 07 as the chapter that owns the topic. The conclusion the measurement was drawn for survives: before 07 existed, no chapter answered "how do I test". A topic can be unowned without being duplicated, and that one was. The correction is kept in the design document rather than quietly replaced, because the wrong criterion is more transferable than the right count. --- .../2026-09-08-the-documentation-as-a-book.md | 46 ++++++++++++------- docs/40-baremetal.md | 11 +++-- docs/zh/01-getting-started.md | 2 + docs/zh/40-baremetal.md | 9 ++-- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 62572fea..596700e2 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -24,23 +24,37 @@ gives it one. ### 1.1 No topic has an owner -A topic is *owned* by the chapter that carries it as a `##` section. Measured -2026-09-08 across the 24 chapters: +A topic is *owned* by the chapter that carries it as a `##` section. -| topic | chapters carrying it as a section | -|---|---| -| testing (`mcpp test`) | **7** | -| dependency resolution and versions | **7** | -| C++ modules themselves | **7** | -| diagnosis and troubleshooting | 4 | -| caching and incrementality | 4 | -| **what mcpp is — the model** | **0** | - -Seven chapters explain testing because seven mechanisms each needed to mention -it. None of them is where a reader goes to learn how to test. The reader who -asks a *task* question — "how do I test", "what happens when two packages want -different versions", "why did this rebuild" — has no chapter to open, and the -seven partial answers do not compose. +The first measurement counted headings whose text contained the topic's name, +and **that criterion was wrong in exactly the way this repository's own style +skill forbids** — a substring search standing in for a question about meaning. +It reported testing as a section in seven chapters. Reading those seven: + +| chapter | what its "test" section is actually about | verdict | +|---|---|---| +| `90-build-from-source` ×2 | **mcpp's own** test suite, for a contributor | a different subject | +| `50-machine-output` | the `--message-format json` schema | it owns that | +| `04-dependencies` | the `[dev-dependencies]` table | it owns that | +| `01-getting-started` | one step of a tutorial | legitimate; should link | +| `06-workspace` | the workspace fan-out | legitimate; should link | +| `11-publishing-a-library` | path overrides, under a heading that says "testing" | a naming defect | +| `03-mcpp-toml` | a worked example that happens to have tests | worked examples leave `03` | +| **`40-baremetal`** | **how a test runs, restated** | **the one real duplicate** | + +So the corrected reading: **one** duplicated explanation, one misnamed heading, +and a worked example that a separate decision moves anyway. + +**The conclusion survives the correction, and it is the part that mattered.** +Before `07` existed, no chapter answered "how do I test" — the seven partial +mentions each assumed a reader who already knew, and none of them was the place +to learn it. A topic can be unowned without being duplicated, and this one was. + +The same caution applies to the other rows of the first measurement, which are +kept here as what they are — a count of headings, not of explanations: +dependency resolution 7, C++ modules 7, diagnosis 4, caching 4, and **the model +0**. The last is the one that needs no re-reading: a section that does not exist +cannot be miscounted. ### 1.2 There is no model to hang anything on diff --git a/docs/40-baremetal.md b/docs/40-baremetal.md index e087545f..cbf90630 100644 --- a/docs/40-baremetal.md +++ b/docs/40-baremetal.md @@ -515,10 +515,13 @@ picolibc present: a cold build links, and `nm` finds one definition. ### Running tests on the target -`mcpp test` builds one image per `tests/*.cpp`, runs each under the emulator -the board-support package supplies, and reads the exit code as the verdict. -Semihosting propagates the firmware's `main` return value to the emulator's -exit code, so the model is identical to a hosted test run. +Testing works here exactly as [07 — Testing](07-testing.md) describes it, and +this section adds only what is specific to a board: each `tests/*.cpp` becomes +its own image, and the runner the board-support package supplies executes it. + +What makes the verdict work is semihosting, which propagates the firmware's +`main` return value to the emulator's exit code. That is why the model is +identical to a hosted run rather than merely similar to one. ```bash mcpp test diff --git a/docs/zh/01-getting-started.md b/docs/zh/01-getting-started.md index 63777306..f84149eb 100644 --- a/docs/zh/01-getting-started.md +++ b/docs/zh/01-getting-started.md @@ -127,6 +127,8 @@ workspace 中运行。插件稳定依赖进程退出码和生成的 `compile_com ## 增量编译与测试 +[07 —— 测试](07-testing.md) 是拥有这个主题的章节;下面只是本教程需要的那一步。 + ```bash mcpp build # 增量构建 mcpp clean # 清理 target/ diff --git a/docs/zh/40-baremetal.md b/docs/zh/40-baremetal.md index c3c42eb6..2a162404 100644 --- a/docs/zh/40-baremetal.md +++ b/docs/zh/40-baremetal.md @@ -450,9 +450,12 @@ C 库经过优化的那份,且没有任何提示。实测(picolibc 在场):冷 ### 在目标上运行测试 -`mcpp test` 为每个 `tests/*.cpp` 构建一个独立镜像,在板级支持包提供的模拟器里运行, -并以退出码为判据。semihosting 把固件 `main` 的返回值传递到模拟器的退出码,因此其 -心智模型与宿主上的测试运行完全一致。 +测试在这里的运作方式与 [07 —— 测试](07-testing.md) 所述完全一致,本节只补充板子 +特有的部分:每个 `tests/*.cpp` 成为它自己的一个镜像,由板级支持包提供的 runner +执行它。 + +让判据成立的是 semihosting —— 它把固件 `main` 的返回值传递到模拟器的退出码。这正是 +这个模型与宿主上的运行**完全一致**而不只是相似的原因。 ```bash mcpp test From 6aabf23965dcc6bc11fa87a1d5f0c127f3412358 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:55:31 +0800 Subject: [PATCH 20/36] docs: a scenario chapter, a lookup index, and the rules that make a book also a reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things the reader-facing side was missing, and the rules for each are in the style skill so they outlive this batch. 02 — SCENARIOS. The tree had an index by example (03) and an index by command (09) and none by the work in front of a developer. Nine scenarios -- a CLI tool, a library, a workspace, a graphical application, cross-compiling, bare metal, GPU compute, graphics, a build step -- each stating the situation, what mcpp contributes to it, the path through the chapters, one project to run, and the one thing that surprises people. Every scenario is backed by a runnable project or a published template; a path with nothing to run is a claim rather than a scenario, and the chapter says so. A LOOKUP INDEX. The chapter list is a reading order and cannot answer "I have `[feature-deps]` in front of me". docs/README.md gains the other index: manifest tables and keys, commands, and concepts, each to the chapter that owns it. The two properties -- a book that is read in order and a reference that is looked up -- are carried by two indexes rather than by one table pretending to do both. THE RULES. R7: an advantage is shown by the artifact, not by an adjective -- six lines of manifest, `Finished dev in 0.06s`, `counters` 0 times without the feature and 2 with it. The criterion is that deleting every adjective leaves the advantage visible. R8: a complex feature is introduced as shortest-runnable → common shape → full surface → edges, one axis at a time; the criterion is how many screens before the reader has something running. Plus the two-index rule above. The review rubric is fourteen dimensions now. --- .agents/skills/mcpp-docs-style/SKILL.md | 58 ++++ .github/tools/check_docs_style.sh | 2 +- CHANGELOG.md | 12 +- README.md | 10 +- README.zh-CN.md | 10 +- docs/00-how-mcpp-works.md | 4 +- docs/01-getting-started.md | 6 +- docs/02-scenarios.md | 262 ++++++++++++++++++ docs/{02-examples.md => 03-examples.md} | 6 +- docs/{03-mcpp-toml.md => 04-mcpp-toml.md} | 18 +- ...{04-dependencies.md => 05-dependencies.md} | 6 +- ...ies.md => 06-features-and-capabilities.md} | 10 +- docs/{06-workspace.md => 07-workspace.md} | 6 +- docs/{07-testing.md => 08-testing.md} | 6 +- ...scenario.md => 09-commands-by-scenario.md} | 4 +- docs/10-pack-and-release.md | 4 +- docs/20-toolchains.md | 2 +- docs/22-target-side.md | 2 +- docs/23-the-project-environment.md | 6 +- docs/24-openkal-cross.md | 2 +- docs/30-build-mcpp.md | 12 +- docs/31-authoring-a-rule-package.md | 6 +- docs/40-baremetal.md | 6 +- docs/41-devices.md | 2 +- docs/42-heterogeneous-builds.md | 4 +- docs/README.md | 57 +++- docs/specs/manifest-semantics.md | 2 +- docs/zh/00-how-mcpp-works.md | 4 +- docs/zh/01-getting-started.md | 8 +- docs/zh/02-scenarios.md | 221 +++++++++++++++ docs/zh/{02-examples.md => 03-examples.md} | 6 +- docs/zh/{03-mcpp-toml.md => 04-mcpp-toml.md} | 16 +- ...{04-dependencies.md => 05-dependencies.md} | 6 +- ...ies.md => 06-features-and-capabilities.md} | 8 +- docs/zh/{06-workspace.md => 07-workspace.md} | 6 +- docs/zh/{07-testing.md => 08-testing.md} | 6 +- ...scenario.md => 09-commands-by-scenario.md} | 4 +- docs/zh/10-pack-and-release.md | 4 +- docs/zh/20-toolchains.md | 4 +- docs/zh/22-target-side.md | 2 +- docs/zh/23-the-project-environment.md | 6 +- docs/zh/24-openkal-cross.md | 2 +- docs/zh/30-build-mcpp.md | 12 +- docs/zh/31-authoring-a-rule-package.md | 6 +- docs/zh/40-baremetal.md | 6 +- docs/zh/41-devices.md | 2 +- docs/zh/42-heterogeneous-builds.md | 4 +- docs/zh/README.md | 57 +++- examples/09-heterogeneous/README.md | 2 +- examples/09-heterogeneous/cuda/app/mcpp.toml | 2 +- mcpp.toml | 2 +- modules/manifest/src/glob.cppm | 4 +- modules/manifest/src/toml.cppm | 2 +- modules/manifest/src/types.cppm | 2 +- src/build/prepare.cppm | 2 +- src/hooks.cppm | 2 +- .../e2e/616_examples_and_their_index_agree.sh | 2 +- 57 files changed, 775 insertions(+), 160 deletions(-) create mode 100644 docs/02-scenarios.md rename docs/{02-examples.md => 03-examples.md} (97%) rename docs/{03-mcpp-toml.md => 04-mcpp-toml.md} (99%) rename docs/{04-dependencies.md => 05-dependencies.md} (98%) rename docs/{05-features-and-capabilities.md => 06-features-and-capabilities.md} (98%) rename docs/{06-workspace.md => 07-workspace.md} (99%) rename docs/{07-testing.md => 08-testing.md} (97%) rename docs/{08-commands-by-scenario.md => 09-commands-by-scenario.md} (99%) create mode 100644 docs/zh/02-scenarios.md rename docs/zh/{02-examples.md => 03-examples.md} (97%) rename docs/zh/{03-mcpp-toml.md => 04-mcpp-toml.md} (99%) rename docs/zh/{04-dependencies.md => 05-dependencies.md} (98%) rename docs/zh/{05-features-and-capabilities.md => 06-features-and-capabilities.md} (98%) rename docs/zh/{06-workspace.md => 07-workspace.md} (99%) rename docs/zh/{07-testing.md => 08-testing.md} (96%) rename docs/zh/{08-commands-by-scenario.md => 09-commands-by-scenario.md} (99%) diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index ea276244..b0b5bea3 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -362,6 +362,59 @@ mcpp 在很多场景支持不止一种做法,但它有自己的设计风格与 (`2026.9.6.5+`),不写在章节顶部,也不写成「从前是 X,后来变成 Y」的叙述 —— 后者是设计记录的句子(第一节)。 +### 书与工具书:两种属性,两种索引 + +一套文档同时要能被**从头读**和被**反查**。这两件事不能由同一张表兼任 —— 一张按 +阅读顺序排的目录,回答不了「我手上有 `[feature-deps]`,该看哪一章」;一张按字母排 +的索引,读者从头读会不知道先读哪个。 + +所以是两种索引,各司其职: + +| 属性 | 承担它的东西 | 判据 | +|---|---|---| +| **书** | 章节的段位与部分内顺序(第九节的梯度) | 一个从头读的人不需要跳级 | +| **工具书** | **反查索引**:manifest 键 / 命令 / 概念 → 章节;以及每章开头的「不在这里」 | 一个拿着一个记号来的人,一步到位 | + +反查索引的**分母取自代码树**(第十节):参考章节里出现的每一个键,反查表里都要有 +一行。少一行,读者就会认为那个键没有文档。 + +### 优势由产物自己说明,不靠形容词 + +用户文档与示例要让读者**明显感到** mcpp 的长处,而做到这一点的方式不是形容词。 +「简洁」「好用」「强大」本身不携带信息,读者读到的是一个主张。 + +写成可验证的三样东西之一: + +| 不采用 | 采用 | +|---|---| +| 打包非常简单 | 六行 manifest,一条 `mcpp pack`,产出一个静态二进制 | +| 增量构建很快 | `Finished dev in 0.06s` | +| 不带加速器时开销很小 | 不点名加速器的构建**一个字节都不下载** | +| 依赖是可选的 | `counters` 不带 feature 出现 **0 次**,带 feature **2 次** | + +**判据:把所有形容词删掉,读者还能不能看出优势?** 能 → 对。删掉之后只剩机制 +描述 → 那份「感受」本来就只在形容词里。 + +**不要在用户文档里与别的工具比较。** 对照属于设计记录(第一节)。用户文档展示的是 +**这一件事在这里要写多少、跑出什么**,读者自己会比较。 + +### 渐进式叙事:最短可跑 → 常见形状 → 完整表面 → 边角 + +复杂或小众的特性不从机制讲起。四段,顺序固定: + +| 段 | 内容 | 读者在这一段结束时 | +|---|---|---| +| 最短可跑 | 能跑的最小形态,连同它的真实输出 | 手上有一个跑起来的东西 | +| 常见形状 | 绝大多数工程实际会写的那一种 | 能照着改成自己的 | +| 完整表面 | 字段、旗标、取值 | 查得到 | +| 边角 | 平台差异、限制、失败形态 | 知道什么时候会撞墙 | + +**判据:读者读到第几屏时手上有一个能跑的东西?** 第一屏之后还没有,就是把机制讲在 +了可跑之前。 + +一次只加一条轴。`examples/09-heterogeneous/boundary` 是这条规则的形状:它先只讲 +边界(不需要设备),`cuda` 再加设备编译器,`multi-backend` 再加第二个后端。 + ### 两个视角都要设计 - **全局**:入口的角色索引、部分的划分、编号所在的段位、章节之间的顺序。 @@ -414,6 +467,9 @@ bash .github/tools/check_docs_style.sh | **一条推荐路径** | 只读正文、不展开任何 `
`,读者能不能不做选择就把事情做对(第十二节) | | **增量标注** | 每个后加的键/旗标/行为,版本下界写在它自己旁边,而不是章节顶部或历史叙述 | | **章节规格** | 第二节那张表能不能填满 —— 读者、那一个问题、包含、排除、位置与理由、前后、判据 | +| **冲击力** | 把形容词删掉,优势还看得出来吗 —— 有没有最短可跑的产物、真实输出、可数的数字 | +| **渐进性(局部)** | 读者读到第几屏手上有一个能跑的东西;是不是一次只加一条轴 | +| **可查阅** | 拿着一个 manifest 键 / 命令 / 概念,能不能一步查到章节;反查索引有没有漏行 | **用户文档额外一条,优先级高于以上八条**:逐段问「删掉它读者还能不能正确地用」 (第一节)。设计理由、被否掉的替代、路线图,一律不在用户文档里。 @@ -433,6 +489,8 @@ bash .github/tools/check_docs_style.sh [ ] 「当前边界」是事实清单,不是说理 [ ] 有多种做法时,正文只写推荐那一条,其余在 `
` 里 [ ] 后加的键/旗标带版本下界,且标在它自己旁边 +[ ] 优势由最短可跑产物 / 真实输出 / 数字说明,不由形容词说明 +[ ] 复杂特性按「最短可跑 → 常见形状 → 完整表面 → 边角」推进 [ ] 第二节的章节规格七格都能填出来 [ ] 没有 docs/** → .agents/** 的引用 [ ] 开头点明了它在梯度里的哪一级,且只链接相邻级 diff --git a/.github/tools/check_docs_style.sh b/.github/tools/check_docs_style.sh index 4a8173c9..364f335e 100755 --- a/.github/tools/check_docs_style.sh +++ b/.github/tools/check_docs_style.sh @@ -20,7 +20,7 @@ fail=0 bad() { echo "FAIL: $*"; fail=1; } # Tutorials address the reader on purpose: the reader is following along. -TUTORIALS="01-getting-started.md 02-examples.md 90-build-from-source.md" +TUTORIALS="01-getting-started.md 03-examples.md 90-build-from-source.md" # Headings outside fenced code blocks. `# …` inside a ```sh block is a shell # comment, and counting it made the first version of this script report a diff --git a/CHANGELOG.md b/CHANGELOG.md index 2570460c..cf50f479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -596,7 +596,7 @@ e2e 616 是双向的,而单向的那一半不值得跑:「列出的路径都存 通过,「盘上的示例都被列出」在一份把一半链接指错地方的文档上通过。两条合起来才是那条性质。 它当场抓到一个既有缺口:`examples/04-workspace`、`05-lib-distribution`、 -`06-openkal-cross`、`07-project-subos` 都在盘上,而中英两份 `docs/02-examples.md` +`06-openkal-cross`、`07-project-subos` 都在盘上,而中英两份 `docs/03-examples.md` 一个都没列。现在列上了。 这是结构判据,它自己也这么说:它不构建任何东西,所以回答不了「这个示例还能不能跑」。 @@ -4317,7 +4317,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 文档 -- `docs/03-mcpp-toml.md` 的示例此前写的是 `import mcpp.rules.protobuf;`,**做不到**:mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一原子段。随之澄清一条会咬人的约束:规则包的名字必须是**合法 C++ 模块名**(`grpcgen` 可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示你名字有问题。 +- `docs/04-mcpp-toml.md` 的示例此前写的是 `import mcpp.rules.protobuf;`,**做不到**:mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一原子段。随之澄清一条会咬人的约束:规则包的名字必须是**合法 C++ 模块名**(`grpcgen` 可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示你名字有问题。 ## [2026.8.5.1] — 2026-08-05 @@ -4516,7 +4516,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 修复 -- **`[build] static_stdlib = false` 对测试二进制静默失效(#336)。** #124 在 0.0.52 明确写下这个 opt-out,`docs/03-mcpp-toml.md` 至今也还这么写;但 #202(0.0.86)把测试二进制改成与分发目标相同的静态 `-load_hidden` libc++ 时,新增的那条推导**没有带上 `staticStdlib` 门**。结果是约一年时间里 macOS 上的 `mcpp test` 没有任何办法回到动态 libc++,而文档一直在承诺它可以。 +- **`[build] static_stdlib = false` 对测试二进制静默失效(#336)。** #124 在 0.0.52 明确写下这个 opt-out,`docs/04-mcpp-toml.md` 至今也还这么写;但 #202(0.0.86)把测试二进制改成与分发目标相同的静态 `-load_hidden` libc++ 时,新增的那条推导**没有带上 `staticStdlib` 门**。结果是约一年时间里 macOS 上的 `mcpp test` 没有任何办法回到动态 libc++,而文档一直在承诺它可以。 - **macOS 上全局对象在静态初始化期访问 `std::cout` 必崩(#336)。** Mach-O 没有按优先级排序的初始化段(`init_priority` 只在单个 TU 内有效),归档成员的初始化器按链接顺序排在最后;而 libc++ 的 `` 不像 libstdc++ / MSVC STL 那样自带 `ios_base::Init` 守卫,流的构造只存在于库内对象里。两件事叠起来的后果是:默认配置下,任何在构造函数里碰 `std::cout` 的全局对象都会读到 vptr 为零的流,进程启动即 SIGSEGV —— 而且**包侧无法修复**,因为 `std::ios_base::Init` 在 libc++ 的头文件里只有前向声明(`ios:70`),标准为静态初始化次序提供的官方解药在 libc++ 上用户根本写不出来。 @@ -4550,7 +4550,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 ### 修复 -- **`--offline` 不再拒绝本地 git 远端。** `docs/03-mcpp-toml.md` 写明 `--offline` 的语义是「完全不碰网络……已安装的东西照常构建」,`prepare.cppm` 里依赖下载闸的注释也把线画在同一处:「这条线以上全是本地操作,一个依赖齐备的离线构建必须成功」。但 `git = "../sibling-repo"` 这种指向本地目录的远端,`ls-remote`/`clone` 都只是文件系统读取,拒绝它买不到任何隔离性。现在按远端形态判定——`file://`、以及不带 scheme 也不是 `git@host:path` 的存在路径,算本地(Windows 盘符 `C:\repo` 含冒号但不含 `@`,因此仍归本地)。 +- **`--offline` 不再拒绝本地 git 远端。** `docs/04-mcpp-toml.md` 写明 `--offline` 的语义是「完全不碰网络……已安装的东西照常构建」,`prepare.cppm` 里依赖下载闸的注释也把线画在同一处:「这条线以上全是本地操作,一个依赖齐备的离线构建必须成功」。但 `git = "../sibling-repo"` 这种指向本地目录的远端,`ls-remote`/`clone` 都只是文件系统读取,拒绝它买不到任何隔离性。现在按远端形态判定——`file://`、以及不带 scheme 也不是 `git@host:path` 的存在路径,算本地(Windows 盘符 `C:\repo` 含冒号但不含 `@`,因此仍归本地)。 - **克隆被中途杀掉后不再永久提供错误的 commit。** 缓存目录以 commit 命名,但内容是 `git clone` 之后再 `git checkout` 两步做出来的;进程死在两步之间,目录名和 HEAD 就对不上了,而后续构建只检查目录存不存在。现在分支依赖会比对 `git rev-parse HEAD`,不符即删除重克隆(tag/rev 以 ref 名为身份,无可比之物)。 @@ -4633,7 +4633,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 **最危险的一条判据**(`SuppressedInconclusive`):「本地查不到」单独不能推出「需要刷新」。xim 描述符不写 `namespace`,`(xim, x)` 永远匹配不上身份门 —— 把这种 miss 当真,任何带 xim 依赖的工程会**每次构建都刷**,比被删掉的 TTL 更糟。判据复用 `IndexRoute::authoritative_for`(#307),单测 + e2e 双闸锁住。 - 语义变化:`^1.2` 对**本地索引已知的版本**求解。上游新发的 1.3.0 需要 `mcpp index update` 或 `mcpp update` 才可见 —— 这正是那两个命令存在的意义,已写进 `docs/03-mcpp-toml.md`。 + 语义变化:`^1.2` 对**本地索引已知的版本**求解。上游新发的 1.3.0 需要 `mcpp index update` 或 `mcpp update` 才可见 —— 这正是那两个命令存在的意义,已写进 `docs/04-mcpp-toml.md`。 - **`mcpp update` 不再是空操作。** 它此前只删 mcpp.lock 条目、然后叫用户去跑 `mcpp build` —— 而构建路径**从不读 mcpp.lock**(`prepare` 只写不读),所以删了等于没删,行为影响为零。它现在先强制刷新索引(显式意图 ⇒ 不看 TTL、不看去抖),并报告索引 rev 的变化;工程里没有任何走共享 registry 的依赖时跳过(刷了也没用)。 @@ -5629,7 +5629,7 @@ xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的 - `[targets.]` 下的不支持键不再被静默丢弃,而是产生 warning(`--strict` 下为 error), 并指引到正确的机制(workspace / features / profile)。 -- 文档 `docs/03-mcpp-toml.md`(及 `docs/zh`)新增"构建配置该放哪"的决策指引。 +- 文档 `docs/04-mcpp-toml.md`(及 `docs/zh`)新增"构建配置该放哪"的决策指引。 设计记录见 `.agents/docs/2026-06-18-per-target-build-config-design.md`。 ## [0.0.54] — 2026-06-10 diff --git a/README.md b/README.md index f89d7fd0..64de02b1 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Module](https://img.shields.io/badge/module-ok-green.svg)](https://en.cppreference.com/w/cpp/language/modules) [![License](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](LICENSE) -| [Documentation](docs/) · [Getting Started](docs/01-getting-started.md) · [mcpp.toml Guide](docs/03-mcpp-toml.md) · [Examples](docs/02-examples.md) · [Toolchains](docs/20-toolchains.md) | +| [Documentation](docs/) · [Getting Started](docs/01-getting-started.md) · [mcpp.toml Guide](docs/04-mcpp-toml.md) · [Examples](docs/03-examples.md) · [Toolchains](docs/20-toolchains.md) | |:---:| | [Package index mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [Module libraries mcpplibs](https://github.com/mcpplibs) · [Community Forum](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) | | [![ci-linux](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml) [![ci-macos](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml) [![ci-windows](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml) | @@ -224,7 +224,7 @@ Then `import` it directly in your code: import mcpplibs.cmdline; ``` -> For more dependency options (version constraints, namespaces, Git references, local paths, etc.), see the [mcpp.toml guide — dependency management](docs/03-mcpp-toml.md). +> For more dependency options (version constraints, namespaces, Git references, local paths, etc.), see the [mcpp.toml guide — dependency management](docs/04-mcpp-toml.md). ## Feature Overview @@ -418,12 +418,12 @@ verified — CI builds **and executes** the artifact end-to-end (qemu/wine inclu ## Documentation - [Getting Started](docs/01-getting-started.md) — install → new → build → run in 5 minutes -- [Examples](docs/02-examples.md) +- [Examples](docs/03-examples.md) - [Packaging & Release](docs/10-pack-and-release.md) - [Toolchain Management](docs/20-toolchains.md) - [Building from Source](docs/90-build-from-source.md) -- [mcpp.toml Guide](docs/03-mcpp-toml.md) -- [Workspaces](docs/06-workspace.md) +- [mcpp.toml Guide](docs/04-mcpp-toml.md) +- [Workspaces](docs/07-workspace.md) Full options for any command are available via `mcpp --help`. diff --git a/README.zh-CN.md b/README.zh-CN.md index 3d3a0132..c2e374f3 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,7 +9,7 @@ [![Module](https://img.shields.io/badge/module-ok-green.svg)](https://en.cppreference.com/w/cpp/language/modules) [![License](https://img.shields.io/badge/license-Apache_2.0-blue.svg)](LICENSE) -| [文档](docs/zh/) · [快速开始](docs/zh/01-getting-started.md) · [mcpp.toml 指南](docs/zh/03-mcpp-toml.md) · [示例项目](docs/zh/02-examples.md) · [工具链管理](docs/zh/20-toolchains.md) | +| [文档](docs/zh/) · [快速开始](docs/zh/01-getting-started.md) · [mcpp.toml 指南](docs/zh/04-mcpp-toml.md) · [示例项目](docs/zh/03-examples.md) · [工具链管理](docs/zh/20-toolchains.md) | |:---:| | [包索引 mcpp-index](https://mcpplibs.github.io/mcpp-index/) · [模块化库 mcpplibs](https://github.com/mcpplibs) · [社区论坛](https://forum.d2learn.org/category/20) · [Issues](https://github.com/mcpp-community/mcpp/issues) · [Releases](https://github.com/mcpp-community/mcpp/releases) | | [![ci-linux](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-linux.yml) [![ci-macos](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-macos.yml) [![ci-windows](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/mcpp-community/mcpp/actions/workflows/ci-windows.yml) | @@ -221,7 +221,7 @@ cmdline = "0.0.2" import mcpplibs.cmdline; ``` -> 更多依赖配置方式(版本约束、命名空间、Git 引用、本地路径等)参见 [mcpp.toml 指南 — 依赖管理](docs/zh/03-mcpp-toml.md)。 +> 更多依赖配置方式(版本约束、命名空间、Git 引用、本地路径等)参见 [mcpp.toml 指南 — 依赖管理](docs/zh/04-mcpp-toml.md)。 ## 功能概览 @@ -388,12 +388,12 @@ mcpp 的身份模型是两条正交轴:**工具链** = `family@version`(family ## 文档 - [快速开始](docs/zh/01-getting-started.md) — 5 分钟完成 install → new → build → run -- [示例项目](docs/zh/02-examples.md) +- [示例项目](docs/zh/03-examples.md) - [发布打包](docs/zh/10-pack-and-release.md) - [工具链管理](docs/zh/20-toolchains.md) - [从源码构建](docs/zh/90-build-from-source.md) -- [mcpp.toml 指南](docs/zh/03-mcpp-toml.md) -- [工作空间](docs/zh/06-workspace.md) +- [mcpp.toml 指南](docs/zh/04-mcpp-toml.md) +- [工作空间](docs/zh/07-workspace.md) 任意命令的完整选项可通过 `mcpp --help` 查阅。 diff --git a/docs/00-how-mcpp-works.md b/docs/00-how-mcpp-works.md index 57b7db7d..714ca78a 100644 --- a/docs/00-how-mcpp-works.md +++ b/docs/00-how-mcpp-works.md @@ -31,7 +31,7 @@ its directory, and not its file name — which is why two packages called A package is also the unit of everything else: the unit that is published, that is cached, that declares features, and that a build program belongs to. -Reference: [03 — The mcpp.toml Manifest](03-mcpp-toml.md). +Reference: [04 — The mcpp.toml Manifest](04-mcpp-toml.md). ### graph @@ -123,7 +123,7 @@ noun names the chapter. | a message about | the noun | where to look | |---|---|---| -| a package name, a version, or "no candidate" | index | [11](11-publishing-a-library.md), [04](04-dependencies.md) | +| a package name, a version, or "no candidate" | index | [11](11-publishing-a-library.md), [05](05-dependencies.md) | | a download, a payload, or a version floor | toolchain | [20](20-toolchains.md), [23](23-the-project-environment.md) | | a triple, or "unsupported target" | target | [21](21-the-target-triple.md) | | a module that cannot be read or is not provided | graph | [30](30-build-mcpp.md) | diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md index 9e887834..f523eca7 100644 --- a/docs/01-getting-started.md +++ b/docs/01-getting-started.md @@ -8,7 +8,7 @@ running, from an empty machine. **Not here:** what mcpp's parts are — that is [00 — How mcpp Works](00-how-mcpp-works.md), which this chapter assumes rather than repeats — and every field a manifest may hold, which is -[03 — The mcpp.toml Manifest](03-mcpp-toml.md). After: [02 — Examples](02-examples.md). +[04 — The mcpp.toml Manifest](04-mcpp-toml.md). After: [03 — Examples](03-examples.md). > Go from install → new → build → run → pack in 5 minutes. @@ -178,7 +178,7 @@ Declare dependencies in `mcpp.toml`: `mcpp build` automatically resolves SemVer constraints against the [mcpp-index](https://github.com/mcpplibs/mcpp-index), fetches the source, and adds it to the build graph. For a complete example, see `02-with-deps` in -[02 — Examples](02-examples.md). +[03 — Examples](03-examples.md). ## Producing a Release Package @@ -195,7 +195,7 @@ For the differences between the four modes and their artifact layouts, see [10 ## Further Reading -- [02 — Examples](02-examples.md) — a collection of ready-to-run minimal projects +- [03 — Examples](03-examples.md) — a collection of ready-to-run minimal projects - [10 — Packaging and Release](10-pack-and-release.md) — building distributable artifacts - [20 — Toolchain Management](20-toolchains.md) — switching compilers and managing multiple versions - The full set of options for any command is available via `mcpp --help` diff --git a/docs/02-scenarios.md b/docs/02-scenarios.md new file mode 100644 index 00000000..acd7cdfb --- /dev/null +++ b/docs/02-scenarios.md @@ -0,0 +1,262 @@ +# 02 — Scenarios + +**Reader:** a developer deciding whether mcpp fits the work in front of them, +and which of its features that work will use. + +**The question this chapter answers:** what kinds of project does mcpp serve, +and for one kind, which features are used and in what order. + +**Not here:** the field reference, which is [04](04-mcpp-toml.md); the catalogue +of examples, which is [03](03-examples.md) and is indexed by example rather than +by scenario; and the command lookup, which is +[09](09-commands-by-scenario.md). This chapter is indexed by **the work**. + +Each scenario states the situation, what mcpp contributes to it, the path +through the chapters, one project to run, and the one thing that surprises +people. Read only the scenario that matches; they do not build on each other. + +## The scenarios + +| | scenario | run | +|---|---|---| +| [1](#1-a-command-line-tool-or-service) | a command-line tool or a service | `examples/01-hello` → `03-pack-static` | +| [2](#2-a-library-other-projects-import) | a library other projects import | `examples/11-features`, `05-lib-distribution` | +| [3](#3-a-repository-with-several-packages) | a repository with several packages | `examples/04-workspace` | +| [4](#4-a-graphical-application) | a graphical application | `mcpp new … --template ocornut.imgui` | +| [5](#5-building-for-another-operating-system) | building for another operating system | `examples/06-openkal-cross` | +| [6](#6-a-target-with-no-operating-system) | a target with no operating system | `mcpp new … --template riscv-virt-rt` | +| [7](#7-compute-on-a-gpu-or-an-accelerator) | compute on a GPU or an accelerator | `examples/09-heterogeneous` | +| [8](#8-graphics-rendering) | graphics rendering | `examples/10-graphics/offscreen` | +| [9](#9-a-build-step-the-project-needs) | a build step the project needs, and sharing it | `examples/08-build-rules`, `12-a-new-device-language` | + +## 1. A command-line tool or service + +**The situation.** A program built from C++23 modules, with a few dependencies, +that has to run on a machine that does not have mcpp. + +**What mcpp contributes.** `import std` works with no configuration; the +compiler is a pinned payload rather than whatever the machine has; and one +command produces a binary that carries what it needs. + +**The path.** + +1. [00 — How mcpp Works](00-how-mcpp-works.md) — the five nouns. +2. [01 — Getting Started](01-getting-started.md) — a program on the screen. +3. [05 — Dependencies and Resolution](05-dependencies.md) — `[dependencies]`, + the lock file. +4. [08 — Testing](08-testing.md) — `tests/**/*.cpp`. +5. [10 — Packaging an Application](10-pack-and-release.md) — `mcpp pack`. + +**Run.** [`examples/01-hello`](../examples/01-hello/), then +[`02-with-deps`](../examples/02-with-deps/), then +[`03-pack-static`](../examples/03-pack-static/). + +**What surprises people.** A debug build and a release build do not invalidate +each other. Each configuration gets its own fingerprinted directory under +`target/`, so alternating between them is not a rebuild. + +## 2. A library other projects import + +**The situation.** Code that other packages will name in their +`[dependencies]`, possibly with optional parts. + +**What mcpp contributes.** A module interface is the published surface; a +feature makes part of it optional without a second package; and a prebuilt +binary can state which toolchains it is compatible with. + +**The path.** + +1. [04 — The mcpp.toml Manifest](04-mcpp-toml.md) — `[lib]`, the library root. +2. [06 — Features and Capabilities](06-features-and-capabilities.md) — optional + parts, and the dependencies they pull. +3. [08 — Testing](08-testing.md) — `[dev-dependencies]`. +4. [11 — Publishing a Library](11-publishing-a-library.md) — the descriptor. +5. [12 — Distributing a Prebuilt Library](12-binary-distribution.md) — if + binaries ship too. + +**Run.** [`examples/11-features`](../examples/11-features/) declares all three +shapes of feature; [`05-lib-distribution`](../examples/05-lib-distribution/) is +the producer and consumer pair. + +**What surprises people.** The criterion for an optional backend is not that the +default build still works — it is that the default build's **resolution does not +name** the optional package. A dependency that is resolved and merely unused +still costs a download. + +## 3. A repository with several packages + +**The situation.** Several packages developed together, depending on each other +by path. + +**What mcpp contributes.** One command builds or tests the set; a member +resolves its siblings without a registry; and each member keeps its own +manifest and identity. + +**The path.** + +1. [07 — Workspaces](07-workspace.md) — `[workspace]`, path dependencies. +2. [05 — Dependencies and Resolution](05-dependencies.md) — what a path + dependency does and does not do. +3. [08 — Testing](08-testing.md) — the fan-out. + +**Run.** [`examples/04-workspace`](../examples/04-workspace/). + +**What surprises people.** A workspace member is not a root. Tooling that +enumerates "every package" has to say which of the two it means, and the +answer changes what gets built. + +## 4. A graphical application + +**The situation.** A desktop program with a window, a renderer and fonts. + +**What mcpp contributes.** The window and rendering stack are ordinary +dependencies; a template scaffolds a working project; and what the artifact +needs at run time is declared rather than discovered. + +**The path.** + +1. [01 — Getting Started](01-getting-started.md) — `mcpp new --template`. +2. [05 — Dependencies and Resolution](05-dependencies.md) — the stack. +3. [10 — Packaging an Application](10-pack-and-release.md) — what ships beside + the executable. + +**Run.** `mcpp new myapp --template ocornut.imgui`. + +**What surprises people.** On Linux an mcpp artifact runs behind a private +loader that does not consult `/usr/lib`. Anything the host must supply — a +graphics driver, a Vulkan ICD — is reached through an adapter package the +project declares, not by being installed on the machine. + +## 5. Building for another operating system + +**The situation.** One source tree that has to produce binaries for Linux, +Windows and macOS. + +**What mcpp contributes.** The target is an argument, not a second checkout; +the cross toolchain is a payload; and a target this host cannot serve is +refused rather than quietly built for the host. + +**The path.** + +1. [21 — The Target Triple](21-the-target-triple.md) — how a target is named, + and which host serves which. +2. [24 — Cross-Compilation Over openkal](24-openkal-cross.md) — the mechanism. +3. [22 — The Target Side](22-target-side.md) — when the manifest must differ + per target. + +**Run.** [`examples/06-openkal-cross`](../examples/06-openkal-cross/) — one +program built for four targets from any host. + +**What surprises people.** `--target` does not require the machine to already +have that toolchain. What it does require is that the target be *servable* from +this host; the support matrix in [21](21-the-target-triple.md) says which are. + +## 6. A target with no operating system + +**The situation.** Firmware for a board or a microcontroller: no OS, no libc by +default, a linker script and a vector table. + +**What mcpp contributes.** A board-support package supplies the whole target +world — the linker script, the startup code, and the **runner** — so `mcpp run` +is the same command on an emulator and on hardware. + +**The path.** + +1. [40 — Bare-Metal and Freestanding Targets](40-baremetal.md) — the target, + the tiers, what of `std` survives. +2. [41 — Reaching a Device](41-devices.md) — runners, named runners. +3. [08 — Testing](08-testing.md) — tests that run on the board. + +**Run.** `mcpp new blinky --template riscv-virt-rt`. + +**What surprises people.** The emulator and the physical board are one package +and one feature apart, not two packages. `mcpp run --features hardware` moves +the default runner; the command a developer types does not change. + +## 7. Compute on a GPU or an accelerator + +**The situation.** Part of the program is a kernel compiled by a vendor's +compiler and linked into an ordinary binary. + +**What mcpp contributes.** The device toolkit is declared by the rule package +and installed by the build; the accelerator is one axis stated once; and the +artifact records which devices it carries code for. + +**The path.** + +1. [42 — Heterogeneous Builds](42-heterogeneous-builds.md) — `accel`, the + island, the seam. +2. [30 — Build Programs](30-build-mcpp.md) — how a rule reaches the graph. +3. [06 — Features and Capabilities](06-features-and-capabilities.md) — the + feature that selects a lane. + +**Run.** [`examples/09-heterogeneous/boundary`](../examples/09-heterogeneous/boundary/) +first — it needs no device — then +[`…/cuda`](../examples/09-heterogeneous/cuda/). + +**What surprises people.** A build that names no accelerator downloads nothing. +Two gates must open before a byte of a multi-gigabyte toolkit is fetched: the +feature that selects the rule, and the `cfg(accelerator = …)` selector that says +this build actually compiles for the device. + +## 8. Graphics rendering + +**The situation.** Shaders compiled to SPIR-V, a pipeline, and pixels that have +to be right. + +**What mcpp contributes.** The shader compiler is a declared payload; compiled +shaders arrive as a **module** rather than as a generated header nobody named; +and a rendering result can be asserted without a GPU. + +**The path.** + +1. [42 — Heterogeneous Builds](42-heterogeneous-builds.md) — the shader lane. +2. [30 — Build Programs](30-build-mcpp.md) — the one line that asks for the + module surface. +3. [10 — Packaging an Application](10-pack-and-release.md) — shipping it. + +**Run.** [`examples/10-graphics/offscreen`](../examples/10-graphics/offscreen/) — +renders a triangle offscreen and compares the pixels against a software +rasteriser. + +**What surprises people.** A software device is not automatically a substitute +for hardware. A framework may reject one on its device *type* even when it +advertises every feature the framework requires, and that is the framework's +policy rather than a packaging defect. + +## 9. A build step the project needs + +**The situation.** Code generation, an embedded asset, a check, or a second +compiler — something the engine has no rule for. + +**What mcpp contributes.** The step becomes edges in the same graph as +everything else: ordered, fingerprinted, incremental, and reported by name when +it fails. It is not a pre-build script. + +**The path.** + +1. [30 — Build Programs](30-build-mcpp.md) — `mcpp::action`, the four roles. +2. [31 — Authoring a Rule Package](31-authoring-a-rule-package.md) — if other + projects should use the step too. +3. [23 — The Project Environment](23-the-project-environment.md) — declaring + the tools the step runs. + +**Run.** [`examples/08-build-rules`](../examples/08-build-rules/) for a rule +that checks and embeds; +[`12-a-new-device-language`](../examples/12-a-new-device-language/) for one that +teaches mcpp a language the engine has never heard of. + +**What surprises people.** The tool the step invokes is an **input** of the +step. Without that, editing the generator leaves every edge clean and the +artifact keeps the bytes the previous generator produced — a green build over a +stale result. + +## Current limitations + +- The scenarios here are the ones with a runnable project or a published + template behind them. A scenario mcpp serves but nothing in this repository + demonstrates is not listed, because a path with no project to run is a claim + rather than a scenario. +- Adopting mcpp inside an existing build system is not a scenario here. mcpp + builds a project it owns; interoperating with another build system's outputs + is not documented and is not covered by any example. diff --git a/docs/02-examples.md b/docs/03-examples.md similarity index 97% rename from docs/02-examples.md rename to docs/03-examples.md index 5206cd1a..cfab0de0 100644 --- a/docs/02-examples.md +++ b/docs/03-examples.md @@ -1,4 +1,4 @@ -# 02 — Examples +# 03 — Examples **Reader:** someone choosing where to start, or looking for a project shaped like theirs. @@ -8,7 +8,7 @@ order they build on each other. **Not here:** the content of any example — each has its own README, which explains only what it adds. Before: [01 — Getting Started](01-getting-started.md). -After: [03 — The mcpp.toml Manifest](03-mcpp-toml.md). +After: [04 — The mcpp.toml Manifest](04-mcpp-toml.md). The [`examples/`](../examples) directory is a curriculum. Each project is runnable on its own, and each one teaches **one thing no earlier example @@ -110,7 +110,7 @@ last one. A new example is warranted when a capability **changes the shape of a project** — the files it contains, the manifest it declares, or the commands its author types. A capability that is one line inside a project an example already contains belongs in that chapter as a code block; one reached only through a -command belongs in [08 — Commands by Scenario](08-commands-by-scenario.md). +command belongs in [09 — Commands by Scenario](09-commands-by-scenario.md). The README states what the example is the first to teach and the criterion by which it is judged to work. For contribution mechanics see diff --git a/docs/03-mcpp-toml.md b/docs/04-mcpp-toml.md similarity index 99% rename from docs/03-mcpp-toml.md rename to docs/04-mcpp-toml.md index bfd521ec..750a7627 100644 --- a/docs/03-mcpp-toml.md +++ b/docs/04-mcpp-toml.md @@ -1,4 +1,4 @@ -# 03 — The mcpp.toml Manifest +# 04 — The mcpp.toml Manifest **Reader:** an author writing or reading a manifest. @@ -6,8 +6,8 @@ field. **Not here:** four topics this file's tables belong to but this chapter does not -own — dependencies are [04](04-dependencies.md), features are -[05](05-features-and-capabilities.md), conditioning on a target is +own — dependencies are [05](05-dependencies.md), features are +[06](06-features-and-capabilities.md), conditioning on a target is [22](22-target-side.md), and the project's environment is [23](23-the-project-environment.md). Each is named where its table would be. @@ -87,7 +87,7 @@ Both spellings of the value are accepted: `standard = "c++26"` and `standard = 2 When a **dependency declares a level above the graph's**, mcpp says so before compiling rather than letting it fail somewhere inside that dependency's sources. See -[workspace §4.2](06-workspace.md). +[workspace §4.2](07-workspace.md). #### Dialect flags and the `import std` BMI @@ -231,7 +231,7 @@ required_features = ["gui"] # only built when feature `gui` is > per-test contract evaluation semantic (`-fcontract-evaluation-semantic=observe`) for a test whose > `main` exercises the violation, a feature macro the entry alone reads, or a local warning > suppression. If a flag must reach **shared** code, it does not belong here — split into a -> [workspace](06-workspace.md) member or use `[features]`, or for a whole-build mode use a +> [workspace](07-workspace.md) member or use `[features]`, or for a whole-build mode use a > `[profile.*]` (`mcpp test --profile ` builds the whole test image, code-under-test > included, under that profile). > @@ -242,7 +242,7 @@ required_features = ["gui"] # only built when feature `gui` is | You want | Use | |---|---| | Different macros/flags on a binary's **own entry** | per-target `defines` / `cxxflags` (above) | -| Two products that differ in code they **share** | split into [workspace](06-workspace.md) members, each with its own `[build]` flags over a shared `lib` | +| Two products that differ in code they **share** | split into [workspace](07-workspace.md) members, each with its own `[build]` flags over a shared `lib` | | To **select a variant** of a shared library (e.g. a backend) | `[features]` on that library (§2.8) — additive, reaches the library's own compile | | A **whole-build mode** (sanitizers, contract semantics, opt level) | `[profile.]` (§2.9) + `--profile`; also honored by `mcpp test --profile ` | @@ -646,7 +646,7 @@ path = "src/capi/lua.cppm" # Override the default lib-root location Default convention: `src/.cppm` (e.g. package name `mcpplibs.cmdline` → `src/cmdline.cppm`). ### 2.5 `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]` -Moved to [04 — Dependencies and Resolution](04-dependencies.md). +Moved to [05 — Dependencies and Resolution](05-dependencies.md). ### 2.7 `[toolchain]` — Toolchain Configuration @@ -811,7 +811,7 @@ ran and passed. `--message-format json` carries `"status":"not_run"` and a record (see [50 — Machine-Readable Output](50-machine-output.md)). ### 2.8 `[features]` — Features -Moved to [05 — Features and Capabilities](05-features-and-capabilities.md), +Moved to [06 — Features and Capabilities](06-features-and-capabilities.md), with `provides` / `requires` and `[feature-deps.]`. @@ -1193,7 +1193,7 @@ command: nothing tracks it, and editing the file produces `ninja: no work to do`. ### 2.16 `[hooks]` — Project Build Lifecycle Commands -Moved to [08 — Commands by Scenario](08-commands-by-scenario.md). +Moved to [09 — Commands by Scenario](09-commands-by-scenario.md). ## Appendix A. Schema Ownership Principle (admission criteria for new fields) diff --git a/docs/04-dependencies.md b/docs/05-dependencies.md similarity index 98% rename from docs/04-dependencies.md rename to docs/05-dependencies.md index 3d698e6e..8722e2ea 100644 --- a/docs/04-dependencies.md +++ b/docs/05-dependencies.md @@ -1,4 +1,4 @@ -# 04 — Dependencies and Resolution +# 05 — Dependencies and Resolution **Reader:** an author whose build now contains more than their own code. @@ -10,8 +10,8 @@ does a version constraint mean, and what happens when two of them disagree. restates — and how to publish one, which is [11 — Publishing a Library](11-publishing-a-library.md). -Before: [03 — The mcpp.toml Manifest](03-mcpp-toml.md) is where these tables -live among the others. After: [05 — Features and Capabilities](05-features-and-capabilities.md) +Before: [04 — The mcpp.toml Manifest](04-mcpp-toml.md) is where these tables +live among the others. After: [06 — Features and Capabilities](06-features-and-capabilities.md) is how a dependency becomes optional. ## `[dependencies]` — Runtime Dependencies diff --git a/docs/05-features-and-capabilities.md b/docs/06-features-and-capabilities.md similarity index 98% rename from docs/05-features-and-capabilities.md rename to docs/06-features-and-capabilities.md index 27f6092d..2a6d8319 100644 --- a/docs/05-features-and-capabilities.md +++ b/docs/06-features-and-capabilities.md @@ -1,4 +1,4 @@ -# 05 — Features and Capabilities +# 06 — Features and Capabilities **Reader:** an author with something optional — an extra source, an extra dependency, or a choice between backends. @@ -8,14 +8,14 @@ optional, and how does a consumer ask for it. **Not here:** which device backends a build targets, which looks like a feature and is not — that is [42 — Heterogeneous Builds](42-heterogeneous-builds.md). -Before: [04 — Dependencies and Resolution](04-dependencies.md). After: -[06 — Workspaces](06-workspace.md). +Before: [05 — Dependencies and Resolution](05-dependencies.md). After: +[07 — Workspaces](07-workspace.md). Features are how a package offers something optional: a compile macro, an extra source, an extra dependency, or a choice between backends. This chapter is the reference for declaring them and for consuming them. -Related documents: [03 — mcpp.toml](03-mcpp-toml.md) is the field reference for +Related documents: [04 — mcpp.toml](04-mcpp-toml.md) is the field reference for the rest of the manifest; [`examples/11-features`](../examples/11-features/) is a package that declares all three shapes and a test that uses a dev-dependency; [42 — Heterogeneous Builds](42-heterogeneous-builds.md) is the @@ -82,7 +82,7 @@ simd = { sources = ["src/simd/**"], flags = [ - The automatic `-DMCPP_FEATURE_` is still defined for every active feature, so `defines` are additive to it. - `flags` (mcpp 0.0.101+) is the same ordered array-of-inline-tables grammar as - `[build].flags` ([03 §2.3](03-mcpp-toml.md): `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ + `[build].flags` ([04 §2.3](04-mcpp-toml.md): `glob` required, plus `cflags`/`cxxflags`/`asmflags`/ `defines`; the `[[features..flags]]` array-of-tables spelling is accepted too, like `[[build.flags]]`). When the feature is active the entries are appended **after** the base `[build].flags`, features in name order, so a diff --git a/docs/06-workspace.md b/docs/07-workspace.md similarity index 99% rename from docs/06-workspace.md rename to docs/07-workspace.md index 3bd393bf..8de6cd5d 100644 --- a/docs/06-workspace.md +++ b/docs/07-workspace.md @@ -1,4 +1,4 @@ -# 06 — Workspaces +# 07 — Workspaces **Reader:** an author whose repository holds more than one package. @@ -7,8 +7,8 @@ and what does a member share with the others. **Not here:** publishing those packages, which is [11 — Publishing a Library](11-publishing-a-library.md). Before: -[05 — Features and Capabilities](05-features-and-capabilities.md). After: -[07 — Testing](07-testing.md). +[06 — Features and Capabilities](06-features-and-capabilities.md). After: +[08 — Testing](08-testing.md). A workspace organizes multiple related mcpp packages (libraries or applications) within a single repository. Member packages share a unified set of dependency versions and toolchain settings while each keeping its own `mcpp.toml` project file. diff --git a/docs/07-testing.md b/docs/08-testing.md similarity index 97% rename from docs/07-testing.md rename to docs/08-testing.md index 38e827e5..8cfff81a 100644 --- a/docs/07-testing.md +++ b/docs/08-testing.md @@ -1,4 +1,4 @@ -# 07 — Testing +# 08 — Testing **Reader:** anyone with code that has to keep working. @@ -10,9 +10,9 @@ considers a test, and how to test something that does not run on this machine. stream, which is [50 — Machine-Readable Output](50-machine-output.md). This chapter states which flag produces it and stops there. -Before: [04 — Dependencies and Resolution](04-dependencies.md) covers +Before: [05 — Dependencies and Resolution](05-dependencies.md) covers `[dev-dependencies]`, which is how a test reaches a package the artifact does -not. After: [08 — Commands by Scenario](08-commands-by-scenario.md) is the +not. After: [09 — Commands by Scenario](09-commands-by-scenario.md) is the lookup for everything else. ## What mcpp considers a test diff --git a/docs/08-commands-by-scenario.md b/docs/09-commands-by-scenario.md similarity index 99% rename from docs/08-commands-by-scenario.md rename to docs/09-commands-by-scenario.md index 36f46eb5..b0902dc7 100644 --- a/docs/08-commands-by-scenario.md +++ b/docs/09-commands-by-scenario.md @@ -1,4 +1,4 @@ -# 08 — Commands by Scenario +# 09 — Commands by Scenario **Reader:** someone who knows the nouns and wants the verb. @@ -7,7 +7,7 @@ reclaiming disk, explaining a resolution, validating a descriptor, diagnosing an environment. **Not here:** what each command means in depth. A scenario names the command and -links to the chapter that owns it. Before: [07 — Testing](07-testing.md). +links to the chapter that owns it. Before: [08 — Testing](08-testing.md). The command reference is `mcpp --help`, and each subcommand carries its own `--help`. This chapter answers a different question: which command applies to a diff --git a/docs/10-pack-and-release.md b/docs/10-pack-and-release.md index aa8e917c..8f3c2ae0 100644 --- a/docs/10-pack-and-release.md +++ b/docs/10-pack-and-release.md @@ -94,7 +94,7 @@ ELF in the bundle for anything under `$MCPP_HOME` and fails on a hit. machine: a graphics driver's user-space half is version-locked to the running kernel module, and for the proprietary stacks redistribution is not permitted. Declare those as run-phase capability requirements (§2.11 of -`docs/03-mcpp-toml.md`), and the mode table gains a column: +`docs/04-mcpp-toml.md`), and the mode table gains a column: | Mode | Program needing a host-provided capability | |---|---| @@ -334,7 +334,7 @@ force_bundle` still overrides this, as it does the ELF skip list. `vcruntime140.dll` and `msvcp140.dll` are **not** Windows' own: they belong to the MSVC toolset, exactly as `libstdc++.so` belongs to gcc. Whether they -travel is decided by `cxx_runtime` (see `docs/03-mcpp-toml.md`), not by this +travel is decided by `cxx_runtime` (see `docs/04-mcpp-toml.md`), not by this list — and `mcpp pack` refuses a combination that cannot deliver what the contract promised: diff --git a/docs/20-toolchains.md b/docs/20-toolchains.md index 3c6d778d..5181aeff 100644 --- a/docs/20-toolchains.md +++ b/docs/20-toolchains.md @@ -222,7 +222,7 @@ It uses the regular GCC module pipeline (`gcm.cache`, `import std` via libstdc++'s `bits/std.cc`). The target's default linkage is **static** — the produced `.exe` is fully self-contained (no `libstdc++-6.dll` to ship, runs directly under wine). To opt out, set it on the target section — -`linkage` is exact-triple only (§2.7 of [mcpp.toml](03-mcpp-toml.md)), and a +`linkage` is exact-triple only (§2.7 of [mcpp.toml](04-mcpp-toml.md)), and a `[build] linkage` key does not exist and is silently ignored: ```toml diff --git a/docs/22-target-side.md b/docs/22-target-side.md index f83a32db..5f9159be 100644 --- a/docs/22-target-side.md +++ b/docs/22-target-side.md @@ -352,7 +352,7 @@ The selector `` has three forms: |---|---|---| | **bare OS alias** | a single OS / family — the concise, common form | `[target.windows]`, `[target.unix]` | | **`cfg(...)` predicate** | a compound condition (arch / env / combinators) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see [03 §2.7.3](03-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | +| **exact triple** | one specific target (also carries `toolchain` / `linkage` / `sysroot` / `runner`; see [04 §2.7.3](04-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | A selector may carry platform-conditional **dependencies** and **build flags**: diff --git a/docs/23-the-project-environment.md b/docs/23-the-project-environment.md index 71020599..7e9128ab 100644 --- a/docs/23-the-project-environment.md +++ b/docs/23-the-project-environment.md @@ -119,7 +119,7 @@ Where a project and a dependency name **one package**, one version of it is installed: identity is `(namespace, name)` and the version is a constraint on it. The declaration nearer the artifact wins and the override is reported; a pin that fails a requirement the other side stated is refused naming both. -See *One package, one version* in [03 — mcpp.toml](03-mcpp-toml.md). +See *One package, one version* in [04 — mcpp.toml](04-mcpp-toml.md). ## 4. Reading an environment, never creating one @@ -184,7 +184,7 @@ downloaded: this chapter. Omitting the tier is the historical behaviour. **The runner.** A program under `[xlings.workspace]` is also where `[target.].runner` looks first for its first element, before `PATH` -([03 §2.7.3](03-mcpp-toml.md)). The two keys together provision a user-mode emulator on a +([04 §2.7.3](04-mcpp-toml.md)). The two keys together provision a user-mode emulator on a CI host and execute a cross-built artifact through it, without the manifest naming the payload's path. @@ -522,5 +522,5 @@ used it. including the `PATH` it runs with. - [91 - Toolchain Internals](91-toolchain-internals.md) — runtime selection, the `RuntimeBinding` snapshot, and the degradation rules. -- [03 - mcpp.toml](03-mcpp-toml.md) — the rest of the manifest. +- [04 - mcpp.toml](04-mcpp-toml.md) — the rest of the manifest. diff --git a/docs/24-openkal-cross.md b/docs/24-openkal-cross.md index 28da38c9..3085020d 100644 --- a/docs/24-openkal-cross.md +++ b/docs/24-openkal-cross.md @@ -216,7 +216,7 @@ runner = ["qemu-aarch64-static"] Without the key, `mcpp run` reports the kernel's refusal (`Exec format error`) and the key to write, and `mcpp test` reports every test as not run and exits 2. A host that executes the artifact natively passes `--no-runner`. The rules -are in [03 — mcpp.toml](03-mcpp-toml.md), §2.7.3. +are in [04 — mcpp.toml](04-mcpp-toml.md), §2.7.3. ### The Source Is The Same, The Program Is Not diff --git a/docs/30-build-mcpp.md b/docs/30-build-mcpp.md index e3da52c2..8e86a071 100644 --- a/docs/30-build-mcpp.md +++ b/docs/30-build-mcpp.md @@ -355,7 +355,7 @@ could not state a floor and every project repeated its rule's package list. version this build actually installed rather than the one the local manifest wrote. One package means one version: where a project and a rule both name it, the declaration nearer the artifact wins and both sides are told the same -answer. See *One package, one version* in [03 — mcpp.toml](03-mcpp-toml.md). +answer. See *One package, one version* in [04 — mcpp.toml](04-mcpp-toml.md). **`[feature-xlings.]` is answered too, while `` is active** *(2026.9.6.2+)*. That table has provisioned its packages since it existed -- @@ -534,7 +534,7 @@ link the command would never run and the build would say nothing. > should not be used for anything the build produces: ldflags is a flat string > in the link command, not a file in the graph, so nothing tracks it and editing > it reports `ninja: no work to do`. For Windows resources specifically, use -> [`[resources]`](03-mcpp-toml.md) — +> [`[resources]`](04-mcpp-toml.md) — > `object` is the escape hatch for everything else. **You must name the output files.** mcpp fixes the source set, the fingerprint @@ -722,7 +722,7 @@ and when to want one; `examples/07-project-subos/` is a working project. A rule — "run protoc over these `.proto` files", "run clang-tidy over these sources" — belongs in a package, not copy-pasted into every consumer's `build.mcpp`. The mechanism is -[`host-module = true`](03-mcpp-toml.md); this section is about the shape of +[`host-module = true`](04-mcpp-toml.md); this section is about the shape of what goes inside. The guidance below generalises from `mcpplibs.grpcgen`, the first such package, @@ -774,7 +774,7 @@ under the package name, so `export module mcpp.rules.spirv;` is what a consumer then imports. Official plugins live in one package, `mcpp:plugins` (repository `mcpp-community/mcpp-plugins`): rule packages are named `mcpp.rules.`, build-time utilities `mcpp.tools.`, and each member is selected by a feature -of that package (see [`host-module = true`](03-mcpp-toml.md)). `mcpp.build.*` +of that package (see [`host-module = true`](04-mcpp-toml.md)). `mcpp.build.*` is the engine's own module family and is not used for plugins. The engine cannot tell who is official, so it keys the check on the package *namespace* and warns when the two disagree — @@ -902,7 +902,7 @@ When nothing changed the output is `build.mcpp up to date (cached)`; otherwise `mcpp build --target ` the program is compiled with a host-resolved toolchain, runs on the host, and sees `MCPP_TARGET` = the cross triple. For purely declarative target gating, `[target.'cfg(...)']` tables remain - the first choice — see [03 - mcpp.toml Manifest Guide](03-mcpp-toml.md). + the first choice — see [04 - mcpp.toml Manifest Guide](04-mcpp-toml.md). - **CWD is the project root**, so relative paths (`src/generated.cpp`) land where expected. - A non-zero exit from `build.mcpp` aborts the build and prints its output. @@ -1089,7 +1089,7 @@ plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } ``` **`[build-dependencies]`, not `[dependencies]`** — a rule package is the case -[03 §2.6.1](03-mcpp-toml.md) describes exactly: its library must never reach the target while its +[04 §2.6.1](04-mcpp-toml.md) describes exactly: its library must never reach the target while its rule is still wanted. The two axes are separate, so `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" on the second axis, and the diff --git a/docs/31-authoring-a-rule-package.md b/docs/31-authoring-a-rule-package.md index 58d29c83..24c5a6b4 100644 --- a/docs/31-authoring-a-rule-package.md +++ b/docs/31-authoring-a-rule-package.md @@ -10,7 +10,7 @@ what does a consumer have to write to use it. **Not here:** adding a step to one project's own build, which is [30 — Build Programs](30-build-mcpp.md) and is the same primitives at a smaller scale; the feature keys themselves, which are -[05](05-features-and-capabilities.md); and the spellings of the shipped rules, +[06](06-features-and-capabilities.md); and the spellings of the shipped rules, which belong to `mcpp:plugins`. Examples: [`08-build-rules`](../examples/08-build-rules/) checks and embeds; [`12-a-new-device-language`](../examples/12-a-new-device-language/) adds a @@ -169,7 +169,7 @@ neither. A bare version is a **choice** a project may override; `>=` is a **requirement** a project may not go below. See *One package, one version* in -[03 — mcpp.toml](03-mcpp-toml.md). +[04 — mcpp.toml](04-mcpp-toml.md). ## Generating an island's boundary @@ -250,4 +250,4 @@ path. `mcpp xpkg parse` reports that key as unknown and exits 1. No descriptor in `mcpp-index` uses it (0 of 218); a package keeping its own `mcpp.toml` omits the `mcpp` field entirely. See *Current limitations* in - [08 — Commands by Scenario](08-commands-by-scenario.md). + [09 — Commands by Scenario](09-commands-by-scenario.md). diff --git a/docs/40-baremetal.md b/docs/40-baremetal.md index cbf90630..aa2c38c6 100644 --- a/docs/40-baremetal.md +++ b/docs/40-baremetal.md @@ -14,7 +14,7 @@ This document describes how mcpp builds, runs and tests software for targets with no operating system underneath, and how a board-support package supplies the parts of such a target that the engine deliberately does not know. -Related documents: [03 — mcpp.toml Manifest Guide](03-mcpp-toml.md) §2.7.2 is +Related documents: [04 — mcpp.toml Manifest Guide](04-mcpp-toml.md) §2.7.2 is the reference for the `[target.]` keys used here; [30 — build.mcpp Build Program](30-build-mcpp.md) is the reference for the directive protocol a board-support package speaks; [91 — Toolchain Internals](91-toolchain-internals.md) @@ -515,7 +515,7 @@ picolibc present: a cold build links, and `nm` finds one definition. ### Running tests on the target -Testing works here exactly as [07 — Testing](07-testing.md) describes it, and +Testing works here exactly as [08 — Testing](08-testing.md) describes it, and this section adds only what is specific to a board: each `tests/*.cpp` becomes its own image, and the runner the board-support package supplies executes it. @@ -645,7 +645,7 @@ The key is not specific to bare metal. A hosted cross target — an `qemu-aarch64-static` in place of the system emulator; on such a target an absent runner is not an error until the kernel refuses the artifact. The rules for hosted targets, the `--no-runner` escape and the not-run reporting of -`mcpp test` are in [03 — mcpp.toml](03-mcpp-toml.md), §2.7.3. +`mcpp test` are in [04 — mcpp.toml](04-mcpp-toml.md), §2.7.3. ## Writing a board-support package diff --git a/docs/41-devices.md b/docs/41-devices.md index 3cf7f3d6..a8a0d327 100644 --- a/docs/41-devices.md +++ b/docs/41-devices.md @@ -8,7 +8,7 @@ and what does a package supply so that one command reaches it. **Not here:** how the target itself is built, which is [40 — Bare-Metal](40-baremetal.md), and what mcpp considers a test, which is -[07 — Testing](07-testing.md). +[08 — Testing](08-testing.md). This document specifies how mcpp executes an artifact that runs somewhere other than the machine that built it, how a package supplies additional ways of diff --git a/docs/42-heterogeneous-builds.md b/docs/42-heterogeneous-builds.md index 258fca91..c0692e03 100644 --- a/docs/42-heterogeneous-builds.md +++ b/docs/42-heterogeneous-builds.md @@ -117,7 +117,7 @@ and less clearly. The table above is what mcpp knows **without being told**: the languages whose support shipped before a package could declare one. A rule package adds to it, through `[features]..device_extensions` (see -[03 — mcpp.toml](03-mcpp-toml.md) §2.8), and that is how a NEW device language +[04 — mcpp.toml](04-mcpp-toml.md) §2.8), and that is how a NEW device language arrives -- with no engine change and no engine release. Slang is the first: `.slang` is not in the list above, and `mcpp:plugins`' `rules-slang` declares it. @@ -303,7 +303,7 @@ the machines it will run on: The nearer declaration wins, one version is installed, and mcpp says which. A pin that does not satisfy the rule's floor is refused naming both sides rather than installed alongside it. See *One package, one version* in -[03 — mcpp.toml](03-mcpp-toml.md) for the full rule; +[04 — mcpp.toml](04-mcpp-toml.md) for the full rule; `examples/09-heterogeneous/multi-backend` is the one example in this repository that takes the override path, and every other one writes only the edge. diff --git a/docs/README.md b/docs/README.md index 4adbe028..119bc0b6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,13 +25,13 @@ anything a reader needs is written here or in a specification instead. | To | Read | Run | |---|---|---| -| write a program | [01](01-getting-started.md), [03](03-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | -| write a library others import | [11](11-publishing-a-library.md), [05](05-features-and-capabilities.md), [03](03-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | +| write a program | [01](01-getting-started.md), [04](04-mcpp-toml.md) §1 | [`01-hello`](../examples/01-hello/), [`02-with-deps`](../examples/02-with-deps/) | +| write a library others import | [11](11-publishing-a-library.md), [06](06-features-and-capabilities.md), [04](04-mcpp-toml.md) §2.4 | [`04-workspace`](../examples/04-workspace/), [`11-features`](../examples/11-features/) | | publish it | [10](10-pack-and-release.md), [11](11-publishing-a-library.md), [12](12-binary-distribution.md) | [`03-pack-static`](../examples/03-pack-static/), [`05-lib-distribution`](../examples/05-lib-distribution/) | | build for another machine | [21](21-the-target-triple.md), [24](24-openkal-cross.md), [40](40-baremetal.md) | [`06-openkal-cross`](../examples/06-openkal-cross/), `mcpp new … --template riscv-virt-rt` | | use a GPU or an accelerator | [42](42-heterogeneous-builds.md), [41](41-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | | add a rule, a language or a generator | [31](31-authoring-a-rule-package.md), [30](30-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`12-a-new-device-language`](../examples/12-a-new-device-language/) | -| add a package to the index | [11](11-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [08](08-commands-by-scenario.md) — the publishing scenarios | +| add a package to the index | [11](11-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [09](09-commands-by-scenario.md) — the publishing scenarios | | change mcpp itself | [90](90-build-from-source.md), [92](92-release.md), [51](51-supported-versions.md) | — | Lessons also arrive as project templates, which a package ships and `mcpp new @@ -51,13 +51,14 @@ order, not an alphabet. - [00 — How mcpp Works](00-how-mcpp-works.md) — the model every other chapter assumes - [01 — Getting Started](01-getting-started.md) — install, create, build, run -- [02 — Examples](02-examples.md) — which example teaches what -- [03 — The mcpp.toml Manifest](03-mcpp-toml.md) — what a manifest may say -- [04 — Dependencies and Resolution](04-dependencies.md) — where a dependency comes from, and which version wins -- [05 — Features and Capabilities](05-features-and-capabilities.md) — making part of a package optional -- [06 — Workspaces](06-workspace.md) — several packages, one build -- [07 — Testing](07-testing.md) — including what does not run on this machine -- [08 — Commands by Scenario](08-commands-by-scenario.md) — the lookup, once the nouns are known +- [02 — Scenarios](02-scenarios.md) — what mcpp is used for, and which features each kind of work uses +- [03 — Examples](03-examples.md) — which example teaches what +- [04 — The mcpp.toml Manifest](04-mcpp-toml.md) — what a manifest may say +- [05 — Dependencies and Resolution](05-dependencies.md) — where a dependency comes from, and which version wins +- [06 — Features and Capabilities](06-features-and-capabilities.md) — making part of a package optional +- [07 — Workspaces](07-workspace.md) — several packages, one build +- [08 — Testing](08-testing.md) — including what does not run on this machine +- [09 — Commands by Scenario](09-commands-by-scenario.md) — the lookup, once the nouns are known ### 1x — Publishing @@ -95,6 +96,42 @@ order, not an alphabet. - [91 — Toolchain Internals](91-toolchain-internals.md) - [92 — Releasing mcpp](92-release.md) +## Look it up + +The chapter list above is a **reading order**. This is the other index: from a +token in front of a reader to the chapter that owns it. + +**Manifest tables and keys** + +| | chapter | | chapter | +|---|---|---|---| +| `[package]`, `[targets.]`, `[build]`, `[lib]` | [04](04-mcpp-toml.md) | `[profile.]`, `[resources]`, `[runtime]` | [04](04-mcpp-toml.md) | +| `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]` | [05](05-dependencies.md) | `scan_overrides`, `module_extensions` | [04](04-mcpp-toml.md) | +| `[features]`, `[feature-deps.]`, `provides` / `requires` | [06](06-features-and-capabilities.md) | `[workspace]` | [07](07-workspace.md) | +| `[toolchain]`, `cxx_runtime` | [20](20-toolchains.md) | `[target.]`, `cfg(…)` | [22](22-target-side.md) | +| `[xlings]`, `[xlings.workspace]`, `[feature-xlings.]` | [23](23-the-project-environment.md) | `[pack]` | [10](10-pack-and-release.md) | +| `[build] accel`, `[package] accelerators`, `device_extensions` | [42](42-heterogeneous-builds.md) | `[hooks]` | [09](09-commands-by-scenario.md) | +| `runner`, `[target..runners]` | [41](41-devices.md) | `rule_module` | [31](31-authoring-a-rule-package.md) | + +**Commands** + +| | chapter | | chapter | +|---|---|---|---| +| `build`, `run` | [01](01-getting-started.md) | `test` | [08](08-testing.md) | +| `new`, `new --template` | [01](01-getting-started.md) | `add`, `update`, `why` | [05](05-dependencies.md) | +| `pack` | [10](10-pack-and-release.md) | `publish`, `emit xpkg`, `xpkg parse` | [11](11-publishing-a-library.md) | +| `toolchain` | [20](20-toolchains.md) | `clean`, `cache`, `index`, `self …` | [09](09-commands-by-scenario.md) | + +**Concepts** + +| | chapter | | chapter | +|---|---|---|---| +| the five nouns; what a build does | [00](00-how-mcpp-works.md) | `import std`, module interfaces, BMIs | [00](00-how-mcpp-works.md), [20](20-toolchains.md) | +| `mcpp::action`, a build program | [30](30-build-mcpp.md) | a rule package, `MCPP_EXPORT_C` | [31](31-authoring-a-rule-package.md) | +| a target triple, the support matrix | [21](21-the-target-triple.md) | a runner, a named runner | [41](41-devices.md) | +| an island, a seam, `accel` | [42](42-heterogeneous-builds.md) | a descriptor, an index | [11](11-publishing-a-library.md) | +| an ABI tag, a prebuilt artifact | [12](12-binary-distribution.md) | exit codes, JSON output | [50](50-machine-output.md) | + ## Specifications Normative documents — semantics, constraints and matching rules, each rule diff --git a/docs/specs/manifest-semantics.md b/docs/specs/manifest-semantics.md index aa21861f..2854fe0d 100644 --- a/docs/specs/manifest-semantics.md +++ b/docs/specs/manifest-semantics.md @@ -10,7 +10,7 @@ | **最低实现版本** | 条件化形状:mcpp **2026.8.29.1**(`[target..build-dependencies]` 起齐备);目标轴:mcpp **2026.9.6.4** | | **作者/维护** | mcpp-community | | **相关设计文档** | `.agents/docs/2026-09-07-mcpp-toml-unified-semantics-design.md`
`.agents/docs/2026-06-04-manifest-schema-ownership.md`
`.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md` | -| **相关使用文档** | [docs/05 —— mcpp.toml 字段参考](../03-mcpp-toml.md) | +| **相关使用文档** | [docs/05 —— mcpp.toml 字段参考](../04-mcpp-toml.md) | ## 规范用语 diff --git a/docs/zh/00-how-mcpp-works.md b/docs/zh/00-how-mcpp-works.md index 90a235c2..24040965 100644 --- a/docs/zh/00-how-mcpp-works.md +++ b/docs/zh/00-how-mcpp-works.md @@ -26,7 +26,7 @@ namespace 的两个 `cmdline` 可以共存。 包同时是其余一切的单位:被发布的单位、被缓存的单位、声明 feature 的单位,以及 构建程序所归属的单位。 -参考:[03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md)。 +参考:[04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md)。 ### 图(graph) @@ -107,7 +107,7 @@ mcpp 写的东西没有一样是隐藏的,而每个存储回答的是不同的 | 消息里出现 | 名词 | 去哪里看 | |---|---|---| -| 包名、版本,或「没有候选」 | 索引 | [11](11-publishing-a-library.md)、[04](04-dependencies.md) | +| 包名、版本,或「没有候选」 | 索引 | [11](11-publishing-a-library.md)、[05](05-dependencies.md) | | 下载、载荷,或版本下界 | 工具链 | [20](20-toolchains.md)、[23](23-the-project-environment.md) | | 三元组,或「不支持的目标」 | 目标 | [21](21-the-target-triple.md) | | 某个模块读不到或没有人提供它 | 图 | [30](30-build-mcpp.md) | diff --git a/docs/zh/01-getting-started.md b/docs/zh/01-getting-started.md index f84149eb..da6234c4 100644 --- a/docs/zh/01-getting-started.md +++ b/docs/zh/01-getting-started.md @@ -6,7 +6,7 @@ **不在这里:**mcpp 有哪些部件 —— 那是 [00 —— mcpp 的运转方式](00-how-mcpp-works.md), 本章假定它而不重复它;以及 manifest 可以写的每一个字段,那是 -[03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md)。下一章:[02 —— 示例项目](02-examples.md)。 +[04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md)。下一章:[03 —— 示例项目](03-examples.md)。 > 5 分钟完成 install → new → build → run → pack 全流程。 @@ -127,7 +127,7 @@ workspace 中运行。插件稳定依赖进程退出码和生成的 `compile_com ## 增量编译与测试 -[07 —— 测试](07-testing.md) 是拥有这个主题的章节;下面只是本教程需要的那一步。 +[08 —— 测试](08-testing.md) 是拥有这个主题的章节;下面只是本教程需要的那一步。 ```bash mcpp build # 增量构建 @@ -164,7 +164,7 @@ linux 1019s、windows 1289s)。给它一个默认上限会把「慢但正确」 `mcpp build` 将自动从 [mcpp-index](https://github.com/mcpplibs/mcpp-index) 解析 SemVer 约束、拉取源码并加入编译图。完整示例参见 -[02 — 示例项目](02-examples.md) 中的 `02-with-deps`。 +[03 — 示例项目](03-examples.md) 中的 `02-with-deps`。 ## 生成发布包 @@ -181,7 +181,7 @@ mcpp pack --mode self-contained # 打包 loader、libc 与依赖 ## 后续阅读 -- [02 — 示例项目](02-examples.md) — 可直接运行的最小工程集合 +- [03 — 示例项目](03-examples.md) — 可直接运行的最小工程集合 - [10 — 发布打包](10-pack-and-release.md) — 构建可分发产物 - [20 — 工具链管理](20-toolchains.md) — 切换编译器与多版本管理 - 任意命令的完整选项可通过 `mcpp --help` 查阅 diff --git a/docs/zh/02-scenarios.md b/docs/zh/02-scenarios.md new file mode 100644 index 00000000..9b858cef --- /dev/null +++ b/docs/zh/02-scenarios.md @@ -0,0 +1,221 @@ +# 02 —— 场景 + +**读者:**正在判断 mcpp 适不适合手头这份工作、以及这份工作会用到它哪些功能的 +开发者。 + +**本章回答的那一个问题:**mcpp 服务哪几类工程,而对其中一类,要用到哪些功能、 +按什么顺序。 + +**不在这里:**字段参考,那是 [04](04-mcpp-toml.md);示例目录,那是 +[03](03-examples.md) —— 它按**示例**索引而不是按场景;以及命令查阅,那是 +[09](09-commands-by-scenario.md)。本章按**工作本身**索引。 + +每个场景陈述:处境、mcpp 对它贡献了什么、穿过章节的路径、可以跑的一个工程,以及 +**最常让人意外的那一件事**。只读与手头工作相符的那个场景即可;它们之间不相互叠加。 + +## 场景一览 + +| | 场景 | 跑什么 | +|---|---|---| +| [1](#1-命令行工具或服务) | 命令行工具或服务 | `examples/01-hello` → `03-pack-static` | +| [2](#2-供他人-import-的库) | 供他人 import 的库 | `examples/11-features`、`05-lib-distribution` | +| [3](#3-一个仓库里的多个包) | 一个仓库里的多个包 | `examples/04-workspace` | +| [4](#4-图形应用) | 图形应用 | `mcpp new … --template ocornut.imgui` | +| [5](#5-为另一个操作系统构建) | 为另一个操作系统构建 | `examples/06-openkal-cross` | +| [6](#6-没有操作系统的目标) | 没有操作系统的目标 | `mcpp new … --template riscv-virt-rt` | +| [7](#7-gpu-或加速器上的计算) | GPU 或加速器上的计算 | `examples/09-heterogeneous` | +| [8](#8-图形渲染) | 图形渲染 | `examples/10-graphics/offscreen` | +| [9](#9-工程需要的一步构建工作) | 工程需要的一步构建工作,以及把它共享出去 | `examples/08-build-rules`、`12-a-new-device-language` | + +## 1. 命令行工具或服务 + +**处境。** 一个由 C++23 模块构建的程序,带几个依赖,而且要跑在一台没有 mcpp 的 +机器上。 + +**mcpp 贡献了什么。** `import std` 零配置可用;编译器是被钉住的载荷,而不是机器 +上恰好有的那个;并且一条命令产出一个自带所需之物的二进制。 + +**路径。** + +1. [00 —— mcpp 的运转方式](00-how-mcpp-works.md) —— 五个名词。 +2. [01 —— 快速开始](01-getting-started.md) —— 把程序跑起来。 +3. [05 —— 依赖与解析](05-dependencies.md) —— `[dependencies]`、锁文件。 +4. [08 —— 测试](08-testing.md) —— `tests/**/*.cpp`。 +5. [10 —— 发布打包](10-pack-and-release.md) —— `mcpp pack`。 + +**跑。** [`examples/01-hello`](../../examples/01-hello/),然后 +[`02-with-deps`](../../examples/02-with-deps/),再 +[`03-pack-static`](../../examples/03-pack-static/)。 + +**最让人意外的一件事。** debug 构建与 release 构建互不失效。每种配置在 `target/` +下有自己的指纹目录,所以在两者之间来回切换不是一次重建。 + +## 2. 供他人 import 的库 + +**处境。** 别的包会把它写进 `[dependencies]` 的代码,其中可能有可选的部分。 + +**mcpp 贡献了什么。** 模块接口就是被发布的表面;feature 让其中一部分成为可选而 +不必拆出第二个包;预建二进制可以声明它与哪些工具链兼容。 + +**路径。** + +1. [04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md) —— `[lib]`、库根。 +2. [06 —— Feature 与能力](06-features-and-capabilities.md) —— 可选部分,以及它们 + 拉取的依赖。 +3. [08 —— 测试](08-testing.md) —— `[dev-dependencies]`。 +4. [11 —— 发布一个库](11-publishing-a-library.md) —— 描述符。 +5. [12 —— 分发预编译库](12-binary-distribution.md) —— 如果同时交付二进制。 + +**跑。** [`examples/11-features`](../../examples/11-features/) 把三种 feature 形态 +都声明了一遍;[`05-lib-distribution`](../../examples/05-lib-distribution/) 是 +生产方与消费方成对的那个。 + +**最让人意外的一件事。** 判断一个可选后端是否真的可选,判据**不是**「默认构建仍然 +能过」,而是「默认构建的**解析里不出现**那个包」。一个被解析出来却没被用到的依赖, +仍然要付下载的代价。 + +## 3. 一个仓库里的多个包 + +**处境。** 多个包一起开发,彼此按路径依赖。 + +**mcpp 贡献了什么。** 一条命令构建或测试整组;成员之间不经注册表就能相互解析; +每个成员保留自己的 manifest 与身份。 + +**路径。** + +1. [07 —— 工作空间](07-workspace.md) —— `[workspace]`、path 依赖。 +2. [05 —— 依赖与解析](05-dependencies.md) —— path 依赖做什么、不做什么。 +3. [08 —— 测试](08-testing.md) —— 扇出。 + +**跑。** [`examples/04-workspace`](../../examples/04-workspace/)。 + +**最让人意外的一件事。** 工作空间的成员**不是**一个根。任何枚举「每一个包」的工具 +都必须说清它指的是两者中的哪一个,而答案会改变构建什么。 + +## 4. 图形应用 + +**处境。** 一个带窗口、渲染器与字体的桌面程序。 + +**mcpp 贡献了什么。** 窗口与渲染栈就是普通依赖;模板脚手架出一个能跑的工程;产物 +在运行期需要什么是**被声明的**,不是被发现的。 + +**路径。** + +1. [01 —— 快速开始](01-getting-started.md) —— `mcpp new --template`。 +2. [05 —— 依赖与解析](05-dependencies.md) —— 那套栈。 +3. [10 —— 发布打包](10-pack-and-release.md) —— 可执行文件旁边还要带什么。 + +**跑。** `mcpp new myapp --template ocornut.imgui`。 + +**最让人意外的一件事。** 在 Linux 上,mcpp 的产物跑在一个**不查 `/usr/lib`** 的 +私有加载器后面。任何必须由宿主供给的东西 —— 图形驱动、Vulkan ICD —— 都经由工程 +声明的适配包到达,而不是靠它装在机器上。 + +## 5. 为另一个操作系统构建 + +**处境。** 一份源码树要产出 Linux、Windows 与 macOS 的二进制。 + +**mcpp 贡献了什么。** 目标是一个参数,不是第二份检出;交叉工具链是载荷;而这台 +宿主服务不了的目标会被**拒绝**,不会被悄悄按宿主构建。 + +**路径。** + +1. [21 —— 目标三元组](21-the-target-triple.md) —— 目标怎么命名,哪台宿主服务哪个。 +2. [24 —— 基于 openkal 的交叉构建](24-openkal-cross.md) —— 机制。 +3. [22 —— 目标侧](22-target-side.md) —— manifest 必须按目标不同时。 + +**跑。** [`examples/06-openkal-cross`](../../examples/06-openkal-cross/) —— 一个 +程序在任意宿主上为四个目标构建。 + +**最让人意外的一件事。** `--target` 不要求机器上**已经有**那条工具链。它要求的是 +这个目标能被这台宿主**服务**;[21](21-the-target-triple.md) 里的支持矩阵说明哪些 +可以。 + +## 6. 没有操作系统的目标 + +**处境。** 板子或单片机上的固件:没有 OS、默认没有 libc、有链接脚本与向量表。 + +**mcpp 贡献了什么。** 板级支持包供给整个目标世界 —— 链接脚本、启动代码,以及 +**runner** —— 于是 `mcpp run` 在模拟器上与在真实硬件上是同一条命令。 + +**路径。** + +1. [40 —— 裸机与 freestanding 目标](40-baremetal.md) —— 目标、档位、`std` 还剩什么。 +2. [41 —— 抵达一台设备](41-devices.md) —— runner、具名 runner。 +3. [08 —— 测试](08-testing.md) —— 跑在板子上的测试。 + +**跑。** `mcpp new blinky --template riscv-virt-rt`。 + +**最让人意外的一件事。** 模拟器与真实板子相差的是**一个 feature**,不是两个包。 +`mcpp run --features hardware` 移动的是默认 runner;开发者敲的那条命令不变。 + +## 7. GPU 或加速器上的计算 + +**处境。** 程序的一部分是由厂商编译器编译、再链接进普通二进制的 kernel。 + +**mcpp 贡献了什么。** 设备工具包由规则包声明、由构建安装;加速器是**一条只写一次 +的轴**;产物记录下它携带了哪些设备的代码。 + +**路径。** + +1. [42 —— 异构硬件构建](42-heterogeneous-builds.md) —— `accel`、岛、接缝。 +2. [30 —— 构建程序](30-build-mcpp.md) —— 规则怎样到达图。 +3. [06 —— Feature 与能力](06-features-and-capabilities.md) —— 选中一条 lane 的 + 那个 feature。 + +**跑。** 先 +[`examples/09-heterogeneous/boundary`](../../examples/09-heterogeneous/boundary/) +—— 它不需要设备 —— 再 +[`…/cuda`](../../examples/09-heterogeneous/cuda/)。 + +**最让人意外的一件事。** 不点名加速器的构建**一个字节都不下载**。多 GB 的工具包 +被取之前要开两道闸:选中规则的那个 feature,以及说明「这次构建确实为设备编译」的 +`cfg(accelerator = …)` 选择器。 + +## 8. 图形渲染 + +**处境。** 编译到 SPIR-V 的着色器、一条管线,以及必须正确的像素。 + +**mcpp 贡献了什么。** 着色器编译器是被声明的载荷;编译好的着色器以**模块**到达, +而不是一个没人写过其名字的生成头;并且渲染结果可以在没有 GPU 的情况下被断言。 + +**路径。** + +1. [42 —— 异构硬件构建](42-heterogeneous-builds.md) —— 着色器那条 lane。 +2. [30 —— 构建程序](30-build-mcpp.md) —— 要模块表面的那一行。 +3. [10 —— 发布打包](10-pack-and-release.md) —— 交付它。 + +**跑。** [`examples/10-graphics/offscreen`](../../examples/10-graphics/offscreen/) +—— 离屏渲染一个三角形,并与软件光栅器逐像素比对。 + +**最让人意外的一件事。** 软件设备不自动等价于硬件的替身。框架可能因为设备的**类型** +就拒绝它,哪怕它满足框架要求的每一项;那是框架自己的策略,不是打包缺陷。 + +## 9. 工程需要的一步构建工作 + +**处境。** 代码生成、嵌入资源、一项检查,或者第二个编译器 —— 引擎没有规则的东西。 + +**mcpp 贡献了什么。** 这一步变成与其余一切同一张图上的边:被定序、进指纹、可增量, +失败时按自己的名字被报出。它不是一个构建前脚本。 + +**路径。** + +1. [30 —— 构建程序](30-build-mcpp.md) —— `mcpp::action`、四种 role。 +2. [31 —— 编写规则包](31-authoring-a-rule-package.md) —— 如果别的工程也该用上它。 +3. [23 —— 项目环境](23-the-project-environment.md) —— 声明这一步要跑的工具。 + +**跑。** [`examples/08-build-rules`](../../examples/08-build-rules/) 是做检查与 +嵌入的规则;[`12-a-new-device-language`](../../examples/12-a-new-device-language/) +教会 mcpp 一门引擎从未听说过的语言。 + +**最让人意外的一件事。** 这一步调用的**那个工具本身是它的输入**。少了这一条,改动 +生成器会让每条边都是干净的,产物保留上一个生成器产生的字节 —— 一次覆盖在陈旧结果 +之上的绿色构建。 + +## 当前边界 + +- 这里列出的场景,都是背后有**可运行的工程或已发布模板**的那些。mcpp 服务得了、 + 但本仓库没有任何东西演示的场景不在此列 —— 一条没有工程可跑的路径是一个主张, + 不是一个场景。 +- 在既有构建系统内部采用 mcpp 不是这里的场景。mcpp 构建它自己拥有的工程;与另一个 + 构建系统的产物互操作既无文档也无示例覆盖。 diff --git a/docs/zh/02-examples.md b/docs/zh/03-examples.md similarity index 97% rename from docs/zh/02-examples.md rename to docs/zh/03-examples.md index f62c06b0..cf13d1b6 100644 --- a/docs/zh/02-examples.md +++ b/docs/zh/03-examples.md @@ -1,4 +1,4 @@ -# 02 —— 示例项目 +# 03 —— 示例项目 **读者:**在挑一个起点,或者在找一个与自己形状相近的工程的人。 @@ -6,7 +6,7 @@ **不在这里:**任何一个示例的内容 —— 每个示例自带 README,只解释它新增的部分。 在此之前:[01 —— 快速开始](01-getting-started.md)。在此之后: -[03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md)。 +[04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md)。 [`examples/`](../../examples) 目录是一套课程。每个工程都可以单独跑起来,而且每个 工程都教**一件更早的示例没有教过的事**。本章说明那件事是什么,于是你可以按自己 @@ -101,7 +101,7 @@ mcpp new blinky --template riscv-virt-rt 值得新增一个示例:当一个能力**改变工程的形状** —— 它包含的文件、它声明的 manifest、 或者作者敲的命令。如果一个能力只是既有示例工程里的一行,它属于对应章节里的一个 代码块;如果它只经由命令到达,它属于 -[08 —— 按场景选命令](08-commands-by-scenario.md)。 +[09 —— 按场景选命令](09-commands-by-scenario.md)。 README 要写明这个示例第一个教什么,以及判断它是否成立的判据。贡献流程见 [90 —— 从源码构建 & 参与贡献](90-build-from-source.md)。 diff --git a/docs/zh/03-mcpp-toml.md b/docs/zh/04-mcpp-toml.md similarity index 99% rename from docs/zh/03-mcpp-toml.md rename to docs/zh/04-mcpp-toml.md index abf1538d..225571cb 100644 --- a/docs/zh/03-mcpp-toml.md +++ b/docs/zh/04-mcpp-toml.md @@ -1,11 +1,11 @@ -# 03 —— mcpp.toml 工程文件指南 +# 04 —— mcpp.toml 工程文件指南 **读者:**正在写或正在读一份 manifest 的作者。 **本章回答的那一个问题:**一份 `mcpp.toml` 可以说什么,逐字段地。 **不在这里:**四个主题的表虽然写在这个文件里,但本章不拥有它们 —— 依赖是 -[04](04-dependencies.md),feature 是 [05](05-features-and-capabilities.md), +[05](05-dependencies.md),feature 是 [06](06-features-and-capabilities.md), 以目标为条件是 [22](22-target-side.md),工程的环境是 [23](23-the-project-environment.md)。每一处都在它的表本该出现的位置点名。 @@ -82,7 +82,7 @@ mcpp 会在编译前失败,并同时报出工具链与工程档位。 值的两种拼法都接受:`standard = "c++26"` 与 `standard = 26`。 当**依赖声明的档位高于当前图**时,mcpp 会在编译前说出来,而不是让它在那个依赖的源码里 -某处失败。见 [workspace §4.2](06-workspace.md)。 +某处失败。见 [workspace §4.2](07-workspace.md)。 #### 方言标志与 `import std` BMI @@ -213,7 +213,7 @@ required_features = ["gui"] # 仅当 feature `gui` 激活时 > compile-once 模型)。当标志只需影响某个二进制(或测试)**自己的入口**时,这正是合适的工具 —— > 例如某个测试的 `main` 里触发契约违规、需要按测试设置契约求值语义 > (`-fcontract-evaluation-semantic=observe`),或入口独享的 feature 宏、局部告警抑制。 -> 若标志必须穿透**共享**代码,就不该放在这里 —— 改用 [workspace](06-workspace.md) member 或 +> 若标志必须穿透**共享**代码,就不该放在这里 —— 改用 [workspace](07-workspace.md) member 或 > `[features]`;若是整次构建的模式,用 `[profile.*]`(`mcpp test --profile ` 会让包括被测 > 代码在内的整个测试镜像都在该 profile 下编译)。 > @@ -224,7 +224,7 @@ required_features = ["gui"] # 仅当 feature `gui` 激活时 | 目标 | 使用 | |---|---| | 某二进制**自己入口**上的不同宏/标志 | per-target `defines` / `cxxflags`(见上) | -| 两个产品差异在它们**共享**的代码里 | 拆成 [workspace](06-workspace.md) member,各自 `[build]` 标志,共享一个 `lib` | +| 两个产品差异在它们**共享**的代码里 | 拆成 [workspace](07-workspace.md) member,各自 `[build]` 标志,共享一个 `lib` | | **选择**某共享库的变体(如某后端) | 在该库上用 `[features]`(§2.8)——additive,作用到库自己的编译 | | **整次构建的模式**(sanitizer、契约语义、优化档) | `[profile.]`(§2.9)+ `--profile`;`mcpp test --profile ` 同样支持 | @@ -558,7 +558,7 @@ path = "src/capi/lua.cppm" # 覆盖默认的 lib-root 位置 默认约定:`src/<包名最后一段>.cppm`(如包名 `mcpplibs.cmdline` → `src/cmdline.cppm`)。 ### 2.5 `[dependencies]`、`[dev-dependencies]`、`[build-dependencies]` -已移入 [04 —— 依赖与解析](04-dependencies.md)。 +已移入 [05 —— 依赖与解析](05-dependencies.md)。 ### 2.7 `[toolchain]` —— 工具链配置 @@ -704,7 +704,7 @@ error: test result: NOT RUN. 0 passed; 0 failed; 1 not run (this host cannot exe 带 `not_run` / `not_run_reason`(见 [50 —— 机器可读输出](50-machine-output.md))。 ### 2.8 `[features]` —— Feature -已移入 [05 —— Feature 与能力](05-features-and-capabilities.md), +已移入 [06 —— Feature 与能力](06-features-and-capabilities.md), 连同 `provides` / `requires` 与 `[feature-deps.]`。 @@ -1034,7 +1034,7 @@ o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o") `ninja: no work to do`。 ### 2.16 `[hooks]` —— 项目构建生命周期命令 -已移入 [08 —— 按场景选命令](08-commands-by-scenario.md)。 +已移入 [09 —— 按场景选命令](09-commands-by-scenario.md)。 ## 附录 A. Schema 所有权原则(新字段准入标准) diff --git a/docs/zh/04-dependencies.md b/docs/zh/05-dependencies.md similarity index 98% rename from docs/zh/04-dependencies.md rename to docs/zh/05-dependencies.md index 52b44022..f8bd01f7 100644 --- a/docs/zh/04-dependencies.md +++ b/docs/zh/05-dependencies.md @@ -1,4 +1,4 @@ -# 04 —— 依赖与解析 +# 05 —— 依赖与解析 **读者:**构建里已经不只有自己代码的作者。 @@ -9,8 +9,8 @@ [SPEC-001](../specs/package-identity.md),本章施用它而不复述它;以及怎么发布一个包, 那是 [11 —— 发布一个库](11-publishing-a-library.md)。 -在此之前:[03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md) 是这些表与其余表同处的 -地方。在此之后:[05 —— Feature 与能力](05-features-and-capabilities.md) 讲一个依赖 +在此之前:[04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md) 是这些表与其余表同处的 +地方。在此之后:[06 —— Feature 与能力](06-features-and-capabilities.md) 讲一个依赖 怎么变成可选的。 ## `[dependencies]` — 运行时依赖 diff --git a/docs/zh/05-features-and-capabilities.md b/docs/zh/06-features-and-capabilities.md similarity index 98% rename from docs/zh/05-features-and-capabilities.md rename to docs/zh/06-features-and-capabilities.md index 4dbadca2..bdbba717 100644 --- a/docs/zh/05-features-and-capabilities.md +++ b/docs/zh/06-features-and-capabilities.md @@ -1,4 +1,4 @@ -# 05 —— Feature 与能力 +# 06 —— Feature 与能力 **读者:**手上有可选内容的作者 —— 一份额外的源、一个额外的依赖,或者在多个后端 之间做选择。 @@ -7,12 +7,12 @@ **不在这里:**一次构建面向哪些设备后端 —— 那看起来像 feature 而不是 feature, 它是 [42 —— 异构硬件构建](42-heterogeneous-builds.md)。在此之前: -[04 —— 依赖与解析](04-dependencies.md)。在此之后:[06 —— 工作空间](06-workspace.md)。 +[05 —— 依赖与解析](05-dependencies.md)。在此之后:[07 —— 工作空间](07-workspace.md)。 Feature 是一个包提供可选内容的方式:一个编译宏、一份额外的源文件、一个额外的 依赖,或者在多个后端之间做选择。本章是声明与消费 feature 的参考。 -相关文档:[03 —— mcpp.toml](03-mcpp-toml.md) 是 manifest 其余部分的字段参考; +相关文档:[04 —— mcpp.toml](04-mcpp-toml.md) 是 manifest 其余部分的字段参考; [`examples/11-features`](../../examples/11-features/) 是一个把三种形态都声明了 一遍、并且用 dev-dependency 写测试的包;[42 —— 异构硬件构建](42-heterogeneous-builds.md) 是这套机制最大的消费者,因为每条加速器 lane 都是一个 feature。 @@ -69,7 +69,7 @@ simd = { sources = ["src/simd/**"], flags = [ 空间宏:feature **不**注入自由的包级 `cflags`/`ldflags`,否则会破坏加性的 feature 并集模型。链接旗标来自 provider 依赖(见下文*`provides` / `requires`*),而非 feature。 - 每个激活的 feature 仍会得到自动的 `-DMCPP_FEATURE_`,`defines` 与之叠加。 -- `flags`(mcpp 0.0.101+)与 `[build].flags`([03 §2.3](03-mcpp-toml.md))共用同一有序 inline-table 数组 +- `flags`(mcpp 0.0.101+)与 `[build].flags`([04 §2.3](04-mcpp-toml.md))共用同一有序 inline-table 数组 文法(`glob` 必填,加 `cflags`/`cxxflags`/`asmflags`/`defines`;与 `[[build.flags]]` 一样也接受 `[[features..flags]]` 拼写)。feature 激活时 条目追加在 base `[build].flags` **之后**(feature 按名 diff --git a/docs/zh/06-workspace.md b/docs/zh/07-workspace.md similarity index 99% rename from docs/zh/06-workspace.md rename to docs/zh/07-workspace.md index 72eef9c7..24be3fa9 100644 --- a/docs/zh/06-workspace.md +++ b/docs/zh/07-workspace.md @@ -1,12 +1,12 @@ -# 06 —— 工作空间 +# 07 —— 工作空间 **读者:**仓库里不止一个包的作者。 **本章回答的那一个问题:**多个包怎样成为一次构建,以及一个成员与其余成员共享什么。 **不在这里:**把这些包发布出去,那是 [11 —— 发布一个库](11-publishing-a-library.md)。 -在此之前:[05 —— Feature 与能力](05-features-and-capabilities.md)。在此之后: -[07 —— 测试](07-testing.md)。 +在此之前:[06 —— Feature 与能力](06-features-and-capabilities.md)。在此之后: +[08 —— 测试](08-testing.md)。 工作空间允许在同一个仓库中组织和管理多个相关的 mcpp 包(库或应用程序)。各成员包共享统一的依赖版本配置和工具链设置,同时保持独立的 `mcpp.toml` 工程文件。 diff --git a/docs/zh/07-testing.md b/docs/zh/08-testing.md similarity index 96% rename from docs/zh/07-testing.md rename to docs/zh/08-testing.md index 522a90a5..53b48770 100644 --- a/docs/zh/07-testing.md +++ b/docs/zh/08-testing.md @@ -1,4 +1,4 @@ -# 07 —— 测试 +# 08 —— 测试 **读者:**任何有代码需要持续可用的人。 @@ -9,9 +9,9 @@ [41 —— 抵达一台设备](41-devices.md);以及机器可读流的 schema,那是 [50 —— 机器可读输出](50-machine-output.md)。本章只说明哪个旗标产生它,到此为止。 -在此之前:[04 —— 依赖与解析](04-dependencies.md) 覆盖 `[dev-dependencies]`, +在此之前:[05 —— 依赖与解析](05-dependencies.md) 覆盖 `[dev-dependencies]`, 那是测试如何取到产物取不到的包。在此之后: -[08 —— 按场景选命令](08-commands-by-scenario.md) 是其余一切的查阅入口。 +[09 —— 按场景选命令](09-commands-by-scenario.md) 是其余一切的查阅入口。 ## mcpp 认为什么是一个测试 diff --git a/docs/zh/08-commands-by-scenario.md b/docs/zh/09-commands-by-scenario.md similarity index 99% rename from docs/zh/08-commands-by-scenario.md rename to docs/zh/09-commands-by-scenario.md index bdeb17fc..0b2a42da 100644 --- a/docs/zh/08-commands-by-scenario.md +++ b/docs/zh/09-commands-by-scenario.md @@ -1,4 +1,4 @@ -# 08 —— 按场景选命令 +# 09 —— 按场景选命令 **读者:**已经认识那些名词、现在想找动词的人。 @@ -6,7 +6,7 @@ 校验一个描述符、诊断环境。 **不在这里:**每个命令的含义细节。一个场景点名命令,并链接到拥有它的那一章。 -在此之前:[07 —— 测试](07-testing.md)。 +在此之前:[08 —— 测试](08-testing.md)。 命令清单是 `mcpp --help`,每个子命令还有自己的 `--help`。本章回答的是另一个问题: 某个情形已经发生时该用哪条命令 —— 构建目录一直变大、解析结果出乎意料、描述符即将 diff --git a/docs/zh/10-pack-and-release.md b/docs/zh/10-pack-and-release.md index 16837e10..6757d450 100644 --- a/docs/zh/10-pack-and-release.md +++ b/docs/zh/10-pack-and-release.md @@ -64,7 +64,7 @@ mcpp 之所以针对私有 glibc 构建,正是为了让产物的行为不取决 「自包含」有一个下限。有些库只能来自目标机器:图形驱动的用户态部分与正在运行的 内核模块版本绑定,而对专有栈而言,再分发是不被允许的。这类依赖应声明为运行期能力 -需求(`docs/zh/03-mcpp-toml.md` §2.11),模式表随之多出一列: +需求(`docs/zh/04-mcpp-toml.md` §2.11),模式表随之多出一列: | Mode | 需要宿主提供能力的程序 | |---|---| @@ -279,7 +279,7 @@ Win32 loader 解析 DLL 的第一顺位就是**可执行文件所在目录**,而 `vcruntime140.dll` / `msvcp140.dll` **不是** Windows 自己的:它们属于 MSVC toolset,就像 `libstdc++.so` 属于 gcc。它们要不要跟着产物走,由 `cxx_runtime` -决定(见 `docs/zh/03-mcpp-toml.md`),不由这张表决定 —— 而 `mcpp pack` 会拒绝 +决定(见 `docs/zh/04-mcpp-toml.md`),不由这张表决定 —— 而 `mcpp pack` 会拒绝 那些无法兑现契约的组合: ``` diff --git a/docs/zh/20-toolchains.md b/docs/zh/20-toolchains.md index c5105ae7..fa3e3d7f 100644 --- a/docs/zh/20-toolchains.md +++ b/docs/zh/20-toolchains.md @@ -209,7 +209,7 @@ mcpp toolchain default gcc@16 --target x86_64-windows-gnu `import std`)。该 target 默认 linkage 为 **static**——产出的 `.exe` 完全自包含(无需随包分发 `libstdc++-6.dll`,可直接在 wine 下运行); 要退出请写在 target 段上——`linkage` 只认精确 triple(见 -[mcpp.toml](03-mcpp-toml.md) §2.7),`[build] linkage` 这个键并不存在,写了会被静默忽略: +[mcpp.toml](04-mcpp-toml.md) §2.7),`[build] linkage` 这个键并不存在,写了会被静默忽略: ```toml [target.x86_64-windows-gnu] @@ -446,7 +446,7 @@ cxx_runtime = "self-contained" # C++ 运行时那根轴 CRT。 toolset 自带的那份可再分发 CRT(`vcruntime140.dll` / `msvcp140.dll`)可以跟着 -产物走 —— 见 `docs/zh/03-mcpp-toml.md` 的 `cxx_runtime = "toolchain-coupled"`。 +产物走 —— 见 `docs/zh/04-mcpp-toml.md` 的 `cxx_runtime = "toolchain-coupled"`。 ## 项目级版本锁定 diff --git a/docs/zh/22-target-side.md b/docs/zh/22-target-side.md index 80e7d538..faa38664 100644 --- a/docs/zh/22-target-side.md +++ b/docs/zh/22-target-side.md @@ -299,7 +299,7 @@ mcpp 不认识的键 —— 打错的字,或来自更新版本 mcpp 的谓词 |---|---|---| | **裸 OS 别名** | 单个 OS / 族 —— 简洁且常用的形式 | `[target.windows]`、`[target.unix]` | | **`cfg(...)` 谓词** | 复合条件(arch / env / 组合子) | `[target.'cfg(all(linux, not(arch = "aarch64")))']` | -| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 [03 §2.7.3](03-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | +| **精确三元组** | 某个具体目标(同时承载 `toolchain` / `linkage` / `sysroot` / `runner`,见 [04 §2.7.3](04-mcpp-toml.md)) | `[target.x86_64-linux-musl]` | 一个选择器可以承载平台条件的**依赖**与**构建 flag**: diff --git a/docs/zh/23-the-project-environment.md b/docs/zh/23-the-project-environment.md index cb570e5c..ee385f7a 100644 --- a/docs/zh/23-the-project-environment.md +++ b/docs/zh/23-the-project-environment.md @@ -96,7 +96,7 @@ SubOS 里。前置让被声明的环境成为默认答案;其余的仍在它后 工程与依赖命名**同一个包**时,只装它的一个版本:身份是 `(namespace, name)`,版本是这个 包上的约束。离产物更近的声明赢,并且覆盖会被报出来;不满足对方所陈述之要求的钉会被拒绝 -并点出两侧。见 [03 — mcpp.toml](03-mcpp-toml.md) 的「一个包一个版本」。 +并点出两侧。见 [04 — mcpp.toml](04-mcpp-toml.md) 的「一个包一个版本」。 ## 4. 只读取环境,从不创建环境 @@ -145,7 +145,7 @@ create/bootstrap that environment instead of falling back to active/default 见本章。不写档位就是从前的行为。 **runner。** `[xlings.workspace]` 下的程序也是 `[target.].runner` 查找其第一个元素 -的首选位置,在 `PATH` 之前([03 §2.7.3](03-mcpp-toml.md))。两个键合起来,在 CI 宿主上供给用户态模拟器, +的首选位置,在 `PATH` 之前([04 §2.7.3](04-mcpp-toml.md))。两个键合起来,在 CI 宿主上供给用户态模拟器, 并通过它执行交叉构建的产物,而清单不必写出载荷的路径。 ## 6. 什么该写在别处 @@ -434,5 +434,5 @@ error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins `PATH`。 - [91 - 工具链内部](91-toolchain-internals.md) —— 运行时选择、`RuntimeBinding` 快照与降级规则。 -- [03 - mcpp.toml](03-mcpp-toml.md) —— manifest 的其余部分。 +- [04 - mcpp.toml](04-mcpp-toml.md) —— manifest 的其余部分。 diff --git a/docs/zh/24-openkal-cross.md b/docs/zh/24-openkal-cross.md index 0ce3fc49..83feeb51 100644 --- a/docs/zh/24-openkal-cross.md +++ b/docs/zh/24-openkal-cross.md @@ -186,7 +186,7 @@ runner = ["qemu-aarch64-static"] 没有这个键时,`mcpp run` 报告内核的拒绝(`Exec format error`)与应当写的键,`mcpp test` 把每个测试报告为未运行并以 2 退出。能原生执行该产物的宿主传 `--no-runner`。规则见 -[03 —— mcpp.toml](03-mcpp-toml.md) §2.7.3。 +[04 —— mcpp.toml](04-mcpp-toml.md) §2.7.3。 ### 源码是同一份,程序不是 diff --git a/docs/zh/30-build-mcpp.md b/docs/zh/30-build-mcpp.md index 774725d6..20170fb0 100644 --- a/docs/zh/30-build-mcpp.md +++ b/docs/zh/30-build-mcpp.md @@ -305,7 +305,7 @@ store 内部结构 —— 与 `dep_dir` 存在的理由相同。 **依赖声明的包同样被作答**(2026.9.6.6+),而且答的是这次构建**真正装上**的版本,不是 本地 manifest 写下的那个。一个包只有一个版本:工程与规则都命名它时,离产物更近的声明赢, -而两侧被告知同一个答案。见 [03 — mcpp.toml](03-mcpp-toml.md) 的「一个包一个版本」。 +而两侧被告知同一个答案。见 [04 — mcpp.toml](04-mcpp-toml.md) 的「一个包一个版本」。 **`[feature-xlings.]` 在 `` 生效时同样被作答**(2026.9.6.2+)。这张表从诞生 起就参与供给 —— 在那里写下一个包,它就会被下载并安装 —— 但构建程序的环境只由 @@ -453,7 +453,7 @@ mcpp 为那条边写出 `depfile =` 与 `deps = gcc`,ninja 读取该文件并把 > 把预编译对象写进 `[build].ldflags` 同样能到达链接器,但**不要**用它承载构建产物: > ldflags 是链接命令里的一串字符、不是图里的文件,没有任何东西跟踪它,改了它得到的是 -> `ninja: no work to do`。Windows 资源请用 [`[resources]`](03-mcpp-toml.md); +> `ninja: no work to do`。Windows 资源请用 [`[resources]`](04-mcpp-toml.md); > `object` 是其余一切的出口。 **必须写出输出文件名。** mcpp 在 prepare 期就定死源码集、fingerprint 与模块图, @@ -619,7 +619,7 @@ shim,而可用的那份就在项目自己的环境里,根本不在 `PATH` 上。 一条规则 ——「对这些 `.proto` 跑 protoc」「对这些源码跑 clang-tidy」—— 属于一个包, 而不该被复制到每个消费者的 `build.mcpp` 里。机制是 -[`host-module = true`](../03-mcpp-toml.md);本节讲的是它里面应该长什么样。 +[`host-module = true`](../04-mcpp-toml.md);本节讲的是它里面应该长什么样。 下面这些从第一个规则包 `mcpplibs.grpcgen` 归纳而来,每一条特征都单独判过是必然还是偶然。 它们是指引而非规则,因为其中没有一条能给出引擎可以检查的判据。 @@ -659,7 +659,7 @@ shim,而可用的那份就在项目自己的环境里,根本不在 `PATH` 上。 而不是以包名注册,所以 `export module mcpp.rules.spirv;` 就是消费者 import 的那个名字。 官方插件集中在一个包里,`mcpp:plugins`(仓库 `mcpp-community/mcpp-plugins`):规则包命名为 `mcpp.rules.`,构建期工具命名为 `mcpp.tools.`,每个成员由该包的一个 feature 选择 -(见 [`host-module = true`](../03-mcpp-toml.md))。`mcpp.build.*` 是引擎自己的模块族, +(见 [`host-module = true`](../04-mcpp-toml.md))。`mcpp.build.*` 是引擎自己的模块族, 不用于插件。引擎判定不了谁是官方,所以检查以包的**命名空间**为键,两者不一致时告警 —— warning: build rule 'mcpplibs.plugins' declares the module @@ -760,7 +760,7 @@ mcpp **不会**每次构建都重跑 `build.mcpp`。它会缓存程序产出的 - **在主机上运行——交叉构建下也是**(mcpp 0.0.95+)。`mcpp build --target ` 下,程序用宿主解析的工具链编译、在宿主运行,并看到 `MCPP_TARGET` = 交叉三元组。 纯声明式的目标门控仍首选 `[target.'cfg(...)']` 表——参见 - [03 - mcpp.toml 工程文件指南](03-mcpp-toml.md)。 + [04 - mcpp.toml 工程文件指南](04-mcpp-toml.md)。 - **当前工作目录是工程根目录**,因此相对路径(`src/generated.cpp`)会落在预期位置。 - `build.mcpp` 非零退出会中止构建并打印其输出。 - **运行有时间上限**(mcpp 2026.8.5.1+):构建程序默认有 **600 秒**,超时后 mcpp @@ -917,7 +917,7 @@ rules-spirv = { sources = ["rules/spirv.cppm"] } # export module mcpp.rules.spir plugins = { version = "0.3.0", features = ["rules-spirv"], host-module = true } ``` -**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 [03 §2.6.1](03-mcpp-toml.md) 描述的那种 +**用 `[build-dependencies]` 而不是 `[dependencies]`** —— 规则包正是 [04 §2.6.1](04-mcpp-toml.md) 描述的那种 情形:它的库绝不该到达目标,而它的规则仍然被需要。两条轴是分开的: `host-module = true` 说的是**要哪一种构建期产物**,而 section 说的是**这个包是否到达 目标**;规则包在第二条轴上的答案是"否",而 section 就是说这件事的地方。写在 diff --git a/docs/zh/31-authoring-a-rule-package.md b/docs/zh/31-authoring-a-rule-package.md index 461e7bf5..d2ade381 100644 --- a/docs/zh/31-authoring-a-rule-package.md +++ b/docs/zh/31-authoring-a-rule-package.md @@ -7,7 +7,7 @@ **不在这里:**给单个工程自己的构建加一步,那是 [30 —— 构建程序](30-build-mcpp.md) —— 同一批原语,规模更小;feature 相关键本身, -那是 [05](05-features-and-capabilities.md);以及已发布规则各自的拼法,那属于 +那是 [06](06-features-and-capabilities.md);以及已发布规则各自的拼法,那属于 `mcpp:plugins`。示例:[`08-build-rules`](../../examples/08-build-rules/) 做检查与 嵌入,[`12-a-new-device-language`](../../examples/12-a-new-device-language/) 新增 一门语言。 @@ -154,7 +154,7 @@ device link 在引擎侧的全部内容:N 个 `artifact` action,其输出不进 都不开。 裸版本是项目可以覆盖的**选择**;`>=` 是项目不得低于的**要求**。见 -[03 —— mcpp.toml](03-mcpp-toml.md) 的*一个包,一个版本*。 +[04 —— mcpp.toml](04-mcpp-toml.md) 的*一个包,一个版本*。 ## 生成岛的边界 @@ -227,4 +227,4 @@ mcpp::floor("cuda.driver", runtime_needs); - `mcpp emit xpkg` 把 `manifest = "mcpp.toml"` 写进 `mcpp` 段,而 `mcpp xpkg parse` 把该键报为未知并以 1 退出。`mcpp-index` 里没有任何描述符使用 它(218 个里 0 个);自带 `mcpp.toml` 的包整个省略 `mcpp` 字段。见 - [08 —— 按场景选命令](08-commands-by-scenario.md)的*当前边界*。 + [09 —— 按场景选命令](09-commands-by-scenario.md)的*当前边界*。 diff --git a/docs/zh/40-baremetal.md b/docs/zh/40-baremetal.md index 2a162404..64d2c778 100644 --- a/docs/zh/40-baremetal.md +++ b/docs/zh/40-baremetal.md @@ -11,7 +11,7 @@ hosted 的交叉构建,那是 [24 —— 基于 openkal 的交叉构建](24-open 本文说明 mcpp 如何为没有操作系统的目标构建、运行与测试软件,以及板级支持包如何 提供引擎刻意不去掌握的那部分目标事实。 -相关文档:[03 — mcpp.toml 清单指南](03-mcpp-toml.md) §2.7.2 是本文使用的 +相关文档:[04 — mcpp.toml 清单指南](04-mcpp-toml.md) §2.7.2 是本文使用的 `[target.]` 各键的参考;[30 — build.mcpp 构建程序](30-build-mcpp.md) 是板级支持包所用指令协议的参考;[91 — 工具链内部机制](91-toolchain-internals.md) 描述了本文所偏离的宿主链接模型。 @@ -450,7 +450,7 @@ C 库经过优化的那份,且没有任何提示。实测(picolibc 在场):冷 ### 在目标上运行测试 -测试在这里的运作方式与 [07 —— 测试](07-testing.md) 所述完全一致,本节只补充板子 +测试在这里的运作方式与 [08 —— 测试](08-testing.md) 所述完全一致,本节只补充板子 特有的部分:每个 `tests/*.cpp` 成为它自己的一个镜像,由板级支持包提供的 runner 执行它。 @@ -569,7 +569,7 @@ error: no runner is configured for 'riscv64-none-elf' — a freestanding artifac 这个键不限于裸机。hosted 交叉目标 —— x86_64 宿主上的 `aarch64-linux-musl` 产物 —— 使用同一个 `[target.].runner`,以 `qemu-aarch64-static` 这类用户态模拟器代替 系统模拟器;在这类目标上,缺少 runner 在内核拒绝产物之前不是错误。hosted 目标的规则、 -`--no-runner` 出口与 `mcpp test` 的未运行报告见 [03 —— mcpp.toml](03-mcpp-toml.md) +`--no-runner` 出口与 `mcpp test` 的未运行报告见 [04 —— mcpp.toml](04-mcpp-toml.md) §2.7.3。 ## 编写板级支持包 diff --git a/docs/zh/41-devices.md b/docs/zh/41-devices.md index 2b5cdfe6..0e13bfb0 100644 --- a/docs/zh/41-devices.md +++ b/docs/zh/41-devices.md @@ -7,7 +7,7 @@ 一条命令抵达它。 **不在这里:**这个目标本身怎么构建,那是 [40 —— 裸机](40-baremetal.md);以及 mcpp -认为什么是一个测试,那是 [07 —— 测试](07-testing.md)。 +认为什么是一个测试,那是 [08 —— 测试](08-testing.md)。 本文规定 mcpp 如何执行一个运行在构建机器之外的产物、包如何提供抵达它的其他方式, 以及工程如何在模拟器与真实硬件之间选择。 diff --git a/docs/zh/42-heterogeneous-builds.md b/docs/zh/42-heterogeneous-builds.md index 134d8f68..105c1531 100644 --- a/docs/zh/42-heterogeneous-builds.md +++ b/docs/zh/42-heterogeneous-builds.md @@ -96,7 +96,7 @@ C++20 modules 的编译器。 上表是 mcpp **不需要被告知**就知道的那些:在「包可以自己声明」之前就已经支持的语言。 规则包通过 `[features]..device_extensions`(见 -[03 — mcpp.toml](03-mcpp-toml.md) §2.8)向它增补,而这正是**一门新设备语言到达的方式** +[04 — mcpp.toml](04-mcpp-toml.md) §2.8)向它增补,而这正是**一门新设备语言到达的方式** —— 不动引擎,也不需要发一版引擎。Slang 是第一个:`.slang` 不在上表里,由 `mcpp:plugins` 的 `rules-slang` 声明。 @@ -248,7 +248,7 @@ plugins = { version = "0.3.0", features = ["rules-cuda"], host-module = true } ``` 更近的声明赢,装一个版本,并且 mcpp 说出用了哪条。不满足规则下界的钉会被拒绝并点出 -两侧,而不是与它并排装下来。完整规则见 [03 — mcpp.toml](03-mcpp-toml.md) 的 +两侧,而不是与它并排装下来。完整规则见 [04 — mcpp.toml](04-mcpp-toml.md) 的 「一个包一个版本」;`examples/09-heterogeneous/multi-backend` 是本仓库里唯一走覆盖 路径的示例,其余每一个都只写那条边。 diff --git a/docs/zh/README.md b/docs/zh/README.md index 5051e62e..2cf5d8a3 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -22,13 +22,13 @@ | 目标 | 阅读 | 运行 | |---|---|---| -| 写一个程序 | [01](01-getting-started.md)、[03](03-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | -| 写一个供他人 import 的库 | [11](11-publishing-a-library.md)、[05](05-features-and-capabilities.md)、[03](03-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | +| 写一个程序 | [01](01-getting-started.md)、[04](04-mcpp-toml.md) §1 | [`01-hello`](../../examples/01-hello/)、[`02-with-deps`](../../examples/02-with-deps/) | +| 写一个供他人 import 的库 | [11](11-publishing-a-library.md)、[06](06-features-and-capabilities.md)、[04](04-mcpp-toml.md) §2.4 | [`04-workspace`](../../examples/04-workspace/)、[`11-features`](../../examples/11-features/) | | 发布它 | [10](10-pack-and-release.md)、[11](11-publishing-a-library.md)、[12](12-binary-distribution.md) | [`03-pack-static`](../../examples/03-pack-static/)、[`05-lib-distribution`](../../examples/05-lib-distribution/) | | 为另一台机器构建 | [21](21-the-target-triple.md)、[24](24-openkal-cross.md)、[40](40-baremetal.md) | [`06-openkal-cross`](../../examples/06-openkal-cross/)、`mcpp new … --template riscv-virt-rt` | | 使用 GPU 或加速器 | [42](42-heterogeneous-builds.md)、[41](41-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | | 增加一条规则、一种语言或一个生成器 | [31](31-authoring-a-rule-package.md)、[30](30-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`12-a-new-device-language`](../../examples/12-a-new-device-language/) | -| 向索引添加一个包 | [11](11-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [08](08-commands-by-scenario.md) —— 发布相关场景 | +| 向索引添加一个包 | [11](11-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [09](09-commands-by-scenario.md) —— 发布相关场景 | | 修改 mcpp 本身 | [90](90-build-from-source.md)、[92](92-release.md)、[51](51-supported-versions.md) | — | 课程也可以以**项目模板**的形式到达:模板由包提供,`mcpp new --template` 实例化 @@ -45,13 +45,14 @@ - [00 —— mcpp 的运转方式](00-how-mcpp-works.md) —— 其余每章都假定的模型 - [01 —— 快速开始](01-getting-started.md) —— 安装、创建、构建、运行 -- [02 —— 示例项目](02-examples.md) —— 哪个示例教什么 -- [03 —— mcpp.toml 工程文件指南](03-mcpp-toml.md) —— manifest 可以说什么 -- [04 —— 依赖与解析](04-dependencies.md) —— 依赖从哪里来,以及哪个版本胜出 -- [05 —— Feature 与能力](05-features-and-capabilities.md) —— 让包的一部分成为可选 -- [06 —— 工作空间](06-workspace.md) —— 多个包,一次构建 -- [07 —— 测试](07-testing.md) —— 包括在本机跑不了的那些 -- [08 —— 按场景选命令](08-commands-by-scenario.md) —— 认识名词之后的查阅入口 +- [02 —— 场景](02-scenarios.md) —— mcpp 被用来做什么,以及每一类工作会用到它的哪些功能 +- [03 —— 示例项目](03-examples.md) —— 哪个示例教什么 +- [04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md) —— manifest 可以说什么 +- [05 —— 依赖与解析](05-dependencies.md) —— 依赖从哪里来,以及哪个版本胜出 +- [06 —— Feature 与能力](06-features-and-capabilities.md) —— 让包的一部分成为可选 +- [07 —— 工作空间](07-workspace.md) —— 多个包,一次构建 +- [08 —— 测试](08-testing.md) —— 包括在本机跑不了的那些 +- [09 —— 按场景选命令](09-commands-by-scenario.md) —— 认识名词之后的查阅入口 ### 1x —— 发布 @@ -89,6 +90,42 @@ - [91 —— 工具链机制内幕](91-toolchain-internals.md) - [92 —— 发布 mcpp](92-release.md) +## 反查 + +上面的章节表是**阅读顺序**。这里是另一种索引:从读者眼前的一个记号,查到拥有它的 +那一章。 + +**manifest 的表与键** + +| | 章节 | | 章节 | +|---|---|---|---| +| `[package]`、`[targets.]`、`[build]`、`[lib]` | [04](04-mcpp-toml.md) | `[profile.]`、`[resources]`、`[runtime]` | [04](04-mcpp-toml.md) | +| `[dependencies]`、`[dev-dependencies]`、`[build-dependencies]` | [05](05-dependencies.md) | `scan_overrides`、`module_extensions` | [04](04-mcpp-toml.md) | +| `[features]`、`[feature-deps.]`、`provides` / `requires` | [06](06-features-and-capabilities.md) | `[workspace]` | [07](07-workspace.md) | +| `[toolchain]`、`cxx_runtime` | [20](20-toolchains.md) | `[target.]`、`cfg(…)` | [22](22-target-side.md) | +| `[xlings]`、`[xlings.workspace]`、`[feature-xlings.]` | [23](23-the-project-environment.md) | `[pack]` | [10](10-pack-and-release.md) | +| `[build] accel`、`[package] accelerators`、`device_extensions` | [42](42-heterogeneous-builds.md) | `[hooks]` | [09](09-commands-by-scenario.md) | +| `runner`、`[target..runners]` | [41](41-devices.md) | `rule_module` | [31](31-authoring-a-rule-package.md) | + +**命令** + +| | 章节 | | 章节 | +|---|---|---|---| +| `build`、`run` | [01](01-getting-started.md) | `test` | [08](08-testing.md) | +| `new`、`new --template` | [01](01-getting-started.md) | `add`、`update`、`why` | [05](05-dependencies.md) | +| `pack` | [10](10-pack-and-release.md) | `publish`、`emit xpkg`、`xpkg parse` | [11](11-publishing-a-library.md) | +| `toolchain` | [20](20-toolchains.md) | `clean`、`cache`、`index`、`self …` | [09](09-commands-by-scenario.md) | + +**概念** + +| | 章节 | | 章节 | +|---|---|---|---| +| 五个名词;一次构建做了什么 | [00](00-how-mcpp-works.md) | `import std`、模块接口、BMI | [00](00-how-mcpp-works.md)、[20](20-toolchains.md) | +| `mcpp::action`、构建程序 | [30](30-build-mcpp.md) | 规则包、`MCPP_EXPORT_C` | [31](31-authoring-a-rule-package.md) | +| 目标三元组、支持矩阵 | [21](21-the-target-triple.md) | runner、具名 runner | [41](41-devices.md) | +| 岛、接缝、`accel` | [42](42-heterogeneous-builds.md) | 描述符、索引 | [11](11-publishing-a-library.md) | +| ABI tag、预建产物 | [12](12-binary-distribution.md) | 退出码、JSON 输出 | [50](50-machine-output.md) | + ## 规范文档 规范性文档 —— 语义、约束与匹配规则,每条规则标注其实现状态。 diff --git a/examples/09-heterogeneous/README.md b/examples/09-heterogeneous/README.md index 547723b0..93eda6d1 100644 --- a/examples/09-heterogeneous/README.md +++ b/examples/09-heterogeneous/README.md @@ -105,7 +105,7 @@ plugins = { version = "0.4.0", features = ["rules-cuda", "tools-island"], host-m `multi-backend/` is the one example here that also pins a version, and it does so to demonstrate the override: the rule owns "which package, and no older than what", the project owns "and exactly this one". One version is installed either -way — see *One package, one version* in `docs/03-mcpp-toml.md`. +way — see *One package, one version* in `docs/04-mcpp-toml.md`. ## The layers underneath diff --git a/examples/09-heterogeneous/cuda/app/mcpp.toml b/examples/09-heterogeneous/cuda/app/mcpp.toml index a2e650f5..c9da8914 100644 --- a/examples/09-heterogeneous/cuda/app/mcpp.toml +++ b/examples/09-heterogeneous/cuda/app/mcpp.toml @@ -38,7 +38,7 @@ cuda-driver = "2026.09.05" # `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/03-mcpp-toml.md. Needs mcpp 2026.9.7.1 and mcpp:plugins 0.3.0. +# docs/04-mcpp-toml.md. Needs mcpp 2026.9.7.1 and mcpp:plugins 0.3.0. # # 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, diff --git a/mcpp.toml b/mcpp.toml index fd56852a..fa4f69c8 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -13,7 +13,7 @@ repo = "https://github.com/mcpp-community/mcpp" # binary would be -O0. A `--profile`/`--dev`/`--release` flag still overrides. default-profile = "release" # Split the module edge: importers start when the BMI is published rather than -# when the compiler exits. See docs/03-mcpp-toml.md. +# when the compiler exits. See docs/04-mcpp-toml.md. # # ON HERE, `auto` (= off) EVERYWHERE ELSE. The key stays opt-in until it has been # through CI on every platform, and nothing was exercising it, so it could never diff --git a/modules/manifest/src/glob.cppm b/modules/manifest/src/glob.cppm index 75c2eb04..3cea1cdb 100644 --- a/modules/manifest/src/glob.cppm +++ b/modules/manifest/src/glob.cppm @@ -86,7 +86,7 @@ std::optional try_narrow(const std::filesystem::path& p) { // The stored spelling is GENERIC (`/`), because that is what try_narrow // produces and there is no second narrowing here to disagree with it. On // Windows the reported path therefore reads `C:/pkg/test/www`, not -// `C:\pkg\test\www`; docs/03-mcpp-toml.md shows it that way too. +// `C:\pkg\test\www`; docs/04-mcpp-toml.md shows it that way too. void note_unnarrowable_path(const std::filesystem::path& p); // Take and clear this run's records. @@ -96,7 +96,7 @@ void note_unnarrowable_path(const std::filesystem::path& p); // CLI reports. Drained in exactly one place (`cli::run`'s scope guard), which // is what keeps "recorded but never shown" from becoming the next silent // failure. The rule is written up in .agents/skills/mcpp-contributing/SKILL.md -// ("路径窄化不变式") and the user-facing behaviour in docs/03-mcpp-toml.md. +// ("路径窄化不变式") and the user-facing behaviour in docs/04-mcpp-toml.md. std::vector take_unnarrowable_paths(); // Does `candidate` match `glob`, interpreted relative to `root`? diff --git a/modules/manifest/src/toml.cppm b/modules/manifest/src/toml.cppm index 6285f6b0..f8a66c31 100644 --- a/modules/manifest/src/toml.cppm +++ b/modules/manifest/src/toml.cppm @@ -2207,7 +2207,7 @@ std::expected parse_string(std::string_view content, // [hooks] — project build lifecycle commands (#496). Parsed HERE rather // than by the module that runs them, for the reason Appendix A of - // docs/03-mcpp-toml.md states: mcpp.toml has one grammar and one parser. + // docs/04-mcpp-toml.md states: mcpp.toml has one grammar and one parser. // A second reader of the same file would report ITS syntax errors in its // own vocabulary — a typo in [package] arriving as "invalid hook // configuration" — and would sit outside the warning/--strict policy every diff --git a/modules/manifest/src/types.cppm b/modules/manifest/src/types.cppm index 99aa6380..fdf1efb0 100644 --- a/modules/manifest/src/types.cppm +++ b/modules/manifest/src/types.cppm @@ -1263,7 +1263,7 @@ struct WorkspaceConfig { // `[hooks]` — project build lifecycle commands (#496). // // The commands are host-shell strings written by the project author, run by -// `mcpp build` around the build it performs. See docs/03-mcpp-toml.md §2.16. +// `mcpp build` around the build it performs. See docs/04-mcpp-toml.md §2.16. // // ONLY THE ROOT PROJECT'S HOOKS ARE EVER RUN. Every manifest mcpp parses // carries this field, including a DEPENDENCY's — and `mcpp build` reaches the diff --git a/src/build/prepare.cppm b/src/build/prepare.cppm index 47349090..776a6467 100644 --- a/src/build/prepare.cppm +++ b/src/build/prepare.cppm @@ -1004,7 +1004,7 @@ export struct BuildOverrides { // Is this git remote reachable without a network round-trip? // -// `--offline` means "never touch the network" (docs/03-mcpp-toml.md), and its +// `--offline` means "never touch the network" (docs/04-mcpp-toml.md), and its // standing promise is that anything already on disk still builds. A remote that // names a local directory — or a file:// URL — is served by plain filesystem // reads, so refusing it would break that promise without buying any isolation. diff --git a/src/hooks.cppm b/src/hooks.cppm index 8866b4e6..2f01c82c 100644 --- a/src/hooks.cppm +++ b/src/hooks.cppm @@ -3,7 +3,7 @@ // EXPERIMENTAL. A hook cannot currently change whether a build succeeded: // every failure is a warning, and `side_effect = true` is refused by the // manifest parser. Only the ROOT project's hooks are ever run — a dependency's -// `[hooks]` is inert. See docs/03-mcpp-toml.md §2.16. +// `[hooks]` is inert. See docs/04-mcpp-toml.md §2.16. // // The CONFIGURATION is not parsed here: `[hooks]` is a section of mcpp.toml // and mcpp.toml has one parser (mcpp.manifest). What lives here is the part diff --git a/tests/e2e/616_examples_and_their_index_agree.sh b/tests/e2e/616_examples_and_their_index_agree.sh index 2672851d..de5c6898 100755 --- a/tests/e2e/616_examples_and_their_index_agree.sh +++ b/tests/e2e/616_examples_and_their_index_agree.sh @@ -22,7 +22,7 @@ ROOT="${ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}" fails=0 fail() { printf 'ASSERT-FAIL: %s\n' "$1"; fails=$((fails + 1)); } -for doc in "$ROOT/docs/02-examples.md" "$ROOT/docs/zh/02-examples.md"; do +for doc in "$ROOT/docs/03-examples.md" "$ROOT/docs/zh/03-examples.md"; do [ -f "$doc" ] || { fail "missing $doc"; continue; } printf -- '--- %s ---\n' "${doc#"$ROOT"/}" From 57e0271358171a30013d024f935dda150f2da179 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 17:57:57 +0800 Subject: [PATCH 21/36] docs: normalise the limits heading, and record the four things this batch leaves open The limits section moves to the end of chapter 30 and takes the canonical name in both languages. It had been left mid-chapter by the split that appended a section after it. Seventeen reference chapters still have none, and they are recorded as open rather than written. A fabricated limits section satisfies the check and measures nothing, which is worse than an absent one: the reader then believes the boundary has been stated. The criterion for closing one is in the record -- facts a reader can reproduce on the current release. Also open and recorded: four chapters that still present every spelling at the same weight (R5 is applied to one section so far), the manifest reference's worked examples and appendix, and the fact that the new lookup index has no check comparing it against the reference chapter's own sections. --- .../2026-09-08-the-documentation-as-a-book.md | 47 +++++++ .agents/skills/mcpp-docs-style/SKILL.md | 7 ++ docs/30-build-mcpp.md | 115 +++++++++--------- docs/zh/30-build-mcpp.md | 93 +++++++------- 4 files changed, 159 insertions(+), 103 deletions(-) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 596700e2..2129145a 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -348,3 +348,50 @@ and designed nothing, which is the failure this document exists to name. grouping was wrong; that is the whole of the structural change. - **It does not settle the specifications' language.** That question stands where the previous record left it. + +--- + +## 11. What is open after this batch, and the criterion for each + +### 11.1 Seventeen reference chapters have no limits section + +The style skill calls the section mandatory for a reference chapter. Two +chapters had the content under another heading and are renamed; the scenario, +model, tutorial and index chapters are exempt, because their scope is stated by +the "Not here" line in their opening and they claim no complete surface. + +Seventeen remain: `04`, `05`, `07`, `10`, `11`, `20`, `21`, `22`, `23`, `24`, +`41`, `50`, `51`, `90`, `91`, `92`. + +**They are open rather than written, and the reason is the rule itself.** A +fabricated limits section satisfies the check and measures nothing, which is +worse than the section being absent — the reader then believes the boundary has +been stated. Writing one requires a fact its area's owner can state and that a +reader can reproduce. + +*Criterion for closing one:* the section lists facts, each of which can be +reproduced on the current release, and each of which a reader could otherwise +only discover by hitting it. + +### 11.2 Four chapters present several spellings as equals + +`04` (50 code blocks), `20` (35), `30` (37) and `40` (33) carry no `
`, +which means every spelling in them is offered to the reader at the same weight. +R5 says one recommended path in the body and the rest folded away. `05` is the +one section converted so far, and it is the shape the other four follow. + +*Criterion:* a reader who reads only the body and opens nothing can do the thing +correctly without choosing. + +### 11.3 The manifest reference still holds worked examples and an appendix + +`04` §3 is six worked examples, which `examples/` owns, and Appendix A is the +admission criteria for new manifest fields, which is contributor material and +belongs to `9x` or to SPEC-004. Both were named in §6 and neither is moved yet. + +### 11.4 The lookup index has no check + +§10's index is hand-built and correct today. Nothing compares it against the +reference chapter's own section list, so a key added to `04` and not indexed is +invisible. The check is the same shape as rule 4 (every specification appears in +every index) and is one loop. diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index b0b5bea3..e45f63df 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -301,6 +301,13 @@ docs/specs/ ──▶ docs/ 允许 - **每份参考章节必须有「当前边界 / Current limitations」一节。** 没写边界的 文档等于声称自己完整。这一节不是可选的,而且**是一份事实清单,不是说理** (写法见第一节)。 + + **教程、模型、索引与场景章节不欠这一节** —— 它们的范围由开头的「不在这里」交代, + 而它们本来就不声称覆盖一个完整的表面。 + + **写不出来就不要写。** 一节编造的边界会让检查通过而什么都没测到,那比缺这一节 + 更坏。写不出来时,把「这一章的边界尚未写出」记进设计记录,连同判据 —— 谁能说出 + 它、以及那句话要能被复现。 - **缺口要写出来,不要留白。** 「这一项尚无示例」是一条信息;什么都不说, 读者只能靠踩到才知道。缺口写在**它所属的那棵树**里:用户文档写「不支持 X」,设计记录写为什么以及打算怎么办。 diff --git a/docs/30-build-mcpp.md b/docs/30-build-mcpp.md index 8e86a071..0ee3ce3e 100644 --- a/docs/30-build-mcpp.md +++ b/docs/30-build-mcpp.md @@ -896,63 +896,6 @@ explicit input/output contract — incremental builds stay correct. When nothing changed the output is `build.mcpp up to date (cached)`; otherwise `build.mcpp compiling` / `running`. -## Notes & limits - -- **Runs on the host — including under cross** (mcpp 0.0.95+). Under - `mcpp build --target ` the program is compiled with a host-resolved - toolchain, runs on the host, and sees `MCPP_TARGET` = the cross triple. - For purely declarative target gating, `[target.'cfg(...)']` tables remain - the first choice — see [04 - mcpp.toml Manifest Guide](04-mcpp-toml.md). -- **CWD is the project root**, so relative paths (`src/generated.cpp`) land where - expected. -- A non-zero exit from `build.mcpp` aborts the build and prints its output. -- **The run is bounded** (mcpp 2026.8.5.1+): a build program gets **600 s** by - default, after which mcpp kills it and fails the build naming the package. - Configure it per package: - - ```toml - [build] - build_program_timeout = 1800 # seconds; 0 = no limit - ``` - - Precedence, highest first — the same shape `macos_deployment_target` uses: - - ``` - MCPP_BUILD_PROGRAM_TIMEOUT= this invocation only - > [build] build_program_timeout the manifest of the package that OWNS the build.mcpp - > 600 built-in default - ``` - - The value comes from the **owning package's** manifest, because its author is - the one who knows how long the generator takes. When a dependency's build - program times out, the error names the exact `mcpp.toml` to edit — editing - a hand-written one would change nothing. - - Omitting the key is not the same as `0`: unset means "use the default bound", - `0` means "no bound at all". - - **The bound is enforced on every platform** as of mcpp 2026.8.11.1. It used - to be POSIX-only: the Windows launcher fell through to an unbounded path, so - this knob — and `mcpp test --timeout`, and `--build-timeout` — silently did - nothing there. Windows now runs the child in a Job object and closes it on - expiry, which takes the whole process tree rather than just the direct child - (a grandchild left holding the capture pipe would otherwise hang the drain - after the kill). - - The **compile** is deliberately *not* bounded — the same asymmetry `mcpp test` - uses: a long compile is usually legitimate (a first-run `std` module build is - minutes) and killing it produces a baffling failure, while a long-running - build *program* is usually stuck, and an unbounded one hangs the whole build - with no diagnostic at all. - - > **Why not "ask the user instead of aborting"** ([#410](https://github.com/mcpp-community/mcpp/issues/410)): - > the program's stdout is already dup2'd into a pipe that carries the `mcpp:` - > directive protocol, so there is no interaction channel; most builds run - > where nobody is watching (CI, a pipeline, ninja's child), and a build - > blocked on a prompt is harder to diagnose than one that failed; and a build - > whose outcome depends on a keystroke is not reproducible. The configurable - > bound plus an error that names the file to edit answers the same need. - ## Host tools from a dependency (mcpp 2026.8.5.1+) A package can build a binary its consumers need *at build time* — `protoc`, a @@ -1227,3 +1170,61 @@ the other conditional dependency tables ([22 — The Target Side](22-target-side registered on every platform** — only what it pulls in is conditional — so requesting it where no predicate matches is not an unknown-feature error. + + +## Current limitations + +- **Runs on the host — including under cross** (mcpp 0.0.95+). Under + `mcpp build --target ` the program is compiled with a host-resolved + toolchain, runs on the host, and sees `MCPP_TARGET` = the cross triple. + For purely declarative target gating, `[target.'cfg(...)']` tables remain + the first choice — see [04 - mcpp.toml Manifest Guide](04-mcpp-toml.md). +- **CWD is the project root**, so relative paths (`src/generated.cpp`) land where + expected. +- A non-zero exit from `build.mcpp` aborts the build and prints its output. +- **The run is bounded** (mcpp 2026.8.5.1+): a build program gets **600 s** by + default, after which mcpp kills it and fails the build naming the package. + Configure it per package: + + ```toml + [build] + build_program_timeout = 1800 # seconds; 0 = no limit + ``` + + Precedence, highest first — the same shape `macos_deployment_target` uses: + + ``` + MCPP_BUILD_PROGRAM_TIMEOUT= this invocation only + > [build] build_program_timeout the manifest of the package that OWNS the build.mcpp + > 600 built-in default + ``` + + The value comes from the **owning package's** manifest, because its author is + the one who knows how long the generator takes. When a dependency's build + program times out, the error names the exact `mcpp.toml` to edit — editing + a hand-written one would change nothing. + + Omitting the key is not the same as `0`: unset means "use the default bound", + `0` means "no bound at all". + + **The bound is enforced on every platform** as of mcpp 2026.8.11.1. It used + to be POSIX-only: the Windows launcher fell through to an unbounded path, so + this knob — and `mcpp test --timeout`, and `--build-timeout` — silently did + nothing there. Windows now runs the child in a Job object and closes it on + expiry, which takes the whole process tree rather than just the direct child + (a grandchild left holding the capture pipe would otherwise hang the drain + after the kill). + + The **compile** is deliberately *not* bounded — the same asymmetry `mcpp test` + uses: a long compile is usually legitimate (a first-run `std` module build is + minutes) and killing it produces a baffling failure, while a long-running + build *program* is usually stuck, and an unbounded one hangs the whole build + with no diagnostic at all. + + > **Why not "ask the user instead of aborting"** ([#410](https://github.com/mcpp-community/mcpp/issues/410)): + > the program's stdout is already dup2'd into a pipe that carries the `mcpp:` + > directive protocol, so there is no interaction channel; most builds run + > where nobody is watching (CI, a pipeline, ninja's child), and a build + > blocked on a prompt is harder to diagnose than one that failed; and a build + > whose outcome depends on a keystroke is not reproducible. The configurable + > bound plus an error that names the file to edit answers the same need. diff --git a/docs/zh/30-build-mcpp.md b/docs/zh/30-build-mcpp.md index 20170fb0..673ea487 100644 --- a/docs/zh/30-build-mcpp.md +++ b/docs/zh/30-build-mcpp.md @@ -755,52 +755,6 @@ mcpp **不会**每次构建都重跑 `build.mcpp`。它会缓存程序产出的 无变化时输出 `build.mcpp up to date (cached)`;否则是 `build.mcpp compiling` / `running`。 -## 说明与限制 - -- **在主机上运行——交叉构建下也是**(mcpp 0.0.95+)。`mcpp build --target ` - 下,程序用宿主解析的工具链编译、在宿主运行,并看到 `MCPP_TARGET` = 交叉三元组。 - 纯声明式的目标门控仍首选 `[target.'cfg(...)']` 表——参见 - [04 - mcpp.toml 工程文件指南](04-mcpp-toml.md)。 -- **当前工作目录是工程根目录**,因此相对路径(`src/generated.cpp`)会落在预期位置。 -- `build.mcpp` 非零退出会中止构建并打印其输出。 -- **运行有时间上限**(mcpp 2026.8.5.1+):构建程序默认有 **600 秒**,超时后 mcpp - 杀掉它并让构建失败,错误里会点名是哪个包。可按包配置: - - ```toml - [build] - build_program_timeout = 1800 # 秒;0 = 不限 - ``` - - 优先级(与 `macos_deployment_target` 同构): - - ``` - MCPP_BUILD_PROGRAM_TIMEOUT=<秒> 本次调用 - > [build] build_program_timeout 拥有该 build.mcpp 的那个包的 manifest - > 600 内置默认 - ``` - - 值取自**拥有该构建程序的包**的 manifest,因为只有它的作者知道生成器要跑多久。 - 当一个**依赖**的构建程序超时时,错误会点名要改的那份 `mcpp.toml` —— 改自己的 - 那份不会有任何效果。 - - 不写这个键与写 `0` 不是一回事:不写=用默认上限,`0`=完全不设上限。 - - **这个上限从 mcpp 2026.8.11.1 起在所有平台生效**。此前它只在 POSIX 上生效: - Windows 的启动器会退回到无界路径,所以这个键——以及 `mcpp test --timeout`、 - `--build-timeout`——在那里都是静默的空操作。现在 Windows 把子进程放进 Job 对象, - 到期时关闭它,于是被杀掉的是**整棵进程树**而不只是直接子进程(否则一个还攥着 - 捕获管道的孙进程会让杀掉之后的读取一直挂住)。 - - **编译**这一步刻意**不设**上限——与 `mcpp test` 同一条不对称纪律: - 编译跑得久通常是正当的(首次构建 `std` 模块就是分钟级),杀掉它只会产生莫名其妙的 - 失败;而构建**程序**跑得久通常是卡住了,不设上限就会让整个构建挂死且毫无诊断。 - - > **为什么不做「超时时询问用户」**([#410](https://github.com/mcpp-community/mcpp/issues/410)): - > 构建程序的 stdout 已经被 dup2 进一根承载 `mcpp:` 指令协议的管道,没有交互通道; - > 多数构建发生在没有人看的地方(CI、流水线、ninja 的子进程),而一个卡在提示上的 - > 构建比一个失败的构建更难诊断;并且构建结果不应该取决于一次击键。 - > 可配置的上限 + 一条点名要改哪个文件的报错,回答的是同一个需求。 - ## 依赖产出的 host 工具(mcpp 2026.8.5.1+) 一个包能构建出消费者在**构建期**需要的二进制 —— `protoc`、`grpc_cpp_plugin`、 @@ -1035,3 +989,50 @@ mcpp 写的包仍然能加载,这个读取器认识的部分照常生效。在 —— 只有它拉进来的东西是条件性的 —— 因此在没有任何谓词匹配的平台上请求它,不是 「未知 feature」错误。 + + +## 当前边界 + +- **在主机上运行——交叉构建下也是**(mcpp 0.0.95+)。`mcpp build --target ` + 下,程序用宿主解析的工具链编译、在宿主运行,并看到 `MCPP_TARGET` = 交叉三元组。 + 纯声明式的目标门控仍首选 `[target.'cfg(...)']` 表——参见 + [04 - mcpp.toml 工程文件指南](04-mcpp-toml.md)。 +- **当前工作目录是工程根目录**,因此相对路径(`src/generated.cpp`)会落在预期位置。 +- `build.mcpp` 非零退出会中止构建并打印其输出。 +- **运行有时间上限**(mcpp 2026.8.5.1+):构建程序默认有 **600 秒**,超时后 mcpp + 杀掉它并让构建失败,错误里会点名是哪个包。可按包配置: + + ```toml + [build] + build_program_timeout = 1800 # 秒;0 = 不限 + ``` + + 优先级(与 `macos_deployment_target` 同构): + + ``` + MCPP_BUILD_PROGRAM_TIMEOUT=<秒> 本次调用 + > [build] build_program_timeout 拥有该 build.mcpp 的那个包的 manifest + > 600 内置默认 + ``` + + 值取自**拥有该构建程序的包**的 manifest,因为只有它的作者知道生成器要跑多久。 + 当一个**依赖**的构建程序超时时,错误会点名要改的那份 `mcpp.toml` —— 改自己的 + 那份不会有任何效果。 + + 不写这个键与写 `0` 不是一回事:不写=用默认上限,`0`=完全不设上限。 + + **这个上限从 mcpp 2026.8.11.1 起在所有平台生效**。此前它只在 POSIX 上生效: + Windows 的启动器会退回到无界路径,所以这个键——以及 `mcpp test --timeout`、 + `--build-timeout`——在那里都是静默的空操作。现在 Windows 把子进程放进 Job 对象, + 到期时关闭它,于是被杀掉的是**整棵进程树**而不只是直接子进程(否则一个还攥着 + 捕获管道的孙进程会让杀掉之后的读取一直挂住)。 + + **编译**这一步刻意**不设**上限——与 `mcpp test` 同一条不对称纪律: + 编译跑得久通常是正当的(首次构建 `std` 模块就是分钟级),杀掉它只会产生莫名其妙的 + 失败;而构建**程序**跑得久通常是卡住了,不设上限就会让整个构建挂死且毫无诊断。 + + > **为什么不做「超时时询问用户」**([#410](https://github.com/mcpp-community/mcpp/issues/410)): + > 构建程序的 stdout 已经被 dup2 进一根承载 `mcpp:` 指令协议的管道,没有交互通道; + > 多数构建发生在没有人看的地方(CI、流水线、ninja 的子进程),而一个卡在提示上的 + > 构建比一个失败的构建更难诊断;并且构建结果不应该取决于一次击键。 + > 可配置的上限 + 一条点名要改哪个文件的报错,回答的是同一个需求。 From b07c06c0eceb260e30bde768a551ca00442a977d Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:00:50 +0800 Subject: [PATCH 22/36] docs: a citation that names a section must land in the chapter that has it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The self-review found five citations, in two languages, reading `See *One package, one version* in [04 — mcpp.toml](04-mcpp-toml.md)`. The section moved to chapter 23 with the `[xlings]` split. Rule 3 could not see it: the path resolves, and the chapter is simply the wrong one. The earlier §-number sweep could not see it either, because this citation names its target by TITLE. All five are repointed, and rule 12 checks the shape: a citation of the form `See *X* in [NN](NN-….md)` -- and its 简体中文 form -- must land in a chapter whose text contains X. Falsified before committing. Two more from the same review pass: chapter 20 appeared in no scenario path, though "which compiler builds this" is a question every reader eventually has, and it joins scenario 1; and the limits section of chapter 30 was left mid-chapter by the split that appended a section after it. The duplicate-sentence scan across 27 chapters found two hits and neither is a duplicated explanation -- one is a shared cross-reference, which is R1 working, and the other is one sentence two chapters reached independently. --- .github/tools/check_docs_structure.sh | 27 ++++++++++++++++++++++++++ docs/02-scenarios.md | 4 +++- docs/23-the-project-environment.md | 2 +- docs/30-build-mcpp.md | 2 +- docs/31-authoring-a-rule-package.md | 2 +- docs/42-heterogeneous-builds.md | 2 +- docs/zh/02-scenarios.md | 4 +++- docs/zh/23-the-project-environment.md | 2 +- docs/zh/30-build-mcpp.md | 2 +- docs/zh/31-authoring-a-rule-package.md | 2 +- docs/zh/42-heterogeneous-builds.md | 2 +- 11 files changed, 41 insertions(+), 10 deletions(-) diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh index 6945bf9d..cc0a648a 100755 --- a/.github/tools/check_docs_structure.sh +++ b/.github/tools/check_docs_structure.sh @@ -15,6 +15,7 @@ # 9. every relative link in docs/ and examples/ resolves # 10. a translation carries the same tables and code blocks # 11. every chapter states its reader, its question and its exclusions +# 12. a citation naming a section lands in the chapter that contains it # # What it deliberately does NOT check: whether a chapter documents what is # implemented, whether an assertion's strength matches its evidence, or whether @@ -219,6 +220,32 @@ for f in docs/[0-9]*.md docs/zh/[0-9]*.md; do || bad "$f: the opening states no exclusions" done +# ── 12. a citation that names a section lands in the chapter that has it ─ +# +# `See *One package, one version* in [04](04-mcpp-toml.md)` survived a split +# that moved the section to chapter 23, in five places and two languages. Rule 3 +# could not see it -- the path resolved; it was the wrong chapter. A citation +# that names a section by TITLE is checkable against that chapter's headings. +python3 - <<'PYCITE' || fail=1 +import re, pathlib, sys +EN = re.compile(r"See \*([^*]{3,60})\* in \[\d{2}[^\]]*\]\((\d{2}-[a-z0-9-]+)\.md\)") +ZH = re.compile(r"见\s*\[\d{2}[^\]]*\]\((\d{2}-[a-z0-9-]+)\.md\)\s*的\*([^*]{2,40})\*") +bad = 0 +for f in list(pathlib.Path("docs").glob("[0-9]*.md")) + list(pathlib.Path("docs/zh").glob("[0-9]*.md")): + text = f.read_text(errors="ignore") + for m in EN.finditer(text): + title, chap = m.group(1), m.group(2) + t = (f.parent / f"{chap}.md") + if not t.exists() or title.lower() not in t.read_text(errors="ignore").lower(): + print(f"FAIL: {f}: cites *{title}* in {chap}, which does not contain it"); bad += 1 + for m in ZH.finditer(text): + chap, title = m.group(1), m.group(2) + t = (f.parent / f"{chap}.md") + if not t.exists() or title not in t.read_text(errors="ignore"): + print(f"FAIL: {f}: cites *{title}* in {chap}, which does not contain it"); bad += 1 +sys.exit(1 if bad else 0) +PYCITE + if [[ "$fail" -eq 0 ]]; then echo "OK: docs structure checks pass" fi diff --git a/docs/02-scenarios.md b/docs/02-scenarios.md index acd7cdfb..b12aa6d1 100644 --- a/docs/02-scenarios.md +++ b/docs/02-scenarios.md @@ -45,7 +45,9 @@ command produces a binary that carries what it needs. 3. [05 — Dependencies and Resolution](05-dependencies.md) — `[dependencies]`, the lock file. 4. [08 — Testing](08-testing.md) — `tests/**/*.cpp`. -5. [10 — Packaging an Application](10-pack-and-release.md) — `mcpp pack`. +5. [20 — Toolchain Management](20-toolchains.md) — when the compiler's version + matters, or the project must pin one. +6. [10 — Packaging an Application](10-pack-and-release.md) — `mcpp pack`. **Run.** [`examples/01-hello`](../examples/01-hello/), then [`02-with-deps`](../examples/02-with-deps/), then diff --git a/docs/23-the-project-environment.md b/docs/23-the-project-environment.md index 7e9128ab..fb56b48d 100644 --- a/docs/23-the-project-environment.md +++ b/docs/23-the-project-environment.md @@ -119,7 +119,7 @@ Where a project and a dependency name **one package**, one version of it is installed: identity is `(namespace, name)` and the version is a constraint on it. The declaration nearer the artifact wins and the override is reported; a pin that fails a requirement the other side stated is refused naming both. -See *One package, one version* in [04 — mcpp.toml](04-mcpp-toml.md). +See *One package, one version* in [23 — The Project Environment](23-the-project-environment.md). ## 4. Reading an environment, never creating one diff --git a/docs/30-build-mcpp.md b/docs/30-build-mcpp.md index 0ee3ce3e..0c01b145 100644 --- a/docs/30-build-mcpp.md +++ b/docs/30-build-mcpp.md @@ -355,7 +355,7 @@ could not state a floor and every project repeated its rule's package list. version this build actually installed rather than the one the local manifest wrote. One package means one version: where a project and a rule both name it, the declaration nearer the artifact wins and both sides are told the same -answer. See *One package, one version* in [04 — mcpp.toml](04-mcpp-toml.md). +answer. See *One package, one version* in [23 — The Project Environment](23-the-project-environment.md). **`[feature-xlings.]` is answered too, while `` is active** *(2026.9.6.2+)*. That table has provisioned its packages since it existed -- diff --git a/docs/31-authoring-a-rule-package.md b/docs/31-authoring-a-rule-package.md index 24c5a6b4..d6f5ebab 100644 --- a/docs/31-authoring-a-rule-package.md +++ b/docs/31-authoring-a-rule-package.md @@ -169,7 +169,7 @@ neither. A bare version is a **choice** a project may override; `>=` is a **requirement** a project may not go below. See *One package, one version* in -[04 — mcpp.toml](04-mcpp-toml.md). +[23 — The Project Environment](23-the-project-environment.md). ## Generating an island's boundary diff --git a/docs/42-heterogeneous-builds.md b/docs/42-heterogeneous-builds.md index c0692e03..420a3c30 100644 --- a/docs/42-heterogeneous-builds.md +++ b/docs/42-heterogeneous-builds.md @@ -303,7 +303,7 @@ the machines it will run on: The nearer declaration wins, one version is installed, and mcpp says which. A pin that does not satisfy the rule's floor is refused naming both sides rather than installed alongside it. See *One package, one version* in -[04 — mcpp.toml](04-mcpp-toml.md) for the full rule; +[23 — The Project Environment](23-the-project-environment.md) for the full rule; `examples/09-heterogeneous/multi-backend` is the one example in this repository that takes the override path, and every other one writes only the edge. diff --git a/docs/zh/02-scenarios.md b/docs/zh/02-scenarios.md index 9b858cef..26efe4c0 100644 --- a/docs/zh/02-scenarios.md +++ b/docs/zh/02-scenarios.md @@ -41,7 +41,9 @@ 2. [01 —— 快速开始](01-getting-started.md) —— 把程序跑起来。 3. [05 —— 依赖与解析](05-dependencies.md) —— `[dependencies]`、锁文件。 4. [08 —— 测试](08-testing.md) —— `tests/**/*.cpp`。 -5. [10 —— 发布打包](10-pack-and-release.md) —— `mcpp pack`。 +5. [20 —— 工具链管理](20-toolchains.md) —— 当编译器版本要紧,或者工程必须钉住 + 一个的时候。 +6. [10 —— 发布打包](10-pack-and-release.md) —— `mcpp pack`。 **跑。** [`examples/01-hello`](../../examples/01-hello/),然后 [`02-with-deps`](../../examples/02-with-deps/),再 diff --git a/docs/zh/23-the-project-environment.md b/docs/zh/23-the-project-environment.md index ee385f7a..9e19acc4 100644 --- a/docs/zh/23-the-project-environment.md +++ b/docs/zh/23-the-project-environment.md @@ -96,7 +96,7 @@ SubOS 里。前置让被声明的环境成为默认答案;其余的仍在它后 工程与依赖命名**同一个包**时,只装它的一个版本:身份是 `(namespace, name)`,版本是这个 包上的约束。离产物更近的声明赢,并且覆盖会被报出来;不满足对方所陈述之要求的钉会被拒绝 -并点出两侧。见 [04 — mcpp.toml](04-mcpp-toml.md) 的「一个包一个版本」。 +并点出两侧。见 [23 — The Project Environment](23-the-project-environment.md) 的「一个包一个版本」。 ## 4. 只读取环境,从不创建环境 diff --git a/docs/zh/30-build-mcpp.md b/docs/zh/30-build-mcpp.md index 673ea487..9af7c6cc 100644 --- a/docs/zh/30-build-mcpp.md +++ b/docs/zh/30-build-mcpp.md @@ -305,7 +305,7 @@ store 内部结构 —— 与 `dep_dir` 存在的理由相同。 **依赖声明的包同样被作答**(2026.9.6.6+),而且答的是这次构建**真正装上**的版本,不是 本地 manifest 写下的那个。一个包只有一个版本:工程与规则都命名它时,离产物更近的声明赢, -而两侧被告知同一个答案。见 [04 — mcpp.toml](04-mcpp-toml.md) 的「一个包一个版本」。 +而两侧被告知同一个答案。见 [23 — The Project Environment](23-the-project-environment.md) 的「一个包一个版本」。 **`[feature-xlings.]` 在 `` 生效时同样被作答**(2026.9.6.2+)。这张表从诞生 起就参与供给 —— 在那里写下一个包,它就会被下载并安装 —— 但构建程序的环境只由 diff --git a/docs/zh/31-authoring-a-rule-package.md b/docs/zh/31-authoring-a-rule-package.md index d2ade381..194afd4b 100644 --- a/docs/zh/31-authoring-a-rule-package.md +++ b/docs/zh/31-authoring-a-rule-package.md @@ -154,7 +154,7 @@ device link 在引擎侧的全部内容:N 个 `artifact` action,其输出不进 都不开。 裸版本是项目可以覆盖的**选择**;`>=` 是项目不得低于的**要求**。见 -[04 —— mcpp.toml](04-mcpp-toml.md) 的*一个包,一个版本*。 +[23 —— 项目环境](23-the-project-environment.md) 的*一个包一个版本*。 ## 生成岛的边界 diff --git a/docs/zh/42-heterogeneous-builds.md b/docs/zh/42-heterogeneous-builds.md index 105c1531..72301b67 100644 --- a/docs/zh/42-heterogeneous-builds.md +++ b/docs/zh/42-heterogeneous-builds.md @@ -248,7 +248,7 @@ plugins = { version = "0.3.0", features = ["rules-cuda"], host-module = true } ``` 更近的声明赢,装一个版本,并且 mcpp 说出用了哪条。不满足规则下界的钉会被拒绝并点出 -两侧,而不是与它并排装下来。完整规则见 [04 — mcpp.toml](04-mcpp-toml.md) 的 +两侧,而不是与它并排装下来。完整规则见 [23 — The Project Environment](23-the-project-environment.md) 的 「一个包一个版本」;`examples/09-heterogeneous/multi-backend` 是本仓库里唯一走覆盖 路径的示例,其余每一个都只写那条边。 From c3b2b6ea6228776c9c06e302ed93edd81421b8bd Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:17:35 +0800 Subject: [PATCH 23/36] =?UTF-8?q?docs:=20the=20ecosystem=20side=20?= =?UTF-8?q?=E2=80=94=20payloads,=20adapters=20and=20board-support=20packag?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The assessment of the finished book found a fourth reader the design had missed, and the measurement that found them is the sharpest in this batch: `xim:` payloads are named 84 times across 12 chapters. How to make one is explained nowhere. `xim-pkgindex` appears 17 times, all in the chapter about releasing mcpp. Every toolchain, device toolkit, shader compiler and emulator in this ecosystem is a payload. The documentation taught all of consuming them and none of producing them, and the omission was hidden by how often they are mentioned. Two more of the same shape: a `compat:` runtime adapter is named in four chapters and authored in none, and a board-support package is described from the consumer's side in 40 and 41 with no chapter on writing one. Three chapters, and `3x` becomes "extending mcpp and its ecosystem": 32 Authoring a Payload what an xim: descriptor must say, the two mirrors, `latest` as a reference, the exports that write a consumer's RPATH, and the `when` tier 33 Authoring a Runtime Adapter why a dlopen fails against a library that is plainly installed, and what a symlink farm plus runtime.library_dirs fixes 34 Authoring a Board-Support Package one package and two environments, the zero-libc tier, the machine table that is not a default, and run_exclusive Every fact is read out of a published package -- glslang.lua, compat.vulkan-runtime.lua, cortex-m-rt -- rather than out of the mechanism. The adapter chapter records a decision that is not a packaging choice: a proprietary driver's userspace is in ABI lockstep with a kernel module and its licence forbids redistribution, so it is a host capability and the adapter is how an artifact reaches it. An open driver is a payload and needs no adapter. Which answer applies is decided by the licence and the ABI, not by preference. Scenario 10 and a role-index row carry the new reader; the lookup index gains `xim:` and `compat:`. --- .../2026-09-08-the-documentation-as-a-book.md | 54 +++++++ docs/02-scenarios.md | 31 ++++ docs/32-authoring-a-payload.md | 139 ++++++++++++++++++ docs/33-authoring-an-adapter.md | 92 ++++++++++++ docs/34-authoring-a-bsp.md | 133 +++++++++++++++++ docs/README.md | 7 +- docs/zh/02-scenarios.md | 26 ++++ docs/zh/32-authoring-a-payload.md | 126 ++++++++++++++++ docs/zh/33-authoring-an-adapter.md | 77 ++++++++++ docs/zh/34-authoring-a-bsp.md | 118 +++++++++++++++ docs/zh/README.md | 7 +- 11 files changed, 808 insertions(+), 2 deletions(-) create mode 100644 docs/32-authoring-a-payload.md create mode 100644 docs/33-authoring-an-adapter.md create mode 100644 docs/34-authoring-a-bsp.md create mode 100644 docs/zh/32-authoring-a-payload.md create mode 100644 docs/zh/33-authoring-an-adapter.md create mode 100644 docs/zh/34-authoring-a-bsp.md diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 2129145a..e8599aae 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -395,3 +395,57 @@ belongs to `9x` or to SPEC-004. Both were named in §6 and neither is moved yet. reference chapter's own section list, so a key added to `04` and not indexed is invisible. The check is the same shape as rule 4 (every specification appears in every index) and is one loop. + +--- + +## 12. The ecosystem side, which the first design missed + +§5 designed the book for three readers — someone using mcpp, someone extending +one project's build, and someone changing mcpp. It missed a fourth, and the +measurement that found it is the sharpest in this record: + +> `xim:` payloads are named **84 times across 12 chapters**. How to make one is +> explained **nowhere**. `xim-pkgindex` appears 17 times, all of them in the +> chapter about releasing mcpp itself. + +Every toolchain, every device toolkit, every shader compiler and every emulator +in this ecosystem is a payload. The documentation taught the whole of consuming +them and none of producing them, and the omission was hidden by how often they +are mentioned. + +Two more of the same shape: a `compat:` runtime adapter — the layer that makes a +host library reachable from an artifact on Linux — is named in four chapters and +authored in none; and a board-support package is described from the consumer's +side in `40` and `41` with no chapter on writing one. + +### 12.1 Three chapters, and the band they complete + +`3x` was "extending the build graph" and is now **extending mcpp and its +ecosystem**: the two scales of one project's own step, then the three kinds of +package that serve everyone else. + +| # | chapter | reader | the one question | excludes | +|---|---|---|---|---| +| 32 | Authoring a Payload | someone packaging a tool or a prebuilt library | what is an `xim:` payload made of, and what must its descriptor say | source packages (11), host libraries (33) | +| 33 | Authoring a Runtime Adapter | someone making a host-supplied library reachable | why an artifact cannot see a library that is installed, and what fixes it | anything redistributable, which is a payload (32) | +| 34 | Authoring a Board-Support Package | someone bringing up a board | what does a BSP supply, and how does one package serve an emulator and hardware | using a BSP (40), the runner a consumer sees (41) | + +Each is written from a real published package rather than from the mechanism: +`xim-pkgindex/pkgs/g/glslang.lua`, `mcpp-index/pkgs/c/compat.vulkan-runtime.lua`, +and `mcpplibs/cortex-m-rt`. Every fact in them was read out of those files. + +### 12.2 The decision the adapter chapter records, which is not a packaging choice + +A proprietary driver's userspace is in ABI lockstep with a kernel module and its +licence forbids redistribution. Neither is solved by effort, so it is modelled +as a **host capability** and the adapter is how an artifact reaches it. An open +driver takes the other answer — it is a payload, and a machine using one needs +no adapter. **Which answer applies is decided by the licence and the ABI, not by +preference**, and that is the sentence a reader of scenario 10 leaves with. + +### 12.3 What this changes about §5's claim + +§5 said 24 chapters for a tool that is three tools is not too many. The count is +31 now, and the reason is that the tool is four things rather than three: a +build system, a package manager, a toolchain provisioner, **and an ecosystem +other people publish into**. The fourth had no chapters at all. diff --git a/docs/02-scenarios.md b/docs/02-scenarios.md index b12aa6d1..cf3fb4a3 100644 --- a/docs/02-scenarios.md +++ b/docs/02-scenarios.md @@ -28,6 +28,7 @@ people. Read only the scenario that matches; they do not build on each other. | [7](#7-compute-on-a-gpu-or-an-accelerator) | compute on a GPU or an accelerator | `examples/09-heterogeneous` | | [8](#8-graphics-rendering) | graphics rendering | `examples/10-graphics/offscreen` | | [9](#9-a-build-step-the-project-needs) | a build step the project needs, and sharing it | `examples/08-build-rules`, `12-a-new-device-language` | +| [10](#10-packaging-a-tool-a-driver-or-a-board) | packaging a tool, a driver or a board for others | the descriptors in `xim-pkgindex` and `mcpp-index` | ## 1. A command-line tool or service @@ -253,6 +254,36 @@ step. Without that, editing the generator leaves every edge clean and the artifact keeps the bytes the previous generator produced — a green build over a stale result. +## 10. Packaging a tool, a driver or a board + +**The situation.** Something other projects should be able to declare and get: +a compiler, a shader compiler, an emulator, a host graphics driver, a board. + +**What mcpp contributes.** A consumer declares it by name and gets a working +program — the payload is installed, its libraries are on the artifact's search +path, and the tier decides whether a build that never runs pays for it at all. + +**The path.** + +1. [32 — Authoring a Payload](32-authoring-a-payload.md) — a tool or a prebuilt + library mcpp installs. +2. [33 — Authoring a Runtime Adapter](33-authoring-an-adapter.md) — when the + library belongs to the host and cannot be redistributed. +3. [34 — Authoring a Board-Support Package](34-authoring-a-bsp.md) — a board, + its memory map, and the way in. +4. [31 — Authoring a Rule Package](31-authoring-a-rule-package.md) — if a build + step drives the tool. + +**Run.** The descriptors themselves: `xim-pkgindex/pkgs/g/glslang.lua` for a +payload, `mcpp-index/pkgs/c/compat.vulkan-runtime.lua` for an adapter, +`mcpplibs/cortex-m-rt` for a board. + +**What surprises people.** Which answer applies is decided by the licence and +the ABI, not by preference. A driver whose userspace is in lockstep with a +kernel module and whose licence forbids redistribution **cannot** be a payload; +it is a host capability, and the adapter is how an artifact reaches it. An open +driver is a payload, and a machine using one needs no adapter at all. + ## Current limitations - The scenarios here are the ones with a runnable project or a published diff --git a/docs/32-authoring-a-payload.md b/docs/32-authoring-a-payload.md new file mode 100644 index 00000000..7a1f2fd2 --- /dev/null +++ b/docs/32-authoring-a-payload.md @@ -0,0 +1,139 @@ +# 32 — Authoring a Payload + +**Reader:** someone packaging a tool or a prebuilt library so that mcpp projects +can declare it and mcpp installs it. + +**The question this chapter answers:** what is an `xim:` payload made of, and +what must its descriptor say for a consumer to name it and get a working +program. + +**Not here:** publishing a **source package** others `import`, which is +[11 — Publishing a Library](11-publishing-a-library.md); making a **host** +library reachable from an artifact, which is +[33 — Authoring a Runtime Adapter](33-authoring-an-adapter.md); and consuming a +payload, which is [23 — The Project Environment](23-the-project-environment.md). + +Before: [31 — Authoring a Rule Package](31-authoring-a-rule-package.md), whose +rules declare the payloads they drive. After: +[33 — Authoring a Runtime Adapter](33-authoring-an-adapter.md). + +## What a payload is + +Everything mcpp installs and does not compile: a compiler, a shader compiler, a +device toolkit, an emulator, a probe driver, a prebuilt C library. A project +names one in `[xlings.workspace]`, or a rule package names it in +`[feature-xlings.]`, and mcpp provisions it before the build runs. + +A payload lives in `xim-pkgindex` as one Lua file: a `package` table that +describes it, and two functions that place and register it. + +The shortest one that works: + +```lua +package = { + spec = "2", + name = "glslang", + description = "Khronos reference GLSL/ESSL front end and validator", + licenses = {"BSD-3-Clause", "Apache-2.0", "MIT"}, + type = "package", + archs = {"x86_64"}, + + xpm = { + linux = { + ["latest"] = { ref = "15.1.0" }, + ["15.1.0"] = { + url = { + GLOBAL = "https://github.com/…/glslang-15.1.0-linux-x86_64.tar.gz", + CN = "https://gitcode.com/…/glslang-15.1.0-linux-x86_64.tar.gz", + }, + sha256 = "87167c9cb32f258addbedb607639b2c1f484c029ba91542a92f19ead21d65d13", + }, + }, + }, +} + +function install() + local dir = pkginfo.install_dir() + os.tryrm(dir) + os.mv("glslang-15.1.0", dir) + return true +end + +function config() + xvm.add(package.name) + return true +end +``` + +`install()` places the extracted tree where mcpp will look for it; `config()` +registers what the payload offers. Everything else in this chapter is one of +those two doing more. + +## The four things a descriptor must get right + +**One version, two URLs.** Every version carries a `GLOBAL` and a `CN` URL and +one `sha256`. The two mirrors serve the same bytes; a consumer behind either +mirror resolves the same hash, and a descriptor with one URL is unusable for +half the ecosystem. + +**`latest` is a reference, not a version.** `["latest"] = { ref = "15.1.0" }`. +It is what a consumer gets when it names no version, and moving it is a +deliberate act — a consumer pinned to `15.1.0` is unaffected. + +**`archs` and the platform table are what a resolution reads.** A payload +published only for `linux` and `x86_64` says so, and a consumer on another +platform is refused by name rather than handed something that will not run. + +**Dependencies are `xim:` addresses with floors.** + +```lua +deps = { "xim:gcc-runtime@>=15", "xim:glibc@>=2.38" }, +``` + +## Making the payload reachable + +A payload that only unpacks is not usable. Three declarations turn a directory +into something a build can consume. + +**A program on the path.** `xvm.add(package.name)` registers the payload's +`bin/` so mcpp can find the program by its bare name. A rule package should +**name the program, not a path** — mcpp searches the `bin/` of every declared +payload and then `PATH`, and can report exactly which directories it searched. + +**Libraries a consumer will link or load.** + +```lua +exports = { + runtime = { libdirs = { "lib" } }, +}, +``` + +`elfpatch` reads this from each dependency and writes the consumer's `RPATH`, +which is what makes a stack of payloads resolve without anyone setting +`LD_LIBRARY_PATH`. + +**Headers, so a compiler in this environment can build against it.** +`sysroot.declare_libs(...)` and the header declaration place the payload into +the SubOS sysroot view. **Declared rather than copied** — xlings removes them +with the package, and a copy would outlive its owner. + +## The tier: when a payload is needed + +```toml +"xim:qemu-arm" = { version = "9.2.4-1", when = "run" } +``` + +`when` is a second, independent gate beside the feature that selects the +payload. The feature says **who** needs the tool; the tier says **when**. An +emulator is needed to run and not to compile, so a CI job that builds firmware +and never flashes it downloads nothing. + +## Current limitations + +- A payload is published to `xim-pkgindex`, which is a separate repository with + its own review; nothing in mcpp publishes one. +- `latest` and "the highest version in the table" are two different questions, + and a consumer that wants the newest published version names `latest`. +- A payload's own CI cannot verify that a consumer resolves it: that is what a + sandbox check against the published descriptor is for, and it is the only + thing that verifies the published bytes rather than the working tree. diff --git a/docs/33-authoring-an-adapter.md b/docs/33-authoring-an-adapter.md new file mode 100644 index 00000000..3fffef0b --- /dev/null +++ b/docs/33-authoring-an-adapter.md @@ -0,0 +1,92 @@ +# 33 — Authoring a Runtime Adapter + +**Reader:** someone making a library the **host** supplies reachable from an +mcpp-built artifact on Linux — a graphics driver, a Vulkan ICD, a proprietary +runtime. + +**The question this chapter answers:** why an artifact cannot see a library that +is plainly installed on the machine, and what a package has to declare to fix +it. + +**Not here:** packaging a library mcpp installs, which is +[32 — Authoring a Payload](32-authoring-a-payload.md) and is the right answer +whenever the library *can* be redistributed; and the runtime contract's fields, +which are [04 — The mcpp.toml Manifest](04-mcpp-toml.md) §`[runtime]`. + +Before: [32 — Authoring a Payload](32-authoring-a-payload.md). After: +[34 — Authoring a Board-Support Package](34-authoring-a-bsp.md). + +## The failure this exists for + +The library is installed. The loader finds its manifest. The `dlopen` fails +anyway: + +``` +DRIVER: Found the following files: /usr/share/vulkan/icd.d/lvp_icd.json … +ERROR: libvulkan_lvp.so: cannot open shared object file +``` + +The libraries are in `/usr/lib/x86_64-linux-gnu`. What cannot reach them is the +**process**: an mcpp-built binary runs under mcpp's own glibc, with its own +search path, + +``` +interp: …/xpkgs/xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2 +rpath : …/xim-x-glibc/2.39/lib64:…/xim-x-gcc/…/lib64:$ORIGIN +``` + +so a bare-soname `dlopen` from inside that process does not search the host's +library path at all. Nothing is broken; the artifact is simply not looking +there, which is the property that makes an mcpp build reproducible in the first +place. + +## What an adapter is + +**A symlink farm plus the metadata that makes it reachable.** Nothing is +vendored, nothing is redistributed, and the package carries no upstream bytes. +`runtime.library_dirs` puts a package-owned directory of symlinks on the +artifact's runtime search path, and the chain resolves. + +A project declares the adapter as an ordinary dependency and does nothing else. + +## Why the driver itself is not a package + +A proprietary driver's userspace is in **ABI lockstep with a kernel module**, +and its licence forbids redistribution. Neither is a packaging problem that +effort solves, so such a driver is modelled as a **host capability** — something +the machine either has or does not — and the adapter is how an artifact reaches +it. + +An **open** driver is a different case and takes the other answer: it is a +payload (`xim:mesa-lavapipe` for the CPU, `xim:mesa` for AMD hardware), and a +machine using one needs nothing from the farm. Since 2026.09.05 an adapter also +prefers the payload when one is published and its symbol set covers the host +copy, so what the farm actually records is proprietary userspace and packaging +backlog. + +## The three things an adapter gets wrong + +**The pattern list must cover transitive dependencies.** The whole chain has to +resolve through the same directory. Mesa's software rasteriser pulls in LLVM; +an NVIDIA driver pulls its own family. Listing the ICD alone produces the same +`cannot open shared object file` one level down. + +**`libstdc++` belongs in the list, and it is not an oversight.** mcpp links +libstdc++ **statically** — it is absent from a built binary's `NEEDED` — so a +`dlopen`ed C++ driver has nothing to resolve against unless the host copy is +provided here. + +**Nothing may be required.** A machine with no such driver at all is a +legitimate configuration, and every CI runner in this ecosystem is one. The farm +is then empty and the program reports what it actually found. An adapter that +errors on a missing host library turns a supported configuration into a build +failure. + +## Current limitations + +- **Linux only, by construction.** macOS's dyld and the Windows PE loader have + no equivalent layer, so a project targeting them declares no adapter. +- An adapter cannot make a driver work that the machine does not have. It + removes one obstacle — reachability — and reports the rest as absence. +- The farm's contents are decided when the adapter is installed. A driver + installed afterwards is not picked up until the adapter is reinstalled. diff --git a/docs/34-authoring-a-bsp.md b/docs/34-authoring-a-bsp.md new file mode 100644 index 00000000..ee0c8856 --- /dev/null +++ b/docs/34-authoring-a-bsp.md @@ -0,0 +1,133 @@ +# 34 — Authoring a Board-Support Package + +**Reader:** someone bringing up a board so that projects can target it with +`mcpp build` and reach it with `mcpp run`. + +**The question this chapter answers:** what does a board-support package supply, +and how does one package serve both an emulator and the physical board. + +**Not here:** using a BSP, which is +[40 — Bare-Metal and Freestanding Targets](40-baremetal.md); the runner +mechanism a consumer sees, which is [41 — Reaching a Device](41-devices.md); +and packaging the emulator or the probe driver themselves, which is +[32 — Authoring a Payload](32-authoring-a-payload.md). + +Before: [33 — Authoring a Runtime Adapter](33-authoring-an-adapter.md). + +## What a BSP supplies + +A freestanding target has no operating system, so everything a hosted program +gets for free has to come from somewhere. A BSP is that somewhere, and it +supplies **the whole target world**: + +| | what it is | +|---|---| +| the memory map | a linker script — the one fact a program can neither derive nor guess | +| the startup code | what runs before `main`, and the vector table | +| an exported module | what the program imports to reach the board's console and peripherals | +| **the runner** | how `mcpp run` and `mcpp test` reach the board at all | + +The runner is the one that is easy to leave out, and without it every consumer +writes its own emulator invocation. + +## One package, two environments + +A board reached through an emulator and the same board reached through a debug +probe differ **in the argv of their runners and in nothing else**. The linker +script, the startup code, the memory map and the exported module are the same +board. Publishing two packages to vary four strings duplicates all of it and +lets the copies drift. + +So the environment is a **feature**: + +```toml +[features] +default = ["emulator"] +emulator = {} +hardware = {} + +[feature-xlings.emulator] +"xim:qemu-arm" = { version = "9.2.4-1", when = "run" } + +[feature-xlings.hardware] +"xim:probe-rs" = { version = "", when = "run" } +``` + +**`emulator` is the default, and that is a decision about who is reading.** +Someone meeting the package has no board on their desk; someone who does has a +reason to say so. A default that required hardware would make the first command +fail for everyone who has not bought anything yet. + +The two tables are symmetric on purpose: neither environment is the engine's +idea of normal, and a consumer downloads exactly what the feature it selected +needs. + +**And both are on the `run` tier, which is a second and independent gate.** The +feature says *who* needs the tool; the tier says *when*. Compiling firmware needs +neither — only reaching the board does — so a CI job that builds and never +flashes downloads nothing at all. + +## The C library is a feature too + +Every `thumb*-none-eabi*` row carries an empty C-library column, so a project +targeting one begins with **no libc** unless it asks. A BSP stays there: it +references no C library symbol, and its console goes through semihosting rather +than through `stdio`. + +```toml +libc = {} + +[feature-deps.libc] +picolibc.picolibc = "1.8.12.3" +``` + +A C library arrives as a **source** package compiled with the program's own +flags, so there is no multilib to match and no ABI convention to get wrong. +`mcpp run --features libc` is the whole of it, and not selecting it leaves the +zero-libc tier exactly as it was. + +## The build program + +```cpp +int main() { + mcpp::link_script("cortex-m.ld"); + mcpp::rerun_if_changed("cortex-m.ld"); + + const std::string target = mcpp::target() ? mcpp::target() : ""; + if (mcpp::has_feature("hardware")) { + for (auto a : {"probe-rs", "run", "--chip", "STM32L475VG"}) + mcpp::runner(a); + mcpp::run_exclusive(); + } else { + /* the emulator's argv for THIS target */ + } + return 0; +} +``` + +**Which machine models a target is a table, not a default.** An image built for +`thumbv6m` does not run on a model that implements `thumbv7em`, and a wrong +guess boots and then faults somewhere unrelated. A target with no row is an +**error that names it**, not a silent absence — a BSP that configured no runner +would leave `mcpp run` reporting a missing runner and advising a `runner` key, +which is true in general and not the cause here. + +**Name the program, not a path.** mcpp searches the `bin/` of every payload +declared by any package in the graph, then `PATH`, and reports exactly which +directories it searched. An absolute path computed from an install directory +introduces a failure mode where the lookup returns empty and nothing says why. + +**The engine knows none of the runner names.** `flash`, `serve` and `erase` are +this package's vocabulary; a different package supplies `serve` meaning +something else, and neither has to be known to mcpp. + +**One probe, one user.** `mcpp::run_exclusive()` states that this target's runs +cannot overlap, and `mcpp test` then serialises them. A project never has to +remember `-j1`. + +## Current limitations + +- A BSP declares its emulator and its probe driver as payloads, so a board + whose tooling is not published for a platform cannot be reached from it. +- The machine table is per-triple. A board that needs a model the table does not + carry is a change to the BSP, not a project-side override. diff --git a/docs/README.md b/docs/README.md index 119bc0b6..84c3b96e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,6 +32,7 @@ anything a reader needs is written here or in a specification instead. | use a GPU or an accelerator | [42](42-heterogeneous-builds.md), [41](41-devices.md) | [`09-heterogeneous`](../examples/09-heterogeneous/), starting at [`boundary/`](../examples/09-heterogeneous/boundary/) | | add a rule, a language or a generator | [31](31-authoring-a-rule-package.md), [30](30-build-mcpp.md) | [`08-build-rules`](../examples/08-build-rules/), [`12-a-new-device-language`](../examples/12-a-new-device-language/) | | add a package to the index | [11](11-publishing-a-library.md), [SPEC-001](specs/package-identity.md) | [09](09-commands-by-scenario.md) — the publishing scenarios | +| package a tool, a driver or a board for others | [32](32-authoring-a-payload.md), [33](33-authoring-an-adapter.md), [34](34-authoring-a-bsp.md) | the descriptors in `xim-pkgindex` and `mcpp-index` | | change mcpp itself | [90](90-build-from-source.md), [92](92-release.md), [51](51-supported-versions.md) | — | Lessons also arrive as project templates, which a package ships and `mcpp new @@ -74,10 +75,13 @@ order, not an alphabet. - [23 — The Project Environment](23-the-project-environment.md) - [24 — Cross-Compilation Over openkal](24-openkal-cross.md) -### 3x — Extending the build graph +### 3x — Extending mcpp and its ecosystem - [30 — Build Programs: `build.mcpp`](30-build-mcpp.md) — a project that needs a step mcpp has no rule for - [31 — Authoring a Rule Package](31-authoring-a-rule-package.md) — packaging that step for other projects +- [32 — Authoring a Payload](32-authoring-a-payload.md) — a tool or a prebuilt library mcpp installs +- [33 — Authoring a Runtime Adapter](33-authoring-an-adapter.md) — reaching a library the host supplies +- [34 — Authoring a Board-Support Package](34-authoring-a-bsp.md) — a board, and the way in ### 4x — Devices and accelerators @@ -131,6 +135,7 @@ token in front of a reader to the chapter that owns it. | a target triple, the support matrix | [21](21-the-target-triple.md) | a runner, a named runner | [41](41-devices.md) | | an island, a seam, `accel` | [42](42-heterogeneous-builds.md) | a descriptor, an index | [11](11-publishing-a-library.md) | | an ABI tag, a prebuilt artifact | [12](12-binary-distribution.md) | exit codes, JSON output | [50](50-machine-output.md) | +| an `xim:` payload, `[xlings.workspace]` | [32](32-authoring-a-payload.md), [23](23-the-project-environment.md) | a `compat:` adapter, `runtime.library_dirs` | [33](33-authoring-an-adapter.md) | ## Specifications diff --git a/docs/zh/02-scenarios.md b/docs/zh/02-scenarios.md index 26efe4c0..73e70618 100644 --- a/docs/zh/02-scenarios.md +++ b/docs/zh/02-scenarios.md @@ -26,6 +26,7 @@ | [7](#7-gpu-或加速器上的计算) | GPU 或加速器上的计算 | `examples/09-heterogeneous` | | [8](#8-图形渲染) | 图形渲染 | `examples/10-graphics/offscreen` | | [9](#9-工程需要的一步构建工作) | 工程需要的一步构建工作,以及把它共享出去 | `examples/08-build-rules`、`12-a-new-device-language` | +| [10](#10-为别人打包一个工具一个驱动或一块板子) | 为别人打包一个工具、一个驱动或一块板子 | `xim-pkgindex` 与 `mcpp-index` 里的描述符 | ## 1. 命令行工具或服务 @@ -214,6 +215,31 @@ 生成器会让每条边都是干净的,产物保留上一个生成器产生的字节 —— 一次覆盖在陈旧结果 之上的绿色构建。 +## 10. 为别人打包一个工具、一个驱动或一块板子 + +**处境。** 别的工程应当能够声明并得到的东西:一个编译器、一个着色器编译器、一个 +模拟器、一个宿主图形驱动、一块板子。 + +**mcpp 贡献了什么。** 消费者按名字声明它就能得到一个能用的程序 —— 载荷被安装、它的 +库进了产物的搜索路径,而档位决定「一次从不运行的构建要不要为它付代价」。 + +**路径。** + +1. [32 —— 编写一个载荷](32-authoring-a-payload.md) —— 由 mcpp 安装的工具或预编译库。 +2. [33 —— 编写运行时适配包](33-authoring-an-adapter.md) —— 当那个库属于宿主、 + 不可再分发时。 +3. [34 —— 编写板级支持包](34-authoring-a-bsp.md) —— 一块板子、它的内存布局,以及 + 抵达它的方式。 +4. [31 —— 编写规则包](31-authoring-a-rule-package.md) —— 如果有一步构建工作要驱动 + 这个工具。 + +**跑。** 描述符本身:载荷看 `xim-pkgindex/pkgs/g/glslang.lua`,适配包看 +`mcpp-index/pkgs/c/compat.vulkan-runtime.lua`,板子看 `mcpplibs/cortex-m-rt`。 + +**最让人意外的一件事。** 该用哪个答案,由**许可证与 ABI** 决定,不由偏好决定。 +用户态与内核模块处于锁步、且许可证禁止再分发的驱动**不能**是载荷;它是宿主能力, +而适配包是产物够到它的方式。开源驱动就是载荷,用着它的机器完全不需要适配包。 + ## 当前边界 - 这里列出的场景,都是背后有**可运行的工程或已发布模板**的那些。mcpp 服务得了、 diff --git a/docs/zh/32-authoring-a-payload.md b/docs/zh/32-authoring-a-payload.md new file mode 100644 index 00000000..ce50bceb --- /dev/null +++ b/docs/zh/32-authoring-a-payload.md @@ -0,0 +1,126 @@ +# 32 —— 编写一个载荷 + +**读者:**要把一个工具或一份预编译库打包出来,好让 mcpp 工程声明它、由 mcpp 安装 +它的人。 + +**本章回答的那一个问题:**一个 `xim:` 载荷由什么构成,以及它的描述符必须说清什么, +消费者才能点名它并得到一个能用的程序。 + +**不在这里:**发布供他人 `import` 的**源码包**,那是 +[11 —— 发布一个库](11-publishing-a-library.md);让**宿主**的库能被产物够到,那是 +[33 —— 编写运行时适配包](33-authoring-an-adapter.md);以及消费一个载荷,那是 +[23 —— 项目环境](23-the-project-environment.md)。 + +在此之前:[31 —— 编写规则包](31-authoring-a-rule-package.md) —— 规则声明它所驱动的 +载荷。在此之后:[33 —— 编写运行时适配包](33-authoring-an-adapter.md)。 + +## 载荷是什么 + +一切由 mcpp 安装而不编译的东西:编译器、着色器编译器、设备工具包、模拟器、探针 +驱动、预编译的 C 库。工程在 `[xlings.workspace]` 里点名它,或者规则包在 +`[feature-xlings.]` 里点名它,mcpp 在构建之前把它供给到位。 + +一个载荷在 `xim-pkgindex` 里是一个 Lua 文件:一个描述它的 `package` 表,加两个把 +它放好并登记的函数。 + +能用的最短形态: + +```lua +package = { + spec = "2", + name = "glslang", + description = "Khronos reference GLSL/ESSL front end and validator", + licenses = {"BSD-3-Clause", "Apache-2.0", "MIT"}, + type = "package", + archs = {"x86_64"}, + + xpm = { + linux = { + ["latest"] = { ref = "15.1.0" }, + ["15.1.0"] = { + url = { + GLOBAL = "https://github.com/…/glslang-15.1.0-linux-x86_64.tar.gz", + CN = "https://gitcode.com/…/glslang-15.1.0-linux-x86_64.tar.gz", + }, + sha256 = "87167c9cb32f258addbedb607639b2c1f484c029ba91542a92f19ead21d65d13", + }, + }, + }, +} + +function install() + local dir = pkginfo.install_dir() + os.tryrm(dir) + os.mv("glslang-15.1.0", dir) + return true +end + +function config() + xvm.add(package.name) + return true +end +``` + +`install()` 把解开的目录树放到 mcpp 会去找的位置;`config()` 登记这个载荷提供什么。 +本章其余内容,都是这两个函数在多做一些事。 + +## 描述符必须做对的四件事 + +**一个版本,两个 URL。** 每个版本都带 `GLOBAL` 与 `CN` 两个 URL 和一个 `sha256`。 +两个镜像服务同一份字节;在任一镜像后面的消费者解析到同一个哈希,而只写一个 URL 的 +描述符对半个生态不可用。 + +**`latest` 是一个引用,不是一个版本。** `["latest"] = { ref = "15.1.0" }`。它是 +消费者不点名版本时拿到的东西,移动它是一个刻意的动作 —— 钉在 `15.1.0` 的消费者 +不受影响。 + +**`archs` 与平台表是解析要读的东西。** 只为 `linux` 与 `x86_64` 发布的载荷就这样 +声明,于是别的平台上的消费者会被**点名拒绝**,而不是拿到一个跑不起来的东西。 + +**依赖是带下界的 `xim:` 地址。** + +```lua +deps = { "xim:gcc-runtime@>=15", "xim:glibc@>=2.38" }, +``` + +## 让载荷可被够到 + +只会解包的载荷不可用。三条声明把一个目录变成构建能消费的东西。 + +**路径上的一个程序。** `xvm.add(package.name)` 登记该载荷的 `bin/`,于是 mcpp 能 +按裸名找到这个程序。规则包应当**写程序名,不写路径** —— mcpp 会搜索图中每个被声明 +载荷的 `bin/`,然后才是 `PATH`,并且能准确报出它搜过哪些目录。 + +**消费者要链接或加载的库。** + +```lua +exports = { + runtime = { libdirs = { "lib" } }, +}, +``` + +`elfpatch` 从每个依赖里读这一项,写进消费者的 `RPATH` —— 这正是一叠载荷不需要任何人 +设 `LD_LIBRARY_PATH` 就能解析的原因。 + +**头文件,好让这个环境里的编译器能对着它构建。** `sysroot.declare_libs(...)` 与 +头文件声明把载荷放进 SubOS 的 sysroot 视图。**是声明而不是复制** —— xlings 会随包 +一起移除它们,而一份复制会比它的主人活得更久。 + +## 档位:载荷什么时候被需要 + +```toml +"xim:qemu-arm" = { version = "9.2.4-1", when = "run" } +``` + +`when` 是选中该载荷的那个 feature 之外的**第二道独立闸门**。feature 说**谁**需要这个 +工具;档位说**什么时候**。模拟器是运行时需要、编译时不需要的,所以一个只构建固件、 +从不烧录的 CI 任务**一个字节都不下载**。 + +## 当前边界 + +- 载荷发布到 `xim-pkgindex` —— 那是一个独立仓库,有它自己的评审;mcpp 里没有任何 + 东西发布载荷。 +- `latest` 与「表里版本号最大的那个」是两个不同的问题,想要最新已发布版本的消费者 + 应当点名 `latest`。 +- 载荷自己的 CI 无法核验消费者能否解析到它:那是「对着已发布描述符做沙箱核验」的 + 用途,也是唯一核验**已发布字节**而不是工作树的东西。 diff --git a/docs/zh/33-authoring-an-adapter.md b/docs/zh/33-authoring-an-adapter.md new file mode 100644 index 00000000..88a7b3f4 --- /dev/null +++ b/docs/zh/33-authoring-an-adapter.md @@ -0,0 +1,77 @@ +# 33 —— 编写运行时适配包 + +**读者:**要让**宿主**提供的某个库能被 mcpp 产物在 Linux 上够到的人 —— 图形驱动、 +Vulkan ICD、某个专有运行时。 + +**本章回答的那一个问题:**为什么产物看不见一个明明装在机器上的库,以及一个包要 +声明什么才能修好它。 + +**不在这里:**打包一个由 mcpp 安装的库,那是 +[32 —— 编写一个载荷](32-authoring-a-payload.md) —— 只要那个库**可以**被再分发,它 +就是正确答案;以及运行时契约的字段,那是 +[04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md) 的 `[runtime]`。 + +在此之前:[32 —— 编写一个载荷](32-authoring-a-payload.md)。在此之后: +[34 —— 编写板级支持包](34-authoring-a-bsp.md)。 + +## 它为之存在的那次失败 + +库装好了。加载器也找到了它的 manifest。`dlopen` 仍然失败: + +``` +DRIVER: Found the following files: /usr/share/vulkan/icd.d/lvp_icd.json … +ERROR: libvulkan_lvp.so: cannot open shared object file +``` + +库就在 `/usr/lib/x86_64-linux-gnu`。够不到它们的是**进程**:mcpp 构建出的二进制跑在 +mcpp 自己的 glibc 之下,带着它自己的搜索路径, + +``` +interp: …/xpkgs/xim-x-glibc/2.39/lib64/ld-linux-x86-64.so.2 +rpath : …/xim-x-glibc/2.39/lib64:…/xim-x-gcc/…/lib64:$ORIGIN +``` + +于是从那个进程内部发起的、按裸 soname 的 `dlopen`,**根本不会去搜索宿主的库路径**。 +没有任何东西坏掉;产物只是不往那里看 —— 而这正是 mcpp 构建可复现的那条性质本身。 + +## 适配包是什么 + +**一个符号链接农场,加上让它可被够到的那点元数据。** 不内嵌任何东西、不再分发任何 +东西,包里没有上游的字节。`runtime.library_dirs` 把一个包自有的符号链接目录放上产物 +的运行期搜索路径,于是整条链解析得开。 + +工程把适配包当作一个普通依赖声明,除此之外什么都不做。 + +## 为什么驱动本身不能是一个包 + +专有驱动的用户态与**内核模块处于 ABI 锁步**,而它的许可证禁止再分发。这两条都不是 +靠努力能解决的打包问题,所以这类驱动被建模为**宿主能力** —— 机器要么有、要么没有 +—— 而适配包是产物够到它的方式。 + +**开源**驱动是另一种情形,取另一个答案:它就是一个载荷(CPU 上是 +`xim:mesa-lavapipe`,AMD 硬件上是 `xim:mesa`),而用着它的机器完全不需要这个农场。 +自 2026.09.05 起,当某个已发布载荷提供了同名库、且它的符号集合覆盖宿主那份时,适配包 +优先取载荷 —— 于是农场实际记录下来的,是专有用户态与打包欠账。 + +## 适配包最容易做错的三件事 + +**模式列表必须覆盖传递依赖。** 整条链都要经由同一个目录解析。Mesa 的软件光栅器会 +拉进 LLVM;NVIDIA 驱动会拉进它自己那一族。只列 ICD 一个,结果是同一句 +`cannot open shared object file` 往下挪一层。 + +**`libstdc++` 应当在列表里,这不是疏忽。** mcpp **静态**链接 libstdc++ —— 它不出现在 +构建产物的 `NEEDED` 里 —— 所以一个被 `dlopen` 的 C++ 驱动,除非宿主那份在这里被提供, +否则没有任何东西可供它解析。 + +**不得有任何一项是必需的。** 一台完全没有这类驱动的机器是**合法配置**,而这个生态的 +每一台 CI runner 都是这样的机器。此时农场为空,程序报告它实际找到了什么。一个在宿主库 +缺失时报错的适配包,会把一种受支持的配置变成构建失败。 + +## 当前边界 + +- **按构造只适用于 Linux。** macOS 的 dyld 与 Windows 的 PE 加载器没有对应的这一层, + 因此面向它们的工程不声明适配包。 +- 适配包无法让一台机器上不存在的驱动工作起来。它只移除一个障碍 —— 可达性 —— 其余的 + 按「缺席」如实报告。 +- 农场的内容在适配包被安装时决定。之后才装上的驱动,要到适配包被重新安装时才会被 + 纳入。 diff --git a/docs/zh/34-authoring-a-bsp.md b/docs/zh/34-authoring-a-bsp.md new file mode 100644 index 00000000..a5bee8db --- /dev/null +++ b/docs/zh/34-authoring-a-bsp.md @@ -0,0 +1,118 @@ +# 34 —— 编写板级支持包 + +**读者:**要把一块板子支起来,好让工程能用 `mcpp build` 面向它、用 `mcpp run` +抵达它的人。 + +**本章回答的那一个问题:**一个板级支持包供给什么,以及一个包怎样同时服务模拟器与 +真实板子。 + +**不在这里:**使用一个 BSP,那是 +[40 —— 裸机与 freestanding 目标](40-baremetal.md);消费者看到的 runner 机制,那是 +[41 —— 抵达一台设备](41-devices.md);以及把模拟器或探针驱动本身打包,那是 +[32 —— 编写一个载荷](32-authoring-a-payload.md)。 + +在此之前:[33 —— 编写运行时适配包](33-authoring-an-adapter.md)。 + +## BSP 供给什么 + +freestanding 目标没有操作系统,所以 hosted 程序白得的一切都必须来自某处。BSP 就是 +那个某处,它供给**整个目标世界**: + +| | 是什么 | +|---|---| +| 内存布局 | 一份链接脚本 —— 程序既推导不出、也猜不到的那一个事实 | +| 启动代码 | `main` 之前跑的东西,以及向量表 | +| 一个导出模块 | 程序 import 它来够到板子的控制台与外设 | +| **runner** | `mcpp run` 与 `mcpp test` 究竟怎样抵达这块板子 | + +最容易被漏掉的是 runner;少了它,每个消费者都要自己写一遍模拟器的调用。 + +## 一个包,两种环境 + +经模拟器抵达的板子,与经调试探针抵达的同一块板子,**差别只在 runner 的 argv,别处 +一个字都不差**。链接脚本、启动代码、内存布局与导出模块是同一块板子。为了改四个字符串 +而发两个包,会把这一切复制一遍,并让两份副本各自漂移。 + +所以环境是一个 **feature**: + +```toml +[features] +default = ["emulator"] +emulator = {} +hardware = {} + +[feature-xlings.emulator] +"xim:qemu-arm" = { version = "9.2.4-1", when = "run" } + +[feature-xlings.hardware] +"xim:probe-rs" = { version = "", when = "run" } +``` + +**`emulator` 是默认,而这是一个关于「谁在读」的决定。** 刚接触这个包的人手边没有 +板子;手边有板子的人有理由把它说出来。一个要求硬件的默认值,会让所有还没买东西的人 +的第一条命令就失败。 + +两张表刻意对称:哪种环境都不是引擎眼里的「正常」,而消费者只下载它所选 feature 需要 +的那一份。 + +**并且两者都在 `run` 档位上 —— 这是第二道独立的闸门。** feature 说**谁**需要这个工具; +档位说**什么时候**。编译固件两者都不需要,只有抵达板子才需要,所以一个只构建、从不 +烧录的 CI 任务**一个字节都不下载**。 + +## C 库也是一个 feature + +每一行 `thumb*-none-eabi*` 的 C 库列都是空的,所以面向它的工程一开始**没有 libc**, +除非它开口要。BSP 停在这一档:它不引用任何 C 库符号,它的控制台走 semihosting 而不是 +`stdio`。 + +```toml +libc = {} + +[feature-deps.libc] +picolibc.picolibc = "1.8.12.3" +``` + +C 库以**源码包**的形式到达,用程序自己的旗标编译,于是没有 multilib 要匹配、也没有 +ABI 约定会弄错。`mcpp run --features libc` 就是全部;不选它,零 libc 档原样保留。 + +## 构建程序 + +```cpp +int main() { + mcpp::link_script("cortex-m.ld"); + mcpp::rerun_if_changed("cortex-m.ld"); + + const std::string target = mcpp::target() ? mcpp::target() : ""; + if (mcpp::has_feature("hardware")) { + for (auto a : {"probe-rs", "run", "--chip", "STM32L475VG"}) + mcpp::runner(a); + mcpp::run_exclusive(); + } else { + /* 这个目标对应的模拟器 argv */ + } + return 0; +} +``` + +**哪个机器型号对应哪个目标,是一张表,不是一个默认值。** 为 `thumbv6m` 构建的镜像 +在实现 `thumbv7em` 的型号上跑不起来,而猜错的结果是它启动了、然后在毫不相干的地方 +faul。表里没有这一行时要**点名报错**,而不是静默缺席 —— 一个没有配置 runner 的 BSP +会让 `mcpp run` 报「缺少 runner」并建议加一个 `runner` 键,那句话一般而言是对的,而 +在这里不是原因。 + +**写程序名,不写路径。** mcpp 会搜索图中任何包声明过的每一个载荷的 `bin/`,然后才是 +`PATH`,并且能准确报出它搜过哪些目录。用安装目录拼出来的绝对路径引入了一种失败形态: +查找返回空,而没有任何东西说明为什么。 + +**引擎不认识上面任何一个 runner 名字。** `flash`、`serve`、`erase` 是这个包的词汇; +另一个包提供的 `serve` 可以是别的意思,而两者都不必被 mcpp 知道。 + +**一个探针,一个使用者。** `mcpp::run_exclusive()` 声明这个目标的运行不可重叠, +`mcpp test` 随后把它们串行化。工程方永远不需要记得加 `-j1`。 + +## 当前边界 + +- BSP 把它的模拟器与探针驱动声明为载荷,因此工具链未在某个平台发布的板子,就无法 + 从那个平台抵达。 +- 机器型号表是按三元组组织的。需要表里没有的型号的板子,要改 BSP,而不是在工程侧 + 覆盖。 diff --git a/docs/zh/README.md b/docs/zh/README.md index 2cf5d8a3..ab8d324d 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -29,6 +29,7 @@ | 使用 GPU 或加速器 | [42](42-heterogeneous-builds.md)、[41](41-devices.md) | [`09-heterogeneous`](../../examples/09-heterogeneous/),从 [`boundary/`](../../examples/09-heterogeneous/boundary/) 开始 | | 增加一条规则、一种语言或一个生成器 | [31](31-authoring-a-rule-package.md)、[30](30-build-mcpp.md) | [`08-build-rules`](../../examples/08-build-rules/)、[`12-a-new-device-language`](../../examples/12-a-new-device-language/) | | 向索引添加一个包 | [11](11-publishing-a-library.md)、[SPEC-001](../specs/package-identity.md) | [09](09-commands-by-scenario.md) —— 发布相关场景 | +| 为别人打包一个工具、一个驱动或一块板子 | [32](32-authoring-a-payload.md)、[33](33-authoring-an-adapter.md)、[34](34-authoring-a-bsp.md) | `xim-pkgindex` 与 `mcpp-index` 里的描述符 | | 修改 mcpp 本身 | [90](90-build-from-source.md)、[92](92-release.md)、[51](51-supported-versions.md) | — | 课程也可以以**项目模板**的形式到达:模板由包提供,`mcpp new --template` 实例化 @@ -68,10 +69,13 @@ - [23 —— 项目环境](23-the-project-environment.md) - [24 —— 基于 openkal 的交叉构建](24-openkal-cross.md) -### 3x —— 扩展构建图 +### 3x —— 扩展 mcpp 与它的生态 - [30 —— 构建程序:`build.mcpp`](30-build-mcpp.md) —— 工程需要一步 mcpp 没有规则的工作 - [31 —— 编写规则包](31-authoring-a-rule-package.md) —— 把那一步打包给别的工程用 +- [32 —— 编写一个载荷](32-authoring-a-payload.md) —— 由 mcpp 安装的工具或预编译库 +- [33 —— 编写运行时适配包](33-authoring-an-adapter.md) —— 够到宿主提供的库 +- [34 —— 编写板级支持包](34-authoring-a-bsp.md) —— 一块板子,以及抵达它的方式 ### 4x —— 设备与加速器 @@ -125,6 +129,7 @@ | 目标三元组、支持矩阵 | [21](21-the-target-triple.md) | runner、具名 runner | [41](41-devices.md) | | 岛、接缝、`accel` | [42](42-heterogeneous-builds.md) | 描述符、索引 | [11](11-publishing-a-library.md) | | ABI tag、预建产物 | [12](12-binary-distribution.md) | 退出码、JSON 输出 | [50](50-machine-output.md) | +| `xim:` 载荷、`[xlings.workspace]` | [32](32-authoring-a-payload.md)、[23](23-the-project-environment.md) | `compat:` 适配包、`runtime.library_dirs` | [33](33-authoring-an-adapter.md) | ## 规范文档 From fcd10d8b6d6bc3421ba585a0ef57bc26632c318a Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:19:57 +0800 Subject: [PATCH 24/36] docs: the contributor chapter, rewritten from the tree it describes `90-build-from-source` was 116 lines and its source layout was wrong: it listed `src/manifest/` and `src/platform/`, which moved into `modules/` in the workspace refactor, and it named none of the nine workspace members. A contributor reading it looked for directories that are not there. Rewritten from the tree: nine workspace members with what each holds, the engine's own directories, and the real test counts -- 108 unit tests and 370 end-to-end scripts. Three sections it did not have, and a contributor needs all three: the capability gate `# requires: elf gcc` decides which e2e scripts run, and a script requiring a capability no job provides never runs anywhere -- its greenness means nothing writing a check that after writing a check, remove the fix and run it measures something once. Three shapes it catches, each of which has shipped here: a criterion that never runs, one that cannot fail, and one applied to the wrong object. And: state the denominator, because an empty enumeration also finds nothing the checks beside the four of the eighteen scripts in .github/tools/, each tests with what it refuses, read out of the script rather than guessed --- docs/90-build-from-source.md | 116 ++++++++++++++++++++++---------- docs/zh/90-build-from-source.md | 103 +++++++++++++++++++--------- 2 files changed, 150 insertions(+), 69 deletions(-) diff --git a/docs/90-build-from-source.md b/docs/90-build-from-source.md index c3aa25dc..ff5d7d67 100644 --- a/docs/90-build-from-source.md +++ b/docs/90-build-from-source.md @@ -43,61 +43,105 @@ mcpp build --target x86_64-linux-musl ## Source Layout +mcpp is a **workspace**. `modules/` holds nine packages that the engine imports; +`src/` is the engine itself. + ``` +modules/ workspace members, imported by src/ +├── manifest/ manifest and descriptor parsing +├── platform/ operating-system abstraction +├── toolchain-model/ triples, dialects, fingerprints, the link model +├── buildmcpp/ the build.mcpp contract: protocol, directives, provisions +├── source-kind/ source-file role classification +├── versioning/ this binary's version, and SemVer requirements +├── dyndep/ ninja dyndep emission +├── libs/ vendored text-format parsers +└── log/ leveled logging + src/ ├── main.cpp entry point -├── cli.cppm command dispatch and argument parsing -├── cli/ command implementations -├── manifest/ manifest model, TOML parsing, and xpkg descriptors -├── lockfile.cppm mcpp.lock -├── version_req.cppm SemVer constraints -├── fetcher.cppm fetcher façade -├── fetcher/ package/index download and installation -├── config.cppm ~/.mcpp/config.toml -├── bmi_cache.cppm cross-project BMI cache -├── bmi_cache/ cache storage and invalidation -├── dyndep.cppm ninja dyndep generation -├── ui.cppm progress bars and output formatting -├── build/ build orchestration and ninja backend -├── fallback/ fallback resolution paths -├── modgraph/ P1689 module scanning and dependency graph -├── pm/ dependency resolver and package-management commands -├── platform/ platform and process abstractions -├── scaffold/ `mcpp new` templates and project creation -├── toolchain/ toolchain detection, fingerprinting, and std module -├── pack/ mcpp pack implementation -├── publish/ mcpp publish and xpkg generation -└── libs/ third-party dependencies (toml parsing, etc.) +├── cli.cppm cli/ command dispatch and the commands +├── build/ build orchestration and the ninja backend +├── modgraph/ P1689 module scanning and the dependency graph +├── pm/ the resolver and the package-management commands +├── toolchain/ detection, fingerprinting, the std module +├── pack/ publish/ mcpp pack, mcpp publish and xpkg generation +├── fetcher/ fallback/ download, installation, fallback resolution +├── bmi_cache/ the cross-project BMI cache +├── runtime/ xlings/ the runtime contract and the xlings bridge +├── freestanding/ bare-metal targets, link line and runner +└── scaffold/ `mcpp new` and templates tests/ -├── unit/ C++ unit and integration tests, generally grouped by subsystem -└── e2e/ end-to-end shell scripts (run_all.sh is the CI entry point) +├── unit/ 108 C++ tests, discovered by `mcpp test` +└── e2e/ 370 shell scripts against a real binary ``` ## Test Organization -Tests are split into two layers: - -- **Unit and integration tests** are C++ files discovered by `mcpp test` under - `tests/**/*.cpp`. They are generally named for the subsystem or module they - exercise (for example, `test_pm_lock_io.cpp` and `test_toolchain_triple.cpp`). -- **E2E tests** live in `tests/e2e/NN_.sh` and exercise a real `mcpp` - binary; `run_all.sh` is the CI entry point. +Two layers, and they answer different questions. -Choose focused unit and/or E2E coverage according to the contract changed. E2E -scripts may require the same sandbox, mirror, and capability setup used by CI. +**Unit tests** (`tests/unit/`, 108 files) are C++ programs `mcpp test` +discovers. They exercise a module's contract with no binary and no filesystem +state. -Run a single e2e script: +**End-to-end tests** (`tests/e2e/NN_.sh`, 370 files) run a real `mcpp` +binary against a real project. `run_all.sh` is the CI entry point. `mcpp test` +does **not** run them. ```bash MCPP= bash tests/e2e/02_new_build_run.sh ``` -Replace `` with the absolute path to the binary built in the -previous step; on Windows that path names `mcpp.exe`. +**A capability gate decides which run.** The first lines of an e2e script +declare what it needs, and a runner without that capability skips it: + +```bash +#!/usr/bin/env bash +# requires: elf gcc +``` + +`gcc` (80 scripts), `elf`, `unix-shell`, `llvm`, `jq` and `fresh-sandbox` are +the ones in use. A script that requires a capability no CI job provides **never +runs anywhere**, and its greenness means nothing — check that some job supplies +what a new script asks for. + +## Writing a check that measures something + +The most transferable rule in this repository, and the one that fails silently +when it is skipped: + +> **After writing a check, remove the fix and run it once.** A check that has +> never been seen to fail is not known to measure anything. + +Three shapes it catches, all of which have shipped here at least once: + +| shape | what it looks like | +|---|---| +| the criterion never runs | a test gated on a capability no job provides | +| the criterion cannot fail | a substring search satisfied by any wording | +| the criterion measures the wrong object | a fixture whose directory accumulates across runs, so the search answers about an earlier build | + +State the denominator too. "Every host in the table was scanned" is a check; +"the scan found nothing" is not, because an empty enumeration also finds +nothing. + +## The checks CI runs beside the tests + +`.github/tools/` holds eighteen scripts. Four are worth knowing before a first +PR: + +| script | what it refuses | +|---|---| +| `check_docs_style.sh` | question headings, second person in a reference chapter, a 简体中文 page whose heading structure has fallen behind | +| `check_docs_structure.sh` | a chapter citing a design record, a `docs/NN-*.md` path that does not resolve, a translation missing a table | +| `check_version_pins.sh` | a version written in one place and not the others | +| `check_modules_wiring.sh` | a workspace member wired into some of the three places that must know about it and not the others | ## Issue and PR Guidelines + + ### Issues File issues at [github.com/mcpp-community/mcpp/issues](https://github.com/mcpp-community/mcpp/issues), ideally including the following: diff --git a/docs/zh/90-build-from-source.md b/docs/zh/90-build-from-source.md index e92514a3..89eba16e 100644 --- a/docs/zh/90-build-from-source.md +++ b/docs/zh/90-build-from-source.md @@ -43,60 +43,97 @@ mcpp build --target x86_64-linux-musl ## 源码结构 +mcpp 是一个**工作空间**。`modules/` 里是引擎 import 的九个包;`src/` 是引擎本身。 + ``` +modules/ 工作空间成员,被 src/ import +├── manifest/ manifest 与描述符解析 +├── platform/ 操作系统抽象 +├── toolchain-model/ 三元组、方言、指纹、链接模型 +├── buildmcpp/ build.mcpp 契约:协议、指令表、provisions +├── source-kind/ 源文件角色分类 +├── versioning/ 本二进制的版本,以及 SemVer 约束解析 +├── dyndep/ ninja dyndep 产出 +├── libs/ 内嵌的文本格式解析器 +└── log/ 分级日志 + src/ ├── main.cpp 入口 -├── cli.cppm 命令分发与参数解析 -├── cli/ 命令实现 -├── manifest/ manifest 模型、TOML 解析与 xpkg 描述符 -├── lockfile.cppm mcpp.lock -├── version_req.cppm SemVer 约束 -├── fetcher.cppm fetcher 门面 -├── fetcher/ 包/索引下载与安装 -├── config.cppm ~/.mcpp/config.toml -├── bmi_cache.cppm 跨项目 BMI 缓存 -├── bmi_cache/ 缓存存储与失效 -├── dyndep.cppm ninja dyndep 生成 -├── ui.cppm 进度条与输出格式 +├── cli.cppm cli/ 命令分发与各条命令 ├── build/ 构建编排与 ninja 后端 -├── fallback/ 回退解析路径 ├── modgraph/ P1689 模块扫描与依赖图 -├── pm/ 依赖解析器与包管理命令 -├── platform/ 平台与进程抽象 -├── scaffold/ `mcpp new` 模板与工程创建 -├── toolchain/ 工具链探测、指纹与 std 模块 -├── pack/ mcpp pack 实现 -├── publish/ mcpp publish 与 xpkg 生成 -└── libs/ 第三方依赖(toml 解析等) +├── pm/ 解析器与包管理命令 +├── toolchain/ 探测、指纹、std 模块 +├── pack/ publish/ mcpp pack、mcpp publish 与 xpkg 产出 +├── fetcher/ fallback/ 下载、安装、回退解析 +├── bmi_cache/ 跨工程 BMI 缓存 +├── runtime/ xlings/ 运行时契约与 xlings 桥 +├── freestanding/ 裸机目标、链接行与 runner +└── scaffold/ `mcpp new` 与模板 tests/ -├── unit/ C++ 单元/集成测试,通常按子系统分组 -└── e2e/ 端到端 shell 脚本(run_all.sh 为 CI 入口) +├── unit/ 108 个 C++ 测试,由 `mcpp test` 发现 +└── e2e/ 370 个对真实二进制运行的 shell 脚本 ``` ## 测试组织 -测试分为两层: +两层,回答的是不同的问题。 -- **单元/集成测试** 是 `tests/**/*.cpp` 下由 `mcpp test` 发现的 C++ 文件。它们通常 - 按所测子系统或模块命名(例如 `test_pm_lock_io.cpp`、`test_toolchain_triple.cpp`)。 -- **E2E 测试** 位于 `tests/e2e/NN_.sh`,通过执行真实的 `mcpp` - 二进制覆盖端到端行为;`run_all.sh` 为 CI 调用入口。 +**单元测试**(`tests/unit/`,108 个)是 `mcpp test` 发现的 C++ 程序。它们检验一个模块 +的契约,不需要二进制、也不需要文件系统状态。 -根据变更的契约选择有针对性的单元和/或 E2E 覆盖。E2E 脚本可能需要 CI 使用的 -同一套沙盒、镜像与 capability 配置。 +**端到端测试**(`tests/e2e/NN_.sh`,370 个)拿真实的 `mcpp` 二进制跑真实工程。 +`run_all.sh` 是 CI 的入口。`mcpp test` **不**运行它们。 + +```bash +MCPP=<新构建的 mcpp 二进制> bash tests/e2e/02_new_build_run.sh +``` -执行单个 e2e 脚本: +**由能力闸门决定哪些会跑。** e2e 脚本开头几行声明它需要什么,不具备该能力的 runner +会跳过它: ```bash -MCPP= bash tests/e2e/02_new_build_run.sh +#!/usr/bin/env bash +# requires: elf gcc ``` -`` 必须替换为前一步刚构建二进制的绝对路径;Windows 上该文件为 -`mcpp.exe`。 +在用的有 `gcc`(80 个脚本)、`elf`、`unix-shell`、`llvm`、`jq` 与 `fresh-sandbox`。 +一个要求「任何 CI job 都不提供的能力」的脚本**在任何地方都不会跑**,它的绿色什么都不 +说明 —— 新增脚本时要核对确有 job 供给它所要的能力。 + +## 写一条真的在测东西的判据 + +本仓库最可迁移的一条规则,也是被跳过时会静默失效的那一条: + +> **写完判据之后,把修复拿掉,跑一次。** 从未被看见失败过的判据,不能说它测到了什么。 + +它能抓住三种形态,每一种在这里都至少发生过一次: + +| 形态 | 长什么样 | +|---|---| +| 判据从没跑到 | 测试被闸在一个没有任何 job 提供的能力上 | +| 判据不可能失败 | 子串搜索,任何措辞都能满足它 | +| 判据施加在错误的对象上 | 夹具目录跨次累积,于是搜索回答的是更早那次构建 | + +也要说出分母。「表里每一台宿主都被扫过」是一条判据;「扫描没发现问题」不是 —— 因为 +空的枚举同样什么都发现不了。 + +## 测试之外 CI 还跑的检查 + +`.github/tools/` 里有十八个脚本。第一个 PR 之前值得知道的有四个: + +| 脚本 | 它拒绝什么 | +|---|---| +| `check_docs_style.sh` | 疑问句标题、参考章节里的第二人称、标题结构落后于英文的中文页 | +| `check_docs_structure.sh` | 章节引用设计记录、解析不到的 `docs/NN-*.md` 路径、翻译里少掉的表格 | +| `check_version_pins.sh` | 版本号只写在一处而别处没跟上 | +| `check_modules_wiring.sh` | 只接进了「三处必须知道它的地方」中的一部分的工作空间成员 | ## Issue 与 PR 提交规范 + + ### Issue 提交至 [github.com/mcpp-community/mcpp/issues](https://github.com/mcpp-community/mcpp/issues), From 1566e9b119503e68141b486bbaace2ae04c462ab Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:21:58 +0800 Subject: [PATCH 25/36] docs: the manifest reference sheds its worked examples and its appendix Four of the six worked examples in chapter 04 are runnable projects under examples/, and the project is the better answer: it builds, and CI checks it. Those four become one table of pointers; the two shapes with no example yet -- a pure C library and a mixed C/C++23 project -- stay as manifests. 04 drops from 1,377 to 1,271 lines. Appendix A was the admission criteria for new manifest fields, which is contributor material rather than a field reference, and it sat BEFORE section 3 -- an ordering the earlier splits left behind. It moves to 90 as *Adding a manifest field: the admission criteria*, before the PR guidelines. Rule 13 is added with it: every table the manifest reference documents must appear in the lookup index of docs/README.md, compared on the key NAME rather than its spelling, because the two documents legitimately write `[targets.]` and `[targets.]`. It found one real omission -- `[package] platforms` was documented and not indexed -- and was falsified before committing. --- .github/tools/check_docs_structure.sh | 25 ++++++ docs/04-mcpp-toml.md | 123 ++------------------------ docs/90-build-from-source.md | 29 +++++- docs/README.md | 1 + docs/zh/04-mcpp-toml.md | 107 ++-------------------- docs/zh/90-build-from-source.md | 14 ++- docs/zh/README.md | 1 + 7 files changed, 85 insertions(+), 215 deletions(-) diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh index cc0a648a..77a008d8 100755 --- a/.github/tools/check_docs_structure.sh +++ b/.github/tools/check_docs_structure.sh @@ -16,6 +16,7 @@ # 10. a translation carries the same tables and code blocks # 11. every chapter states its reader, its question and its exclusions # 12. a citation naming a section lands in the chapter that contains it +# 13. every table the manifest reference documents is in the lookup index # # What it deliberately does NOT check: whether a chapter documents what is # implemented, whether an assertion's strength matches its evidence, or whether @@ -246,6 +247,30 @@ for f in list(pathlib.Path("docs").glob("[0-9]*.md")) + list(pathlib.Path("docs/ sys.exit(1 if bad else 0) PYCITE +# ── 13. every table the manifest reference documents is in the lookup index ─ +# +# docs/README.md carries a reverse index -- a key in front of a reader to the +# chapter that owns it -- and it is hand-built. A key added to the reference and +# not indexed is invisible: the reader concludes it is undocumented. The +# denominator is the reference chapter's own `###` headings, and the comparison +# is on the KEY NAME rather than on its spelling, because the two documents +# legitimately write `[targets.]` and `[targets.]`. +python3 - <<'PYLOOKUP' || fail=1 +import re, pathlib, sys +ref = pathlib.Path("docs/04-mcpp-toml.md").read_text(errors="ignore") +idx = pathlib.Path("docs/README.md").read_text(errors="ignore") +keys = set() +for m in re.finditer(r"^### [0-9.b]+ `([^`]+)`", ref, re.M): + tok = m.group(1) + name = re.sub(r"^\[|\].*$", "", tok) # [targets.] -> targets. + name = name.split(".")[0].split(" ")[-1] # -> targets ; "[package] platforms" -> platforms + keys.add(name) +missing = sorted(k for k in keys if k not in idx) +for k in missing: + print(f"FAIL: docs/README.md lookup index does not mention `{k}`, which docs/04 documents") +sys.exit(1 if missing else 0) +PYLOOKUP + if [[ "$fail" -eq 0 ]]; then echo "OK: docs structure checks pass" fi diff --git a/docs/04-mcpp-toml.md b/docs/04-mcpp-toml.md index 750a7627..c1be6858 100644 --- a/docs/04-mcpp-toml.md +++ b/docs/04-mcpp-toml.md @@ -1196,104 +1196,19 @@ do`. Moved to [09 — Commands by Scenario](09-commands-by-scenario.md). -## Appendix A. Schema Ownership Principle (admission criteria for new fields) - -> **Closed syntax, open vocabulary**: whoever owns the parsing semantics defines the keys; whoever owns the domain knowledge defines the values. - -- mcpp only defines **mechanisms** (feature union/closure, capability - require/provide/override, profile→compiler flags, platform→triple); the keys and - shapes are fixed. Domain vocabulary such as feature names, capability names, and - backend names **appears only in values**, never in mcpp's code. -- **Package-custom toml keys are not supported**: key legitimacy must not depend on - "first parsing the target package," otherwise the manifest loses static - parseability (a prerequisite for lockfiles/LSP/auditing). A package's extension - point = open value domains within fixed mechanisms. -- Package-level knobs all converge into features; for sugar keys (such as `backend=`) - to enter the core syntax, they must satisfy: ① domain-neutral (a cross-ecosystem - general pattern) ② 1:1 desugaring with zero new parsing semantics. -- **A key that duplicates an answer another section already gives is not admitted.** - Two places to state one fact is two places that can disagree, and the failure - is silent — whichever reader loses the race is simply wrong. Library packaging - ([12](12-binary-distribution.md)) is the worked example: it added **zero** - manifest keys, because what to pack is `[targets.].kind`, which interface - to publish is `[lib]` plus the module graph, which headers are public is - `[build].include_dirs`, and the per-artifact evidence is `[[runtime.artifacts]]`. -- A field that describes what a *generated* package IS (rather than what a build - should DO) belongs on `[[runtime.artifacts]]` — see §2.11. `provenance` - beginning with `mcpp-pack` is what marks a directory as one, and mcpp refuses - to `build` inside it. - ## 3. Worked Examples -### 3.1 Simple Hello World - -```toml -[package] -name = "hello" -version = "0.1.0" -``` - -```cpp -// src/main.cpp -import std; -int main() { std::println("Hello, mcpp!"); } -``` - -```bash -mcpp build && mcpp run -``` - -### 3.2 Module-based Library + Tests - -```toml -[package] -name = "mymath" -version = "1.0.0" - -[targets.mymath] -kind = "lib" - -[dev-dependencies.compat] -gtest = "1.15.2" -``` - -```cpp -// src/mymath.cppm -export module mymath; -export int add(int a, int b) { return a + b; } -``` - -```cpp -// tests/test_add.cpp -#include -import mymath; -TEST(Math, Add) { EXPECT_EQ(add(1, 2), 3); } -``` - -```bash -mcpp build # Compile the library -mcpp test # Compile + run tests -``` - -### 3.3 An Application Depending on Other Packages - -```toml -[package] -name = "myapp" -version = "0.1.0" - -[dependencies] -ftxui = "6.1.9" +Four of these are runnable projects rather than snippets, and the project is +the better answer: it builds, and it is checked by CI. -[dependencies.mcpplibs] -cmdline = "0.0.2" -llmapi = "0.2.5" -``` +| shape | run | +|---|---| +| a hello world | [`examples/01-hello`](../examples/01-hello/) | +| a module library with tests | [`examples/11-features`](../examples/11-features/) | +| an application with dependencies | [`examples/02-with-deps`](../examples/02-with-deps/) | +| a cross-compiled static release | [`examples/03-pack-static`](../examples/03-pack-static/) | -mcpp automatically: -1. Downloads source tarballs from mcpp-index -2. Propagates header search paths per `[build].include_dirs` -3. Pulls transitive dependencies into the graph (llmapi → tinyhttps → mbedtls, fully automatic) +Two shapes have no example yet and stay here as manifests. ### 3.4 Pure C Library @@ -1329,25 +1244,6 @@ lua = "5.4.7" # Pure C library; mcpp compiles .c files with the C compiler a kind = "bin" ``` -### 3.6 Cross-Compiled Static Release - -```toml -[package] -name = "mytool" -version = "1.0.0" - -[toolchain] -default = "gcc@16.1.0" - -[target.x86_64-linux-musl] -toolchain = "gcc@16.1.0" -linkage = "static" -``` - -```bash -mcpp build --target x86_64-linux-musl -# → Produces a fully statically linked binary that can be scp'd directly to any Linux x86_64 machine and run -``` ## 4. Conventions and Defaults Cheat Sheet @@ -1373,4 +1269,3 @@ standard = "c++26" ``` New projects should use `[package].standard`. If both locations are present, `[package].standard` is authoritative. - diff --git a/docs/90-build-from-source.md b/docs/90-build-from-source.md index ff5d7d67..31210c5b 100644 --- a/docs/90-build-from-source.md +++ b/docs/90-build-from-source.md @@ -138,6 +138,34 @@ PR: | `check_version_pins.sh` | a version written in one place and not the others | | `check_modules_wiring.sh` | a workspace member wired into some of the three places that must know about it and not the others | +## Adding a manifest field: the admission criteria + +> **Closed syntax, open vocabulary**: whoever owns the parsing semantics defines the keys; whoever owns the domain knowledge defines the values. + +- mcpp only defines **mechanisms** (feature union/closure, capability + require/provide/override, profile→compiler flags, platform→triple); the keys and + shapes are fixed. Domain vocabulary such as feature names, capability names, and + backend names **appears only in values**, never in mcpp's code. +- **Package-custom toml keys are not supported**: key legitimacy must not depend on + "first parsing the target package," otherwise the manifest loses static + parseability (a prerequisite for lockfiles/LSP/auditing). A package's extension + point = open value domains within fixed mechanisms. +- Package-level knobs all converge into features; for sugar keys (such as `backend=`) + to enter the core syntax, they must satisfy: ① domain-neutral (a cross-ecosystem + general pattern) ② 1:1 desugaring with zero new parsing semantics. +- **A key that duplicates an answer another section already gives is not admitted.** + Two places to state one fact is two places that can disagree, and the failure + is silent — whichever reader loses the race is simply wrong. Library packaging + ([12](12-binary-distribution.md)) is the worked example: it added **zero** + manifest keys, because what to pack is `[targets.].kind`, which interface + to publish is `[lib]` plus the module graph, which headers are public is + `[build].include_dirs`, and the per-artifact evidence is `[[runtime.artifacts]]`. +- A field that describes what a *generated* package IS (rather than what a build + should DO) belongs on `[[runtime.artifacts]]` — see §2.11. `provenance` + beginning with `mcpp-pack` is what marks a directory as one, and mcpp refuses + to `build` inside it. + + ## Issue and PR Guidelines @@ -167,4 +195,3 @@ mcpp is in early iteration and its interfaces may change. Before submitting a PR - Chat group QQ: 1067245099 - [mcpp-index](https://github.com/mcpplibs/mcpp-index) — the default package index - [mcpplibs](https://github.com/mcpplibs) — the companion collection of modular C++ libraries - diff --git a/docs/README.md b/docs/README.md index 84c3b96e..26acf85f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -115,6 +115,7 @@ token in front of a reader to the chapter that owns it. | `[toolchain]`, `cxx_runtime` | [20](20-toolchains.md) | `[target.]`, `cfg(…)` | [22](22-target-side.md) | | `[xlings]`, `[xlings.workspace]`, `[feature-xlings.]` | [23](23-the-project-environment.md) | `[pack]` | [10](10-pack-and-release.md) | | `[build] accel`, `[package] accelerators`, `device_extensions` | [42](42-heterogeneous-builds.md) | `[hooks]` | [09](09-commands-by-scenario.md) | +| `[package] platforms`, `[build] cache` | [04](04-mcpp-toml.md) | `[targets.]`, `[profile.]` | [04](04-mcpp-toml.md) | | `runner`, `[target..runners]` | [41](41-devices.md) | `rule_module` | [31](31-authoring-a-rule-package.md) | **Commands** diff --git a/docs/zh/04-mcpp-toml.md b/docs/zh/04-mcpp-toml.md index 225571cb..c0c06990 100644 --- a/docs/zh/04-mcpp-toml.md +++ b/docs/zh/04-mcpp-toml.md @@ -1037,89 +1037,18 @@ o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o") 已移入 [09 —— 按场景选命令](09-commands-by-scenario.md)。 -## 附录 A. Schema 所有权原则(新字段准入标准) - -> **语法封闭,词汇开放**:谁拥有解析语义谁定义键;谁拥有领域知识谁定义值。 - -- mcpp 只定义**机制**(features 并集/闭包、capability require/provide/override、 - profile→编译器旗标、platform→triple),键与形状固定;feature 名、能力名、 - 后端名等**领域词汇只出现在值里**,不进 mcpp 代码。 -- **不支持包自定义 toml 键**:键合法性不得依赖"先解析目标包",否则 manifest - 失去静态可解析性(lockfile/LSP/审计的前提)。包的扩展点 = 固定机制内的开放值域。 -- 包级旋钮统一收敛进 features;糖键(如 `backend=`)进入核心语法须满足: - ① 领域中立(跨生态通用模式)② 1:1 脱糖、零新增解析语义。 - ## 3. 实战示例 -### 3.1 简单 Hello World - -```toml -[package] -name = "hello" -version = "0.1.0" -``` - -```cpp -// src/main.cpp -import std; -int main() { std::println("Hello, mcpp!"); } -``` - -```bash -mcpp build && mcpp run -``` - -### 3.2 模块化库 + 测试 - -```toml -[package] -name = "mymath" -version = "1.0.0" - -[targets.mymath] -kind = "lib" - -[dev-dependencies.compat] -gtest = "1.15.2" -``` - -```cpp -// src/mymath.cppm -export module mymath; -export int add(int a, int b) { return a + b; } -``` - -```cpp -// tests/test_add.cpp -#include -import mymath; -TEST(Math, Add) { EXPECT_EQ(add(1, 2), 3); } -``` - -```bash -mcpp build # 编译库 -mcpp test # 编译 + 跑测试 -``` - -### 3.3 依赖其他包的应用 - -```toml -[package] -name = "myapp" -version = "0.1.0" - -[dependencies] -ftxui = "6.1.9" +其中四个是**可运行的工程**而不是片段,而工程是更好的答案:它能构建,而且由 CI 检查。 -[dependencies.mcpplibs] -cmdline = "0.0.2" -llmapi = "0.2.5" -``` +| 形态 | 跑什么 | +|---|---| +| 一个 hello world | [`examples/01-hello`](../../examples/01-hello/) | +| 带测试的模块化库 | [`examples/11-features`](../../examples/11-features/) | +| 带依赖的应用 | [`examples/02-with-deps`](../../examples/02-with-deps/) | +| 交叉编译的静态发布 | [`examples/03-pack-static`](../../examples/03-pack-static/) | -mcpp 自动: -1. 从 mcpp-index 下载源码 tarball -2. 按 `[build].include_dirs` 传播头文件路径 -3. 传递依赖自动入图(llmapi → tinyhttps → mbedtls 全自动) +还有两种形态暂时没有对应示例,以 manifest 的形式留在这里。 ### 3.4 纯 C 库 @@ -1155,25 +1084,6 @@ lua = "5.4.7" # 纯 C 库,mcpp 自动用 C 编译器编译 .c 文件 kind = "bin" ``` -### 3.6 跨编译静态发布 - -```toml -[package] -name = "mytool" -version = "1.0.0" - -[toolchain] -default = "gcc@16.1.0" - -[target.x86_64-linux-musl] -toolchain = "gcc@16.1.0" -linkage = "static" -``` - -```bash -mcpp build --target x86_64-linux-musl -# → 产出完全静态链接的二进制,可直接 scp 到任意 Linux x86_64 机器运行 -``` ## 4. 约定与默认值速查 @@ -1199,4 +1109,3 @@ standard = "c++26" ``` 新项目请使用 `[package].standard`。如果两个位置都出现,`[package].standard` 是权威配置。 - diff --git a/docs/zh/90-build-from-source.md b/docs/zh/90-build-from-source.md index 89eba16e..d3f01a66 100644 --- a/docs/zh/90-build-from-source.md +++ b/docs/zh/90-build-from-source.md @@ -130,6 +130,19 @@ MCPP=<新构建的 mcpp 二进制> bash tests/e2e/02_new_build_run.sh | `check_version_pins.sh` | 版本号只写在一处而别处没跟上 | | `check_modules_wiring.sh` | 只接进了「三处必须知道它的地方」中的一部分的工作空间成员 | + +## 新增一个 manifest 字段:准入标准 + +> **语法封闭,词汇开放**:谁拥有解析语义谁定义键;谁拥有领域知识谁定义值。 + +- mcpp 只定义**机制**(features 并集/闭包、capability require/provide/override、 + profile→编译器旗标、platform→triple),键与形状固定;feature 名、能力名、 + 后端名等**领域词汇只出现在值里**,不进 mcpp 代码。 +- **不支持包自定义 toml 键**:键合法性不得依赖"先解析目标包",否则 manifest + 失去静态可解析性(lockfile/LSP/审计的前提)。包的扩展点 = 固定机制内的开放值域。 +- 包级旋钮统一收敛进 features;糖键(如 `backend=`)进入核心语法须满足: + ① 领域中立(跨生态通用模式)② 1:1 脱糖、零新增解析语义。 + ## Issue 与 PR 提交规范 @@ -160,4 +173,3 @@ mcpp 处于早期迭代阶段,接口可能调整,提交 PR 前请注意: - 交流群 QQ: 1067245099 - [mcpp-index](https://github.com/mcpplibs/mcpp-index) — 默认包索引 - [mcpplibs](https://github.com/mcpplibs) — 配套的模块化 C++ 库集合 - diff --git a/docs/zh/README.md b/docs/zh/README.md index ab8d324d..191bb747 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -109,6 +109,7 @@ | `[toolchain]`、`cxx_runtime` | [20](20-toolchains.md) | `[target.]`、`cfg(…)` | [22](22-target-side.md) | | `[xlings]`、`[xlings.workspace]`、`[feature-xlings.]` | [23](23-the-project-environment.md) | `[pack]` | [10](10-pack-and-release.md) | | `[build] accel`、`[package] accelerators`、`device_extensions` | [42](42-heterogeneous-builds.md) | `[hooks]` | [09](09-commands-by-scenario.md) | +| `[package] platforms`、`[build] cache` | [04](04-mcpp-toml.md) | `[targets.]`、`[profile.]` | [04](04-mcpp-toml.md) | | `runner`、`[target..runners]` | [41](41-devices.md) | `rule_module` | [31](31-authoring-a-rule-package.md) | **命令** From 7acc7121f5ae918addbfb84466ccbe9b308ec7b3 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:24:22 +0800 Subject: [PATCH 26/36] docs: eight limits sections, written from facts the chapters already stated The method that works, and the one this batch was missing: a limit a chapter already states somewhere in its body is PROMOTED into the section that collects them, rather than invented. Scanning each chapter for sentences of the shape "is not / cannot / only on / not yet" turned up real material in eight: 05 two things need the network and only two; a namespaced selector is not covered by the refresh window 20 a macOS host has no Linux-targeting payload at all; an explicit [toolchain] is never overruled 21 a `planned` row is registered and nothing is wired; host_can_serve is a different question from the tier 22 a dependency cannot be conditioned on the accelerator -- the layer is resolved FROM the graph, so it would decide the answer it is asking 23 a tool cannot be conditioned on the accelerator, for the same reason, and a manifest that writes one is refused 41 exactly one dependency may supply a runner name; a runner the environment does not supply stays absent 50 the exit-code table is scoped to the commands it names; absence of JSON means "not supported" and a client cannot tell which reason 51 mcpp.lock records and verifies a resolution and does not constrain one Eight remain -- 04, 07, 10, 11, 24, 90, 91, 92 -- and scanning them returns nothing usable. That is recorded rather than filled, for the reason the design already gives: a fabricated limits section satisfies the check and measures nothing. --- .../docs/2026-09-08-the-documentation-as-a-book.md | 10 ++++++++-- docs/05-dependencies.md | 11 +++++++++++ docs/20-toolchains.md | 7 +++++++ docs/21-the-target-triple.md | 9 +++++++++ docs/22-target-side.md | 10 ++++++++++ docs/23-the-project-environment.md | 9 +++++++++ docs/41-devices.md | 9 +++++++++ docs/50-machine-output.md | 10 ++++++++++ docs/51-supported-versions.md | 10 ++++++++++ docs/zh/05-dependencies.md | 10 ++++++++++ docs/zh/20-toolchains.md | 6 ++++++ docs/zh/21-the-target-triple.md | 7 +++++++ docs/zh/22-target-side.md | 7 +++++++ docs/zh/23-the-project-environment.md | 7 +++++++ docs/zh/41-devices.md | 8 ++++++++ docs/zh/50-machine-output.md | 8 ++++++++ docs/zh/51-supported-versions.md | 8 ++++++++ 17 files changed, 144 insertions(+), 2 deletions(-) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index e8599aae..4794c8f7 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -360,8 +360,14 @@ chapters had the content under another heading and are renamed; the scenario, model, tutorial and index chapters are exempt, because their scope is stated by the "Not here" line in their opening and they claim no complete surface. -Seventeen remain: `04`, `05`, `07`, `10`, `11`, `20`, `21`, `22`, `23`, `24`, -`41`, `50`, `51`, `90`, `91`, `92`. +Eight of the original seventeen were written from facts the chapter already +stated somewhere in its body — `05`, `20`, `21`, `22`, `23`, `41`, `50`, `51`. +That is the method that works: **promote a limit the chapter already states into +the section that collects them**, rather than inventing one. + +Eight remain: `04`, `07`, `10`, `11`, `24`, `90`, `91`, `92`. Scanning their +text for a limit statement returns nothing usable, which is the honest reading — +each needs a fact its area's owner can state. **They are open rather than written, and the reason is the rule itself.** A fabricated limits section satisfies the check and measures nothing, which is diff --git a/docs/05-dependencies.md b/docs/05-dependencies.md index 8722e2ea..cc40a69d 100644 --- a/docs/05-dependencies.md +++ b/docs/05-dependencies.md @@ -293,3 +293,14 @@ unconditionally, so "only when needed" needs no separate table. > The section has been parsed since early versions and, until 2026.8.29.1, read > by nothing that made a decision: writing it produced a manifest that loaded, > no diagnostic, and no effect. + +## Current limitations + +- **Two things need the network, and only two:** resolving a branch that has no + commit in the lock, and cloning a commit that is not cached yet. A `git =` + value naming a local directory or a `file://` URL needs neither and is never + refused offline. +- The index-refresh window **does not apply to a selector that states a + namespace**. `mcpplibs.gtest` misses and stays missed until the next refresh. +- `mcpp.lock` records and verifies a resolution; it does not constrain one. See + [51 — Supported Versions and Compatibility](51-supported-versions.md). diff --git a/docs/20-toolchains.md b/docs/20-toolchains.md index 5181aeff..708e2c78 100644 --- a/docs/20-toolchains.md +++ b/docs/20-toolchains.md @@ -903,3 +903,10 @@ skipped). Limits: `.asm` targets x86 only (hard error elsewhere — gate the files off other targets), `.S` is unavailable on the MSVC toolchain, and `.asm` means NASM syntax (MASM sources should be `!`-excluded). +## Current limitations + +- **A macOS host has no Linux-targeting payload at all**, so no Linux target is + reachable from there. This is a publishing boundary rather than an engine one. +- An explicit `[toolchain]` in the manifest, or a `[target.X].toolchain`, is + **never overruled**. A project that pins one and then meets a target the pin + cannot serve is refused rather than silently given another compiler. diff --git a/docs/21-the-target-triple.md b/docs/21-the-target-triple.md index fafaea8b..6a2f6ba6 100644 --- a/docs/21-the-target-triple.md +++ b/docs/21-the-target-triple.md @@ -518,3 +518,12 @@ the target row's own default. See [chapter 13](40-baremetal.md). [chapter 03](20-toolchains.md) for the toolchain axis, which is separate: a target does not determine a compiler. +## Current limitations + +- A row at the `planned` tier is **registered in the vocabulary and nothing is + wired yet**. Naming such a target is accepted by the parser and refused by the + build. +- `host_can_serve` decides whether this machine can produce a target, and it is + a different question from the tier. A target mcpp supports may still be + unreachable from this host; the escape hatch is an explicit + `[target.X] toolchain = "…"`. diff --git a/docs/22-target-side.md b/docs/22-target-side.md index 5f9159be..a323e0d7 100644 --- a/docs/22-target-side.md +++ b/docs/22-target-side.md @@ -481,3 +481,13 @@ this returns. To branch on the resolved layer, use a layer predicate: Side](22-target-side.md)). This paragraph said "which C library was resolved" until 2026.9.1.1, which was the wrong one of the two. +## Current limitations + +- **A dependency cannot be conditioned on the accelerator.** The accelerator + layer is resolved from the dependency graph, so a dependency chosen by it would + decide the answer it is asking for. mcpp reports the predicate and ignores it; + packages are unconditional or conditioned on the platform, and `[build] + sources` is what the accelerator selects. +- A layer cannot select a dependency for the same reason, in the general case: + any derivation earlier than the graph is an inference about a fact that does + not yet exist. diff --git a/docs/23-the-project-environment.md b/docs/23-the-project-environment.md index fb56b48d..55b4355e 100644 --- a/docs/23-the-project-environment.md +++ b/docs/23-the-project-environment.md @@ -524,3 +524,12 @@ used it. the `RuntimeBinding` snapshot, and the degradation rules. - [04 - mcpp.toml](04-mcpp-toml.md) — the rest of the manifest. +## Current limitations + +- **A tool cannot be conditioned on the accelerator.** The accelerator is + resolved after the dependency graph, so such a tool would be declared and never + installed — a build that succeeds with the tool simply absent. A manifest that + writes one is refused. +- The declaration nearer the artifact wins, and the override is reported. A pin + that fails a requirement the other side stated is refused naming both sides + rather than installed alongside it. diff --git a/docs/41-devices.md b/docs/41-devices.md index a8a0d327..7f68450a 100644 --- a/docs/41-devices.md +++ b/docs/41-devices.md @@ -173,3 +173,12 @@ about emulators or probes; `mcpp::has_feature` already existed. That the question is answerable without adding anything is the layering working as specified. +## Current limitations + +- **Exactly one dependency may supply a given runner name.** A second is an + error naming both packages; there is no ordering rule that picks a winner. +- A runner the selected environment does not supply **stays absent**. Under an + emulator feature there is no debug probe, so `mcpp run --runner debug` reports + that no such runner exists and lists the ones that do. +- Termination is declared, not inferred. A long-lived runner that does not say + so is waited on until the operator ends it. diff --git a/docs/50-machine-output.md b/docs/50-machine-output.md index 79ddb039..3f8e9350 100644 --- a/docs/50-machine-output.md +++ b/docs/50-machine-output.md @@ -454,3 +454,13 @@ must also read `not_run`. existing `not_run` list, which continues to name members the `--workspace-timeout` stopped before they started. +## Current limitations + +- **The exit-code table is scoped to the commands it names.** A code another + command returns is not in it, and adding one would document something those + commands cannot promise. +- A diagnostic without a location omits `path` and `range` rather than sending + zeros: `line: 0` would point at a position that does not exist. +- Absence of JSON on stdout means "not supported", whatever the reason — an + older engine, an unknown option, or a command that has no machine format. A + client cannot distinguish the three from the stream alone. diff --git a/docs/51-supported-versions.md b/docs/51-supported-versions.md index f8aa7de5..a536423f 100644 --- a/docs/51-supported-versions.md +++ b/docs/51-supported-versions.md @@ -99,3 +99,13 @@ owns the component — the engine, the package index, or the package itself. A report that names the version, the host, the target and the command is actionable; one that does not usually results in a request for those four. +## Current limitations + +- **`mcpp.lock` does not yet constrain resolution.** It records what a build + resolved and `--locked` verifies that a new resolution matches it; pinning a + resolution to the lock as an input is a separate change to the resolver. +- The SBOM describes the **recorded** resolution rather than a fresh one, so a + document written after a manifest edit and before an `mcpp update` describes + the older graph. +- A component whose licence mcpp does not know is emitted as `NOASSERTION` + rather than omitted, because an absent field reads as "not examined". diff --git a/docs/zh/05-dependencies.md b/docs/zh/05-dependencies.md index f8bd01f7..1909c6dc 100644 --- a/docs/zh/05-dependencies.md +++ b/docs/zh/05-dependencies.md @@ -259,3 +259,13 @@ feature 可以为构建期请求划定范围而无需第二个声明处 —— ` > 这个段很早就能被解析,而直到 2026.8.29.1 之前没有任何做决定的代码读它:写下它得到的是 > 一份能加载的清单、零诊断、零效果。 + +## 当前边界 + +- **只有两件事需要网络,也只有这两件:**解析一个在锁里没有 commit 的分支,以及克隆 + 一个尚未缓存的 commit。指向本地目录或 `file://` URL 的 `git =` 两者都不需要,因此 + 离线时从不会被拒绝。 +- 索引刷新窗口**不适用于写明了 namespace 的选择器**。`mcpplibs.gtest` 一旦未命中就 + 保持未命中,直到下一次刷新。 +- `mcpp.lock` 记录并核验一次解析,但不约束解析。见 + [51 —— 受支持的版本与兼容性](51-supported-versions.md)。 diff --git a/docs/zh/20-toolchains.md b/docs/zh/20-toolchains.md index fa3e3d7f..4844b4d0 100644 --- a/docs/zh/20-toolchains.md +++ b/docs/zh/20-toolchains.md @@ -828,3 +828,9 @@ nasm 则**硬失败**(汇编绝不静默跳过)。限制:`.asm` 仅限 x86 目 报错——用条件 sources 门控)、MSVC 工具链不支持 `.S`、`.asm` 即 NASM 语法 (MASM 源请用 `!` 排除)。 +## 当前边界 + +- **macOS 宿主上完全没有面向 Linux 的载荷**,因此从那里够不到任何 Linux 目标。这是 + 发布边界,不是引擎边界。 +- manifest 里显式写的 `[toolchain]`,或 `[target.X].toolchain`,**永不被推翻**。工程 + 钉了一个、随后遇到这个钉子服务不了的目标时,会被拒绝,而不是悄悄换成另一个编译器。 diff --git a/docs/zh/21-the-target-triple.md b/docs/zh/21-the-target-triple.md index dc35528f..dc6fd649 100644 --- a/docs/zh/21-the-target-triple.md +++ b/docs/zh/21-the-target-triple.md @@ -466,3 +466,10 @@ runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic", [第 15 章](24-openkal-cross.md)完整讲构建期体系。 [第 03 章](20-toolchains.md)讲工具链轴,它是分开的:目标不决定编译器。 +## 当前边界 + +- 处于 `planned` 档位的行,含义是**词汇表里已登记,而还没有任何东西接线**。点名这样 + 的目标会被解析器接受、被构建拒绝。 +- `host_can_serve` 判断的是「这台机器能不能产出这个目标」,与档位是两个不同的问题。 + mcpp 支持的目标仍可能从这台宿主够不着;出口是显式的 + `[target.X] toolchain = "…"`。 diff --git a/docs/zh/22-target-side.md b/docs/zh/22-target-side.md index faa38664..e66e683f 100644 --- a/docs/zh/22-target-side.md +++ b/docs/zh/22-target-side.md @@ -404,3 +404,10 @@ C 库,那时解析出的 `c-abi` 就不是这里返回的东西。要按已解 这一段在 2026.9.1.1 之前写的是「解析到的是哪份 C 库」,那是两者里错的那一个。 参见[40 —— 裸机与 freestanding 目标](40-baremetal.md)。 +## 当前边界 + +- **依赖不能以加速器为条件。** 加速器这一层是从依赖图解析出来的,因此由它选择的依赖 + 会决定它自己在问的那个答案。mcpp 会报告该谓词并忽略它;包要么无条件、要么以平台为 + 条件,而由加速器选择的是 `[build] sources`。 +- 一般而言,层也不能选择依赖,理由相同:任何早于图的推导,都是在推断一个尚不存在的 + 事实。 diff --git a/docs/zh/23-the-project-environment.md b/docs/zh/23-the-project-environment.md index 9e19acc4..4ce234a8 100644 --- a/docs/zh/23-the-project-environment.md +++ b/docs/zh/23-the-project-environment.md @@ -436,3 +436,10 @@ error: `xim:cuda-nvcc` is pinned to 12.0.0 by this project, and mcpp:plugins 快照与降级规则。 - [04 - mcpp.toml](04-mcpp-toml.md) —— manifest 的其余部分。 +## 当前边界 + +- **工具不能以加速器为条件。** 加速器在依赖图之后才解析,因此这样的工具会被声明而 + 永远装不上 —— 一次「工具干脆不在」却仍然成功的构建。写了这种条件的 manifest 会被 + 拒绝。 +- 离产物更近的那条声明胜出,且覆盖会被报告。不满足另一方所述要求的钉子会被拒绝并 + 点名两边,而不是与之并存安装。 diff --git a/docs/zh/41-devices.md b/docs/zh/41-devices.md index 0e13bfb0..becca874 100644 --- a/docs/zh/41-devices.md +++ b/docs/zh/41-devices.md @@ -142,3 +142,11 @@ cortex-m-rt = { version = "0.1.0", features = ["hardware"] } `mcpp::has_feature` 本来就在。**一个问题不必新增任何东西就能回答,是分层按规定在 起作用。** +## 当前边界 + +- **同一个 runner 名字只允许一个依赖提供。** 出现第二个是错误,并会点名两个包; + 没有任何「按顺序取胜」的规则。 +- 所选环境没有提供的 runner **就是不存在**。在 emulator feature 下没有调试探针, + 于是 `mcpp run --runner debug` 会报告没有这个 runner,并列出确实存在的那些。 +- 终止方式是被声明的,不是被推断的。一个不声明自己长驻的 runner,会被一直等到 + 操作者结束它。 diff --git a/docs/zh/50-machine-output.md b/docs/zh/50-machine-output.md index 53fe3a50..23f2515c 100644 --- a/docs/zh/50-machine-output.md +++ b/docs/zh/50-machine-output.md @@ -399,3 +399,11 @@ mcpp test [pattern] [--workspace] --message-format json 测试都 `not_run` 的成员),与既有的 `not_run` 列表并列;后者仍然指 `--workspace-timeout` 到达时尚未开始的成员。 +## 当前边界 + +- **退出码表的作用域只到它点名的那些命令。** 别的命令返回的码不在表里,而把它加进来 + 等于记录一件那些命令承诺不了的事。 +- 没有位置的诊断会**省略** `path` 与 `range`,而不是送 0:`line: 0` 会指向一个不存在 + 的位置。 +- stdout 上没有 JSON 就意味着「不支持」,无论原因是什么 —— 旧版引擎、未知选项,或者 + 这条命令根本没有机器格式。客户端仅凭这条流分辨不出这三者。 diff --git a/docs/zh/51-supported-versions.md b/docs/zh/51-supported-versions.md index ed1983a1..fccd09cd 100644 --- a/docs/zh/51-supported-versions.md +++ b/docs/zh/51-supported-versions.md @@ -84,3 +84,11 @@ mcpp emit sbom -o sbom.json 自身。一份点明了版本、宿主、目标与命令的报告是可执行的;没有这四样的报告通常换来 一次索要它们的回复。 +## 当前边界 + +- **`mcpp.lock` 目前还不约束解析。** 它记录一次构建解析出的结果,`--locked` 核验新的 + 解析与它一致;把锁作为解析的**输入**来钉住结果,是对解析器的另一项改动。 +- SBOM 描述的是**已记录的**那次解析,而不是重新解析一次,所以在改了 manifest 之后、 + `mcpp update` 之前写出的文档,描述的是更早的那张图。 +- 许可证 mcpp 不认识的组件会被写成 `NOASSERTION` 而不是省略,因为缺失的字段读起来 + 是「没有检查过」。 From f9711d1dcfbe3f9987182d570a940a832fdfa564 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:28:57 +0800 Subject: [PATCH 27/36] docs: the 0x band is named for what it is, not for who reads it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `0x — Everyone` / `0x —— 人人都需要` describes an audience in a sentence fragment. The style skill requires a noun phrase, and the band names are the skeleton of the table of contents -- the place the rule matters most. It is `0x — Fundamentals` / `0x —— 基础` now. The paragraph that explained the bands went stale in the same place: it still said `3x` extends the build graph, which stopped being true when the band took in payloads, adapters and board-support packages. It is a table now, which is also what the skill says to use for an enumeration. The rule is extended in the skill to say it governs part and band names, not only chapter headings, with this rename as the worked example: the audience belongs on each chapter's "Reader" line, not in the skeleton of the contents. --- .../2026-09-08-the-documentation-as-a-book.md | 2 +- .agents/skills/mcpp-docs-style/SKILL.md | 6 ++++++ docs/README.md | 20 +++++++++++++------ docs/zh/README.md | 18 +++++++++++++---- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 4794c8f7..a783a5db 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -202,7 +202,7 @@ Three, each answering a question §1.1 measured as unowned. Bands are meaningful: the first digit is the part. Within a part the order is the order that part's reader needs, which is R4. -### 5.1 `0x` — Everyone +### 5.1 `0x` — Fundamentals The arc: understand the parts, get one program running, know where the examples are, write the manifest, add dependencies, make things optional, test. diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index e45f63df..d395d871 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -157,10 +157,16 @@ docs/specs/ ──▶ docs/ 允许 | What decides what gets packed | What determines the package contents | | Consuming one | Consuming a package | | What you may rely on, and what changes | Stability guarantees | +| 0x —— 人人都需要 | 0x —— 基础 | +| 0x — Everyone | 0x — Fundamentals | 「…的原因」「…的依据」「…的范围」是把 why 型标题转成名词短语的常用形。 **保留 why 本身,去掉疑问语气。** +这条同样管**部分名与段位名**,不只管章节标题。「人人都需要」描述的是受众、 +是一个句子片段;「基础」是这一段**是什么**。受众写在每章开头的「读者」那一行, +不写在目录的骨架上。 + ## 五、词汇 ### 不采用的类别 diff --git a/docs/README.md b/docs/README.md index 26acf85f..5b1c2fd5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -42,13 +42,21 @@ one names it. ## Chapters -The first digit is the part, so a number says where a chapter belongs: `0x` is -what everyone needs, `1x` publishes, `2x` is toolchains and targets, `3x` -extends the build graph, `4x` is devices and accelerators, `5x` is what a -program may parse, `9x` is mcpp itself. Within a part the order is a reading -order, not an alphabet. +The first digit is the part, so a number says where a chapter belongs: -### 0x — Everyone +| | | +|---|---| +| `0x` | the fundamentals | +| `1x` | publishing | +| `2x` | toolchains and targets | +| `3x` | extending mcpp and its ecosystem | +| `4x` | devices and accelerators | +| `5x` | the contracts a program may parse | +| `9x` | mcpp itself | + +Within a part the order is a reading order, not an alphabet. + +### 0x — Fundamentals - [00 — How mcpp Works](00-how-mcpp-works.md) — the model every other chapter assumes - [01 — Getting Started](01-getting-started.md) — install, create, build, run diff --git a/docs/zh/README.md b/docs/zh/README.md index 191bb747..f420a73f 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -38,11 +38,21 @@ ## 章节 -首位数字就是部分,所以编号本身说明一章属于哪里:`0x` 是人人都需要的,`1x` 发布, -`2x` 工具链与目标,`3x` 扩展构建图,`4x` 设备与加速器,`5x` 是程序可以解析的东西, -`9x` 是 mcpp 自身。同一部分内部的排列是阅读顺序,不是字母序。 +首位数字就是部分,所以编号本身说明一章属于哪里: -### 0x —— 人人都需要 +| | | +|---|---| +| `0x` | 基础 | +| `1x` | 发布 | +| `2x` | 工具链与目标 | +| `3x` | 扩展 mcpp 与它的生态 | +| `4x` | 设备与加速器 | +| `5x` | 程序可以解析的契约 | +| `9x` | mcpp 自身 | + +同一部分内部的排列是阅读顺序,不是字母序。 + +### 0x —— 基础 - [00 —— mcpp 的运转方式](00-how-mcpp-works.md) —— 其余每章都假定的模型 - [01 —— 快速开始](01-getting-started.md) —— 安装、创建、构建、运行 From 277792ff905845c8f4ba120fb225fbbf49a63050 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:38:27 +0800 Subject: [PATCH 28/36] docs: chapter 00 is what mcpp is, not how it works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The review's verdict on the previous 00 was that it scored zero, and it was right. `How mcpp Works` was accurate and was the wrong chapter: a general user does not care about the operating principle. It failed two of the design's own rules -- R2, because "understand the machinery" is a maintainer's task and already has chapters in 9x; and R7, because a model chapter explains how an advantage is produced and never demonstrates it. `00 — What mcpp Is` replaces it with what a first reader actually wants: the definition mcpp = build system + build plugins + package manager + toolchain management + the environment and runtime (xlings) an analogy CMake, xmake rules, Conan, GCC/LLVM/MSVC, Nix/conda -- so a reader with existing tools can place each part, with the disclaimer that places rather than equates the guarantee clone any mcpp project and `mcpp build` works, without installing a compiler, configuring an environment or hunting dependencies -- and the two boundaries that make it credible a real session a five-line manifest with no compiler and no standard declared, `import std` compiling on a machine whose own g++ is 13.3.0 and cannot, `mcpp self env` showing the GCC 16 mcpp installed, 1.25s wall clock including the first run what it is for modules and the newest features, and the ecosystems that follow: embedded, heterogeneous and GPU, graphics, kernel The one part of the old chapter a user wanted -- the table from a message's shape to the stage that produced it -- moves to 09 beside the other diagnosis scenarios. The rest is deleted. The style skill gains the distinction this needed: an ANALOGY places a part against a tool the reader knows and is admissible in user documentation; a COMPARISON claims mcpp is better on some axis and belongs to a design record. An analogy carries a disclaimer, or the placement is read as equivalence. --- .../2026-09-08-the-documentation-as-a-book.md | 48 ++++++ .agents/skills/mcpp-docs-style/SKILL.md | 14 +- docs/00-how-mcpp-works.md | 139 ---------------- docs/00-what-mcpp-is.md | 155 ++++++++++++++++++ docs/01-getting-started.md | 2 +- docs/02-scenarios.md | 2 +- docs/09-commands-by-scenario.md | 16 ++ docs/README.md | 4 +- docs/zh/00-how-mcpp-works.md | 123 -------------- docs/zh/00-what-mcpp-is.md | 147 +++++++++++++++++ docs/zh/01-getting-started.md | 2 +- docs/zh/02-scenarios.md | 2 +- docs/zh/09-commands-by-scenario.md | 16 ++ docs/zh/README.md | 4 +- 14 files changed, 402 insertions(+), 272 deletions(-) delete mode 100644 docs/00-how-mcpp-works.md create mode 100644 docs/00-what-mcpp-is.md delete mode 100644 docs/zh/00-how-mcpp-works.md create mode 100644 docs/zh/00-what-mcpp-is.md diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index a783a5db..0236b563 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -455,3 +455,51 @@ preference**, and that is the sentence a reader of scenario 10 leaves with. 31 now, and the reason is that the tool is four things rather than three: a build system, a package manager, a toolchain provisioner, **and an ecosystem other people publish into**. The fourth had no chapters at all. + +--- + +## 13. Chapter 00 was designed wrong, and the review said so + +§4.1 specified `00 — How mcpp Works`: five nouns, three seams, where state lives +on disk. It was written, it was accurate, and it was the wrong chapter. + +**The review's verdict was that a general user does not care about the operating +principle.** What a reader opening the first chapter wants is what mcpp is, what +it can do, what its advantage is, and one example they can actually run. + +The design failed two of its own rules to get there: + +- **R2** — a chapter exists for a reader with a task. "Understand the machinery" + is not a task a first-time reader has; it is a task a maintainer has, and it + already has chapters in `9x`. +- **R7** — an advantage is shown by the artifact, not by the mechanism. A model + chapter explains how the advantage is produced and never demonstrates it. + +`00 — What mcpp Is` replaces it: + +1. **The definition**, in the form the reviewer gave: mcpp = build system + + build plugins + package manager + toolchain management + the environment and + runtime (xlings), in one program. +2. **An analogy table** — CMake, xmake rules, Conan, GCC/LLVM/MSVC, Nix/conda — + so a reader with existing tools can place each part. With the disclaimer that + places rather than equates. +3. **The guarantee, stated once**: clone any mcpp project and `mcpp build` + works, without installing a compiler, configuring an environment, or hunting + dependencies. Plus the two boundaries that make it trustworthy. +4. **A session that was run**, with its real output: a five-line manifest, no + declared compiler or standard, `import std` compiling on a machine whose own + `g++` is 13.3.0 and cannot, and `mcpp self env` showing the GCC 16 mcpp + installed. 1.25 s of wall clock including the first run. +5. **What mcpp is for**: modules and the newest language features, and the + ecosystems that follow — embedded, heterogeneous and GPU, graphics, kernel + work. + +The one part of the old chapter a *user* wanted — the table from a message's +shape to the stage that produced it — moved to `09`, beside the other +diagnosis scenarios. The rest is deleted rather than relocated. + +**What this says about the method.** The seven-cell spec was filled for the old +chapter and it still produced the wrong chapter, because the cell that decides +everything — the reader — was answered with "anyone" and then served as if that +meant "someone who wants the model". A reader cell that names no task is not +filled in. diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index d395d871..cd6cf101 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -408,8 +408,18 @@ mcpp 在很多场景支持不止一种做法,但它有自己的设计风格与 **判据:把所有形容词删掉,读者还能不能看出优势?** 能 → 对。删掉之后只剩机制 描述 → 那份「感受」本来就只在形容词里。 -**不要在用户文档里与别的工具比较。** 对照属于设计记录(第一节)。用户文档展示的是 -**这一件事在这里要写多少、跑出什么**,读者自己会比较。 +**类比可以,对照不可以 —— 两者的区别是它服务谁。** + +| | 目的 | 归属 | +|---|---|---| +| **类比** | 让读者把新概念挂到已有认知上 | 用户文档,**可以** | +| **对照** | 主张 mcpp 在某个维度上更好 | 设计记录,用户文档**不可以** | + +「工具链管理大致相当于 rustup 在 Rust 里的角色」是类比 —— 它给一个部分定位,不作 +评价。「比 CMake 简洁」是对照 —— 它是一个主张,而用户文档不是提出主张的地方。 + +类比要**带一句免责**:上面每个工具在它自己的领域里做的都比 mcpp 多。少了这一句, +定位会被读成等价。 ### 渐进式叙事:最短可跑 → 常见形状 → 完整表面 → 边角 diff --git a/docs/00-how-mcpp-works.md b/docs/00-how-mcpp-works.md deleted file mode 100644 index 714ca78a..00000000 --- a/docs/00-how-mcpp-works.md +++ /dev/null @@ -1,139 +0,0 @@ -# 00 — How mcpp Works - -**Reader:** anyone, before anything else. Every other chapter assumes this one. - -**The question this chapter answers:** what are the moving parts, and which one -is failing when something fails. - -mcpp is three tools in one program — a build system, a package manager, and a -toolchain provisioner. That is unusual, and it is the reason a reader who starts -with a command reference finds a list of verbs with nothing to attach them to. -This chapter is the model. It names no field and no flag; those are the -reference chapters, which this one makes readable. - -**Not here:** every field, flag and command. A model that also tries to be a -reference is neither, so each noun below names the chapter that owns it. - -Next: [01 — Getting Started](01-getting-started.md) puts a program on the screen. - -## The five nouns - -Everything in this documentation is about five things and the seams between -them. - -### package - -A directory with an `mcpp.toml`. It states a name, a version, what it is built -from and what it depends on. Its identity is the pair `(namespace, name)` — not -its directory, and not its file name — which is why two packages called -`cmdline` from different namespaces can coexist. - -A package is also the unit of everything else: the unit that is published, that -is cached, that declares features, and that a build program belongs to. - -Reference: [04 — The mcpp.toml Manifest](04-mcpp-toml.md). - -### graph - -A build is a graph. Sources become objects, objects become an artifact, and -C++20 module interfaces add edges between sources because one unit must be -compiled before another that imports it. mcpp computes that ordering by scanning -the sources; nothing in the manifest states it. - -Two things extend the graph rather than sitting outside it: a **build program** -(`build.mcpp`) declares additional edges, and a **rule package** supplies those -declarations for a whole class of projects. Both add nodes to the same graph; -neither is a pre-build script. - -Reference: [30 — Build Programs](30-build-mcpp.md). - -### toolchain - -The compiler mcpp uses is a **payload it installs and pins**, not a program -found on the machine. This is the property most other build systems do not have, -and it decides the shape of much else: a build is reproducible because the -compiler is part of what was resolved, and a project can state which compiler it -needs rather than documenting it in a README. - -Reference: [20 — Toolchain Management](20-toolchains.md). - -### target - -The machine the artifact runs on. It is named by a triple and it is not the -host: a build on Linux can produce a Windows executable, a bare-metal image, or -an object for a GPU. Everything conditioned on "where this runs" hangs off the -target, and the manifest can say "only there" without a second manifest. - -Reference: [21 — The Target Triple](21-the-target-triple.md). - -### index - -Where packages come from. An index holds **descriptors** — a package's identity, -its versions, and where each version's source or artifact is fetched from. A -descriptor is data; mcpp is the program that reads it. - -Reference: [11 — Publishing a Library to mcpp-index](11-publishing-a-library.md). - -## What a build does, end to end - -``` - mcpp.toml ──▶ resolve ──▶ provision ──▶ scan ──▶ compile ──▶ link - │ │ │ │ │ │ - package index + toolchain graph toolchain artifact - versions + tools edges - - ╰── three seams a first build crosses ──╯ -``` - -**Seam 1 — manifest to resolution.** What the manifest names becomes a set of -exact versions. A failure here is about the index or a version constraint, and -nothing has been compiled. - -**Seam 2 — resolution to environment.** What was resolved becomes payloads on -disk: the toolchain, and any tool a package declared. A failure here is about a -download, a platform that has no such payload, or a version floor. - -**Seam 3 — sources to graph.** The sources are scanned for `import` and the -edges are computed. A failure here names a module, not a file. - -Compilation and linking come after all three, which is why "it did not compile" -is one of four quite different situations. - -## Where the state lives - -Nothing mcpp writes is hidden, and each store answers a different question. - -| store | scope | holds | emptied by | -|---|---|---|---| -| `target///` | one project | objects, module interfaces, the artifact | `mcpp clean` | -| the build cache | the machine | compiled dependencies and `std` | `mcpp cache gc` | -| the package store | the machine | toolchain payloads and declared tools | the package manager | -| `mcpp.lock` | one project, checked in | what a resolution produced | rewritten by `mcpp update` | - -`mcpp self env` prints where each of these is on this machine. - -The fingerprint in the build directory is why two configurations do not fight: -a debug build, a release build and a cross build occupy different directories -and none invalidates the others. - -## Which noun a failure is about - -The single most useful thing this model buys. A message names a noun, and the -noun names the chapter. - -| a message about | the noun | where to look | -|---|---|---| -| a package name, a version, or "no candidate" | index | [11](11-publishing-a-library.md), [05](05-dependencies.md) | -| a download, a payload, or a version floor | toolchain | [20](20-toolchains.md), [23](23-the-project-environment.md) | -| a triple, or "unsupported target" | target | [21](21-the-target-triple.md) | -| a module that cannot be read or is not provided | graph | [30](30-build-mcpp.md) | -| a compile or link error in a file of the project | none of them | the compiler's own message | - -The last row is the useful one: when a compiler error is about the code, none of -mcpp's parts is involved, and reading mcpp's documentation will not help. - -## What this chapter leaves out - -Everything operational. No field of the manifest, no flag, and no command other -than `mcpp build` appears here on purpose — a model that also tries to be a -reference is neither. Each noun's section names the chapter that owns it. diff --git a/docs/00-what-mcpp-is.md b/docs/00-what-mcpp-is.md new file mode 100644 index 00000000..32567825 --- /dev/null +++ b/docs/00-what-mcpp-is.md @@ -0,0 +1,155 @@ +# 00 — What mcpp Is + +**Reader:** anyone, before anything else. + +**The question this chapter answers:** what is mcpp, what does it do for a +project, and what does it cost to try. + +**Not here:** how any of it works internally, and every field and flag. This +chapter ends with a session that has been run; the chapters after it are the +reference. + +## Five things in one program + +``` +mcpp = build system + + build plugins + + package manager + + toolchain management + + the environment and runtime (xlings) +``` + +Most C++ projects assemble those five from separate tools, and the seams between +them are where a new contributor loses an afternoon: the build file assumes a +compiler the machine does not have, the package manager assumes a build file it +did not write, and the environment is a paragraph in a README. + +mcpp is one program, so there are no seams to assemble. + +For a reader who already has tools for these jobs, the parts land roughly here: + +| the part | in mcpp | roughly the job of | +|---|---|---| +| build system | `mcpp.toml`, the module graph, the ninja backend | CMake, Meson | +| build plugins | `build.mcpp`, rule packages | xmake rules | +| package manager | `[dependencies]`, `mcpp.lock`, the index | Conan, vcpkg | +| toolchain management | the compiler as an installed, pinned payload | installing GCC / LLVM / MSVC by hand, or rustup's role in Rust | +| environment and runtime | `[xlings]`, payloads, the runtime search path | Nix, conda | + +**The table places the parts; it does not claim equivalence.** Each of those +tools does more in its own area than mcpp does, and a project that needs that +depth should use it. What the row is saying is which familiar job the part +corresponds to, so the rest of this documentation has somewhere to attach. + +## The guarantee + +> **Clone any mcpp project and `mcpp build` works** — without installing a +> compiler, configuring an environment, or hunting down dependencies. + +That is the whole claim, and everything below is it being demonstrated rather +than repeated. + +Two boundaries, stated here so the claim can be trusted: a project that targets +a device still downloads that device's toolkit the first time, and a target this +machine cannot serve is refused by name rather than built wrongly. + +## A first session, end to end + +Run on a machine whose only C++ compiler is GCC 13, which cannot compile +`import std`. + +```console +$ mcpp new hello +Created bin package 'hello' at /tmp/zero-demo/hello +Next: cd hello && mcpp build && mcpp run (or `mcpp test`) +``` + +Four files, and the manifest is five lines: + +``` +hello/ +├── mcpp.toml +├── src/main.cpp +├── tests/test_smoke.cpp +└── .gitignore +``` + +```toml +[package] +name = "hello" +version = "0.1.0" +description = "A modular C++23 package" +license = "Apache-2.0" +``` + +**No compiler, no language standard and no dependency is declared**, and the +source uses a feature the machine's own compiler does not have: + +```cpp +import std; + +int main() { + std::println("Hello from hello!"); +} +``` + +```console +$ mcpp run + Inferred target hello (bin from src/main.cpp) + Compiling hello v0.1.0 (.) + Finished dev [unoptimized + debuginfo] in 0.64s + Running `target/x86_64-linux-gnu/0946988e9e4b52ba/bin/hello` + +Hello from hello! +Built with import std + std::println on modular C++23. +``` + +1.25 seconds of wall clock, first run included. + +The compiler that did it is not the one on the machine: + +```console +$ g++ --version +g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 + +$ mcpp self env +default toolchain = gcc@16.1.0 +``` + +mcpp installed GCC 16 and used it. Nothing on the host was changed, and a +colleague who clones this project gets the same compiler rather than the one +their distribution happens to ship. + +**A dependency is one line and needs no other step:** + +```toml +[dependencies] +"mcpplibs.cmdline" = "^0.0.1" +``` + +`mcpp build` resolves it, fetches it, builds it and links it. + +## What mcpp is for + +mcpp is built around **C++20/23 modules and the newest language features**, and +the ecosystem it maintains follows from that rather than from a general +ambition: + +| | | +|---|---| +| modular C++ | `import std` with no configuration, module scanning, a cross-project BMI cache | +| embedded and bare metal | freestanding targets, board-support packages, one command from source to a running image | +| heterogeneous computing and GPUs | CUDA, HIP, SYCL, Vulkan/SPIR-V and Ascend C, each a rule package rather than an engine feature | +| graphics | shaders compiled as part of the build and reached as modules | +| kernel and low-level work | zero-libc tiers, an explicit link model, no hidden host dependency | + +A project that wants none of those still gets the guarantee above; a project +that wants one of them does not leave the tool to get it. + +## Where to go next + +| | | +|---|---| +| put a program on the screen | [01 — Getting Started](01-getting-started.md) | +| decide whether mcpp fits the work at hand | [02 — Scenarios](02-scenarios.md) | +| read a project of the same shape | [03 — Examples](03-examples.md) | diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md index f523eca7..e578f588 100644 --- a/docs/01-getting-started.md +++ b/docs/01-getting-started.md @@ -6,7 +6,7 @@ running, from an empty machine. **Not here:** what mcpp's parts are — that is -[00 — How mcpp Works](00-how-mcpp-works.md), which this chapter assumes rather +[00 — What mcpp Is](00-what-mcpp-is.md), which this chapter assumes rather than repeats — and every field a manifest may hold, which is [04 — The mcpp.toml Manifest](04-mcpp-toml.md). After: [03 — Examples](03-examples.md). diff --git a/docs/02-scenarios.md b/docs/02-scenarios.md index cf3fb4a3..d5d461db 100644 --- a/docs/02-scenarios.md +++ b/docs/02-scenarios.md @@ -41,7 +41,7 @@ command produces a binary that carries what it needs. **The path.** -1. [00 — How mcpp Works](00-how-mcpp-works.md) — the five nouns. +1. [00 — What mcpp Is](00-what-mcpp-is.md) — the five nouns. 2. [01 — Getting Started](01-getting-started.md) — a program on the screen. 3. [05 — Dependencies and Resolution](05-dependencies.md) — `[dependencies]`, the lock file. diff --git a/docs/09-commands-by-scenario.md b/docs/09-commands-by-scenario.md index b0902dc7..0f27c544 100644 --- a/docs/09-commands-by-scenario.md +++ b/docs/09-commands-by-scenario.md @@ -365,6 +365,22 @@ written out rather than left to the default: it is the value this manifest wants on its own terms — a missing audio device should never fail a build — so it will still say so once the key has more than one accepted value. +## Which part a failure is about + +A build crosses several stages, and a message names the one that failed. Reading +that first saves opening the wrong chapter. + +| a message about | the stage | where to look | +|---|---|---| +| a package name, a version, or "no candidate" | resolution | [05](05-dependencies.md), [11](11-publishing-a-library.md) | +| a download, a payload, or a version floor | provisioning | [20](20-toolchains.md), [23](23-the-project-environment.md) | +| a triple, or "unsupported target" | the target | [21](21-the-target-triple.md) | +| a module that cannot be read or is not provided | the module graph | [30](30-build-mcpp.md) | +| a compile or link error inside a file of the project | none of them | the compiler's own message | + +The last row is the useful one: when the error is about the code, no part of +mcpp is involved and its documentation will not help. + ## Current limitations - `mcpp why --format json` is defined for the `toolchain` topic only. The other diff --git a/docs/README.md b/docs/README.md index 5b1c2fd5..91eb099d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -58,7 +58,7 @@ Within a part the order is a reading order, not an alphabet. ### 0x — Fundamentals -- [00 — How mcpp Works](00-how-mcpp-works.md) — the model every other chapter assumes +- [00 — What mcpp Is](00-what-mcpp-is.md) — what it is, what it does, and one session that has been run - [01 — Getting Started](01-getting-started.md) — install, create, build, run - [02 — Scenarios](02-scenarios.md) — what mcpp is used for, and which features each kind of work uses - [03 — Examples](03-examples.md) — which example teaches what @@ -139,7 +139,7 @@ token in front of a reader to the chapter that owns it. | | chapter | | chapter | |---|---|---|---| -| the five nouns; what a build does | [00](00-how-mcpp-works.md) | `import std`, module interfaces, BMIs | [00](00-how-mcpp-works.md), [20](20-toolchains.md) | +| what mcpp is, and the guarantee | [00](00-what-mcpp-is.md) | `import std`, module interfaces, BMIs | [20](20-toolchains.md), [30](30-build-mcpp.md) | | `mcpp::action`, a build program | [30](30-build-mcpp.md) | a rule package, `MCPP_EXPORT_C` | [31](31-authoring-a-rule-package.md) | | a target triple, the support matrix | [21](21-the-target-triple.md) | a runner, a named runner | [41](41-devices.md) | | an island, a seam, `accel` | [42](42-heterogeneous-builds.md) | a descriptor, an index | [11](11-publishing-a-library.md) | diff --git a/docs/zh/00-how-mcpp-works.md b/docs/zh/00-how-mcpp-works.md deleted file mode 100644 index 24040965..00000000 --- a/docs/zh/00-how-mcpp-works.md +++ /dev/null @@ -1,123 +0,0 @@ -# 00 —— mcpp 的运转方式 - -**读者:**任何人,在读别的之前。其余每一章都假定读过这一章。 - -**本章回答的那一个问题:**有哪些活动部件,以及出问题时是哪一个在出问题。 - -mcpp 是三样东西合在一个程序里 —— 构建系统、包管理器、工具链供给者。这不常见, -也正是「从命令参考读起的人只看到一串动词、无处安放」的原因。本章是模型。它不写 -任何字段、任何旗标;那些在参考章节里,而本章让它们可读。 - -**不在这里:**每一个字段、旗标与命令。一份既想当模型又想当参考的文档两样都不是, -所以下面每个名词都点名了拥有它的章节。 - -下一章:[01 —— 快速开始](01-getting-started.md) 把一个程序跑到屏幕上。 - -## 五个名词 - -本套文档讲的全部内容,就是五样东西以及它们之间的缝。 - -### 包(package) - -一个带 `mcpp.toml` 的目录。它声明名字、版本、由什么构建、依赖什么。它的身份是 -`(namespace, name)` 这一对 —— 不是它的目录,也不是它的文件名 —— 所以来自不同 -namespace 的两个 `cmdline` 可以共存。 - -包同时是其余一切的单位:被发布的单位、被缓存的单位、声明 feature 的单位,以及 -构建程序所归属的单位。 - -参考:[04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md)。 - -### 图(graph) - -一次构建是一张图。源产出目标文件,目标文件产出产物,而 C++20 模块接口会在源与源 -之间加边 —— 一个单元必须先于 import 它的那个单元被编译。这个次序由 mcpp 扫描源 -算出;manifest 里不写它。 - -有两样东西是**扩展这张图**而不是待在图外面的:**构建程序**(`build.mcpp`)声明 -额外的边,**规则包**为一整类工程供给这些声明。两者都往同一张图里加节点;都不是 -构建前脚本。 - -参考:[30 —— 构建程序](30-build-mcpp.md)。 - -### 工具链(toolchain) - -mcpp 使用的编译器是**它自己安装并钉住的载荷**,不是在机器上找到的程序。这是大多数 -其它构建系统没有的性质,而它决定了很多别的形状:构建可复现,因为编译器本身是被 -解析出来的一部分;工程可以**声明**它需要哪个编译器,而不是写在 README 里让人照做。 - -参考:[20 —— 工具链管理](20-toolchains.md)。 - -### 目标(target) - -产物运行的那台机器。它由三元组命名,而且它不是宿主:在 Linux 上的一次构建可以产出 -Windows 可执行文件、裸机镜像,或者给 GPU 的一个目标文件。一切以「它在哪里运行」为 -条件的东西都挂在目标上,而 manifest 说「只在那里」不需要第二份 manifest。 - -参考:[21 —— 目标三元组](21-the-target-triple.md)。 - -### 索引(index) - -包从哪里来。索引里存的是**描述符** —— 一个包的身份、它有哪些版本、每个版本的源码 -或产物从哪里取。描述符是数据;mcpp 是读它的那个程序。 - -参考:[11 —— 发布一个库到 mcpp-index](11-publishing-a-library.md)。 - -## 一次构建从头到尾做了什么 - -``` - mcpp.toml ──▶ 解析 ──▶ 供给 ──▶ 扫描 ──▶ 编译 ──▶ 链接 - │ │ │ │ │ │ - 包 索引 + 工具链 图的边 工具链 产物 - 版本 + 工具 - - ╰──── 第一次构建跨过的三道缝 ────╯ -``` - -**第一道缝 —— 从 manifest 到解析结果。** manifest 点名的东西变成一组确切的版本。 -这里失败是索引或版本约束的事,而且还没有编译过任何东西。 - -**第二道缝 —— 从解析结果到环境。** 解析出来的东西变成磁盘上的载荷:工具链,以及 -任何包声明过的工具。这里失败是下载、某平台没有这样的载荷、或者版本下界的事。 - -**第三道缝 —— 从源到图。** 源被扫描 `import`,边被算出。这里失败点名的是一个模块, -不是一个文件。 - -编译与链接在这三道缝之后,所以「它编不过」其实是四种相当不同的处境。 - -## 状态落在磁盘的哪里 - -mcpp 写的东西没有一样是隐藏的,而每个存储回答的是不同的问题。 - -| 存储 | 作用域 | 存什么 | 由什么清空 | -|---|---|---|---| -| `target///` | 单个工程 | 目标文件、模块接口、产物 | `mcpp clean` | -| 构建缓存 | 整台机器 | 编译过的依赖与 `std` | `mcpp cache gc` | -| 包存储 | 整台机器 | 工具链载荷与被声明的工具 | 包管理器 | -| `mcpp.lock` | 单个工程,进版本库 | 一次解析产生的结果 | `mcpp update` 重写 | - -`mcpp self env` 打印这几样在本机的位置。 - -构建目录名里的那个指纹,正是两种配置互不打架的原因:debug 构建、release 构建与 -交叉构建各占一个目录,谁也不让谁失效。 - -## 一次失败是关于哪个名词的 - -这个模型买到的最有用的一件事。一条消息点名一个名词,而名词点名章节。 - -| 消息里出现 | 名词 | 去哪里看 | -|---|---|---| -| 包名、版本,或「没有候选」 | 索引 | [11](11-publishing-a-library.md)、[05](05-dependencies.md) | -| 下载、载荷,或版本下界 | 工具链 | [20](20-toolchains.md)、[23](23-the-project-environment.md) | -| 三元组,或「不支持的目标」 | 目标 | [21](21-the-target-triple.md) | -| 某个模块读不到或没有人提供它 | 图 | [30](30-build-mcpp.md) | -| 工程自己某个文件里的编译或链接错误 | 一个都不是 | 编译器自己的消息 | - -最后一行是有用的那一行:当编译器报的是代码本身的错误时,mcpp 的任何部件都没有 -参与,读 mcpp 的文档不会有帮助。 - -## 本章有意不写什么 - -一切操作性的东西。manifest 的字段、旗标,以及 `mcpp build` 之外的命令,都刻意不在 -这里出现 —— 一份既想当模型又想当参考的文档,两样都不是。每个名词那一节点名了拥有 -它的章节。 diff --git a/docs/zh/00-what-mcpp-is.md b/docs/zh/00-what-mcpp-is.md new file mode 100644 index 00000000..55035e0b --- /dev/null +++ b/docs/zh/00-what-mcpp-is.md @@ -0,0 +1,147 @@ +# 00 —— mcpp 是什么 + +**读者:**任何人,在读别的之前。 + +**本章回答的那一个问题:**mcpp 是什么、它为一个工程做了什么、以及试一下的代价有 +多大。 + +**不在这里:**这些东西内部怎么运转,以及每一个字段和旗标。本章以一段**真的跑过**的 +会话结束;它之后的章节才是参考。 + +## 五样东西合在一个程序里 + +``` +mcpp = 通用构建系统 + + 构建插件 + + 包管理 + + 工具链管理 + + 环境与运行时(xlings) +``` + +多数 C++ 工程要把这五样从不同工具里拼起来,而新人一个下午就消耗在拼缝上:构建文件 +假定机器上有某个编译器,包管理器假定一份不是它写的构建文件,而环境是 README 里的 +一段话。 + +mcpp 是一个程序,所以没有缝需要拼。 + +对于已经有工具做这些事的读者,各部分大致落在这里: + +| 部分 | 在 mcpp 里是 | 大致相当于谁的活 | +|---|---|---| +| 通用构建系统 | `mcpp.toml`、模块图、ninja 后端 | CMake、Meson | +| 构建插件 | `build.mcpp`、规则包 | xmake rules | +| 包管理 | `[dependencies]`、`mcpp.lock`、索引 | Conan、vcpkg | +| 工具链管理 | 编译器作为被安装并钉住的载荷 | 手工装 GCC / LLVM / MSVC,或 Rust 里 rustup 的角色 | +| 环境与运行时 | `[xlings]`、载荷、运行期搜索路径 | Nix、conda | + +**这张表是给各部分定位,不是宣称等价。** 上面每一个工具在它自己的领域里做的都比 mcpp +多,需要那种深度的工程应当去用它。这一行说的只是「这个部分对应哪件熟悉的活」,好让 +本套文档其余部分有地方可挂。 + +## 核心保证 + +> **拿到任何一个 mcpp 工程,`mcpp build` 就能构建** —— 不需要自己装编译器、配环境, +> 也不需要去找依赖的库。 + +全部主张就是这一句,下面的内容是把它**演示出来**,而不是把它重复一遍。 + +两条边界写在这里,好让这句话可以被信任:面向设备的工程第一次仍会下载那个设备的 +工具包;而这台机器服务不了的目标会被**点名拒绝**,不会被错误地构建出来。 + +## 第一段会话,从头到尾 + +跑在一台唯一的 C++ 编译器是 GCC 13 的机器上 —— 那个编译器编不了 `import std`。 + +```console +$ mcpp new hello +Created bin package 'hello' at /tmp/zero-demo/hello +Next: cd hello && mcpp build && mcpp run (or `mcpp test`) +``` + +四个文件,manifest 五行: + +``` +hello/ +├── mcpp.toml +├── src/main.cpp +├── tests/test_smoke.cpp +└── .gitignore +``` + +```toml +[package] +name = "hello" +version = "0.1.0" +description = "A modular C++23 package" +license = "Apache-2.0" +``` + +**没有声明编译器、没有声明语言档位、没有声明任何依赖**,而源码用的是这台机器自己的 +编译器不具备的特性: + +```cpp +import std; + +int main() { + std::println("Hello from hello!"); +} +``` + +```console +$ mcpp run + Inferred target hello (bin from src/main.cpp) + Compiling hello v0.1.0 (.) + Finished dev [unoptimized + debuginfo] in 0.64s + Running `target/x86_64-linux-gnu/0946988e9e4b52ba/bin/hello` + +Hello from hello! +Built with import std + std::println on modular C++23. +``` + +墙钟 1.25 秒,含首次运行。 + +干这件事的编译器不是机器上的那一个: + +```console +$ g++ --version +g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 + +$ mcpp self env +default toolchain = gcc@16.1.0 +``` + +mcpp 装了 GCC 16 并用了它。宿主上没有任何东西被改动,而克隆这个工程的同事拿到的是 +**同一个编译器**,不是他的发行版恰好带的那个。 + +**加一个依赖是一行,不需要别的步骤:** + +```toml +[dependencies] +"mcpplibs.cmdline" = "^0.0.1" +``` + +`mcpp build` 会解析它、取回它、构建它、链接它。 + +## mcpp 面向什么 + +mcpp 围绕 **C++20/23 模块与最新语言特性**建立,它维护的生态由此而来,而不是来自一个 +笼统的雄心: + +| | | +|---|---| +| 模块化 C++ | `import std` 零配置、模块扫描、跨工程 BMI 缓存 | +| 嵌入式与裸机 | freestanding 目标、板级支持包、从源码到一个跑起来的镜像只要一条命令 | +| 异构计算与 GPU | CUDA、HIP、SYCL、Vulkan/SPIR-V 与 Ascend C,每一条都是规则包而不是引擎特性 | +| 图形 | 着色器作为构建的一部分被编译,并以模块到达 | +| 内核与底层 | 零 libc 档、显式的链接模型、没有隐藏的宿主依赖 | + +这些一样都不需要的工程,照样得到上面那条保证;需要其中之一的工程,不必离开这个工具 +去得到它。 + +## 接下来去哪 + +| | | +|---|---| +| 把一个程序跑到屏幕上 | [01 —— 快速开始](01-getting-started.md) | +| 判断 mcpp 适不适合手头的工作 | [02 —— 场景](02-scenarios.md) | +| 读一个形状相近的工程 | [03 —— 示例项目](03-examples.md) | diff --git a/docs/zh/01-getting-started.md b/docs/zh/01-getting-started.md index da6234c4..68b256a2 100644 --- a/docs/zh/01-getting-started.md +++ b/docs/zh/01-getting-started.md @@ -4,7 +4,7 @@ **本章回答的那一个问题:**从一台空机器开始,怎样把一个程序编译并运行起来。 -**不在这里:**mcpp 有哪些部件 —— 那是 [00 —— mcpp 的运转方式](00-how-mcpp-works.md), +**不在这里:**mcpp 有哪些部件 —— 那是 [00 —— mcpp 是什么](00-what-mcpp-is.md), 本章假定它而不重复它;以及 manifest 可以写的每一个字段,那是 [04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md)。下一章:[03 —— 示例项目](03-examples.md)。 diff --git a/docs/zh/02-scenarios.md b/docs/zh/02-scenarios.md index 73e70618..d325bafa 100644 --- a/docs/zh/02-scenarios.md +++ b/docs/zh/02-scenarios.md @@ -38,7 +38,7 @@ **路径。** -1. [00 —— mcpp 的运转方式](00-how-mcpp-works.md) —— 五个名词。 +1. [00 —— mcpp 是什么](00-what-mcpp-is.md) —— 五个名词。 2. [01 —— 快速开始](01-getting-started.md) —— 把程序跑起来。 3. [05 —— 依赖与解析](05-dependencies.md) —— `[dependencies]`、锁文件。 4. [08 —— 测试](08-testing.md) —— `tests/**/*.cpp`。 diff --git a/docs/zh/09-commands-by-scenario.md b/docs/zh/09-commands-by-scenario.md index 0b2a42da..8e35b48b 100644 --- a/docs/zh/09-commands-by-scenario.md +++ b/docs/zh/09-commands-by-scenario.md @@ -310,6 +310,22 @@ deps = ["xim:mcpp-hooks-audioplayer@0.0.1"] 这份 manifest 自己就想要的值——缺个音频设备不该让构建失败——所以等这个键有了不止 一个可接受的值之后,它仍然会这么写。 +## 一次失败是关于哪一段的 + +一次构建要跨过若干阶段,而消息会点名失败的那一段。先读这一点,可以省掉打开错误 +章节的功夫。 + +| 消息里出现 | 阶段 | 去哪里看 | +|---|---|---| +| 包名、版本,或「没有候选」 | 解析 | [05](05-dependencies.md)、[11](11-publishing-a-library.md) | +| 下载、载荷,或版本下界 | 供给 | [20](20-toolchains.md)、[23](23-the-project-environment.md) | +| 三元组,或「不支持的目标」 | 目标 | [21](21-the-target-triple.md) | +| 某个模块读不到或没有人提供它 | 模块图 | [30](30-build-mcpp.md) | +| 工程自己某个文件内部的编译或链接错误 | 一段都不是 | 编译器自己的消息 | + +最后一行是有用的那一行:当错误是关于代码本身时,mcpp 的任何一段都没有参与,它的 +文档不会有帮助。 + ## 当前边界 - `mcpp why --format json` 只对 `toolchain` 话题有定义。其余话题报 diff --git a/docs/zh/README.md b/docs/zh/README.md index f420a73f..e5a8257e 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -54,7 +54,7 @@ ### 0x —— 基础 -- [00 —— mcpp 的运转方式](00-how-mcpp-works.md) —— 其余每章都假定的模型 +- [00 —— mcpp 是什么](00-what-mcpp-is.md) —— 它是什么、它做什么,以及一段真的跑过的会话 - [01 —— 快速开始](01-getting-started.md) —— 安装、创建、构建、运行 - [02 —— 场景](02-scenarios.md) —— mcpp 被用来做什么,以及每一类工作会用到它的哪些功能 - [03 —— 示例项目](03-examples.md) —— 哪个示例教什么 @@ -135,7 +135,7 @@ | | 章节 | | 章节 | |---|---|---|---| -| 五个名词;一次构建做了什么 | [00](00-how-mcpp-works.md) | `import std`、模块接口、BMI | [00](00-how-mcpp-works.md)、[20](20-toolchains.md) | +| mcpp 是什么,以及那条保证 | [00](00-what-mcpp-is.md) | `import std`、模块接口、BMI | [20](20-toolchains.md)、[30](30-build-mcpp.md) | | `mcpp::action`、构建程序 | [30](30-build-mcpp.md) | 规则包、`MCPP_EXPORT_C` | [31](31-authoring-a-rule-package.md) | | 目标三元组、支持矩阵 | [21](21-the-target-triple.md) | runner、具名 runner | [41](41-devices.md) | | 岛、接缝、`accel` | [42](42-heterogeneous-builds.md) | 描述符、索引 | [11](11-publishing-a-library.md) | From c248ad053b6e7d20a7b5f7cd1f5266dfc81a29f4 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:41:50 +0800 Subject: [PATCH 29/36] docs: name Cargo and Zig, which are the closest single-tool analogues The per-part table places each of mcpp's five parts against a tool a reader may already use. What it did not say is that two tools are close analogues of the WHOLE shape, and for different halves of the same idea: Cargo one program that is the build, the packages, the lock file and the test runner -- so a project is cloned and built with no preliminary step. That is chapter 00's guarantee, in another language. Zig the toolchain ships with the tool and cross-compiles by default -- so the compiler is not something the machine must already have. That is the toolchain row. `build.zig` joins xmake rules on the build-plugins row, since both are the same idea: the build is a program the project writes. The part neither analogue has is the environment layer, which is why an mcpp project can also declare the non-compiler tools its build needs. Stating that is what keeps the analogy from being read as equivalence -- alongside the disclaimer the skill now requires of every analogy. --- .../2026-09-08-the-documentation-as-a-book.md | 11 ++++++++--- docs/00-what-mcpp-is.md | 15 +++++++++++++-- docs/zh/00-what-mcpp-is.md | 12 ++++++++++-- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 0236b563..0f53ead4 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -480,9 +480,14 @@ The design failed two of its own rules to get there: 1. **The definition**, in the form the reviewer gave: mcpp = build system + build plugins + package manager + toolchain management + the environment and runtime (xlings), in one program. -2. **An analogy table** — CMake, xmake rules, Conan, GCC/LLVM/MSVC, Nix/conda — - so a reader with existing tools can place each part. With the disclaimer that - places rather than equates. +2. **An analogy table** — CMake/Meson, `build.zig` and xmake rules, Conan/vcpkg, + Zig's bundled toolchain and rustup, Nix/conda — so a reader with existing + tools can place each part, with the disclaimer that places rather than + equates. Named beside it: **Cargo and Zig are the closest single-tool + analogues**, and for different halves of the same idea — Cargo for one + program being build, packages, lock and tests, Zig for the toolchain shipping + with the tool. The part neither has is the environment layer, which is why a + project can declare the non-compiler tools its build needs. 3. **The guarantee, stated once**: clone any mcpp project and `mcpp build` works, without installing a compiler, configuring an environment, or hunting dependencies. Plus the two boundaries that make it trustworthy. diff --git a/docs/00-what-mcpp-is.md b/docs/00-what-mcpp-is.md index 32567825..609c322f 100644 --- a/docs/00-what-mcpp-is.md +++ b/docs/00-what-mcpp-is.md @@ -31,11 +31,22 @@ For a reader who already has tools for these jobs, the parts land roughly here: | the part | in mcpp | roughly the job of | |---|---|---| | build system | `mcpp.toml`, the module graph, the ninja backend | CMake, Meson | -| build plugins | `build.mcpp`, rule packages | xmake rules | +| build plugins | `build.mcpp`, rule packages | `build.zig`, xmake rules | | package manager | `[dependencies]`, `mcpp.lock`, the index | Conan, vcpkg | -| toolchain management | the compiler as an installed, pinned payload | installing GCC / LLVM / MSVC by hand, or rustup's role in Rust | +| toolchain management | the compiler as an installed, pinned payload | Zig's bundled toolchain; rustup's role in Rust; installing GCC / LLVM / MSVC by hand | | environment and runtime | `[xlings]`, payloads, the runtime search path | Nix, conda | +**The closest single-tool analogues are Cargo and Zig**, and for different +halves of the same idea. Cargo is one program that is the build, the packages, +the lock file and the test runner, so a Rust project is cloned and built without +a preliminary step — that is the guarantee below, in another language. Zig ships +its toolchain with the tool and cross-compiles by default, so the compiler is +not something the machine has to already have — that is the toolchain row. + +mcpp is that shape for C++, with one part neither of them has: the environment +layer, which is why a project can also declare the *non-compiler* tools its +build needs. + **The table places the parts; it does not claim equivalence.** Each of those tools does more in its own area than mcpp does, and a project that needs that depth should use it. What the row is saying is which familiar job the part diff --git a/docs/zh/00-what-mcpp-is.md b/docs/zh/00-what-mcpp-is.md index 55035e0b..da53e5e6 100644 --- a/docs/zh/00-what-mcpp-is.md +++ b/docs/zh/00-what-mcpp-is.md @@ -29,11 +29,19 @@ mcpp 是一个程序,所以没有缝需要拼。 | 部分 | 在 mcpp 里是 | 大致相当于谁的活 | |---|---|---| | 通用构建系统 | `mcpp.toml`、模块图、ninja 后端 | CMake、Meson | -| 构建插件 | `build.mcpp`、规则包 | xmake rules | +| 构建插件 | `build.mcpp`、规则包 | `build.zig`、xmake rules | | 包管理 | `[dependencies]`、`mcpp.lock`、索引 | Conan、vcpkg | -| 工具链管理 | 编译器作为被安装并钉住的载荷 | 手工装 GCC / LLVM / MSVC,或 Rust 里 rustup 的角色 | +| 工具链管理 | 编译器作为被安装并钉住的载荷 | Zig 自带的工具链;Rust 里 rustup 的角色;手工装 GCC / LLVM / MSVC | | 环境与运行时 | `[xlings]`、载荷、运行期搜索路径 | Nix、conda | +**整体上最接近的两个类比是 Cargo 与 Zig**,而且它们各自对应同一个想法的一半。Cargo +是一个程序同时充当构建、包管理、锁文件与测试运行器,于是一个 Rust 工程克隆下来直接 +构建、没有前置步骤 —— 那就是下面那条保证,换一门语言的说法。Zig 把工具链随工具一起 +发,并且默认就能交叉编译,于是编译器不是机器必须先有的东西 —— 那就是工具链那一行。 + +mcpp 在 C++ 上是这个形状,外加一样它们两个都没有的东西:**环境层** —— 这也是工程为什么 +还能声明它的构建所需的**非编译器**工具。 + **这张表是给各部分定位,不是宣称等价。** 上面每一个工具在它自己的领域里做的都比 mcpp 多,需要那种深度的工程应当去用它。这一行说的只是「这个部分对应哪件熟悉的活」,好让 本套文档其余部分有地方可挂。 From 7ba7073b24d5fc4a18486d43bb564a3657c067be Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:48:35 +0800 Subject: [PATCH 30/36] docs: chapter 00 earns its definition, and the install section recommends one way Two corrections from review, and both are about what a reader meets first. CHAPTER 00 IS RESTRUCTURED. It opened with a metadata block -- reader, question, exclusions -- which is a reference-chapter device and reads as machinery on the front door. And it stated what mcpp is before saying what problem that answers. It now runs background -> who pays -> what mcpp is -> the guarantee -> the smallest example that shows it: C++20 gave the language modules, and adopting them turned out to be a build-system problem: scan for `import`, order the compiles, cache and invalidate the interfaces, with a compiler new enough to have the feature -- and `import std` needs the standard library's own module built first. The gap is concrete: this machine's g++ is 13.3.0 and cannot compile it. A person pays an afternoon per machine. An agent pays context, in three places before it writes a line: reading the build system, reconstructing the environment it assumes, and following a header through its transitive includes to find what is declared. Modules remove the third; mcpp removes the other two. Rule 11 exempts `00` by name, with that reason in the script: it has no "not here" to declare because everything else IS elsewhere, which its closing paragraph says in a sentence. THE INSTALL SECTION FOLLOWS R5. `xlings install mcpp -y` is the recommended way and is the only one in the body. The standalone curl script and the per-host default-toolchain table fold into a `
` -- they were offered at equal weight, which asks a first-time reader to choose between two installs before they have any basis to. --- .../2026-09-08-the-documentation-as-a-book.md | 11 ++- .github/tools/check_docs_structure.sh | 4 + docs/00-what-mcpp-is.md | 97 +++++++++++-------- docs/01-getting-started.md | 34 +++++-- docs/zh/00-what-mcpp-is.md | 90 +++++++++-------- docs/zh/01-getting-started.md | 29 ++++-- 6 files changed, 166 insertions(+), 99 deletions(-) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 0f53ead4..210d73dc 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -475,7 +475,10 @@ The design failed two of its own rules to get there: - **R7** — an advantage is shown by the artifact, not by the mechanism. A model chapter explains how the advantage is produced and never demonstrates it. -`00 — What mcpp Is` replaces it: +`00 — What mcpp Is` replaces it, and its shape came from the review too: +**background → who pays → what mcpp is → the guarantee → the smallest example +that shows it.** A first chapter earns the definition by first stating the +problem, and only then shows it solved. 1. **The definition**, in the form the reviewer gave: mcpp = build system + build plugins + package manager + toolchain management + the environment and @@ -508,3 +511,9 @@ chapter and it still produced the wrong chapter, because the cell that decides everything — the reader — was answered with "anyone" and then served as if that meant "someone who wants the model". A reader cell that names no task is not filled in. + +**And the opening block came off.** Every other chapter opens with reader, +question and exclusions; on the front door that block reads as machinery. `00` +has no "not here" to declare because everything else *is* elsewhere, which its +closing paragraph says in a sentence instead. Rule 11 exempts `00` by name, with +that reason in the script. diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh index 77a008d8..f5cb6617 100755 --- a/.github/tools/check_docs_structure.sh +++ b/.github/tools/check_docs_structure.sh @@ -211,8 +211,12 @@ PYPARITY # half -- they are the gate that stops a chapter re-absorbing a topic another # chapter owns. Five of 24 chapters had this before the design; a rule nothing # checks is a rule that decays back to five. +# Chapter 00 is exempt: it is the book's front door, and a metadata block is a +# reference-chapter device. It has no "not here" to declare, because everything +# else IS elsewhere -- which is what its closing paragraph says instead. for f in docs/[0-9]*.md docs/zh/[0-9]*.md; do [ -f "$f" ] || continue + case "$(basename "$f")" in 00-*) continue ;; esac head -18 "$f" | grep -qE '^\*\*(Reader|读者)' \ || bad "$f: no designed opening — the first lines must name the reader" head -18 "$f" | grep -qE '(question this chapter answers|本章回答的那一个问题)' \ diff --git a/docs/00-what-mcpp-is.md b/docs/00-what-mcpp-is.md index 609c322f..93bfde2a 100644 --- a/docs/00-what-mcpp-is.md +++ b/docs/00-what-mcpp-is.md @@ -1,15 +1,46 @@ # 00 — What mcpp Is -**Reader:** anyone, before anything else. +## The background: modules arrived, and the tooling did not -**The question this chapter answers:** what is mcpp, what does it do for a -project, and what does it cost to try. +C++20 gave the language modules. They change what a translation unit costs: an +interface is declared once and imported, instead of being re-parsed out of +headers by every file that needs it, and what a consumer sees is what the author +exported rather than everything the header happened to include. -**Not here:** how any of it works internally, and every field and flag. This -chapter ends with a session that has been run; the chapters after it are the -reference. +Adopting them turned out to be a build-system problem rather than a language +one. A build has to scan sources for `import`, order the compiles accordingly, +cache the compiled interfaces and invalidate them correctly — with a compiler +new enough to have the feature at all. `import std` adds one more: the standard +library's own module has to be built before anything can use it. -## Five things in one program +The gap is concrete rather than theoretical. On the machine this chapter was +written on: + +```console +$ g++ --version +g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 +``` + +That compiler cannot compile `import std`. Nothing about the project is wrong; +the machine is simply not the machine the project needs. + +## Who pays for that gap + +**A person pays an afternoon**, once per machine and once per new contributor: +install a newer compiler, work out which build-system flags turn modules on, +find the dependencies, and then work out which of the three broke when it does +not link. + +**An agent pays context**, in three places before it writes a line of code: +reading the build system to learn what it does, reconstructing the environment +it assumes, and following a header through its transitive includes to find out +what is actually declared. + +Modules remove the third — an interface is explicit, and `import` says exactly +what is used. mcpp exists to remove the other two, and to make the first work +without the afternoon. + +## What mcpp is ``` mcpp = build system @@ -20,9 +51,9 @@ mcpp = build system ``` Most C++ projects assemble those five from separate tools, and the seams between -them are where a new contributor loses an afternoon: the build file assumes a -compiler the machine does not have, the package manager assumes a build file it -did not write, and the environment is a paragraph in a README. +them are where the afternoon goes: the build file assumes a compiler the machine +does not have, the package manager assumes a build file it did not write, and +the environment is a paragraph in a README. mcpp is one program, so there are no seams to assemble. @@ -38,10 +69,9 @@ For a reader who already has tools for these jobs, the parts land roughly here: **The closest single-tool analogues are Cargo and Zig**, and for different halves of the same idea. Cargo is one program that is the build, the packages, -the lock file and the test runner, so a Rust project is cloned and built without -a preliminary step — that is the guarantee below, in another language. Zig ships -its toolchain with the tool and cross-compiles by default, so the compiler is -not something the machine has to already have — that is the toolchain row. +the lock file and the test runner, so a Rust project is cloned and built with no +preliminary step. Zig ships its toolchain with the tool and cross-compiles by +default, so the compiler is not something the machine must already have. mcpp is that shape for C++, with one part neither of them has: the environment layer, which is why a project can also declare the *non-compiler* tools its @@ -49,25 +79,20 @@ build needs. **The table places the parts; it does not claim equivalence.** Each of those tools does more in its own area than mcpp does, and a project that needs that -depth should use it. What the row is saying is which familiar job the part -corresponds to, so the rest of this documentation has somewhere to attach. +depth should use it. ## The guarantee > **Clone any mcpp project and `mcpp build` works** — without installing a > compiler, configuring an environment, or hunting down dependencies. -That is the whole claim, and everything below is it being demonstrated rather -than repeated. - Two boundaries, stated here so the claim can be trusted: a project that targets a device still downloads that device's toolkit the first time, and a target this machine cannot serve is refused by name rather than built wrongly. -## A first session, end to end +## The smallest example that shows it -Run on a machine whose only C++ compiler is GCC 13, which cannot compile -`import std`. +On the same machine, whose only C++ compiler is the GCC 13 above. ```console $ mcpp new hello @@ -77,14 +102,6 @@ Next: cd hello && mcpp build && mcpp run (or `mcpp test`) Four files, and the manifest is five lines: -``` -hello/ -├── mcpp.toml -├── src/main.cpp -├── tests/test_smoke.cpp -└── .gitignore -``` - ```toml [package] name = "hello" @@ -93,8 +110,9 @@ description = "A modular C++23 package" license = "Apache-2.0" ``` -**No compiler, no language standard and no dependency is declared**, and the -source uses a feature the machine's own compiler does not have: +**No compiler, no language standard and no dependency is declared** — that is +the whole of what a reader has to understand before changing this project. The +source uses the feature the machine's compiler does not have: ```cpp import std; @@ -115,21 +133,16 @@ Hello from hello! Built with import std + std::println on modular C++23. ``` -1.25 seconds of wall clock, first run included. - -The compiler that did it is not the one on the machine: +1.25 seconds of wall clock, first run included. The compiler that did it: ```console -$ g++ --version -g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 - $ mcpp self env default toolchain = gcc@16.1.0 ``` mcpp installed GCC 16 and used it. Nothing on the host was changed, and a -colleague who clones this project gets the same compiler rather than the one -their distribution happens to ship. +colleague — or an agent — cloning this project gets the same compiler rather +than the one their machine happens to ship. **A dependency is one line and needs no other step:** @@ -164,3 +177,7 @@ that wants one of them does not leave the tool to get it. | put a program on the screen | [01 — Getting Started](01-getting-started.md) | | decide whether mcpp fits the work at hand | [02 — Scenarios](02-scenarios.md) | | read a project of the same shape | [03 — Examples](03-examples.md) | + +Everything after this chapter is reference: what a manifest may say, how +dependencies resolve, how a target is named. This one states no field and no +flag on purpose. diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md index e578f588..cf2e3166 100644 --- a/docs/01-getting-started.md +++ b/docs/01-getting-started.md @@ -14,27 +14,43 @@ than repeats — and every field a manifest may hold, which is ## Installation -Supported hosts are Linux x86_64 / aarch64, macOS ARM64, and Windows x86_64. You do not need to install GCC, xlings, or any other build dependency beforehand. -On its first run, mcpp installs a default toolchain into an isolated sandbox (`~/.mcpp/`). The choice is host-aware: Linux x86_64 uses `gcc@16.1.0`; other Linux architectures use `gcc@15.1.0-musl`; macOS uses `llvm@20.1.7`; Windows uses `llvm@20.1.7` when usable MSVC is available and otherwise uses `gcc@16.1.0` for `x86_64-windows-gnu`. +Supported hosts are Linux x86_64 / aarch64, macOS ARM64, and Windows x86_64. +GCC, xlings and every other build dependency are installed by mcpp; none of them +has to be there first. -We recommend installing via [xlings](https://xlings.d2learn.org), which keeps mcpp isolated from your system environment: +**The recommended way is [xlings](https://xlings.d2learn.org)**, which keeps +mcpp isolated from the system environment: ```bash xlings install mcpp -y ``` -Alternatively, on Linux x86_64/aarch64 or macOS ARM64, use the one-line -installer script (xlings is bundled, and everything is installed under -`~/.mcpp/`): +
+Other ways: the standalone script, and what the first run installs + +On Linux x86_64/aarch64 or macOS ARM64 a one-line installer bundles xlings and +puts everything under `~/.mcpp/`. It does not support Windows, where the +PowerShell xlings command in the README is the way in. ```bash curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bash ``` -The script does not support Windows; install through the PowerShell xlings -command in the README instead. +On its first run mcpp installs a default toolchain into `~/.mcpp/`, chosen for +the host: + +| host | default | +|---|---| +| Linux x86_64 | `gcc@16.1.0` | +| other Linux architectures | `gcc@15.1.0-musl` | +| macOS | `llvm@20.1.7` | +| Windows with usable MSVC | `llvm@20.1.7` | +| Windows without it | `gcc@16.1.0` for `x86_64-windows-gnu` | + +Full installation instructions, including Windows, are in the +["Installation" section of the README](../README.md#install). -For full installation instructions (including xlings install commands, Windows support, and more), see the ["Installation" section of the README](../README.md#install). +
Once installation is complete, start a new shell session, then verify: diff --git a/docs/zh/00-what-mcpp-is.md b/docs/zh/00-what-mcpp-is.md index da53e5e6..6f323d93 100644 --- a/docs/zh/00-what-mcpp-is.md +++ b/docs/zh/00-what-mcpp-is.md @@ -1,14 +1,38 @@ # 00 —— mcpp 是什么 -**读者:**任何人,在读别的之前。 +## 背景:模块到了,工具链没跟上 -**本章回答的那一个问题:**mcpp 是什么、它为一个工程做了什么、以及试一下的代价有 -多大。 +C++20 给了语言模块。它改变的是一个翻译单元的代价:接口只声明一次、被 import,而不是 +被每个需要它的文件从头文件里重新解析一遍;而消费者看到的是作者**导出**的东西,不是 +头文件恰好 include 进来的一切。 -**不在这里:**这些东西内部怎么运转,以及每一个字段和旗标。本章以一段**真的跑过**的 -会话结束;它之后的章节才是参考。 +结果采用它是一个**构建系统问题**,不是语言问题。构建要扫描源里的 `import`、据此给 +编译定序、缓存编译好的接口并正确地让它失效 —— 而且要在一个**新到具备这个特性**的 +编译器上做这些。`import std` 再加一条:标准库自己的模块必须先被构建出来,别的东西 +才能用它。 -## 五样东西合在一个程序里 +这个落差是具体的,不是理论上的。在写这一章的这台机器上: + +```console +$ g++ --version +g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 +``` + +这个编译器编不了 `import std`。工程本身没有任何问题;只是这台机器不是这个工程需要的 +那台机器。 + +## 谁在为这个落差付账 + +**人付一个下午**,每台机器一次、每个新同事一次:装一个更新的编译器、弄清哪些构建系统 +旗标能把模块打开、把依赖找齐,然后在链接不过时再弄清这三件里坏的是哪一件。 + +**Agent 付上下文**,而且在写下第一行代码之前就付掉三处:读构建系统以搞清它做了什么、 +重建它所假定的环境、以及顺着一个头文件穿过它的传递 include 去弄清究竟声明了什么。 + +模块消掉了第三处 —— 接口是显式的,`import` 恰好说明用到了什么。mcpp 存在的理由是消掉 +另外两处,并让第一处不必花掉那个下午。 + +## mcpp 是什么 ``` mcpp = 通用构建系统 @@ -18,9 +42,8 @@ mcpp = 通用构建系统 + 环境与运行时(xlings) ``` -多数 C++ 工程要把这五样从不同工具里拼起来,而新人一个下午就消耗在拼缝上:构建文件 -假定机器上有某个编译器,包管理器假定一份不是它写的构建文件,而环境是 README 里的 -一段话。 +多数 C++ 工程要把这五样从不同工具里拼起来,而那个下午就消耗在拼缝上:构建文件假定 +机器上有某个编译器,包管理器假定一份不是它写的构建文件,而环境是 README 里的一段话。 mcpp 是一个程序,所以没有缝需要拼。 @@ -34,31 +57,28 @@ mcpp 是一个程序,所以没有缝需要拼。 | 工具链管理 | 编译器作为被安装并钉住的载荷 | Zig 自带的工具链;Rust 里 rustup 的角色;手工装 GCC / LLVM / MSVC | | 环境与运行时 | `[xlings]`、载荷、运行期搜索路径 | Nix、conda | -**整体上最接近的两个类比是 Cargo 与 Zig**,而且它们各自对应同一个想法的一半。Cargo -是一个程序同时充当构建、包管理、锁文件与测试运行器,于是一个 Rust 工程克隆下来直接 -构建、没有前置步骤 —— 那就是下面那条保证,换一门语言的说法。Zig 把工具链随工具一起 -发,并且默认就能交叉编译,于是编译器不是机器必须先有的东西 —— 那就是工具链那一行。 +**整体上最接近的两个类比是 Cargo 与 Zig**,而且它们各自对应同一个想法的一半。Cargo 是 +一个程序同时充当构建、包管理、锁文件与测试运行器,于是一个 Rust 工程克隆下来直接构建、 +没有前置步骤。Zig 把工具链随工具一起发,并且默认就能交叉编译,于是编译器不是机器必须 +先有的东西。 mcpp 在 C++ 上是这个形状,外加一样它们两个都没有的东西:**环境层** —— 这也是工程为什么 还能声明它的构建所需的**非编译器**工具。 **这张表是给各部分定位,不是宣称等价。** 上面每一个工具在它自己的领域里做的都比 mcpp -多,需要那种深度的工程应当去用它。这一行说的只是「这个部分对应哪件熟悉的活」,好让 -本套文档其余部分有地方可挂。 +多,需要那种深度的工程应当去用它。 -## 核心保证 +## 那条保证 > **拿到任何一个 mcpp 工程,`mcpp build` 就能构建** —— 不需要自己装编译器、配环境, > 也不需要去找依赖的库。 -全部主张就是这一句,下面的内容是把它**演示出来**,而不是把它重复一遍。 - -两条边界写在这里,好让这句话可以被信任:面向设备的工程第一次仍会下载那个设备的 -工具包;而这台机器服务不了的目标会被**点名拒绝**,不会被错误地构建出来。 +两条边界写在这里,好让这句话可以被信任:面向设备的工程第一次仍会下载那个设备的工具包; +而这台机器服务不了的目标会被**点名拒绝**,不会被错误地构建出来。 -## 第一段会话,从头到尾 +## 展示它的最小示例 -跑在一台唯一的 C++ 编译器是 GCC 13 的机器上 —— 那个编译器编不了 `import std`。 +还是那台机器 —— 唯一的 C++ 编译器就是上面那个 GCC 13。 ```console $ mcpp new hello @@ -68,14 +88,6 @@ Next: cd hello && mcpp build && mcpp run (or `mcpp test`) 四个文件,manifest 五行: -``` -hello/ -├── mcpp.toml -├── src/main.cpp -├── tests/test_smoke.cpp -└── .gitignore -``` - ```toml [package] name = "hello" @@ -84,8 +96,8 @@ description = "A modular C++23 package" license = "Apache-2.0" ``` -**没有声明编译器、没有声明语言档位、没有声明任何依赖**,而源码用的是这台机器自己的 -编译器不具备的特性: +**没有声明编译器、没有声明语言档位、没有声明任何依赖** —— 这就是一个读者在改动这个 +工程之前需要理解的全部。而源码用的正是这台机器的编译器不具备的那个特性: ```cpp import std; @@ -106,20 +118,15 @@ Hello from hello! Built with import std + std::println on modular C++23. ``` -墙钟 1.25 秒,含首次运行。 - -干这件事的编译器不是机器上的那一个: +墙钟 1.25 秒,含首次运行。干这件事的编译器: ```console -$ g++ --version -g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 - $ mcpp self env default toolchain = gcc@16.1.0 ``` -mcpp 装了 GCC 16 并用了它。宿主上没有任何东西被改动,而克隆这个工程的同事拿到的是 -**同一个编译器**,不是他的发行版恰好带的那个。 +mcpp 装了 GCC 16 并用了它。宿主上没有任何东西被改动,而克隆这个工程的同事 —— 或者 +agent —— 拿到的是**同一个编译器**,不是他那台机器恰好带的那个。 **加一个依赖是一行,不需要别的步骤:** @@ -153,3 +160,6 @@ mcpp 围绕 **C++20/23 模块与最新语言特性**建立,它维护的生态由 | 把一个程序跑到屏幕上 | [01 —— 快速开始](01-getting-started.md) | | 判断 mcpp 适不适合手头的工作 | [02 —— 场景](02-scenarios.md) | | 读一个形状相近的工程 | [03 —— 示例项目](03-examples.md) | + +本章之后的一切都是参考:manifest 可以说什么、依赖怎样解析、目标怎样命名。这一章刻意 +不写任何字段与旗标。 diff --git a/docs/zh/01-getting-started.md b/docs/zh/01-getting-started.md index 68b256a2..88daf05c 100644 --- a/docs/zh/01-getting-started.md +++ b/docs/zh/01-getting-started.md @@ -12,27 +12,38 @@ ## 安装 -支持的宿主为 Linux x86_64 / aarch64、macOS ARM64 与 Windows x86_64,无需预先安装 GCC、xlings 或其他构建依赖。 -mcpp 在首次运行时会将默认工具链安装至独立沙盒(`~/.mcpp/`)。选择会随宿主变化:Linux x86_64 使用 `gcc@16.1.0`; 其他 Linux 架构使用 `gcc@15.1.0-musl`; macOS 使用 `llvm@20.1.7`; Windows 在存在可用 MSVC 时使用 `llvm@20.1.7`,否则使用面向 `x86_64-windows-gnu` 的 `gcc@16.1.0`。 +支持的宿主为 Linux x86_64 / aarch64、macOS ARM64 与 Windows x86_64。GCC、xlings 以及 +其余构建依赖都由 mcpp 安装,一个都不需要预先具备。 -推荐通过 [xlings](https://xlings.d2learn.org) 进行安装,可与系统 -环境保持隔离: +**推荐方式是 [xlings](https://xlings.d2learn.org)**,它让 mcpp 与系统环境保持隔离: ```bash xlings install mcpp -y ``` -Linux x86_64/aarch64 或 macOS ARM64 也可使用一键安装脚本(内置 xlings,统一安装至 -`~/.mcpp/`): +
+其它方式:独立安装脚本,以及首次运行会装什么 + +在 Linux x86_64/aarch64 或 macOS ARM64 上,有一个内置 xlings 的一键脚本,把一切装到 +`~/.mcpp/` 下。它不支持 Windows —— 那里的入口是 README 里的 PowerShell xlings 命令。 ```bash curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bash ``` -该脚本不支持 Windows;请改用 README 中的 PowerShell xlings 安装命令。 +mcpp 首次运行时会把一条默认工具链装进 `~/.mcpp/`,按宿主选择: + +| 宿主 | 默认 | +|---|---| +| Linux x86_64 | `gcc@16.1.0` | +| 其它 Linux 架构 | `gcc@15.1.0-musl` | +| macOS | `llvm@20.1.7` | +| 有可用 MSVC 的 Windows | `llvm@20.1.7` | +| 没有 MSVC 的 Windows | 面向 `x86_64-windows-gnu` 的 `gcc@16.1.0` | + +完整安装说明(含 Windows)见 [README 的「安装」小节](../../README.zh-CN.md#安装)。 -完整安装说明(包括 xlings 安装命令、Windows 支持等)参见 -[README 的"安装"小节](../../README.zh-CN.md#安装)。 +
安装完成后,启动新的 shell 会话,然后验证: From fb247086338729f339eab6a8208c68f82d3694bc Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:50:31 +0800 Subject: [PATCH 31/36] docs: R5's debt was over-reported, because the measurement counted a proxy The record said four chapters offer several spellings at equal weight, on the evidence that they carry many code blocks and no
. That counted a proxy instead of the property. A section with five code blocks is usually five features documented in sequence -- chapter 20's are glob exclusion, per-glob flags and generated files -- or one continuous session, which is what chapter 00's six blocks are. Folding any of those away would break what they belong to. Measured on the property -- a section that offers ALTERNATIVES at equal weight, found by the markers that introduce one -- the whole tree returns three hits, and none is a choice presented to a reader: two are prose about what is accepted, one is a comment in a command line. R5's real debt after 05 and 01 is close to zero, and what remains is to apply the rule as new alternatives appear. This is the third criterion in this batch that counted something adjacent to the property: headings containing "test" for chapters that explain testing, substring spellings for the lookup index, code blocks for offered alternatives. The skill says a criterion must not be a substring search; the variant now named beside it is COUNTING A PROXY, which survives review precisely because the number is real. --- .../2026-09-08-the-documentation-as-a-book.md | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 210d73dc..9220779b 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -379,15 +379,36 @@ reader can reproduce. reproduced on the current release, and each of which a reader could otherwise only discover by hitting it. -### 11.2 Four chapters present several spellings as equals - -`04` (50 code blocks), `20` (35), `30` (37) and `40` (33) carry no `
`, -which means every spelling in them is offered to the reader at the same weight. -R5 says one recommended path in the body and the rest folded away. `05` is the -one section converted so far, and it is the shape the other four follow. - -*Criterion:* a reader who reads only the body and opens nothing can do the thing -correctly without choosing. +### 11.2 R5's debt was over-reported, and the measurement is why + +The first reading said four chapters present several spellings as equals, on the +evidence that `04`, `20`, `30` and `40` carry many code blocks and no +`
`. **That counted a proxy instead of the property.** A section with +five code blocks is usually five features documented in sequence — `20`'s are +glob exclusion, per-glob flags and generated files — or one continuous session, +which is what chapter `00`'s six blocks are. Folding any of those away would +break the thing they belong to. + +Measured on the property instead — a section that *offers alternatives at equal +weight*, marked by "alternatively", "either form", "也可以", "等价写法" — the +whole tree returns **three** hits, and none of the three is a choice presented +to a reader: two are prose about what is accepted, one is a comment in a command +line. + +So R5's real debt after `05` (the dependency spellings) and `01` (the two +installs) is close to zero. What remains is to apply the rule as new +alternatives appear, not to convert a backlog. + +*Criterion, restated so it is not measured by proxy again:* a reader who reads +only the body and opens nothing can do the thing correctly **without choosing +between two ways of doing it**. + +> Third time in this batch that a criterion counted something adjacent to the +> property: headings containing "test" for chapters that explain testing, +> substring spellings for a lookup index, code blocks for offered alternatives. +> The skill already says a criterion must not be a substring search; the variant +> worth naming is **counting a proxy**, which passes review because the number +> is real. ### 11.3 The manifest reference still holds worked examples and an appendix From c3cebe8846c9a6e5d17d1aff38ab7da2618bf085 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 18:51:01 +0800 Subject: [PATCH 32/36] docs(skill): counting a proxy is the twin of a substring search The previous commit corrected the record and the rule it drew did not reach the skill, because the anchor had shifted. It is there now, as item 7 beside "a criterion must not be a substring search": counting code blocks to decide whether alternatives are offered at equal weight; counting headings that contain a word to decide which chapter owns a topic. The number is real and the thing counted is not the property -- and because the number is real, review does not catch it. With the three instances from this batch recorded beside it, so the shape is recognisable rather than abstract. --- .agents/skills/mcpp-docs-style/SKILL.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index cd6cf101..35f9d006 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -260,6 +260,14 @@ docs/specs/ ──▶ docs/ 允许 6. **过期的判据不要用子串搜索。** 「grep 到这个词就算讲过了」会在有人改一次 措辞时静默变空转。要判断一份文档是否覆盖某个能力,读**结构化的东西** —— 示例的 `mcpp.toml`、源码里的键表、`print_usage()` 的正文。 +7. **也不要数一个代理量。** 子串搜索的孪生形态:数**代码块个数**来判断「是不是把 + 几种做法并列了」、数**含某词的标题**来判断「哪一章拥有这个主题」。数字是真的, + 而被量的对象不是那个性质 —— **正因为数字是真的,评审很难发现**。判据要直接指向 + 性质:并列的替代由「alternatively / 也可以 / 等价写法」这类**并列标记**识别, + 主题归属由「这一节是不是在解释它」识别。 + + 本轮三次同形:用含 test 的标题数「哪几章解释测试」、用字面拼写查反查索引、用 + 代码块数量查并列替代。三次的数字都对,三次量的都不是那个性质。 ## 九、梯度:一个主题的五级台阶,以及只链接相邻级 From 4a5d6814fd79103c8722784b3bfe97270bdee16e Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:52:20 +0800 Subject: [PATCH 33/36] docs(style): the test is an interrogative word, not a question mark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chapter 00's section headings `背景:模块到了,工具链没跟上` and `谁在为这个落差付账`, and the analogy table's column header `大致相当于谁的活`, are all colloquial, all violate the register rule, and all passed check_docs_style.sh. Two independent defects in one check. THE PREDICATE TESTED A MARKER RATHER THAN THE PROPERTY. It matched `?`, 吗 and 呢 -- the punctuation a question usually carries. The property is the interrogative word, and neither heading carries the marker. THE OBJECT SET WAS ENUMERATED SYNTACTICALLY. It read lines beginning with `#`. The property a heading has -- it names a topic, it is read out of order, it is what a reader scans -- is held equally by a table's header cells, which were therefore never examined. - Chapter 00 rewritten. The scope of a background section is decided by the problem, not by the tool: a C++ project needs four things at once and no single tool owns all four; CMake is the de facto standard for one of them, and a de facto standard is a statement about adoption rather than about the experience of use; the environment is the layer that fails most often because it is the only one nothing checks. Modules are the fourth constraint on that structure rather than the structure. - 100 headings and 57 column headers become noun phrases, in both languages. - check_docs_style.sh: the predicate is now the interrogative word set, and a fourth rule covers column headers. Chapter 00's title is exempt by name, with the reason stated. - check_docs_structure.sh rule 9 discarded the link fragment, so a link to a renamed section resolved to the file and passed. It now computes GitHub's slug and verifies the fragment. It immediately reported two anchors in chapter 30 that were wrong when they were written. - The skill records R5 (the scope of a background section), the predicate rule and the column-header rule, each with this batch's own sentences as the worked example. Each of the four new checks was falsified before commit: an interrogative heading with no question mark, a Chinese column header, an English column header, and an interrogative inside inline code that must not fire. --- .agents/skills/mcpp-docs-style/SKILL.md | 19 ++- .github/tools/check_docs_structure.sh | 67 ++++++++++- .github/tools/check_docs_style.sh | 65 +++++++++- docs/00-what-mcpp-is.md | 152 +++++++++++++----------- docs/03-examples.md | 12 +- docs/04-mcpp-toml.md | 2 +- docs/06-features-and-capabilities.md | 2 +- docs/07-workspace.md | 2 +- docs/08-testing.md | 4 +- docs/09-commands-by-scenario.md | 6 +- docs/10-pack-and-release.md | 6 +- docs/12-binary-distribution.md | 6 +- docs/20-toolchains.md | 14 +-- docs/21-the-target-triple.md | 12 +- docs/23-the-project-environment.md | 16 +-- docs/24-openkal-cross.md | 6 +- docs/30-build-mcpp.md | 4 +- docs/31-authoring-a-rule-package.md | 6 +- docs/32-authoring-a-payload.md | 4 +- docs/33-authoring-an-adapter.md | 4 +- docs/34-authoring-a-bsp.md | 4 +- docs/41-devices.md | 4 +- docs/42-heterogeneous-builds.md | 16 +-- docs/50-machine-output.md | 6 +- docs/51-supported-versions.md | 4 +- docs/90-build-from-source.md | 4 +- docs/91-toolchain-internals.md | 14 +-- docs/92-release.md | 4 +- docs/README.md | 2 +- docs/specs/exit-codes.md | 4 +- docs/zh/00-what-mcpp-is.md | 131 +++++++++++--------- docs/zh/02-scenarios.md | 2 +- docs/zh/03-examples.md | 12 +- docs/zh/04-mcpp-toml.md | 4 +- docs/zh/06-features-and-capabilities.md | 2 +- docs/zh/07-workspace.md | 2 +- docs/zh/08-testing.md | 4 +- docs/zh/09-commands-by-scenario.md | 6 +- docs/zh/10-pack-and-release.md | 6 +- docs/zh/12-binary-distribution.md | 4 +- docs/zh/20-toolchains.md | 12 +- docs/zh/21-the-target-triple.md | 14 +-- docs/zh/22-target-side.md | 2 +- docs/zh/23-the-project-environment.md | 22 ++-- docs/zh/24-openkal-cross.md | 6 +- docs/zh/30-build-mcpp.md | 4 +- docs/zh/31-authoring-a-rule-package.md | 6 +- docs/zh/32-authoring-a-payload.md | 4 +- docs/zh/33-authoring-an-adapter.md | 4 +- docs/zh/34-authoring-a-bsp.md | 4 +- docs/zh/41-devices.md | 6 +- docs/zh/42-heterogeneous-builds.md | 18 +-- docs/zh/50-machine-output.md | 6 +- docs/zh/51-supported-versions.md | 2 +- docs/zh/90-build-from-source.md | 4 +- docs/zh/91-toolchain-internals.md | 12 +- docs/zh/92-release.md | 4 +- 57 files changed, 467 insertions(+), 307 deletions(-) diff --git a/.agents/skills/mcpp-docs-style/SKILL.md b/.agents/skills/mcpp-docs-style/SKILL.md index 35f9d006..9f248a39 100644 --- a/.agents/skills/mcpp-docs-style/SKILL.md +++ b/.agents/skills/mcpp-docs-style/SKILL.md @@ -95,18 +95,19 @@ docs/specs/ ──▶ docs/ 允许 ### 四条产生每个决定的规则 -| | 规则 | 它排除了什么 | +| | 规则 | 排除的形态 | |---|---|---| | **R1** | **一个主题一个拥有者。** 恰好一章拥有一个主题;其余每一章只写一句话并链接过去 | 同一件事被解释两遍,然后各自过期 | | **R2** | **一章为**有任务的读者**而存在,不为**有名字的机制**而存在** | 按机制建目录,于是「测试」「依赖解析」这类**任务**没有家 | | **R3** | 每章在**前 15 行**内写明读者、它回答的那一个问题、以及它**排除**什么 | 排除是承重的:它是防止这一章重新吸收 R1 已经分配出去的主题的闸 | | **R4** | 一个部分是**某类读者的一段弧**,其内部顺序是那类读者需要它的顺序 | 字母序、按特性发布时间排序 | +| **R5** | **背景一节的范围由问题决定,不由方案决定。** 先把读者实际面对的那个问题写完整,再写本工具触及其中的哪一部分 | 只写与本工具最近的那一条成因,读者据此以为问题就这么大 | ### 每章的设计规格 动一章之前,先把这张表填出来。填不出来的那一格,就是还没设计的地方。 -| 项 | 要回答什么 | +| 项 | 填写要求 | |---|---| | 读者 | 谁在读它。一句话说不出来就是没定位 | | 那一个问题 | 它存在的理由,一个问句 | @@ -159,10 +160,24 @@ docs/specs/ ──▶ docs/ 允许 | What you may rely on, and what changes | Stability guarantees | | 0x —— 人人都需要 | 0x —— 基础 | | 0x — Everyone | 0x — Fundamentals | +| 背景:模块到了,工具链没跟上 | 背景:C++ 工程侧的工具现状 | +| 谁在为这个落差付账 | 这一现状的代价 | +| 大致相当于谁的活 | 可对照的工具 | +| 长什么样 | 形式 | +| 一个 flag 由哪根轴决定 | 决定一个 flag 的轴 | +| 一条运行时搜索路径可以住在哪里 | 运行时搜索路径的允许位置 | 「…的原因」「…的依据」「…的范围」是把 why 型标题转成名词短语的常用形。 **保留 why 本身,去掉疑问语气。** +**判据是疑问词,不是问号。**「谁在为这个落差付账」「打什么由谁决定」都不带问号, +都是疑问句。检查脚本第一版只匹配 `?` / 吗 / 呢,两句全部通过。判据是这一组词: +谁、哪、什么、多少、为何、如何、怎样、怎么。 + +**表头单元格与标题同规。** 一个列头按每一条要紧的性质都是标题:它命名一个主题、 +被跳读、并且是读者扫描时看的那一行。`| 部分 | 大致相当于谁的活 |` 通过了当时 +全部的检查,而它是全树最直白的一处违规。 + 这条同样管**部分名与段位名**,不只管章节标题。「人人都需要」描述的是受众、 是一个句子片段;「基础」是这一段**是什么**。受众写在每章开头的「读者」那一行, 不写在目录的骨架上。 diff --git a/.github/tools/check_docs_structure.sh b/.github/tools/check_docs_structure.sh index f5cb6617..911ba7cf 100755 --- a/.github/tools/check_docs_structure.sh +++ b/.github/tools/check_docs_structure.sh @@ -144,23 +144,78 @@ for f in .agents/docs/[0-9]*.md; do || bad "$f: front matter declares no valid \`status\` (active | landed | superseded | abandoned)" done -# ── 9. every relative link in docs/ and examples/ resolves ─────────────── +# ── 9. every relative link in docs/ and examples/ resolves, fragment included ─ # # Rule 3 catches `docs/NN-*.md` named anywhere, including from source comments. # This is the other half: a Markdown link in a document that points at a file # which is not there. Both halves are needed -- a chapter moved in this batch # would satisfy one and break the other. +# +# THE FRAGMENT IS PART OF THE LINK. The first version of this rule discarded +# it (`(?:#[^)]*)?`), so a link to a section that had been renamed resolved to +# the file and was reported correct. Renaming 100 headings for register in one +# batch is exactly the change that produces those, and two hand-written anchors +# in chapter 30 were already wrong before the renames began. python3 - <<'PYCHECK' || fail=1 -import re, pathlib, sys +import re, pathlib, sys, unicodedata + +def slug(heading): + """GitHub's heading slug: lowercase, drop punctuation and symbols, spaces to hyphens. + + Category P* and S* covers what github-slugger removes -- ASCII punctuation, + the em dash, the backticks around inline code, and the full-width comma and + colon the Chinese chapters use -- while `-` and `_` are kept because an + anchor is allowed to contain them. + """ + t = re.sub(r"^#+\s+", "", heading).strip().lower() + keep = [] + for ch in t: + if ch in "-_": + keep.append(ch) + elif ch.isspace(): + keep.append(" ") + elif unicodedata.category(ch)[0] in "PS": + continue + else: + keep.append(ch) + return "".join(keep).replace(" ", "-") + +def anchors_of(path): + """Every anchor the file defines, with GitHub's -1/-2 suffix for repeats.""" + seen, out, infence = {}, set(), False + for line in path.read_text(errors="ignore").splitlines(): + if line.startswith("```"): + infence = not infence + continue + if infence or not re.match(r"^#{1,6} ", line): + continue + s = slug(line) + n = seen.get(s, 0) + seen[s] = n + 1 + out.add(s if n == 0 else f"{s}-{n}") + return out + +files = list(pathlib.Path("docs").rglob("*.md")) + list(pathlib.Path("examples").rglob("*.md")) +cache = {} bad = 0 -for f in list(pathlib.Path("docs").rglob("*.md")) + list(pathlib.Path("examples").rglob("*.md")): - for m in re.finditer(r"\]\(([^)#]+?)(?:#[^)]*)?\)", f.read_text(errors="ignore")): - t = m.group(1) +for f in files: + for m in re.finditer(r"\]\(([^)\s]*?)(?:#([^)\s]+))?\)", f.read_text(errors="ignore")): + t, frag = m.group(1), m.group(2) if t.startswith(("http", "mailto:")): continue - if not (f.parent / t).exists(): + target = (f.parent / t) if t else f + if t and not target.exists(): print(f"FAIL: {f}: link to `{t}` does not resolve") bad += 1 + continue + if not frag or target.suffix != ".md" or not target.is_file(): + continue + key = target.resolve() + if key not in cache: + cache[key] = anchors_of(target) + if frag not in cache[key]: + print(f"FAIL: {f}: `#{frag}` is not a heading in {target.as_posix()}") + bad += 1 sys.exit(1 if bad else 0) PYCHECK diff --git a/.github/tools/check_docs_style.sh b/.github/tools/check_docs_style.sh index 364f335e..5062115e 100755 --- a/.github/tools/check_docs_style.sh +++ b/.github/tools/check_docs_style.sh @@ -8,6 +8,13 @@ # 2. reference docs do not address the reader in the second person # (tutorials do — they are listed below, not inferred) # 3. docs/X.md and docs/zh/X.md have the same heading structure +# 4. a table's header cells follow the same register as a heading +# +# Rule 4 exists because rule 1 read only lines beginning with `#`, and a column +# header is a heading by every property that matters: it names a topic, it is +# read out of order, and it is what a reader scans. `| 部分 | 大致相当于谁的活 |` +# passed every check in this file while being the plainest register violation +# in the tree. # # What it deliberately does NOT check: whether a claim's strength matches its # evidence. That is the most important rule in the skill and it needs a reader. @@ -43,19 +50,69 @@ for f in docs/*.md docs/zh/*.md docs/specs/*.md; do base="$(basename "$f")" # ── 1. heading register ─────────────────────────────────────────────── + # + # An interrogative WORD is the test, not a question mark. `谁在为这个落差付账` + # and `打什么由谁决定` carry no `?` and are questions all the same, and the + # first version of this rule matched `?`/`吗`/`呢` and passed both. + # + # THE ONE EXEMPTION IS BY NAME. `# 00 —— mcpp 是什么` mirrors the English + # `What mcpp Is`, which is a noun clause rather than a question, and a chapter + # title is the chapter's registered name: it appears in three indexes and in + # every citation of the chapter. Exempting it here states the decision; + # narrowing the rule to `##` and deeper would have hidden it. while IFS= read -r h; do - case "$h" in - *"?"*|*"吗"*|*"呢"*) + if [[ "$f" == "docs/zh/00-what-mcpp-is.md" && "$h" == "# 00 —— mcpp 是什么" ]]; then + continue + fi + # Inline code is quoted material: `cxx_stdlib` and `mcpp.why.toolchain` + # must not be read for register. + bare="$(sed -E 's/`[^`]*`//g' <<< "$h")" + case "$bare" in + *"?"*|*"?"*|*"吗"*|*"呢"*) bad "$f: question heading — use a noun phrase: $h" ;; + *"谁"*|*"哪"*|*"如何"*|*"为何"*|*"什么"*|*"多少"*|*"怎样"*|*"怎么"*) + bad "$f: interrogative heading — use a noun phrase (…的原因 / …的依据 / …的范围): $h" ;; esac - case "$h" in - *"一段话"*|*"讲完"*|*"姊妹篇"*|*"干活"*|*"怎么"*|*"会怎样"*|*"不许"*) + case "$bare" in + *"一段话"*|*"讲完"*|*"姊妹篇"*|*"干活"*|*"会怎样"*|*"不许"*|*"跟上"*|*"付账"*|*"长什么样"*) bad "$f: conversational heading: $h" ;; *"The whole idea"*|*"in one paragraph"*|*"Consuming one"*|*"the thing that"*) bad "$f: conversational heading: $h" ;; esac done < <(headings "$f") + # ── 4. table header register ────────────────────────────────────────── + # + # A header row is the row directly above the `|---|---|` separator, so the + # separator is what identifies it; matching every `|` line would read data. + while IFS= read -r cell; do + case "$cell" in + *"?"*|*"?"*|*"吗"*|*"呢"*) + bad "$f: question in a table header — use a noun phrase: $cell" ;; + *"谁"*|*"哪"*|*"如何"*|*"为何"*|*"什么"*|*"多少"*|*"怎样"*|*"怎么"*) + bad "$f: interrogative table header — use a noun phrase: $cell" ;; + esac + shopt -s nocasematch + if [[ "$cell" =~ ^(what|which|who|how|why|where|whether)([[:space:]]|$) ]]; then + bad "$f: interrogative table header — use a noun phrase: $cell" + fi + shopt -u nocasematch + done < <(awk ' + /^```/ { infence = !infence; prev = ""; next } + infence { next } + /^\|[[:space:]:|-]+\|[[:space:]]*$/ && prev ~ /^\|/ { + line = prev + gsub(/`[^`]*`/, "", line) # quoted material, as above + n = split(line, cells, "|") + for (i = 2; i < n; i++) { + c = cells[i] + gsub(/^[[:space:]]+|[[:space:]]+$/, "", c) + if (c != "") print c + } + } + { prev = $0 } + ' "$f") + # ── 2. second person in reference docs ──────────────────────────────── case " $TUTORIALS " in *" $base "*) ;; diff --git a/docs/00-what-mcpp-is.md b/docs/00-what-mcpp-is.md index 93bfde2a..8d0228ba 100644 --- a/docs/00-what-mcpp-is.md +++ b/docs/00-what-mcpp-is.md @@ -1,20 +1,38 @@ # 00 — What mcpp Is -## The background: modules arrived, and the tooling did not - -C++20 gave the language modules. They change what a translation unit costs: an -interface is declared once and imported, instead of being re-parsed out of -headers by every file that needs it, and what a consumer sees is what the author -exported rather than everything the header happened to include. - -Adopting them turned out to be a build-system problem rather than a language -one. A build has to scan sources for `import`, order the compiles accordingly, -cache the compiled interfaces and invalidate them correctly — with a compiler -new enough to have the feature at all. `import std` adds one more: the standard -library's own module has to be built before anything can use it. - -The gap is concrete rather than theoretical. On the machine this chapter was -written on: +## Background: the state of C++ project tooling + +A C++ project needs four things in place at the same time: a build description, +a set of dependencies, a compiler new enough for the code, and an environment in +which the result runs. No single tool in C++ owns all four. Four unrelated +classes of tool each carry one, and aligning them is the project's own work. + +CMake is the de facto standard for the first of the four. A de facto standard is +a statement about adoption, not about the experience of use. CMake does not +resolve dependencies, does not install a compiler and does not describe the +runtime environment, and those three are what a new contributor meets on the +first day. Supplying them means adding a package manager, the distribution's +packages, and a paragraph of instructions in a README: three further models, +with the alignment between them left to the project. + +The environment is the layer that fails most often, because it is the only one +nothing checks. A build description reports an error at configure time and a +package manager fails to resolve, but a library present on the machine that is +not the library the code was built against produces no signal until the link, +and sometimes not until the program runs. + +Modules add one more constraint to that structure. C++20 modules change what a +translation unit costs: an interface is declared once and imported instead of +being re-parsed out of headers by every file that needs it, and a consumer sees +what the author exported rather than everything a header happened to include. +The price is that a language feature is now implemented by the build system. The +build scans sources for `import`, orders the compiles accordingly, caches the +compiled interfaces and invalidates them correctly, on a compiler new enough to +have the feature at all. `import std` adds a further requirement: the standard +library's own module is built before anything else can use it. + +A project that adopts modules therefore needs all four layers rather than three. +The gap is concrete. On the machine this chapter was written on: ```console $ g++ --version @@ -22,25 +40,25 @@ g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 ``` That compiler cannot compile `import std`. Nothing about the project is wrong; -the machine is simply not the machine the project needs. +the machine is not the machine the project requires. -## Who pays for that gap +## The cost of that state -**A person pays an afternoon**, once per machine and once per new contributor: -install a newer compiler, work out which build-system flags turn modules on, -find the dependencies, and then work out which of the three broke when it does -not link. +**For a person the cost is a setup**, repeated once per machine and once per new +contributor: install a newer compiler, determine which build flags enable +modules, obtain the dependencies, and then determine which of the three is at +fault when the link fails. The cost does not fall as a project matures. It +repeats per person and per machine. -**An agent pays context**, in three places before it writes a line of code: -reading the build system to learn what it does, reconstructing the environment -it assumes, and following a header through its transitive includes to find out -what is actually declared. +**For an agent the cost is context**, spent in three places before a line of +code is written: reading the build description to determine what it does, +reconstructing the environment it assumes, and following a header through its +transitive includes to determine what is declared. -Modules remove the third — an interface is explicit, and `import` says exactly -what is used. mcpp exists to remove the other two, and to make the first work -without the afternoon. +Modules remove the third, because an interface is explicit and `import` states +what is used. mcpp removes the other two, and reduces the first to one command. -## What mcpp is +## The composition of mcpp ``` mcpp = build system @@ -50,47 +68,48 @@ mcpp = build system + the environment and runtime (xlings) ``` -Most C++ projects assemble those five from separate tools, and the seams between -them are where the afternoon goes: the build file assumes a compiler the machine -does not have, the package manager assumes a build file it did not write, and -the environment is a paragraph in a README. +Most C++ projects assemble those five parts from separate tools, and the setup +cost described above is the cost of that assembly: the build file assumes a +compiler the machine does not have, the package manager assumes a build file it +did not write, and the environment is a paragraph in a README. -mcpp is one program, so there are no seams to assemble. +mcpp is one program, and the five parts share one model. -For a reader who already has tools for these jobs, the parts land roughly here: +For a reader who already uses tools for these jobs: -| the part | in mcpp | roughly the job of | +| component | form in mcpp | comparable tools | |---|---|---| | build system | `mcpp.toml`, the module graph, the ninja backend | CMake, Meson | | build plugins | `build.mcpp`, rule packages | `build.zig`, xmake rules | | package manager | `[dependencies]`, `mcpp.lock`, the index | Conan, vcpkg | -| toolchain management | the compiler as an installed, pinned payload | Zig's bundled toolchain; rustup's role in Rust; installing GCC / LLVM / MSVC by hand | +| toolchain management | the compiler as an installed, pinned payload | Zig's bundled toolchain, rustup, manual GCC / LLVM / MSVC installation | | environment and runtime | `[xlings]`, payloads, the runtime search path | Nix, conda | -**The closest single-tool analogues are Cargo and Zig**, and for different -halves of the same idea. Cargo is one program that is the build, the packages, +The closest single-tool analogues are Cargo and Zig, each for a different half +of the same idea. Cargo is one program that is the build, the package manager, the lock file and the test runner, so a Rust project is cloned and built with no preliminary step. Zig ships its toolchain with the tool and cross-compiles by default, so the compiler is not something the machine must already have. mcpp is that shape for C++, with one part neither of them has: the environment -layer, which is why a project can also declare the *non-compiler* tools its -build needs. +layer, through which a project declares the non-compiler tools its build +requires. -**The table places the parts; it does not claim equivalence.** Each of those -tools does more in its own area than mcpp does, and a project that needs that -depth should use it. +**The table places the parts; it does not claim equivalence.** Each tool listed +does more in its own area than mcpp does, and a project that needs that depth +uses it. ## The guarantee > **Clone any mcpp project and `mcpp build` works** — without installing a -> compiler, configuring an environment, or hunting down dependencies. +> compiler, configuring an environment, or locating dependencies. -Two boundaries, stated here so the claim can be trusted: a project that targets -a device still downloads that device's toolkit the first time, and a target this -machine cannot serve is refused by name rather than built wrongly. +Two boundaries are stated here so that the claim can be relied on: a project +that targets a device still downloads that device's toolkit on the first build, +and a target this machine cannot serve is refused by name rather than built +incorrectly. -## The smallest example that shows it +## A minimal example On the same machine, whose only C++ compiler is the GCC 13 above. @@ -110,8 +129,8 @@ description = "A modular C++23 package" license = "Apache-2.0" ``` -**No compiler, no language standard and no dependency is declared** — that is -the whole of what a reader has to understand before changing this project. The +**No compiler, no language standard and no dependency is declared.** That is the +whole of what a reader has to understand before changing this project. The source uses the feature the machine's compiler does not have: ```cpp @@ -133,18 +152,18 @@ Hello from hello! Built with import std + std::println on modular C++23. ``` -1.25 seconds of wall clock, first run included. The compiler that did it: +1.25 seconds of wall clock, first run included. The compiler that performed it: ```console $ mcpp self env default toolchain = gcc@16.1.0 ``` -mcpp installed GCC 16 and used it. Nothing on the host was changed, and a -colleague — or an agent — cloning this project gets the same compiler rather +mcpp installed GCC 16 and used it. Nothing on the host was modified, and a +colleague — or an agent — cloning this project receives the same compiler rather than the one their machine happens to ship. -**A dependency is one line and needs no other step:** +**A dependency is one line and requires no further step:** ```toml [dependencies] @@ -153,11 +172,10 @@ than the one their machine happens to ship. `mcpp build` resolves it, fetches it, builds it and links it. -## What mcpp is for +## Scope -mcpp is built around **C++20/23 modules and the newest language features**, and -the ecosystem it maintains follows from that rather than from a general -ambition: +mcpp is built around **C++20/23 modules and recent language features**, and the +ecosystem it maintains follows from that rather than from a general ambition: | | | |---|---| @@ -167,17 +185,17 @@ ambition: | graphics | shaders compiled as part of the build and reached as modules | | kernel and low-level work | zero-libc tiers, an explicit link model, no hidden host dependency | -A project that wants none of those still gets the guarantee above; a project -that wants one of them does not leave the tool to get it. +A project that requires none of these still receives the guarantee above; a +project that requires one of them does not leave the tool to obtain it. -## Where to go next +## Reading path | | | |---|---| -| put a program on the screen | [01 — Getting Started](01-getting-started.md) | -| decide whether mcpp fits the work at hand | [02 — Scenarios](02-scenarios.md) | +| run a first program | [01 — Getting Started](01-getting-started.md) | +| determine whether mcpp fits the work at hand | [02 — Scenarios](02-scenarios.md) | | read a project of the same shape | [03 — Examples](03-examples.md) | -Everything after this chapter is reference: what a manifest may say, how -dependencies resolve, how a target is named. This one states no field and no -flag on purpose. +Everything after this chapter is reference: what a manifest may state, how +dependencies resolve, how a target is named. This chapter states no field and no +flag by design. diff --git a/docs/03-examples.md b/docs/03-examples.md index cfab0de0..3f192f4f 100644 --- a/docs/03-examples.md +++ b/docs/03-examples.md @@ -31,7 +31,7 @@ not repeated. ### A — The shape of a project -| example | first to teach | +| example | first to introduce | |---|---| | [`01-hello`](../examples/01-hello/) | a package, `import std`, `mcpp build` and `mcpp run` | | [`02-with-deps`](../examples/02-with-deps/) | `[dependencies]`, the lock file, `mcpp add` | @@ -40,20 +40,20 @@ not repeated. ### B — Publishing -| example | first to teach | +| example | first to introduce | |---|---| | [`03-pack-static`](../examples/03-pack-static/) | `mcpp pack --mode static`, `[target.]`, `[pack]` | | [`05-lib-distribution`](../examples/05-lib-distribution/) | a library's interface and its prebuilt binaries; a C header and a C++ module from one source | ### C — The environment -| example | first to teach | +| example | first to introduce | |---|---| | [`07-project-subos`](../examples/07-project-subos/) | `[xlings]`, `[xlings.workspace]`, a build program whose `PATH` is the environment the project declared | ### D — Targets -| example | first to teach | +| example | first to introduce | |---|---| | [`06-openkal-cross`](../examples/06-openkal-cross/) | `--target`, one source built for four machines from any host | @@ -65,7 +65,7 @@ Bare metal is taught by a **template** rather than by a directory here — see Read [`09-heterogeneous`](../examples/09-heterogeneous/) in order. Its README is the map; the table below is what each sub-example adds. -| example | first to teach | +| example | first to introduce | |---|---| | [`…/boundary`](../examples/09-heterogeneous/boundary/) | the island boundary alone: a generated module the consumer imports, with no seam and no header in the project. Needs no device | | [`…/cuda`](../examples/09-heterogeneous/cuda/) | a device compiler, a seam over the generated boundary, the driver stated as a fact and a floor | @@ -78,7 +78,7 @@ the map; the table below is what each sub-example adds. ### F — Authoring for the ecosystem -| example | first to teach | +| example | first to introduce | |---|---| | [`08-build-rules`](../examples/08-build-rules/) | two rule packages and a project using both; `host-module = true`, `mcpp::action` with `role = "check"` | | [`12-a-new-device-language`](../examples/12-a-new-device-language/) | `device_extensions` and `rule_module`: a rule package teaching mcpp a language the engine has never heard of | diff --git a/docs/04-mcpp-toml.md b/docs/04-mcpp-toml.md index c1be6858..9f6d5685 100644 --- a/docs/04-mcpp-toml.md +++ b/docs/04-mcpp-toml.md @@ -156,7 +156,7 @@ the loader opens and the import library the linker consumes, with the export list generated from the objects on the MSVC ABI (which exports nothing without `__declspec(dllexport)` or a `.def`). See `tests/e2e/08`, `257` and `259`. -#### `exports` — which symbols the artifact publishes (mcpp 2026.9.6.5+) +#### `exports` — the artifact’s published symbol set (mcpp 2026.9.6.5+) ```toml [targets.mydriver] diff --git a/docs/06-features-and-capabilities.md b/docs/06-features-and-capabilities.md index 2a6d8319..66485e46 100644 --- a/docs/06-features-and-capabilities.md +++ b/docs/06-features-and-capabilities.md @@ -399,7 +399,7 @@ std-freestanding-alloc-kal = "0.1.x" Three usages, one line each: -| Consumer needs | What the manifest says | +| Consumer needs | The manifest form | |---|---| | none of the allocating parts | `std-freestanding = "0.2.0"` — no allocator enters the graph | | the default | `features = ["alloc-kal"]` — the implementation arrives with it, and its package name never has to be known | diff --git a/docs/07-workspace.md b/docs/07-workspace.md index 8de6cd5d..6f5df7c9 100644 --- a/docs/07-workspace.md +++ b/docs/07-workspace.md @@ -301,7 +301,7 @@ member. `mcpp test --workspace` reports each member separately and continues pas failing member, exiting non-zero if any member failed — ideal as a single, shell-free CI step for a workspace that tests many libraries. -#### What the fan-out reports +#### The fan-out report ``` Workspace testing member 'libs/core' (3/97) diff --git a/docs/08-testing.md b/docs/08-testing.md index 8cfff81a..95415712 100644 --- a/docs/08-testing.md +++ b/docs/08-testing.md @@ -15,7 +15,7 @@ Before: [05 — Dependencies and Resolution](05-dependencies.md) covers not. After: [09 — Commands by Scenario](09-commands-by-scenario.md) is the lookup for everything else. -## What mcpp considers a test +## The definition of a test Every `tests/**/*.cpp` is a test: mcpp compiles each one into its own program and runs it. A test passes when its program exits zero. @@ -48,7 +48,7 @@ mcpp test -- --verbose # everything after `--` goes to each test binary Tests build with the same axes as `mcpp build`, so a test runs against the configuration it is meant to check rather than against the default one: -| flag | what it selects | +| flag | the set it selects | |---|---| | `--profile ` | `dev` (default), `release`, `dist`, or a `[profile.*]` the manifest declares | | `--features ` | the feature set for the test build | diff --git a/docs/09-commands-by-scenario.md b/docs/09-commands-by-scenario.md index 0f27c544..97f63594 100644 --- a/docs/09-commands-by-scenario.md +++ b/docs/09-commands-by-scenario.md @@ -28,7 +28,7 @@ Every output below was produced by the version of mcpp this chapter ships with. Two stores grow, they grow for different reasons, and one command each empties them. Confusing them costs a full rebuild. -| Store | Scope | Grows when | Emptied by | +| Store | Scope | Growth trigger | Emptied by | |---|---|---|---| | `target///` | one project | a configuration fingerprint changes and opens a new directory | `mcpp clean`, `mcpp clean --stale` | | the build cache (`mcpp cache dir`) | the whole machine | any project compiles a dependency or a `std` module | `mcpp cache gc`, `mcpp cache prune`, `mcpp cache clean` | @@ -365,12 +365,12 @@ written out rather than left to the default: it is the value this manifest wants on its own terms — a missing audio device should never fail a build — so it will still say so once the key has more than one accepted value. -## Which part a failure is about +## The stage a failure belongs to A build crosses several stages, and a message names the one that failed. Reading that first saves opening the wrong chapter. -| a message about | the stage | where to look | +| a message about | the stage | the chapter to read | |---|---|---| | a package name, a version, or "no candidate" | resolution | [05](05-dependencies.md), [11](11-publishing-a-library.md) | | a download, a payload, or a version floor | provisioning | [20](20-toolchains.md), [23](23-the-project-environment.md) | diff --git a/docs/10-pack-and-release.md b/docs/10-pack-and-release.md index 8f3c2ae0..d99bb095 100644 --- a/docs/10-pack-and-release.md +++ b/docs/10-pack-and-release.md @@ -30,7 +30,7 @@ private glibc precisely so a binary's behaviour does not depend on which distribution happens to be underneath it, and reaching back out to the host's libc to distribute would give that away at the last step. -| | Route | Command | Where its C runtime comes from | Choose it when | +| | Route | Command | Source of its C runtime | Applicability | |---|---|---|---|---| | **A** | Through the ecosystem | `mcpp emit xpkg` → `xlings install ` | the target machine's own xlings payloads | the target has xlings | | **B** | One static file | `mcpp build --target x86_64-linux-musl` | nowhere — it is linked in | a single file with no runtime dependency | @@ -152,7 +152,7 @@ when it includes a directory (relative or absolute), the literal path is used. For the full set of options, see `mcpp pack --help`. -### What a packed artifact is built with, and what travels inside it +### The build inputs of a packed artifact, and its contents Two things differ from `mcpp build`, and both exist because a package leaves this machine: @@ -177,7 +177,7 @@ producer's source tree and build directory. What is removed depends on what the artifact *is* — this is dh_strip's division, and the archive row is the one that matters: -| artifact | strip flags | why not more | +| artifact | strip flags | the reason for no more | |---|---|---| | executable | `--strip-all` | nothing links against it | | shared library | `--strip-unneeded` | keeps `.dynsym` — that IS the export list | diff --git a/docs/12-binary-distribution.md b/docs/12-binary-distribution.md index 27b3dd76..6b44891f 100644 --- a/docs/12-binary-distribution.md +++ b/docs/12-binary-distribution.md @@ -89,7 +89,7 @@ links `lib.so` and the loader then asks for the `SONAME`, and those are different filenames. Shipping only the built file links cleanly and then fails to start. -### Why neither set may be trimmed +### The reason neither set may be trimmed A **source** distribution of the same package puts every one of its `include_dirs` on its consumers' include path. If a binary package shipped a @@ -266,7 +266,7 @@ That is a degradation, not a break, and it is the right direction. But it means **the gate protects new clients only**, which belongs in the release notes of any package published to a mixed-version audience. -## What travels inside a package, and what deliberately does not +## The package contents, and the deliberate exclusions A published package must work on a machine that is not the publisher's. Two steps enforce that, and both run on every artifact the packer stages. @@ -295,7 +295,7 @@ error while loading shared libraries: libstdc++.so.6: cannot open shared object **`$ORIGIN` is not the fix.** Measured on a real package with the build machine's store made unreachable: -| state on the shipped `.so` | consumer's `DT_RPATH` inherited? | result | +| state on the shipped `.so` | inheritance of the consumer's `DT_RPATH` | result | |---|---|---| | stale absolute `DT_RUNPATH` | no | fails | | **no tag at all** | **yes** | **runs** | diff --git a/docs/20-toolchains.md b/docs/20-toolchains.md index 708e2c78..0756c215 100644 --- a/docs/20-toolchains.md +++ b/docs/20-toolchains.md @@ -91,7 +91,7 @@ The pair persists as `[toolchain] default = "gcc@16.1.0"` + configs with combined spellings like `default = "gcc@15.1.0-musl"` keep working unchanged.) -### What decides a build's compiler +### Compiler selection for a build Five things can name it. They are ranked, and the rank is what makes the two statements a project can write outrank everything mcpp keeps on its own: @@ -173,7 +173,7 @@ Available toolchains (run `mcpp toolchain install `): `*` marks the default pair. The Targets block is the live view of the target vocabulary, in four statuses: -| Status | Meaning | What to do next | +| Status | Meaning | Next step | |---|---|---| | `installed` | a payload here already produces it | nothing | | `available` | a payload exists for this host | `mcpp toolchain install` | @@ -339,7 +339,7 @@ error: [toolchain] linux = "system" is not supported: mcpp builds only with *family* whose installation mcpp locates and identifies, on the one platform where the compiler cannot be redistributed. See the section above. -#### Why the toolchain and the libraries get different answers +#### The reason the toolchain and the libraries differ mcpp's rule about host dependence is not uniform across axes, and the split is deliberate: @@ -443,7 +443,7 @@ environment from the VC tools + Windows SDK (no `vcvarsall` involved), stages **The Windows SDK follows the origin**, because the two origins answer different questions and so must the SDK: -| origin | how the SDK is chosen | +| origin | SDK selection | |---|---| | `msvc@` | the `xim:windows-sdk` payload installed **with that toolset**, in mcpp's own store. `WindowsSdkDir` / `WindowsSdkVersion` in the environment are **ignored**, and mcpp prints a `note:` saying so. | | `msvc@system` | **`WindowsSdkDir`** (+ `WindowsSdkVersion`) if declared, then `C:\Program Files (x86)\Windows Kits\10`. | @@ -543,7 +543,7 @@ error: target 'x86_64-linux-musl' takes its C library from the 'gcc@16.1.0' Two rows answer a different question, and their pin cannot be overridden at all: -| row | why | +| row | reason | |---|---| | every `*-none-elf` | no per-host cross payload exists; clang and lld are cross-compilers by construction and gcc is not | | `x86_64-windows-musl` | no gcc payload emits a PE with a musl C library — the mingw payload emits PE with the MinGW CRT, which is the separate `-gnu` row | @@ -710,7 +710,7 @@ on request). because the hazard does. A `.so`/`.dylib`/`.dll` is not a small executable — it is loaded *into* a process that already has a C++ runtime. -| target | default for `kind = "shared"` | why | +| target | default for `kind = "shared"` | reason | |---|---|---| | ELF (Linux, …) | `toolchain-coupled` | ELF has one global symbol namespace and the first definition loaded wins. A `.so` that statically embedded libstdc++ **exports** it, and the executable linking that library binds *its* `std::` references there — its own `self-contained` contract silently becomes a no-op, and its C++ runtime is whichever build of that library happens to load. | | Mach-O | `self-contained` | the mechanism there is already `-load_hidden`, i.e. hidden visibility, so dyld never unifies those symbols; and toolchain-coupled is not available on macOS at all (see the note below). | @@ -743,7 +743,7 @@ bakes `_MSVC_MT`/`_MSVC_MD` into the one `std` module a project builds, so a per-role contract that disagrees with the project's cannot be honoured and is reported rather than ignored. -| value | what it is on MSVC | +| value | meaning on MSVC | |---|---| | `self-contained` | `/MT` — the static CRT. `linkage = "static"` selects the same thing from the libc axis. | | `host-coupled` (default under `/MD`) | the target provides `vcruntime140.dll` / `msvcp140.dll` — i.e. Visual Studio or the redistributable is installed there. | diff --git a/docs/21-the-target-triple.md b/docs/21-the-target-triple.md index 6a2f6ba6..b6a74e99 100644 --- a/docs/21-the-target-triple.md +++ b/docs/21-the-target-triple.md @@ -129,7 +129,7 @@ from `planned`, the lexical answer wins again with nothing to edit. is the escape hatch for opting into a row early with an explicit `[target.] toolchain`. -### Which Spelling To Use +### The Spelling To Use **Under the build-time system, decline it.** The graph supplies the C library and the runtimes, so the segment states a request that is not consulted. Under @@ -144,7 +144,7 @@ different artefacts. Writing the segment is how that choice is made. default fill, and `msvc` is a different object ABI rather than a different C library, so the segment is meaningful there in both systems. -## What The Build Reports +## The Build Report The report heads with the target as written and resolves it to the compiler's own spelling: @@ -186,7 +186,7 @@ convention the **objects follow**, which several layers must agree on. Reading it as `c++-abi libc++` is a second wrong answer, since libstdc++ sits on the same ABI. -## Three Vocabularies, And Why They Differ +## Three Vocabularies, And The Reason They Differ A triple is written by three parties that do not share a convention, and mcpp translates between them. Knowing which one a string belongs to removes most of @@ -234,7 +234,7 @@ a payload. It is also why the build-time system needs only one compiler. MinGW's own triple is `x86_64-w64-mingw32`: -| Field | Value | Why | +| Field | Value | Reason | |---|---|---| | arch | `x86_64` | | | vendor | `w64` | the project is `mingw-w64`, distinguishing it from the stalled original `mingw32` | @@ -419,7 +419,7 @@ not from `linux-aarch64`, while `aarch64-linux-gnu` is the mirror case and is `planned` on both. Collapsing them to `linux` would let one overwrite the other's rows. -### Which build host serves which target +### Build hosts and the targets they serve | target | tier | pin | linux-x86_64 | linux-aarch64 | macos-arm64 | windows-x86_64 | |---|---|---|---|---|---|---| @@ -452,7 +452,7 @@ installed by mcpp · `SDK` the platform's own · `—` unreachable from this hos ### The rule behind the columns -| target class | which build hosts serve it | why | +| target class | the build hosts that serve it | reason | |---|---|---| | `*-linux-musl` | Linux (any arch), Windows (same arch only) | the musl payloads are self-contained | | `*-linux-gnu` | Linux, same arch only | additionally needs the host-native `xim:glibc` / `xim:linux-headers` | diff --git a/docs/23-the-project-environment.md b/docs/23-the-project-environment.md index 55b4355e..fe70bcd5 100644 --- a/docs/23-the-project-environment.md +++ b/docs/23-the-project-environment.md @@ -24,7 +24,7 @@ subos = "tools" Working project: `examples/07-project-subos/`. -## 1. What a SubOS is +## 1. The definition of a SubOS A SubOS is a directory that holds a userspace: its own `bin`, its own library view, its own installed package versions, and a `subos_info` block describing @@ -43,7 +43,7 @@ Two kinds exist, and the difference is where the directory lives: The third row is the isolated one. It belongs to the project, it sits beside the manifest, and removing the project removes it. -## 2. What the declaration decides +## 2. The scope of the declaration **The C library.** A payload-first build links against one specific glibc, and which one is a fact about the project rather than about the machine. Chapter 8 @@ -74,7 +74,7 @@ Declaring it is what puts it there. the declared environment the default answer; everything else stays reachable behind it. -### 2.1 Which version pins apply (2026.9.3+) +### 2.1 The version pins that apply (2026.9.3+) Naming an environment also changes where a tool's version comes from. A project's own `[xlings.workspace]` entries always win — over the environment @@ -95,7 +95,7 @@ An `xlings use` performed inside the project outranks both, until mcpp rewrites the environment: it is the layer merged last, and an action a person took should beat a file. -## 3. What the declaration does not decide +## 3. Outside the declaration’s scope `[xlings.workspace]` names packages to be present in the environment, and each one's payload directory is delivered separately as `MCPP_XPKG__DIR`. That is a @@ -142,7 +142,7 @@ than fails**: the runtime binding reports `inconclusive`, no payload-first binding is available, a note is printed, and the build continues. Chapter 8 gives the full rule. -## 5. When a private environment is worth it +## 5. The case for a private environment - **A generator whose version changes what it emits.** `protoc`, `flatc`, a shader compiler: the output is an input to everything downstream, so the @@ -188,9 +188,9 @@ downloaded: this chapter. Omitting the tier is the historical behaviour. CI host and execute a cross-built artifact through it, without the manifest naming the payload's path. -## 6. What belongs somewhere else +## 6. Declarations that belong elsewhere -| Need | Where it goes | +| Need | Declared in | |---|---| | a library the program links | `[dependencies]` | | the compiler | `[toolchain]`, chapter 3 | @@ -344,7 +344,7 @@ build compiles against. See [SPEC-004](specs/manifest-semantics.md) for the general rule these two axes are an instance of. -### `when` — which verbs need this tool (mcpp 2026.9.4.2+) +### `when` — the verbs that need this tool (mcpp 2026.9.4.2+) ```toml [xlings.workspace] diff --git a/docs/24-openkal-cross.md b/docs/24-openkal-cross.md index 3085020d..14ac6f31 100644 --- a/docs/24-openkal-cross.md +++ b/docs/24-openkal-cross.md @@ -74,7 +74,7 @@ preprocessor directive is required in the source. A worked example is A project names the last of these. The others follow from its dependencies. -## Why The Compiler Must Be LLVM +## The Reason The Compiler Must Be LLVM `openkal-llvm-runtime` declares the requirement rather than leaving it to be discovered: @@ -95,7 +95,7 @@ With the requirement declared, the build refuses the combination before it compiles anything, and names the command that selects a compiler which satisfies it. -## How The Target Is Chosen +## Target Selection The target row of mcpp's own vocabulary may carry a toolchain convention. That convention names the payload which supplies **that target's C library**, and it @@ -275,7 +275,7 @@ execution contexts, traps, per-CPU state and address spaces — presented as one interface over several instruction sets, with a backend package per instruction set. A kernel depends on it and supplies its own platform layer, or none. -### Why x86_64 Bare Metal Required Engine Work +### The Engine Work x86_64 Bare Metal Required `riscv64-none-elf` and `aarch64-none-elf` are rows in a table and nothing more: Clang has a BareMetal toolchain for both, drives their links itself and reaches diff --git a/docs/30-build-mcpp.md b/docs/30-build-mcpp.md index 0c01b145..02661222 100644 --- a/docs/30-build-mcpp.md +++ b/docs/30-build-mcpp.md @@ -289,7 +289,7 @@ the first one sees. and is empty on a hosted target, which is exactly the case this pair exists for. Either of these two is empty when mcpp passes no such flag. -### Which C++ standard library resolved: `cxx_stdlib` (2026.9.6.3+) +### The resolved C++ standard library: `cxx_stdlib` (2026.9.6.3+) ```cpp const char* impl = mcpp::cxx_stdlib(); // "libstdc++" | "libc++" | "msvc-stl" | "" @@ -817,7 +817,7 @@ knobs produced the command — and that belongs in each edge's `description`. **Failure and advice use different channels.** mcpp prints what it captured from a build program only when the program exits non-zero, so a failure writes to stderr and returns non-zero. A message that must be seen on a *successful* -build has to go through [`mcpp::warning`](#warning--succeeding-and-still-being-heard-2026821-2); +build has to go through [`mcpp::warning`](#warning--succeeding-and-still-being-heard-20268212); stderr on success is discarded, which means the wrong channel is silent on exactly the builds that needed the message. diff --git a/docs/31-authoring-a-rule-package.md b/docs/31-authoring-a-rule-package.md index d6f5ebab..da207bd8 100644 --- a/docs/31-authoring-a-rule-package.md +++ b/docs/31-authoring-a-rule-package.md @@ -16,11 +16,11 @@ which belong to `mcpp:plugins`. Examples: [`12-a-new-device-language`](../examples/12-a-new-device-language/) adds a language. -## What a rule package is +## The definition of a rule package Three parts, and none of them is special to mcpp: -| part | what it is | +| part | content | |---|---| | a package | an ordinary `mcpp.toml` with a version and a licence | | a module | a `.cppm` exporting `options` and a function that submits build edges | @@ -228,7 +228,7 @@ through it. A probe that reaches no answer states none. ## Finding the rule's own files -| what | how | +| object | accessor | |---|---| | the rule package's own tree | `mcpp::dep_dir("")` — under the name the **consumer** declared in `[dependencies]` | | a payload declared under `[xlings.workspace]` | `mcpp::xpkg_dir("")` | diff --git a/docs/32-authoring-a-payload.md b/docs/32-authoring-a-payload.md index 7a1f2fd2..1c86104b 100644 --- a/docs/32-authoring-a-payload.md +++ b/docs/32-authoring-a-payload.md @@ -17,7 +17,7 @@ Before: [31 — Authoring a Rule Package](31-authoring-a-rule-package.md), whose rules declare the payloads they drive. After: [33 — Authoring a Runtime Adapter](33-authoring-an-adapter.md). -## What a payload is +## The definition of a payload Everything mcpp installs and does not compile: a compiler, a shader compiler, a device toolkit, an emulator, a probe driver, a prebuilt C library. A project @@ -117,7 +117,7 @@ which is what makes a stack of payloads resolve without anyone setting the SubOS sysroot view. **Declared rather than copied** — xlings removes them with the package, and a copy would outlive its owner. -## The tier: when a payload is needed +## The tier: the commands that need a payload ```toml "xim:qemu-arm" = { version = "9.2.4-1", when = "run" } diff --git a/docs/33-authoring-an-adapter.md b/docs/33-authoring-an-adapter.md index 3fffef0b..0abe6b20 100644 --- a/docs/33-authoring-an-adapter.md +++ b/docs/33-authoring-an-adapter.md @@ -40,7 +40,7 @@ library path at all. Nothing is broken; the artifact is simply not looking there, which is the property that makes an mcpp build reproducible in the first place. -## What an adapter is +## The definition of an adapter **A symlink farm plus the metadata that makes it reachable.** Nothing is vendored, nothing is redistributed, and the package carries no upstream bytes. @@ -49,7 +49,7 @@ artifact's runtime search path, and the chain resolves. A project declares the adapter as an ordinary dependency and does nothing else. -## Why the driver itself is not a package +## The reason the driver itself is not a package A proprietary driver's userspace is in **ABI lockstep with a kernel module**, and its licence forbids redistribution. Neither is a packaging problem that diff --git a/docs/34-authoring-a-bsp.md b/docs/34-authoring-a-bsp.md index ee0c8856..3bd11f6d 100644 --- a/docs/34-authoring-a-bsp.md +++ b/docs/34-authoring-a-bsp.md @@ -14,13 +14,13 @@ and packaging the emulator or the probe driver themselves, which is Before: [33 — Authoring a Runtime Adapter](33-authoring-an-adapter.md). -## What a BSP supplies +## The contents of a BSP A freestanding target has no operating system, so everything a hosted program gets for free has to come from somewhere. A BSP is that somewhere, and it supplies **the whole target world**: -| | what it is | +| | content | |---|---| | the memory map | a linker script — the one fact a program can neither derive nor guess | | the startup code | what runs before `main`, and the vector table | diff --git a/docs/41-devices.md b/docs/41-devices.md index 7f68450a..5dedf1ae 100644 --- a/docs/41-devices.md +++ b/docs/41-devices.md @@ -55,7 +55,7 @@ and `logcat` are equally unknown to it: it knows only that a package may supply named runners, and performs the argv it finds. A fixed set of names in the engine would decide, in the engine, which domains are expressible. -## What a package supplies +## The package’s contribution ```cpp mcpp::runner("qemu-system-arm"); // the default: argv token by token @@ -78,7 +78,7 @@ failure mode — a declaration is not an install, so the lookup can return empty and leave no runner configured with nothing said about why. Naming the program lets mcpp report exactly which directories it searched. -## What a project overrides +## The project’s overrides ```toml [target.thumbv7em-none-eabihf] diff --git a/docs/42-heterogeneous-builds.md b/docs/42-heterogeneous-builds.md index 420a3c30..ba30b6a1 100644 --- a/docs/42-heterogeneous-builds.md +++ b/docs/42-heterogeneous-builds.md @@ -14,7 +14,7 @@ GPU and AI accelerator targets, and mixed host/device compilation: how mcpp builds device code, and how a prebuilt artifact states which devices it can run on. -## What is supported +## The support surface A build names the device backends it targets, and it may name **several**: @@ -41,7 +41,7 @@ Vulkan/SPIR-V and Ascend C -- and the table under "The lanes" says which compiler and which payloads each drives. Nothing in the engine holds a vendor name, so a sixth is a package rather than an engine change. -## Two shapes, and why one mechanism reaches both +## Two shapes, and the mechanism common to both Accelerator toolchains come in two shapes. They describe how a toolchain is normally used, not how many mechanisms a build system needs. @@ -373,7 +373,7 @@ not a gate. It is a different field from an artifact's `accel` on purpose — a declaration is written by hand and may be aspirational, while an artifact's field is measured from the build that produced it. -## What a prebuilt artifact states +## The declarations of a prebuilt artifact An artifact that carries device code records it beside its compatibility tag: @@ -393,7 +393,7 @@ already contains a variable number of dashes. An absent `accel` means the artifact carries no device code and constrains nothing, which is why a CPU-only library is usable by every build. -### How a consumer is matched +### Consumer matching A build's request is satisfied by an artifact when, for each backend the build asks for, the artifact declares that backend, agrees on the toolkit's major @@ -580,7 +580,7 @@ in precisely the way the dimension exists to prevent. A publisher writes the field explicitly today, which is what index descriptors do; `mcpp pack` will emit it once `kind = "device"` puts the device compilation inside mcpp. -## The lanes, and what each one drives +## The lanes, and the toolchain each drives A rule package owns one compiler's spelling. The engine knows none of these names: `tests/unit/test_core_vendor_probes.cpp` asserts that no vendor tool @@ -621,7 +621,7 @@ unwinder symbols they share. Nothing may cross the seam: a SYCL exception is caught in the device translation unit and returned as a code, because the runtime that threw it is not the one the caller would unwind with. -## Which platforms each lane reaches +## The platforms each lane reaches A lane reaches a platform when three things hold there: the device compiler is published for it, the runtime the produced artifact needs can be reached, and @@ -632,7 +632,7 @@ it downloads nothing and asks only whether the modules compile); that fixture turned three latent host differences into compile errors on the runners that had them, and none of the three had been visible to a Linux build. -| lane | Linux | macOS | Windows | what decides it | +| lane | Linux | macOS | Windows | deciding factor | |---|---|---|---|---| | `rules-spirv` | yes | yes | yes | the shader compiler is published for all three: `xim:glslang` on Linux, `xim:shaderc` on macOS arm64 and Windows x86_64 | | `rules-cuda` | yes | no | yes | NVIDIA publishes the redistributable components for Linux and Windows and has published no macOS toolkit since CUDA 10.2 | @@ -686,7 +686,7 @@ onto the artifact's own search path. macOS (dyld) and Windows (the PE loader) have no such layer by construction, and a project targeting them declares no adapter. -## What a framework looks like on top of this +## The shape of a framework built on this The five lanes prove a rule package can drive five compilers, the newest of them a vendor outside the NVIDIA and Khronos lineages. A framework is diff --git a/docs/50-machine-output.md b/docs/50-machine-output.md index 3f8e9350..3387159b 100644 --- a/docs/50-machine-output.md +++ b/docs/50-machine-output.md @@ -233,7 +233,7 @@ sees a version number assumes there is a contract behind it. ## 8. Kinds -### `mcpp.env` — where mcpp keeps things +### `mcpp.env` — the locations mcpp uses ``` mcpp self env --format json @@ -285,7 +285,7 @@ mcpp cache list --format json `data` is `{root, entries[]}`, the same document `--json` prints bare. -### `mcpp.toolchain.list` — what is installed, and which targets this host serves +### `mcpp.toolchain.list` — the installed toolchains and the targets this host serves ``` mcpp toolchain list --format json @@ -304,7 +304,7 @@ convention, and is empty for a row that has none. `x86_64-linux-gnu` has an installed gcc and no convention at all, so selecting "rows whose convention is a gcc" must read `pin`. -### `mcpp.why.toolchain` — what a build for one pair would resolve to +### `mcpp.why.toolchain` — the resolution for one (target, toolchain) pair ``` mcpp why toolchain [--target ] [--toolchain ] --format json diff --git a/docs/51-supported-versions.md b/docs/51-supported-versions.md index a536423f..f39fbd36 100644 --- a/docs/51-supported-versions.md +++ b/docs/51-supported-versions.md @@ -22,7 +22,7 @@ that day's release. The scheme carries no compatibility promise in its digits: `2026.9.4.1` is not "a minor release" of `2026.9.3.2`. What may and may not change is stated below rather than encoded in the number. -## What is supported +## Support scope | | | |---|---| @@ -34,7 +34,7 @@ A release is superseded rather than withdrawn. Published assets and index entries for older versions remain in place, because a project may have pinned one and removing it would break a build that was working. -## What may change between releases +## Changes permitted between releases The engine's own interfaces are not all equally stable, and the difference is worth stating precisely. diff --git a/docs/90-build-from-source.md b/docs/90-build-from-source.md index 31210c5b..76663392 100644 --- a/docs/90-build-from-source.md +++ b/docs/90-build-from-source.md @@ -116,7 +116,7 @@ when it is skipped: Three shapes it catches, all of which have shipped here at least once: -| shape | what it looks like | +| shape | form | |---|---| | the criterion never runs | a test gated on a capability no job provides | | the criterion cannot fail | a substring search satisfied by any wording | @@ -131,7 +131,7 @@ nothing. `.github/tools/` holds eighteen scripts. Four are worth knowing before a first PR: -| script | what it refuses | +| script | the condition it refuses | |---|---| | `check_docs_style.sh` | question headings, second person in a reference chapter, a 简体中文 page whose heading structure has fallen behind | | `check_docs_structure.sh` | a chapter citing a design record, a `docs/NN-*.md` path that does not resolve, a translation missing a table | diff --git a/docs/91-toolchain-internals.md b/docs/91-toolchain-internals.md index 845202ea..b153c261 100644 --- a/docs/91-toolchain-internals.md +++ b/docs/91-toolchain-internals.md @@ -74,7 +74,7 @@ is resolved/auto-installed via the xlings backend into the sandbox `detect`/`probe` (`src/toolchain/detect.cppm`, `probe.cppm`) then derive: -| Field | How | +| Field | Derivation | |---|---| | `targetTriple` | ` -dumpmachine` | | `sysroot` | `-print-sysroot` (validated: must actually carry libc headers), with a remap fallback for xlings-built GCC whose baked build-time path doesn't exist locally | @@ -84,7 +84,7 @@ is resolved/auto-installed via the xlings backend into the sandbox Note the probe deliberately does **not** mine the clang cfg for `--sysroot` anymore: the cfg is an output of this machinery, not an input (§5). -### 2.1 The runtime binding — which libc, decided once +### 2.1 The runtime binding — one libc, decided once A payload-first build links against a specific glibc, and *which* one is a fact about the root project's local development OS, not something to infer @@ -207,7 +207,7 @@ decision, or a link succeeds and the artifact cannot start. The closure is one ordered list, each entry tagged with where it came from: -| origin | example | mutable? | ships? | +| origin | example | mutability | redistributable | |---|---|---|---| | `payload` | `/xim-x-glibc/2.39/lib64` | no — written once at install | no | | `package` | a dependency's `[runtime]` dir | no | no | @@ -470,7 +470,7 @@ Post-install alignment follows the same identity rule: `glibc@2.44` resolves only `/xim-x-glibc/2.44/{lib64,lib}`. A missing/stale exact payload is an error; another installed version is never a fallback. -### 6.2 Where a runtime search path is allowed to live (`runtime_env_contract.cppm`) +### 6.2 The permitted locations of a runtime search path (`runtime_env_contract.cppm`) There are two ways to tell a loader where to look, and they differ by blast radius, not by convenience: @@ -582,7 +582,7 @@ everything §3–§4 does for ELF. and the difference is not a flag spelling — it is what the artifact records about itself: -| format | what the producer emits | what the consumer links | +| format | the producer's output | the consumer's link input | |---|---|---| | ELF | `-Wl,-soname,` when declared | `-L` + `-l`, `-Wl,-rpath,$ORIGIN` | | Mach-O | `-Wl,-install_name,@rpath/` **always** | `-L` + `-l`, `-Wl,-rpath,@loader_path` | @@ -607,7 +607,7 @@ question "can this machine produce it", and `prepare.cppm` now asks it — with explicit `[target.X] toolchain = "…"` as the escape hatch for a cross toolchain supplied by the author. -### 7.5 Which axis decides a flag +### 7.5 The axis that decides a flag Four flags changed in the 2026.8.18 round, and each had been keyed on the wrong axis. Every one of those mistakes showed up the same way: an inexplicable @@ -617,7 +617,7 @@ the decision behind it. There are three axes, and the question that picks between them is **who finally reads this flag**. -| axis | the question | examples | how it is asked | +| axis | the question | examples | the form of the query | |---|---|---|---| | **target format** | what kind of image is produced | `-fPIC` (PE code is position independent by design; clang refuses the flag outright) | `triple::parse(...)->is_pe()`, host fallback | | **target ABI** | which linker will consume this | `--out-implib` vs `/IMPLIB:`, `/DEF:`, the SONAME / install-name form | `is_msvc_target(tc)`, `triple->is_msvc_env()` | diff --git a/docs/92-release.md b/docs/92-release.md index dc2b7c03..22db5ba9 100644 --- a/docs/92-release.md +++ b/docs/92-release.md @@ -186,7 +186,7 @@ clean-room that still reports the old `latest` has not failed — it has not cau up. `ci-fresh-install`'s `wait-index` job encodes exactly this with a bounded 15-minute wait. -## 4. The bootstrap pin: what it is, and when to bump it +## 4. The bootstrap pin: its definition and its update conditions `.xlings.json`'s `[workspace].mcpp` is the **starting point of self-hosting** — the released mcpp that `xlings install mcpp` puts in the workspace so CI can build @@ -224,7 +224,7 @@ curl -fsSL https://github.com/xlings-res/xim-index/releases/download/latest/xim- pushed. Nothing enforces this, and in the job log the resulting failure is indistinguishable from a genuinely wrong version name. -## 5. `MCPP_PIN` is derived, and why that matters +## 5. `MCPP_PIN` is derived, and the consequence `ci-fresh-install.yml` used to carry a second hand-edited copy of the pin. It was never the same thing: `MCPP_PIN` is the version **under test** — always the newest diff --git a/docs/README.md b/docs/README.md index 91eb099d..70206d3c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ records, which are not user documentation. ## Where a document lives -| tree | reader | what it holds | +| tree | reader | contents | |---|---|---| | `docs/**` | someone with a task in hand | how to use what mcpp implements | | [`docs/specs/**`](specs/README.md) | someone implementing against a mechanism: index authors, downstream tools, contributors | semantics, constraints and matching rules, each tagged with its implementation status | diff --git a/docs/specs/exit-codes.md b/docs/specs/exit-codes.md index 9aa954d6..6d5975ba 100644 --- a/docs/specs/exit-codes.md +++ b/docs/specs/exit-codes.md @@ -11,7 +11,7 @@ | 相关设计文档 | `.agents/docs/2026-08-08-machine-readable-output-protocol-design.md` §R4、`.agents/docs/2026-08-31-issue540-seven-audit-findings.md` §4 | | 相关 issue | #379、#540 | -## 0. 这份规范为什么存在 +## 0. 这份规范存在的原因 机器可读输出协议的设计记录(§R4)在实测四个退出码之后写下: @@ -100,7 +100,7 @@ issue;`1` 通常不是。 逐个归类: -| 数 | 是退出码? | 出处 | +| 数 | 属于退出码 | 出处 | |---|---|---| | `0` `1` `2` `127` | 是 | 全仓;`127` 在 `cli.cppm` | | `4` | 是,**11 处,全部同一个原因** | `config::load_or_init` 失败:`index_management.cppm`×6、`doctor.cppm`×2、`pack/pipeline.cppm`、`cli/cmd_toolchain.cppm`、`pm/commands.cppm` 各 1 | diff --git a/docs/zh/00-what-mcpp-is.md b/docs/zh/00-what-mcpp-is.md index 6f323d93..a572265b 100644 --- a/docs/zh/00-what-mcpp-is.md +++ b/docs/zh/00-what-mcpp-is.md @@ -1,38 +1,52 @@ # 00 —— mcpp 是什么 -## 背景:模块到了,工具链没跟上 +## 背景:C++ 工程侧的工具现状 -C++20 给了语言模块。它改变的是一个翻译单元的代价:接口只声明一次、被 import,而不是 -被每个需要它的文件从头文件里重新解析一遍;而消费者看到的是作者**导出**的东西,不是 -头文件恰好 include 进来的一切。 +一个 C++ 工程需要四样东西同时成立:一份构建描述、一组依赖、一个新到足以编译这份 +代码的编译器,以及一个能让产物真正运行起来的环境。C++ 没有任何一个工具同时负责 +这四样,它们由四类互不隶属的工具分别承担,而把它们对齐是工程自己的工作。 -结果采用它是一个**构建系统问题**,不是语言问题。构建要扫描源里的 `import`、据此给 -编译定序、缓存编译好的接口并正确地让它失效 —— 而且要在一个**新到具备这个特性**的 -编译器上做这些。`import std` 再加一条:标准库自己的模块必须先被构建出来,别的东西 -才能用它。 +CMake 是其中第一样的事实标准。事实标准陈述的是采用率,不是使用体验:CMake 不解析 +依赖、不安装编译器、也不描述运行期环境,而这三件恰好是新参与者在第一天遇到的。 +补齐它们意味着再引入一个包管理器、发行版的软件包,以及 README 里的一段环境说明 +—— 三套新的模型,而它们之间的对齐仍然落在工程身上。 -这个落差是具体的,不是理论上的。在写这一章的这台机器上: +最经常失效的是环境这一层,因为只有它没有任何东西在检查。构建描述会在配置阶段 +报错,包管理器会解析失败,而「机器上的库不是这份代码构建时所依据的那一份」在 +链接之前不产生任何信号,有时到程序运行时才产生。 + +模块给这个结构再加一条约束。C++20 的模块改变的是一个翻译单元的代价:接口只声明 +一次并被 import,而不是被每个需要它的文件从头文件里重新解析一遍;消费者看到的是 +作者导出的东西,不是头文件恰好 include 进来的一切。代价是这个语言特性由构建系统 +实现:构建要扫描源里的 `import`、据此给编译定序、缓存编译好的接口并正确地让它 +失效,而且要在一个新到具备这个特性的编译器上完成这些。`import std` 再加一条: +标准库自己的模块必须先被构建出来,别的东西才能用它。 + +于是采用模块的工程需要上述四层全部成立,而不是其中三层。这个落差是具体的。在写 +这一章的这台机器上: ```console $ g++ --version g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 ``` -这个编译器编不了 `import std`。工程本身没有任何问题;只是这台机器不是这个工程需要的 -那台机器。 +这个编译器编不了 `import std`。工程本身没有任何问题;是这台机器不是这个工程需要 +的那台机器。 -## 谁在为这个落差付账 +## 这一现状的代价 -**人付一个下午**,每台机器一次、每个新同事一次:装一个更新的编译器、弄清哪些构建系统 -旗标能把模块打开、把依赖找齐,然后在链接不过时再弄清这三件里坏的是哪一件。 +**对人,代价是一次搭建**,每台机器一次、每个新参与者一次:装一个更新的编译器、 +确定哪些构建旗标把模块打开、取得依赖,然后在链接失败时判断坏的是这三者中的哪一 +件。这个代价不随工程成熟而下降,它按人数和机器数重复。 -**Agent 付上下文**,而且在写下第一行代码之前就付掉三处:读构建系统以搞清它做了什么、 -重建它所假定的环境、以及顺着一个头文件穿过它的传递 include 去弄清究竟声明了什么。 +**对 agent,代价是上下文**,并且在写下第一行代码之前就消耗在三处:读构建描述以 +确定它究竟做了什么、重建它所假定的环境,以及顺着一个头文件穿过传递 include 去 +确定究竟声明了什么。 -模块消掉了第三处 —— 接口是显式的,`import` 恰好说明用到了什么。mcpp 存在的理由是消掉 -另外两处,并让第一处不必花掉那个下午。 +模块消掉了第三处,因为接口是显式的,`import` 陈述了用到什么。mcpp 消掉另外两处, +并把第一处收敛为一条命令。 -## mcpp 是什么 +## mcpp 的组成 ``` mcpp = 通用构建系统 @@ -42,43 +56,44 @@ mcpp = 通用构建系统 + 环境与运行时(xlings) ``` -多数 C++ 工程要把这五样从不同工具里拼起来,而那个下午就消耗在拼缝上:构建文件假定 -机器上有某个编译器,包管理器假定一份不是它写的构建文件,而环境是 README 里的一段话。 +多数 C++ 工程要把这五个部分从不同工具里拼起来,而上面那份搭建代价正是拼装本身的 +代价:构建文件假定机器上有某个编译器,包管理器假定一份不是它写的构建文件,而环境 +是 README 里的一段话。 -mcpp 是一个程序,所以没有缝需要拼。 +mcpp 是一个程序,五个部分共用同一套模型。 -对于已经有工具做这些事的读者,各部分大致落在这里: +对于已经在用相应工具的读者: -| 部分 | 在 mcpp 里是 | 大致相当于谁的活 | +| 组成部分 | 在 mcpp 中的形式 | 可对照的工具 | |---|---|---| | 通用构建系统 | `mcpp.toml`、模块图、ninja 后端 | CMake、Meson | | 构建插件 | `build.mcpp`、规则包 | `build.zig`、xmake rules | | 包管理 | `[dependencies]`、`mcpp.lock`、索引 | Conan、vcpkg | -| 工具链管理 | 编译器作为被安装并钉住的载荷 | Zig 自带的工具链;Rust 里 rustup 的角色;手工装 GCC / LLVM / MSVC | +| 工具链管理 | 编译器作为被安装并钉住的载荷 | Zig 自带的工具链、rustup、手工安装 GCC / LLVM / MSVC | | 环境与运行时 | `[xlings]`、载荷、运行期搜索路径 | Nix、conda | -**整体上最接近的两个类比是 Cargo 与 Zig**,而且它们各自对应同一个想法的一半。Cargo 是 -一个程序同时充当构建、包管理、锁文件与测试运行器,于是一个 Rust 工程克隆下来直接构建、 -没有前置步骤。Zig 把工具链随工具一起发,并且默认就能交叉编译,于是编译器不是机器必须 -先有的东西。 +整体上最接近的两个类比是 Cargo 与 Zig,二者各对应同一个想法的一半。Cargo 是一个 +程序同时充当构建、包管理、锁文件与测试运行器,于是一个 Rust 工程克隆下来直接构建、 +没有前置步骤。Zig 把工具链随工具一起发布,并且默认支持交叉编译,于是编译器不是 +机器必须先具备的东西。 -mcpp 在 C++ 上是这个形状,外加一样它们两个都没有的东西:**环境层** —— 这也是工程为什么 -还能声明它的构建所需的**非编译器**工具。 +mcpp 在 C++ 上是这个形状,外加二者都没有的一部分:环境层 —— 工程通过它声明构建 +所需的非编译器工具。 -**这张表是给各部分定位,不是宣称等价。** 上面每一个工具在它自己的领域里做的都比 mcpp -多,需要那种深度的工程应当去用它。 +**这张表为各部分定位,不宣称等价。** 表中每个工具在它自己的领域里做的都比 mcpp +多,需要那种深度的工程使用它。 -## 那条保证 +## 核心保证 -> **拿到任何一个 mcpp 工程,`mcpp build` 就能构建** —— 不需要自己装编译器、配环境, -> 也不需要去找依赖的库。 +> **拿到任何一个 mcpp 工程,`mcpp build` 就能构建** —— 不需要自行安装编译器、 +> 配置环境,也不需要寻找依赖。 -两条边界写在这里,好让这句话可以被信任:面向设备的工程第一次仍会下载那个设备的工具包; -而这台机器服务不了的目标会被**点名拒绝**,不会被错误地构建出来。 +这里写下两条边界,以便这句话可以被依赖:面向设备的工程第一次构建仍会下载该设备的 +工具包;而这台机器服务不了的目标会被点名拒绝,不会被错误地构建出来。 -## 展示它的最小示例 +## 最小示例 -还是那台机器 —— 唯一的 C++ 编译器就是上面那个 GCC 13。 +还是那台机器,唯一的 C++ 编译器是上面那个 GCC 13。 ```console $ mcpp new hello @@ -96,8 +111,8 @@ description = "A modular C++23 package" license = "Apache-2.0" ``` -**没有声明编译器、没有声明语言档位、没有声明任何依赖** —— 这就是一个读者在改动这个 -工程之前需要理解的全部。而源码用的正是这台机器的编译器不具备的那个特性: +**没有声明编译器、没有声明语言档位、没有声明任何依赖。** 这就是一个读者在改动 +这个工程之前需要理解的全部。而源码用的正是这台机器的编译器不具备的那个特性: ```cpp import std; @@ -118,17 +133,17 @@ Hello from hello! Built with import std + std::println on modular C++23. ``` -墙钟 1.25 秒,含首次运行。干这件事的编译器: +墙钟 1.25 秒,含首次运行。完成这次构建的编译器: ```console $ mcpp self env default toolchain = gcc@16.1.0 ``` -mcpp 装了 GCC 16 并用了它。宿主上没有任何东西被改动,而克隆这个工程的同事 —— 或者 -agent —— 拿到的是**同一个编译器**,不是他那台机器恰好带的那个。 +mcpp 安装了 GCC 16 并使用它。宿主上没有任何东西被改动,而克隆这个工程的同事 —— +或者 agent —— 得到的是同一个编译器,不是那台机器恰好自带的那个。 -**加一个依赖是一行,不需要别的步骤:** +**增加一个依赖是一行,不需要其他步骤:** ```toml [dependencies] @@ -137,29 +152,29 @@ agent —— 拿到的是**同一个编译器**,不是他那台机器恰好带 `mcpp build` 会解析它、取回它、构建它、链接它。 -## mcpp 面向什么 +## 适用范围 -mcpp 围绕 **C++20/23 模块与最新语言特性**建立,它维护的生态由此而来,而不是来自一个 -笼统的雄心: +mcpp 围绕 **C++20/23 模块与较新的语言特性**建立,它维护的生态由此而来,而不是 +来自一个泛化的目标: | | | |---|---| | 模块化 C++ | `import std` 零配置、模块扫描、跨工程 BMI 缓存 | -| 嵌入式与裸机 | freestanding 目标、板级支持包、从源码到一个跑起来的镜像只要一条命令 | +| 嵌入式与裸机 | freestanding 目标、板级支持包、从源码到一个可运行镜像只需一条命令 | | 异构计算与 GPU | CUDA、HIP、SYCL、Vulkan/SPIR-V 与 Ascend C,每一条都是规则包而不是引擎特性 | | 图形 | 着色器作为构建的一部分被编译,并以模块到达 | | 内核与底层 | 零 libc 档、显式的链接模型、没有隐藏的宿主依赖 | -这些一样都不需要的工程,照样得到上面那条保证;需要其中之一的工程,不必离开这个工具 -去得到它。 +不需要其中任何一项的工程,同样得到上述保证;需要其中之一的工程,不必离开这个 +工具去得到它。 -## 接下来去哪 +## 阅读路径 | | | |---|---| -| 把一个程序跑到屏幕上 | [01 —— 快速开始](01-getting-started.md) | -| 判断 mcpp 适不适合手头的工作 | [02 —— 场景](02-scenarios.md) | -| 读一个形状相近的工程 | [03 —— 示例项目](03-examples.md) | +| 运行第一个程序 | [01 —— 快速开始](01-getting-started.md) | +| 判断 mcpp 是否适用于当前工作 | [02 —— 场景](02-scenarios.md) | +| 阅读一个结构相近的工程 | [03 —— 示例项目](03-examples.md) | -本章之后的一切都是参考:manifest 可以说什么、依赖怎样解析、目标怎样命名。这一章刻意 -不写任何字段与旗标。 +本章之后的一切都是参考:manifest 可以陈述什么、依赖怎样解析、目标怎样命名。本章 +按设计不写任何字段与旗标。 diff --git a/docs/zh/02-scenarios.md b/docs/zh/02-scenarios.md index d325bafa..4f4f375c 100644 --- a/docs/zh/02-scenarios.md +++ b/docs/zh/02-scenarios.md @@ -15,7 +15,7 @@ ## 场景一览 -| | 场景 | 跑什么 | +| | 场景 | 运行 | |---|---|---| | [1](#1-命令行工具或服务) | 命令行工具或服务 | `examples/01-hello` → `03-pack-static` | | [2](#2-供他人-import-的库) | 供他人 import 的库 | `examples/11-features`、`05-lib-distribution` | diff --git a/docs/zh/03-examples.md b/docs/zh/03-examples.md index cf13d1b6..062f092b 100644 --- a/docs/zh/03-examples.md +++ b/docs/zh/03-examples.md @@ -27,7 +27,7 @@ mcpp build && mcpp run ### A —— 工程的形状 -| 示例 | 第一个教什么 | +| 示例 | 首次引入的内容 | |---|---| | [`01-hello`](../../examples/01-hello/) | 一个包、`import std`、`mcpp build` 与 `mcpp run` | | [`02-with-deps`](../../examples/02-with-deps/) | `[dependencies]`、锁文件、`mcpp add` | @@ -36,20 +36,20 @@ mcpp build && mcpp run ### B —— 发布 -| 示例 | 第一个教什么 | +| 示例 | 首次引入的内容 | |---|---| | [`03-pack-static`](../../examples/03-pack-static/) | `mcpp pack --mode static`、`[target.]`、`[pack]` | | [`05-lib-distribution`](../../examples/05-lib-distribution/) | 一个库的接口与它的预编译二进制;从同一份源产出 C 头文件与 C++ 模块 | ### C —— 环境 -| 示例 | 第一个教什么 | +| 示例 | 首次引入的内容 | |---|---| | [`07-project-subos`](../../examples/07-project-subos/) | `[xlings]`、`[xlings.workspace]`,以及 `PATH` 来自工程声明环境的构建程序 | ### D —— 目标 -| 示例 | 第一个教什么 | +| 示例 | 首次引入的内容 | |---|---| | [`06-openkal-cross`](../../examples/06-openkal-cross/) | `--target`,同一份源在任意宿主上为四台机器构建 | @@ -60,7 +60,7 @@ mcpp build && mcpp run [`09-heterogeneous`](../../examples/09-heterogeneous/) 按顺序读。它的 README 是 地图;下表是每个子示例新增的部分。 -| 示例 | 第一个教什么 | +| 示例 | 首次引入的内容 | |---|---| | [`…/boundary`](../../examples/09-heterogeneous/boundary/) | 单独的岛边界:消费者 import 一个生成的模块,工程里没有接缝也没有头文件。不需要设备 | | [`…/cuda`](../../examples/09-heterogeneous/cuda/) | 设备编译器、生成边界之上的接缝、把驱动陈述为 fact 与 floor | @@ -73,7 +73,7 @@ mcpp build && mcpp run ### F —— 为生态编写扩展 -| 示例 | 第一个教什么 | +| 示例 | 首次引入的内容 | |---|---| | [`08-build-rules`](../../examples/08-build-rules/) | 两个规则包与同时使用它们的工程;`host-module = true`、`role = "check"` 的 `mcpp::action` | | [`12-a-new-device-language`](../../examples/12-a-new-device-language/) | `device_extensions` 与 `rule_module`:规则包教会 mcpp 一门引擎从未听说过的语言 | diff --git a/docs/zh/04-mcpp-toml.md b/docs/zh/04-mcpp-toml.md index c0c06990..78ee1835 100644 --- a/docs/zh/04-mcpp-toml.md +++ b/docs/zh/04-mcpp-toml.md @@ -147,7 +147,7 @@ soname = "libmylib.so.1" # 可选: Linux/ELF ABI 名称,运行时会生成同 MSVC ABI 上从对象生成导出表(该 ABI 没有 `__declspec(dllexport)` 或 `.def` 时 不导出任何符号)。参见 `tests/e2e/08`、`257`、`259`。 -#### `exports` —— 产物发布哪些符号(mcpp 2026.9.6.5+) +#### `exports` —— 产物发布的符号集合(mcpp 2026.9.6.5+) ```toml [targets.mydriver] @@ -1041,7 +1041,7 @@ o.arg("./mkblob.sh").arg("blob.bin").arg("${mcpp.out_dir}/blob.o") 其中四个是**可运行的工程**而不是片段,而工程是更好的答案:它能构建,而且由 CI 检查。 -| 形态 | 跑什么 | +| 形态 | 运行 | |---|---| | 一个 hello world | [`examples/01-hello`](../../examples/01-hello/) | | 带测试的模块化库 | [`examples/11-features`](../../examples/11-features/) | diff --git a/docs/zh/06-features-and-capabilities.md b/docs/zh/06-features-and-capabilities.md index bdbba717..7d6cd9d5 100644 --- a/docs/zh/06-features-and-capabilities.md +++ b/docs/zh/06-features-and-capabilities.md @@ -346,7 +346,7 @@ std-freestanding-alloc-kal = "0.1.x" 三种用法各一行: -| 消费方需要 | 清单里怎么写 | +| 消费方需要 | 清单中的写法 | |---|---| | 不用会分配的那部分 | `std-freestanding = "0.2.0"` —— 分配器不进图 | | 默认实现 | `features = ["alloc-kal"]` —— 实现随之进图,**无需知道其包名** | diff --git a/docs/zh/07-workspace.md b/docs/zh/07-workspace.md index 24be3fa9..45f4652f 100644 --- a/docs/zh/07-workspace.md +++ b/docs/zh/07-workspace.md @@ -283,7 +283,7 @@ mcpp run -p server -- --port 8080 `mcpp test --workspace` 逐成员独立汇报、遇失败继续,只要有任一成员失败即非零退出—— 非常适合作为「一个测试众多库的工作空间」的单条、无 shell 的 CI 步骤。 -#### 扇出汇报什么 +#### 扇出的汇报内容 ``` Workspace testing member 'libs/core' (3/97) diff --git a/docs/zh/08-testing.md b/docs/zh/08-testing.md index 53b48770..8d44470a 100644 --- a/docs/zh/08-testing.md +++ b/docs/zh/08-testing.md @@ -13,7 +13,7 @@ 那是测试如何取到产物取不到的包。在此之后: [09 —— 按场景选命令](09-commands-by-scenario.md) 是其余一切的查阅入口。 -## mcpp 认为什么是一个测试 +## 测试的定义 每一个 `tests/**/*.cpp` 都是一个测试:mcpp 把每个文件编译成它自己的程序并运行它。 测试通过的判据是它的程序以 0 退出。 @@ -45,7 +45,7 @@ mcpp test -- --verbose # `--` 之后的一切传给每个测试程序 测试的构建轴与 `mcpp build` 相同,因此测试跑在它要检查的那个配置上,而不是默认 配置上: -| 旗标 | 选择什么 | +| 旗标 | 选中的集合 | |---|---| | `--profile ` | `dev`(默认)、`release`、`dist`,或 manifest 声明的某个 `[profile.*]` | | `--features ` | 这次测试构建的 feature 集合 | diff --git a/docs/zh/09-commands-by-scenario.md b/docs/zh/09-commands-by-scenario.md index 8e35b48b..07f790cc 100644 --- a/docs/zh/09-commands-by-scenario.md +++ b/docs/zh/09-commands-by-scenario.md @@ -65,7 +65,7 @@ key kind size last used package `std` BMI 被机器上每个工程共享,实现以「重建它是用大量时间换少量磁盘」为由把它排除在 按体积驱逐之外。`mcpp cache clean --std` 仍是显式移除它的做法。 -## 一个包发布了哪些版本 +## 一个包已发布的版本 `mcpp search` 按子串匹配,并在每个命中行后附上该包发布的版本 —— 跨描述符的 per-OS 表 合并,按 semver 降序: @@ -310,12 +310,12 @@ deps = ["xim:mcpp-hooks-audioplayer@0.0.1"] 这份 manifest 自己就想要的值——缺个音频设备不该让构建失败——所以等这个键有了不止 一个可接受的值之后,它仍然会这么写。 -## 一次失败是关于哪一段的 +## 失败所属的阶段 一次构建要跨过若干阶段,而消息会点名失败的那一段。先读这一点,可以省掉打开错误 章节的功夫。 -| 消息里出现 | 阶段 | 去哪里看 | +| 消息里出现 | 阶段 | 参考章节 | |---|---|---| | 包名、版本,或「没有候选」 | 解析 | [05](05-dependencies.md)、[11](11-publishing-a-library.md) | | 下载、载荷,或版本下界 | 供给 | [20](20-toolchains.md)、[23](23-the-project-environment.md) | diff --git a/docs/zh/10-pack-and-release.md b/docs/zh/10-pack-and-release.md index 6757d450..62e5d6c1 100644 --- a/docs/zh/10-pack-and-release.md +++ b/docs/zh/10-pack-and-release.md @@ -18,7 +18,7 @@ mcpp 之所以针对私有 glibc 构建,正是为了让产物的行为不取决于底下是哪个发行版; 如果最后一步又伸手去拿宿主的 libc,前面这件事就白做了。 -| | 方式 | 命令 | C 运行时来自哪里 | 何时选它 | +| | 方式 | 命令 | C 运行时的来源 | 适用条件 | |---|---|---|---|---| | **A** | 走生态 | `mcpp emit xpkg` → `xlings install ` | 目标机自己的 xlings 载荷 | 目标机装了 xlings | | **B** | 静态单文件 | `mcpp build --target x86_64-linux-musl` | 不来自任何地方 —— 已链进去 | 想要一个无任何运行时依赖的单文件 | @@ -118,7 +118,7 @@ mcpp pack --debug-symbols dbg/ # 把分离出的 *.debug 写到 dbg/ 完整选项参见 `mcpp pack --help`。 -### 打包产物用什么构建,里面带什么走 +### 打包产物的构建输入与随包内容 与 `mcpp build` 有两点不同,都因为「这个产物要离开本机」: @@ -138,7 +138,7 @@ profile 的工程仍然拿到它声明的那个,`mcpp pack` 也不会产出一 DWARF 带着发布者源码树与构建目录的绝对路径。剥什么取决于产物**是什么** —— 这是 dh_strip 的分档,而其中归档那一行是要命的: -| 产物 | strip 参数 | 为什么不能更狠 | +| 产物 | strip 参数 | 不能更进一步的原因 | |---|---|---| | 可执行文件 | `--strip-all` | 没有人链接它 | | 共享库 | `--strip-unneeded` | 保留 `.dynsym` —— 那**就是**导出表 | diff --git a/docs/zh/12-binary-distribution.md b/docs/zh/12-binary-distribution.md index 502cfb90..48ca5c7a 100644 --- a/docs/zh/12-binary-distribution.md +++ b/docs/zh/12-binary-distribution.md @@ -238,7 +238,7 @@ ldflags = ["-Llib/x86_64-linux-musl", "-lmathkit"] 这是**降级**而不是变砖,方向是对的。但它意味着**闸门只保护新客户端**, 面向混合版本用户群发布时,这一条应写进发布说明。 -## 包里带什么走,以及刻意不带什么 +## 随包内容与刻意排除的部分 发布出去的包必须能在**不是发布者的**机器上工作。两个步骤保证这件事, 它们作用在打包器暂存的每一个产物上。 @@ -264,7 +264,7 @@ error while loading shared libraries: libstdc++.so.6: cannot open shared object **`$ORIGIN` 不是解药。** 在真实的包上、把构建机的 store 变成不可达之后实测: -| 发货 `.so` 上的状态 | 消费方 `DT_RPATH` 被继承? | 结果 | +| 发货 `.so` 上的状态 | 消费方 `DT_RPATH` 的继承 | 结果 | |---|---|---| | 失效的绝对路径 `DT_RUNPATH` | 否 | 失败 | | **完全没有这条 tag** | **是** | **能跑** | diff --git a/docs/zh/20-toolchains.md b/docs/zh/20-toolchains.md index 4844b4d0..45c87e6b 100644 --- a/docs/zh/20-toolchains.md +++ b/docs/zh/20-toolchains.md @@ -88,7 +88,7 @@ mcpp toolchain default gcc@16 --target x86_64-linux-musl # "默认就要全静 `[toolchain] default = "gcc@16.1.0"` + `default_target = "x86_64-linux-musl"`。 (存量 config 里 `default = "gcc@15.1.0-musl"` 这类合并拼写原样可用。) -### 谁决定一次构建的编译器 +### 一次构建的编译器选定 有五种来源会给它命名。它们是分级的,而这套分级正是让工程能写下的那两条压过 mcpp 自己保管的一切的原因: @@ -165,7 +165,7 @@ Available toolchains (run `mcpp toolchain install `): `*` 标记当前的默认对。Targets 块是 target 词汇表的实时视图,共四种状态: -| 状态 | 含义 | 下一步做什么 | +| 状态 | 含义 | 后续步骤 | |---|---|---| | `installed` | 本机已有的载荷就能产出它 | 无 | | `available` | 本宿主存在可装的载荷 | `mcpp toolchain install` | @@ -316,7 +316,7 @@ error: [toolchain] linux = "system" is not supported: mcpp builds only with `msvc@system` 是**唯一的例外**,而且是另一种拼法:它点名的是一个**族**,mcpp 负责定位并识别 其安装 —— 那是唯一一个编译器不能被重新分发的平台。见上一节。 -#### 为什么工具链与库得到的答案不同 +#### 工具链与库答案不同的原因 mcpp 对 host 依赖的规则并不是各条轴统一的,这个分叉是刻意的: @@ -407,7 +407,7 @@ STL,以及通过它的 `xim:windows-sdk` 依赖带来 ucrt/um 的头文件与导 **Windows SDK 跟着来源走**,因为两条来源回答的是不同的问题,SDK 也必须如此: -| 来源 | SDK 怎么选 | +| 来源 | SDK 的选定方式 | |---|---| | `msvc@` | **随该 toolset 一起装进 mcpp store 的** `xim:windows-sdk` payload。环境里的 `WindowsSdkDir` / `WindowsSdkVersion` 会被**忽略**,并且 mcpp 会打印一行 `note:` 说明。 | | `msvc@system` | 先 **`WindowsSdkDir`**(+ `WindowsSdkVersion`),再 `C:\Program Files (x86)\Windows Kits\10`。 | @@ -498,7 +498,7 @@ error: target 'x86_64-linux-musl' takes its C library from the 'gcc@16.1.0' 有两类行回答的是另一个问题,它们的 pin 根本不可被推翻: -| 行 | 为什么 | +| 行 | 原因 | |---|---| | 所有 `*-none-elf` | 不存在按宿主分的交叉载荷;clang 与 lld 按构造就是交叉编译器,gcc 不是 | | `x86_64-windows-musl` | 没有任何 gcc 载荷发得出 PE + musl —— mingw 载荷发的是 PE + MinGW CRT,那是隔壁 `-gnu` 那一行 | @@ -684,7 +684,7 @@ manifest 所述不同的产物。 `_MSVC_MD` 烘进工程唯一的那份 `std` 模块,所以与工程不一致的按角色契约无法兑现, 会被报出来而不是被忽略。 -| 取值 | 在 MSVC 上是什么 | +| 取值 | 在 MSVC 上的含义 | |---|---| | `self-contained` | `/MT`,静态 CRT。`linkage = "static"` 从 libc 那根轴选中的是同一件事。 | | `host-coupled`(`/MD` 下的默认) | 由目标机器提供 `vcruntime140.dll` / `msvcp140.dll` —— 即那台机器装了 Visual Studio 或 redistributable。 | diff --git a/docs/zh/21-the-target-triple.md b/docs/zh/21-the-target-triple.md index dc6fd649..9707ca39 100644 --- a/docs/zh/21-the-target-triple.md +++ b/docs/zh/21-the-target-triple.md @@ -111,7 +111,7 @@ $ mcpp build --target aarch64-linux-musl `--target aarch64-linux-gnu` 仍会撞上 `planned` 行的拒绝 —— 那正是用显式 `[target.] toolchain` 提前加入某一行的逃生口。 -### 该用哪种拼法 +### 采用的拼法 **在构建期体系下,省略它。** 图供给 C 库与各运行时,那一段陈述的是一个 不会被查询的请求。在这种体系下,`x86_64-windows` 不只是比 @@ -124,7 +124,7 @@ $ mcpp build --target aarch64-linux-musl **在 Windows 上,想要微软那套 ABI 时写 `msvc`。** `gnu` 是默认填充, 而 `msvc` 是不同的对象 ABI 而非不同的 C 库,因此那一段在两种体系下都有意义。 -## 构建报告了什么 +## 构建的报告内容 报告以写下的目标为标题,并把它解析为编译器自己的拼写: @@ -160,7 +160,7 @@ Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu selects the Itanium 横切事项。把它读成 `c++-abi libc++` 是第二个错误答案,因为 libstdc++ 坐在同一套 ABI 上。 -## 三套词表,以及它们为何不同 +## 三套词表,以及它们不同的原因 一个三元组由三方书写,而三方并不共用一套约定;mcpp 在它们之间翻译。 知道手上这个字符串属于哪一套,第三段带来的困惑就消掉大半。 @@ -204,7 +204,7 @@ clang++ → x86_64-unknown-linux-gnu,而 --target= 可改 MinGW 自己的三元组是 `x86_64-w64-mingw32`: -| 段 | 值 | 为什么 | +| 段 | 值 | 原因 | |---|---|---| | arch | `x86_64` | | | vendor | `w64` | 项目名 `mingw-w64`,用以区别于已停滞的原 `mingw32` 项目 | @@ -239,7 +239,7 @@ LLVM x86_64 - unknown - windows - gnu ### mcpp 保留的那一套 -| 词表 | 例 | 谁读 | +| 词表 | 例 | 读者 | |---|---|---| | GCC / autoconf | `x86_64-w64-mingw32` | 预构建载荷的编译器,以文件名的形式 | | LLVM | `x86_64-w64-windows-gnu` | `clang --target=` | @@ -373,7 +373,7 @@ CRT;图供给时是 `musl`。一个目标字符串,两个不同的 C 库 —— `linux-x86_64` 够得着,从 `linux-aarch64` 够不着;`aarch64-linux-gnu` 是镜像的 情形,两台上都是 `planned`。把它们并成 `linux`,一台会把另一台的行覆盖掉。 -### 哪台构建机服务哪个目标 +### 构建机与它服务的目标 | target | tier | pin | linux-x86_64 | linux-aarch64 | macos-arm64 | windows-x86_64 | |---|---|---|---|---|---|---| @@ -405,7 +405,7 @@ CRT;图供给时是 `musl`。一个目标字符串,两个不同的 C 库 —— ### 列背后的规则 -| 目标类别 | 哪些构建机服务它 | 为什么 | +| 目标类别 | 服务它的构建机 | 原因 | |---|---|---| | `*-linux-musl` | Linux(任意架构)、Windows(仅同架构) | musl 载荷是自足的 | | `*-linux-gnu` | Linux,且仅同架构 | 还要本机架构的 `xim:glibc` / `xim:linux-headers` | diff --git a/docs/zh/22-target-side.md b/docs/zh/22-target-side.md index e66e683f..0bf0a674 100644 --- a/docs/zh/22-target-side.md +++ b/docs/zh/22-target-side.md @@ -82,7 +82,7 @@ C 库、平台接口与 C++ 运行时是互斥的选择,而非可叠加的贡献 引擎仅在两层来自不同来源时为它们接线。 -| 组合 | 关系由谁表达 | 引擎 | +| 组合 | 关系的表达方 | 引擎 | |---|---|---| | 两层均来自 `graph` | 包之间的普通依赖 | 不介入 | | 两层均来自 `payload` | 载荷自身一致 | 不介入 | diff --git a/docs/zh/23-the-project-environment.md b/docs/zh/23-the-project-environment.md index 4ce234a8..264e2b85 100644 --- a/docs/zh/23-the-project-environment.md +++ b/docs/zh/23-the-project-environment.md @@ -21,7 +21,7 @@ subos = "tools" 可运行的工程:`examples/07-project-subos/`。 -## 1. SubOS 是什么 +## 1. SubOS 的定义 SubOS 是一个目录,里面是一份用户态:它自己的 `bin`、自己的库视图、自己那套已 装包版本,以及一个自述用的 `subos_info` 块。mcpp 把它当作「这个项目对着什么 @@ -30,7 +30,7 @@ SubOS 是一个目录,里面是一份用户态:它自己的 `bin`、自己的库 存在两种,区别在于目录落在哪里: -| 声明 | 目录 | 与谁共享 | +| 声明 | 目录 | 共享范围 | |---|---|---| | 未声明 | mcpp 初始化的 `subos/default` | 机器上的每个项目 | | `subos = "default"` | 同一个目录,只是被显式点名 | 机器上的每个项目 | @@ -38,7 +38,7 @@ SubOS 是一个目录,里面是一份用户态:它自己的 `bin`、自己的库 第三行是隔离的那种。它属于该项目,就放在清单旁边,删掉项目它也随之消失。 -## 2. 这条声明决定什么 +## 2. 这条声明的决定范围 **C 库。** payload-first 的构建链接的是某一个确定的 glibc,而「哪一个」是项目 的性质而非机器的性质。第 8 章讲绑定本身、降级规则,以及一个不自述的 SubOS 会 @@ -62,7 +62,7 @@ PATH=<被声明环境的 bin>: **前置而非替换。** 构建程序理应会调 `git`、`python3` 或 shell,这些都不在 SubOS 里。前置让被声明的环境成为默认答案;其余的仍在它后面可达。 -### 2.1 哪些版本钉生效(2026.9.3+) +### 2.1 生效的版本钉(2026.9.3+) 指名一个环境,同时改变了工具的版本从哪来。工程自己 `[xlings.workspace]` 里的条目 总是胜出;不同的是它们叠在什么之上: @@ -79,7 +79,7 @@ SubOS 里。前置让被声明的环境成为默认答案;其余的仍在它后 在工程内执行的 `xlings use` 压过这两者,直到 mcpp 重写环境为止:它是最后合并的那 一层,而人做出的动作应当压过一份文件。 -## 3. 这条声明不决定什么 +## 3. 这条声明不决定的部分 `[xlings.workspace]` 声明的是「环境里要有哪些包」,而每个包的载荷目录另有通道交付, 即 `MCPP_XPKG__DIR`。这与 `PATH` 是两个问题,答案也保持分开:需要某个包 @@ -115,7 +115,7 @@ create/bootstrap that environment instead of falling back to active/default `inconclusive`,没有 payload-first 绑定可用,打印一条提示,构建继续。完整规则见 第 8 章。 -## 5. 什么时候值得用私有环境 +## 5. 采用私有环境的条件 - **产物取决于版本的代码生成器。** `protoc`、`flatc`、着色器编译器:它的输出是 下游一切的输入,所以项目钉住生产者,而不是指望机器上那个恰好兼容。 @@ -148,9 +148,9 @@ create/bootstrap that environment instead of falling back to active/default 的首选位置,在 `PATH` 之前([04 §2.7.3](04-mcpp-toml.md))。两个键合起来,在 CI 宿主上供给用户态模拟器, 并通过它执行交叉构建的产物,而清单不必写出载荷的路径。 -## 6. 什么该写在别处 +## 6. 应当写在别处的声明 -| 需求 | 写在哪里 | +| 需求 | 写入位置 | |---|---| | 程序链接的库 | `[dependencies]` | | 编译器 | `[toolchain]`,第 3 章 | @@ -209,7 +209,7 @@ mcpp 既供给它——机器上没有就装,有就映射——也把它物化 一条工具条目回答的是两个不同问题中的一个,写在哪张表里决定了是哪一个: -| 写法 | 轴 | 按什么解析 | +| 写法 | 轴 | 解析依据 | |---|---|---| | `[xlings.workspace]`,平台键写在值里 | 宿主 | 跑这次构建的机器 | | `[target..xlings.workspace]` | 目标 | 解析后的目标(`--target`,否则是宿主) | @@ -282,7 +282,7 @@ this applies to. 这两条轴所属的一般规则见 [SPEC-004](../specs/manifest-semantics.md)。 -### `when` —— 哪些命令需要这个工具(mcpp 2026.9.4.2+) +### `when` —— 需要这个工具的命令(mcpp 2026.9.4.2+) ```toml [xlings.workspace] @@ -296,7 +296,7 @@ this applies to. `[dev-dependencies]`。工具只有一张表,于是一个同时点名模拟器与调试探针的板级支持包 会把两个都装给每一位消费者,包括只想把库编出来的那一位。 -| `when` | 由谁安装 | 是否传播到消费者 | +| `when` | 安装方 | 传播到消费者 | |---|---|---| | *(不写)* | 每个构建命令 | 是 | | `build` | 每个构建命令 | 是 | diff --git a/docs/zh/24-openkal-cross.md b/docs/zh/24-openkal-cross.md index 83feeb51..632291dc 100644 --- a/docs/zh/24-openkal-cross.md +++ b/docs/zh/24-openkal-cross.md @@ -66,7 +66,7 @@ mcpp build --target x86_64-windows-gnu 一个工程命名其中最后一个。其余由它的依赖推出。 -## 编译器为何必须是 LLVM +## 编译器必须是 LLVM 的原因 `openkal-llvm-runtime` 把这项要求声明出来,而不是留待被发现: @@ -85,7 +85,7 @@ fatal error: __config: No such file or directory 有了这项声明,构建在编译任何东西之前拒绝该组合, 并指出选择一个满足它的编译器的那条命令。 -## 目标如何被选定 +## 目标的选定 mcpp 自身词表的目标行可以携带一条工具链约定。该约定命名的是 **供给该目标 C 库的那份载荷**,并且仅在两个条件同时成立时生效: @@ -233,7 +233,7 @@ UEFI 应用是 PE/COFF,经微软 x64 调用约定进入。两者都是 LLVM 工 作为一个跨若干指令集的接口呈现,每个指令集一个后端包。 一个内核依赖它,并供给自己的平台层,或者不供给。 -### x86_64 裸机为何需要引擎侧的工作 +### x86_64 裸机需要引擎侧工作的原因 `riscv64-none-elf` 与 `aarch64-none-elf` 是表中的行,除此之外别无他物: Clang 对两者都有 BareMetal 工具链,自行驱动它们的链接并到达 `ld.lld`。 diff --git a/docs/zh/30-build-mcpp.md b/docs/zh/30-build-mcpp.md index 9af7c6cc..da2db95d 100644 --- a/docs/zh/30-build-mcpp.md +++ b/docs/zh/30-build-mcpp.md @@ -251,7 +251,7 @@ crt/host_config.h:218: fatal error: features.h: No such file or directory **不是 `sysroot_dir()`。** 那个回答的是目标**档位**的问题,在宿主目标上为空, 而宿主目标恰恰是这一对存在的场合。mcpp 不传某个开关时,对应的那个为空串。 -### 解析出的是哪个 C++ 标准库:`cxx_stdlib`(2026.9.6.3+) +### 解析出的 C++ 标准库:`cxx_stdlib`(2026.9.6.3+) ```cpp const char* impl = mcpp::cxx_stdlib(); // "libstdc++" | "libc++" | "msvc-stl" | "" @@ -690,7 +690,7 @@ action,由引擎调度。工具说明的是构建程序需要、而没有任何 **失败与提示走不同的通道。** mcpp 只在构建程序非零退出时打印抓到的输出,所以失败写 stderr 并返回非零。而必须在**成功**构建上被看见的消息要走 -[`mcpp::warning`](#warning--成功了而且仍然被听见2026821-2);成功时的 stderr 被丢弃, +[`mcpp::warning`](#warning--成功了而且仍然被听见20268212);成功时的 stderr 被丢弃, 也就是说选错通道恰好在需要它的那些构建上一言不发。 **一个 `(名字, 版本)` 只对应一份载荷。** mcpp 用这个二元组标识已安装的包,所以一个重新 diff --git a/docs/zh/31-authoring-a-rule-package.md b/docs/zh/31-authoring-a-rule-package.md index 194afd4b..c789bdcf 100644 --- a/docs/zh/31-authoring-a-rule-package.md +++ b/docs/zh/31-authoring-a-rule-package.md @@ -12,11 +12,11 @@ 嵌入,[`12-a-new-device-language`](../../examples/12-a-new-device-language/) 新增 一门语言。 -## 规则包是什么 +## 规则包的定义 三部分,没有一部分是 mcpp 特有的: -| 部分 | 是什么 | +| 部分 | 内容 | |---|---| | 一个包 | 普通的 `mcpp.toml`,有版本与许可证 | | 一个模块 | 一个 `.cppm`,导出 `options` 与一个提交构建边的函数 | @@ -209,7 +209,7 @@ mcpp::floor("cuda.driver", runtime_needs); ## 找到规则自己的文件 -| 要找什么 | 怎么找 | +| 对象 | 获取方式 | |---|---| | 规则包自己的目录树 | `mcpp::dep_dir("")` —— 按**消费者**在 `[dependencies]` 里写的那个名字 | | `[xlings.workspace]` 声明的载荷 | `mcpp::xpkg_dir("")` | diff --git a/docs/zh/32-authoring-a-payload.md b/docs/zh/32-authoring-a-payload.md index ce50bceb..15b1ef24 100644 --- a/docs/zh/32-authoring-a-payload.md +++ b/docs/zh/32-authoring-a-payload.md @@ -14,7 +14,7 @@ 在此之前:[31 —— 编写规则包](31-authoring-a-rule-package.md) —— 规则声明它所驱动的 载荷。在此之后:[33 —— 编写运行时适配包](33-authoring-an-adapter.md)。 -## 载荷是什么 +## 载荷的定义 一切由 mcpp 安装而不编译的东西:编译器、着色器编译器、设备工具包、模拟器、探针 驱动、预编译的 C 库。工程在 `[xlings.workspace]` 里点名它,或者规则包在 @@ -106,7 +106,7 @@ exports = { 头文件声明把载荷放进 SubOS 的 sysroot 视图。**是声明而不是复制** —— xlings 会随包 一起移除它们,而一份复制会比它的主人活得更久。 -## 档位:载荷什么时候被需要 +## 档位:需要载荷的命令 ```toml "xim:qemu-arm" = { version = "9.2.4-1", when = "run" } diff --git a/docs/zh/33-authoring-an-adapter.md b/docs/zh/33-authoring-an-adapter.md index 88a7b3f4..b1dff02f 100644 --- a/docs/zh/33-authoring-an-adapter.md +++ b/docs/zh/33-authoring-an-adapter.md @@ -34,7 +34,7 @@ rpath : …/xim-x-glibc/2.39/lib64:…/xim-x-gcc/…/lib64:$ORIGIN 于是从那个进程内部发起的、按裸 soname 的 `dlopen`,**根本不会去搜索宿主的库路径**。 没有任何东西坏掉;产物只是不往那里看 —— 而这正是 mcpp 构建可复现的那条性质本身。 -## 适配包是什么 +## 适配包的定义 **一个符号链接农场,加上让它可被够到的那点元数据。** 不内嵌任何东西、不再分发任何 东西,包里没有上游的字节。`runtime.library_dirs` 把一个包自有的符号链接目录放上产物 @@ -42,7 +42,7 @@ rpath : …/xim-x-glibc/2.39/lib64:…/xim-x-gcc/…/lib64:$ORIGIN 工程把适配包当作一个普通依赖声明,除此之外什么都不做。 -## 为什么驱动本身不能是一个包 +## 驱动本身不能成为一个包的原因 专有驱动的用户态与**内核模块处于 ABI 锁步**,而它的许可证禁止再分发。这两条都不是 靠努力能解决的打包问题,所以这类驱动被建模为**宿主能力** —— 机器要么有、要么没有 diff --git a/docs/zh/34-authoring-a-bsp.md b/docs/zh/34-authoring-a-bsp.md index a5bee8db..5a76198d 100644 --- a/docs/zh/34-authoring-a-bsp.md +++ b/docs/zh/34-authoring-a-bsp.md @@ -13,12 +13,12 @@ 在此之前:[33 —— 编写运行时适配包](33-authoring-an-adapter.md)。 -## BSP 供给什么 +## BSP 的供给内容 freestanding 目标没有操作系统,所以 hosted 程序白得的一切都必须来自某处。BSP 就是 那个某处,它供给**整个目标世界**: -| | 是什么 | +| | 内容 | |---|---| | 内存布局 | 一份链接脚本 —— 程序既推导不出、也猜不到的那一个事实 | | 启动代码 | `main` 之前跑的东西,以及向量表 | diff --git a/docs/zh/41-devices.md b/docs/zh/41-devices.md index becca874..a56d48ab 100644 --- a/docs/zh/41-devices.md +++ b/docs/zh/41-devices.md @@ -43,7 +43,7 @@ runner,于是开发者从模拟器换到真板时,敲的命令不变。 `logcat` 对它一样陌生:它只知道「包可以提供具名 runner」这件事,然后执行它找到的 argv。**引擎里若有一份固定的名字表,就等于由引擎决定哪些领域可被表达。** -## 包提供什么 +## 包的供给内容 ```cpp mcpp::runner("qemu-system-arm"); // 默认:一次一个 token @@ -63,7 +63,7 @@ mcpp::run_exclusive(); // 这个目标的运行不能重叠 失败模式 —— **声明不是安装**,查询可能返回空,于是没有配置任何 runner 而没有任何 话说明原因。写程序名则让 mcpp 报出它究竟搜过哪些目录。 -## 工程覆盖什么 +## 工程的覆盖项 ```toml [target.thumbv7em-none-eabihf] @@ -77,7 +77,7 @@ monitor = ["probe-rs", "attach", "--chip", "STM32L475VG"] 优先级是通常那个:工程作者写的胜过依赖提供的,且覆盖会被报告。一个名字只允许一个 依赖提供,第二个是点名两个包的错误。 -## 是否终止由声明决定,不由推断 +## 终止由声明决定,不由推断 | | 含义 | |---|---| diff --git a/docs/zh/42-heterogeneous-builds.md b/docs/zh/42-heterogeneous-builds.md index 72301b67..6ca6757c 100644 --- a/docs/zh/42-heterogeneous-builds.md +++ b/docs/zh/42-heterogeneous-builds.md @@ -12,7 +12,7 @@ GPU 与 AI 加速器目标,以及宿主/设备混合编译:mcpp 如何构建设备代码,以及一个预建产物如何 声明它能在哪些设备上运行。 -## 支持什么 +## 支持范围 一个构建点名它面向哪些设备后端,而且**可以点多个**: @@ -35,7 +35,7 @@ accel = "cuda12.9+{sm_89}, vulkan1.2" 「各条 lane」那张表写明每个驱动哪个编译器、需要哪些载荷。引擎里不持有任何厂商名字, 所以第六个是**一个包**而不是一次引擎改动。 -## 两种形态,以及为什么一套机制够到两者 +## 两种形态,以及两者共用的机制 加速器工具链有两种形态。它们描述的是**一个工具链通常怎么被使用**,不是一个构建系统 需要几套机制。 @@ -180,7 +180,7 @@ mcpp 2026.9.7.1 之前,那条 lane 是唯一的例外:生成的头文件**就是 所以规则不是「接口要生成」也不是「接口要手写」。规则是:**机械的接口生成,设计出来的 接口手写,而两种情况下头文件都是没有任何消费者会写出其名字的中间产物。** -#### 一份载荷以什么名字到达 +#### 载荷到达时的名字 模块名与命名空间是同一条标识符路径,由工程已经写下的名字推导。 @@ -304,7 +304,7 @@ accelerators = ["cuda", "rocm"] 它与产物的 `accel` 刻意是不同的字段 —— 声明由人手写、可以是期望值, 而产物的字段是从产生它的那次构建**测量**出来的。 -## 一个预建产物声明了什么 +## 预建产物的声明内容 携带设备代码的产物把它记在兼容性标签旁边: @@ -323,7 +323,7 @@ accel = "cuda12.8+{sm_80,sm_90f} ptx>=90" `accel` 缺席表示该产物不携带设备代码,因而不施加任何约束 —— 这就是为什么一个纯 CPU 的库可被任何构建使用。 -### 消费者如何被匹配 +### 消费者的匹配 当一次构建请求的每个后端,产物都声明了该后端、工具包主版本一致、 且覆盖了请求的每个架构时,该产物满足这次构建。一个架构被覆盖,当它被直接列出, @@ -484,7 +484,7 @@ sources = ["src/cpu/*.cpp"] 今天由发布者显式写这个字段,索引描述符就是这么做的; 等 `kind = "device"` 把设备编译放进 mcpp 之后,`mcpp pack` 才会发它。 -## 各条 lane,以及每条驱动什么 +## 各条 lane 及其驱动的工具链 一个规则包拥有一个编译器的拼写。引擎不认识其中任何一个名字: `tests/unit/test_core_vendor_probes.cpp` 断言剥掉注释后 `src/` 里不出现任何厂商工具名, @@ -516,7 +516,7 @@ CUDA 入口点的内联包装,所以目标文件链接的是 CUDA 运行时,机 重复符号检查会报出它们共有的那些 unwinder 符号。任何东西都不得穿过接缝:SYCL 异常在 设备编译单元里被捕获并转成返回码,因为抛出它的那个运行时不是调用方会用来展开的那个。 -## 每条 lane 到得了哪些平台 +## 每条 lane 到达的平台 一条 lane 在某个平台上成立,要三件事同时为真:设备编译器为它发布了、产物需要的运行时 在那里够得到、以及这条规则自己那段按宿主分岔的代码在那里编译得过。第三件是最容易被默认 @@ -525,7 +525,7 @@ CUDA 入口点的内联包装,所以目标文件链接的是 CUDA 运行时,机 下载,只问六个模块编不编得过);它把三处潜伏的宿主差异变成了对应 runner 上的编译错误, 而三处没有一处是 Linux 构建看得见的。 -| lane | Linux | macOS | Windows | 由什么决定 | +| lane | Linux | macOS | Windows | 决定因素 | |---|---|---|---|---| | `rules-spirv` | 是 | 是 | 是 | 着色器编译器三个平台都有发布:Linux 上 `xim:glslang`,macOS arm64 与 Windows x86_64 上 `xim:shaderc` | | `rules-cuda` | 是 | 否 | 是 | NVIDIA 为 Linux 与 Windows 发布可再分发组件,而自 CUDA 10.2 之后没有为 macOS 发布过工具包 | @@ -565,7 +565,7 @@ CUDA 工具包。生态能做的是在一次构建请求跨过那条边界的地 私有 loader 后面,于是驱动包装的厂商库必须被搬到产物自己的搜索路径上。macOS(dyld) 与 Windows(PE loader)按构造就没有这一层,面向它们的项目一条适配声明都不写。 -## 在此之上,一个框架是什么形状 +## 建立在此之上的框架形态 五条 lane 证明了规则包能驱动五个编译器,其中最新的一个来自 NVIDIA 与 Khronos 两个 谱系之外的厂商。框架是下一个问题 —— 这套机制能不能扛起一个 diff --git a/docs/zh/50-machine-output.md b/docs/zh/50-machine-output.md index 23f2515c..f0a2793a 100644 --- a/docs/zh/50-machine-output.md +++ b/docs/zh/50-machine-output.md @@ -199,7 +199,7 @@ capability,其 `outputSchema` 全部只有 `{"exitCode": integer}`,而客户端 ## 8. 各 kind -### `mcpp.env` —— mcpp 把东西放在哪 +### `mcpp.env` —— mcpp 使用的路径 ``` mcpp self env --format json @@ -248,7 +248,7 @@ mcpp cache list --format json `data` 是 `{root, entries[]}`,与 `--json` 裸打印的一致。 -### `mcpp.toolchain.list` —— 装了什么,以及这台宿主服务哪些目标 +### `mcpp.toolchain.list` —— 已安装的工具链,以及这台宿主服务的目标 ``` mcpp toolchain list --format json @@ -265,7 +265,7 @@ mcpp toolchain list --format json 没有约定的行为空。`x86_64-linux-gnu` 装了 gcc 而根本没有约定,所以要挑「约定是 gcc 的行」必须读 `pin`。 -### `mcpp.why.toolchain` —— 一对 (目标, 工具链) 会解析成什么 +### `mcpp.why.toolchain` —— 一对 (目标, 工具链) 的解析结果 ``` mcpp why toolchain [--target ] [--toolchain ] --format json diff --git a/docs/zh/51-supported-versions.md b/docs/zh/51-supported-versions.md index fccd09cd..f0a3588f 100644 --- a/docs/zh/51-supported-versions.md +++ b/docs/zh/51-supported-versions.md @@ -28,7 +28,7 @@ schema,那是 [50 —— 机器可读输出](50-machine-output.md)。 一个发布是被取代,而不是被撤回。更旧版本的已发布资产与索引条目原样保留,因为某个 工程可能钉住了它,移除会弄坏一个本来正常的构建。 -## 版本之间什么可以变化 +## 版本之间允许的变化 引擎自身的各个接口稳定性并不相同,这个差别值得精确陈述。 diff --git a/docs/zh/90-build-from-source.md b/docs/zh/90-build-from-source.md index d3f01a66..b9cc2a28 100644 --- a/docs/zh/90-build-from-source.md +++ b/docs/zh/90-build-from-source.md @@ -110,7 +110,7 @@ MCPP=<新构建的 mcpp 二进制> bash tests/e2e/02_new_build_run.sh 它能抓住三种形态,每一种在这里都至少发生过一次: -| 形态 | 长什么样 | +| 形态 | 形式 | |---|---| | 判据从没跑到 | 测试被闸在一个没有任何 job 提供的能力上 | | 判据不可能失败 | 子串搜索,任何措辞都能满足它 | @@ -123,7 +123,7 @@ MCPP=<新构建的 mcpp 二进制> bash tests/e2e/02_new_build_run.sh `.github/tools/` 里有十八个脚本。第一个 PR 之前值得知道的有四个: -| 脚本 | 它拒绝什么 | +| 脚本 | 拒绝的内容 | |---|---| | `check_docs_style.sh` | 疑问句标题、参考章节里的第二人称、标题结构落后于英文的中文页 | | `check_docs_structure.sh` | 章节引用设计记录、解析不到的 `docs/NN-*.md` 路径、翻译里少掉的表格 | diff --git a/docs/zh/91-toolchain-internals.md b/docs/zh/91-toolchain-internals.md index 77534230..e7caef4b 100644 --- a/docs/zh/91-toolchain-internals.md +++ b/docs/zh/91-toolchain-internals.md @@ -70,7 +70,7 @@ xlings 后端解析/自动安装到沙箱 | `payloadPaths` | 由解析出的 runtime binding(§2.1)**精确指名** glibc payload;linux-headers 仍按兄弟 xpkg 发现。没有 binding 就不走 payload-first——这是设计,不是缺陷 | | 运行库目录 | 工具链私有 lib 目录,用于产物的 `-L`/`-rpath` | -### 2.1 runtime binding:绑哪个 libc,只决定一次 +### 2.1 runtime binding:绑定的 libc,只决定一次 payload-first 的构建会链接到某个具体的 glibc,而**是哪一个**是根项目本地开发 OS 的事实, 不该从编译器路径或 shell 状态推断。mcpp 只有两种选择: @@ -170,7 +170,7 @@ mcpp 在**编译与链接**两条线上都发 `--sysroot=`,所以 subos 闭包是一张有序表,每条带来源: -| origin | 例子 | 可变? | 可随产物分发? | +| origin | 例子 | 可变性 | 可随产物分发 | |---|---|---|---| | `payload` | `/xim-x-glibc/2.39/lib64` | 否 —— 装一次不再动 | 否 | | `package` | 依赖描述符的 `[runtime]` 目录 | 否 | 否 | @@ -382,7 +382,7 @@ verdict 以 `.mcpp-runtime-verdicts.json` 存在 `build.ninja` 旁,键包含产 `/xim-x-glibc/2.44/{lib64,lib}`。精确 payload 缺失/陈旧就是错误,其他已安装版本 永远不是回退项。 -### 6.2 一条运行时搜索路径可以住在哪里(`runtime_env_contract.cppm`) +### 6.2 运行时搜索路径的允许位置(`runtime_env_contract.cppm`) 告诉 loader「去哪找」有两条通道,差别不在便利性,而在**波及范围**: @@ -475,7 +475,7 @@ mcpp 把运行时 DLL 部署到产物 exe 旁,这正是该平台对 §3–§4 **工程自己产出的共享库**(`kind = "shared"`)确实按格式而不同,而这个差别不是 flag 的拼法 —— 它是产物记录下的关于它自己的东西: -| 格式 | 生产方发出什么 | 消费方链接什么 | +| 格式 | 生产方的产出 | 消费方链接的对象 | |---|---|---| | ELF | 声明了 soname 时发 `-Wl,-soname,` | `-L` + `-l`、`-Wl,-rpath,$ORIGIN` | | Mach-O | **总是**发 `-Wl,-install_name,@rpath/` | `-L` + `-l`、`-Wl,-rpath,@loader_path` | @@ -496,7 +496,7 @@ flag 的拼法 —— 它是产物记录下的关于它自己的东西: 而 `prepare.cppm` 现在会问它 —— 显式的 `[target.X] toolchain = "…"` 是作者自备 交叉工具链时的出口。 -### 7.5 一个 flag 由哪根轴决定 +### 7.5 决定一个 flag 的轴 2026.8.18 那一轮改了四个 flag,每一个此前都挂在错误的轴上。而这类错误的表现 永远相同:**在恰好一个平台上莫名其妙地失败**,报错既不点名那个 flag, @@ -504,7 +504,7 @@ flag 的拼法 —— 它是产物记录下的关于它自己的东西: 一共三根轴,而在它们之间做选择的问题是:**这个 flag 最终被谁读到。** -| 轴 | 问题 | 例子 | 怎么问 | +| 轴 | 问题 | 例子 | 提问方式 | |---|---|---|---| | **目标格式** | 产出的是哪种映像 | `-fPIC`(PE 代码本就位置无关;clang 直接拒绝这个 flag) | `triple::parse(...)->is_pe()`,宿主兜底 | | **目标 ABI** | 哪个链接器会消费它 | `--out-implib` vs `/IMPLIB:`、`/DEF:`、SONAME / install-name 的形式 | `is_msvc_target(tc)`、`triple->is_msvc_env()` | diff --git a/docs/zh/92-release.md b/docs/zh/92-release.md index a0fdc282..76736b5e 100644 --- a/docs/zh/92-release.md +++ b/docs/zh/92-release.md @@ -166,7 +166,7 @@ $(find "$XLINGS_HOME" -name mcpp -type f -path '*/bin/*' | head -1) --version 记录在案的上限约 40 分钟)。clean-room 里仍然报旧的 `latest` 不是失败,是**还没追上**。 `ci-fresh-install` 的 `wait-index` job 正是把这件事编码成了 15 分钟有界等待。 -## 4. 自举 pin:它是什么,什么时候该 bump +## 4. 自举 pin 的定义与更新条件 `.xlings.json` 的 `[workspace].mcpp` 是**自举的起点** —— 那个由 `xlings install mcpp` 装进 workspace、供 CI 从源码构建 mcpp 的已发布 mcpp。 @@ -197,7 +197,7 @@ curl -fsSL https://github.com/xlings-res/xim-index/releases/download/latest/xim- 推 pin 之前,`index_version` 必须等于 xim-pkgindex `main` 的短 SHA。没有任何东西强制 这一点,而在作业日志里,由此产生的失败与「版本名真的写错了」无法区分。 -## 5. `MCPP_PIN` 改为推导,以及这为什么重要 +## 5. `MCPP_PIN` 改为推导,以及由此产生的结果 `ci-fresh-install.yml` 过去带着 pin 的第二份手工副本。它们从来就不是一回事: `MCPP_PIN` 是**被测版本** —— 永远是最新的已发布版本;而 `.xlings.json` 是 From 0bf8445e8aa383e6577fdf0d9ffd62a0136a1558 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:52:20 +0800 Subject: [PATCH 34/36] docs(examples): a real compiler for the device language, and the extension model `.toy` was "one integer per line, and the entry point returns their sum", compiled by a shell script. That is not a language, and not a shape any ecosystem author meets. THE LANGUAGE. `.toy` now has `let`, assignment, `if`/`else`, `while`, calls between kernels, and the arithmetic and comparison operators. The grammar is stated in the README and at the top of `compile.cppm`. The sample source is Euclid's algorithm and a caller. THE COMPILER. `toyc/` is an ordinary mcpp package: a lexer, a recursive-descent parser, semantic checks and a C++ emitter, in three modules and a driver. It is built FOR THE BUILD MACHINE through `tools = ["toyc"]` + `reexport = true`, and the rule reaches it with `mcpp::dep_bin`. This is the repository's first example of a dependency producing a host tool, a capability that until now existed only in prose. Four criteria measured: the `.toy` compiles and joins the link (`answer() = 42`); the language is executed rather than pattern-matched (`gcd(1071, 462) = 21`, computed by the emitted loop); editing the `.toy` reaches the artifact (42 to 63); and with the feature removed the build stops at the module import with no `toyc` in the tool store. AND ONE BOUNDARY MEASURED. Editing the compiler's SOURCE does not reach the artifact. The tool store's key is the package identity, version, host triple, compiler identity, profile, features and the versions of its transitive dependencies -- it holds no source content. That is exact for a tool from an index, because a published version is immutable, and not exact for a `path` dependency being edited: `mcpp run` reported `Finished dev in 0.00s` and printed the previous answer. Chapter 30 now states it with the two ways out. CHAPTER 31 GAINS "THE EXTENSION MODEL", which answers what mcpp's plugin system is and where it is already used: the five extension points with their effect and where each is declared; seven things the ecosystem has built from them; the three shapes the model expresses (a new language whatever compiles it, preprocessing and code generation, and a file that is partly C++ and partly another language); and the boundary, measured rather than asserted -- a declaration cannot reclassify an extension the engine owns (adding `.cpp` to a rule's `device_extensions` is not diagnosed and has no effect), module-interface extensions are the project's axis, and an extension in neither table is refused by name, quoted from the run. build_examples.sh lists `toyc` in BUILD and states why it and the app are two signals rather than one. 12 of 12. --- .github/tools/build_examples.sh | 10 +- docs/03-examples.md | 2 +- docs/09-commands-by-scenario.md | 2 +- docs/30-build-mcpp.md | 10 + docs/31-authoring-a-rule-package.md | 78 +++ docs/zh/03-examples.md | 2 +- docs/zh/09-commands-by-scenario.md | 2 +- docs/zh/30-build-mcpp.md | 8 + docs/zh/31-authoring-a-rule-package.md | 68 +++ examples/12-a-new-device-language/README.md | 219 ++++++-- .../12-a-new-device-language/app/mcpp.toml | 5 +- .../app/src/kernels/answer.toy | 30 +- .../12-a-new-device-language/app/src/main.cpp | 16 +- .../rules-toy/mcpp.toml | 18 +- .../rules-toy/src/rules-toy.cppm | 47 +- .../rules-toy/tools/toyc.sh | 20 - .../12-a-new-device-language/toyc/mcpp.toml | 19 + .../toyc/src/compile.cppm | 487 ++++++++++++++++++ .../toyc/src/lexer.cppm | 148 ++++++ .../toyc/src/main.cpp | 49 ++ 20 files changed, 1131 insertions(+), 109 deletions(-) delete mode 100755 examples/12-a-new-device-language/rules-toy/tools/toyc.sh create mode 100644 examples/12-a-new-device-language/toyc/mcpp.toml create mode 100644 examples/12-a-new-device-language/toyc/src/compile.cppm create mode 100644 examples/12-a-new-device-language/toyc/src/lexer.cppm create mode 100644 examples/12-a-new-device-language/toyc/src/main.cpp diff --git a/.github/tools/build_examples.sh b/.github/tools/build_examples.sh index a6ffe113..1f4a3b44 100755 --- a/.github/tools/build_examples.sh +++ b/.github/tools/build_examples.sh @@ -48,8 +48,14 @@ BUILD=( # name the optional package -- which a build alone cannot show. examples/11-features/counters examples/11-features/greeter - # A device language the engine does not know. No payload: its compiler is a - # shell script, because the subject is the graph rather than a vendor. + # A device language the engine does not know, and the compiler for it. + # + # `toyc` is built here as an ordinary package as well as by the app as a + # host tool, and the two are not the same signal: this one fails at the + # compiler, the app's fails somewhere in `tools = [...]` / `reexport` / + # `dep_bin`, and a single line telling them apart is worth one build of a + # three-file package. + examples/12-a-new-device-language/toyc examples/12-a-new-device-language/app ) diff --git a/docs/03-examples.md b/docs/03-examples.md index 3f192f4f..98e26828 100644 --- a/docs/03-examples.md +++ b/docs/03-examples.md @@ -81,7 +81,7 @@ the map; the table below is what each sub-example adds. | example | first to introduce | |---|---| | [`08-build-rules`](../examples/08-build-rules/) | two rule packages and a project using both; `host-module = true`, `mcpp::action` with `role = "check"` | -| [`12-a-new-device-language`](../examples/12-a-new-device-language/) | `device_extensions` and `rule_module`: a rule package teaching mcpp a language the engine has never heard of | +| [`12-a-new-device-language`](../examples/12-a-new-device-language/) | `device_extensions` and `rule_module`: a rule package teaching mcpp a language the engine has never heard of, whose compiler is a package built through `tools = [...]` for the build machine | [31 — Authoring a Rule Package](31-authoring-a-rule-package.md) is the reference these two illustrate. diff --git a/docs/09-commands-by-scenario.md b/docs/09-commands-by-scenario.md index 97f63594..4d9f3f55 100644 --- a/docs/09-commands-by-scenario.md +++ b/docs/09-commands-by-scenario.md @@ -31,7 +31,7 @@ them. Confusing them costs a full rebuild. | Store | Scope | Growth trigger | Emptied by | |---|---|---|---| | `target///` | one project | a configuration fingerprint changes and opens a new directory | `mcpp clean`, `mcpp clean --stale` | -| the build cache (`mcpp cache dir`) | the whole machine | any project compiles a dependency or a `std` module | `mcpp cache gc`, `mcpp cache prune`, `mcpp cache clean` | +| the build cache (`mcpp cache dir`) | the whole machine | any project compiles a dependency or a `std` module, or builds a host tool | `mcpp cache gc`, `mcpp cache prune`, `mcpp cache clean` | `mcpp clean` removes `target/` entirely, and the next build recompiles everything. `mcpp clean --stale` removes only the fingerprint directories that diff --git a/docs/30-build-mcpp.md b/docs/30-build-mcpp.md index 02661222..7f901620 100644 --- a/docs/30-build-mcpp.md +++ b/docs/30-build-mcpp.md @@ -931,6 +931,16 @@ Four properties worth knowing: - **Cached globally**, keyed on package version × host toolchain × features × its own dependency closure — built once per machine, not once per project. +**The key holds no source content, and for a `path` dependency that is visible.** +A published version is immutable, so for a tool that arrives from an index the +key is exact. A tool being edited next door has the same version from one build +to the next, and the cached binary stays: measured on +[`examples/12-a-new-device-language`](../examples/12-a-new-device-language/), +a change to the tool's emitter left `mcpp run` reporting `Finished dev in 0.00s` +and printing the previous answer. Bump the tool package's version, or empty the +build cache with `mcpp cache clean` — the tool store lives inside it, at +`/tool//@/`. + ### `[tools.overrides]` — use an existing binary ```toml diff --git a/docs/31-authoring-a-rule-package.md b/docs/31-authoring-a-rule-package.md index da207bd8..6e68733e 100644 --- a/docs/31-authoring-a-rule-package.md +++ b/docs/31-authoring-a-rule-package.md @@ -16,6 +16,84 @@ which belong to `mcpp:plugins`. Examples: [`12-a-new-device-language`](../examples/12-a-new-device-language/) adds a language. +## The extension model + +mcpp's build surface is extended from packages rather than from releases. Five +points do the extending, and the engine holds no name that comes through any of +them. + +| extension point | effect | declared on | +|---|---|---| +| `mcpp::action` | one edge in the build graph: a command with declared inputs and outputs | a build program, or a rule module it imports | +| `device_extensions` | an extension the engine classifies as a **device source** instead of refusing it | a feature of a package | +| `rule_module` | the module a consumer's build program imports to reach the rule | the same feature | +| `tools = [...]` | a generator or compiler **built from source for the build machine**, reached with `mcpp::dep_bin` | a dependency edge | +| `[xlings]`, `[feature-xlings]` | a prebuilt tool the rule runs, installed on demand | the package, or one of its features | + +What the ecosystem has built out of them: + +| surface | package | points used | +|---|---|---| +| CUDA, HIP, SYCL, Ascend C | `mcpp:plugins`, one feature each ([42](42-heterogeneous-builds.md)) | actions driving a vendor compiler, plus payloads gated on the accelerator | +| Slang | `mcpp:plugins`' `rules-slang` | `device_extensions = [".slang"]` — the first language mcpp supports without naming it in the engine | +| GLSL and HLSL to SPIR-V, and the module over the result | `mcpp:plugins`' `rules-spirv` | one action per shader, plus a generated module | +| the island boundary between a device and C++ | `mcpp.tools.island` | a generator, plus `mcpp::generated` | +| an asset as a linkable object | [`08-build-rules`](../examples/08-build-rules/) | `role = "object"` | +| a check that can fail the build | [`08-build-rules`](../examples/08-build-rules/) | `role = "check"` | +| a language the engine has never heard of | [`12-a-new-device-language`](../examples/12-a-new-device-language/) | `device_extensions`, plus a compiler built through `tools = [...]` | + +### The shapes the model expresses + +**A new language, whatever compiles it.** A rule claims the extension, submits +one action per source, and declares the compiler among that action's inputs. +The compiler may be a vendor toolkit, an LLVM front end, an interpreter that +emits a device binary, or a program the rule package builds from source. Whether +it produces a device binary, an object or C++ is the action's `role` and nothing +else. The engine never learns the language: it learns that an extension is a +device source and that an action claims it. + +**Preprocessing and code generation.** An action with `role = "source"` produces +C++ that the declaring package then compiles, and every compile edge of that +package waits for it. The input can be a template, an interface definition, a +table, or another action's output — chaining is ordinary, because actions are +ordered and fingerprinted by their files. + +**A file that is partly C++ and partly another language.** Classification +happens before any rule runs, so a file with an extension the engine owns is +compiled as C++ and never reaches a rule. A source carrying a foreign block +therefore uses an extension the rule claims, and the rule splits it: the C++ it +extracts goes through `role = "source"`, the foreign half through its own +compiler, and the seam between the two is the `extern "C"` boundary of +[42 — Heterogeneous Builds](42-heterogeneous-builds.md). No package in this +repository ships that shape today. + +### The boundary + +**A declaration cannot reclassify what the engine already owns.** A dependency's +`device_extensions` is consulted *after* the built-in roles, so a rule package +cannot claim `.cpp`, `.cppm`, `.c` or `.S`. Those are the engine's own +vocabulary, and a package must not be able to move a file out of it. Claiming +one is not diagnosed and has no effect: measured by adding `".cpp"` to a rule's +`device_extensions`, after which the consumer's `main.cpp` was still compiled as +C++ and the build succeeded. + +**Module-interface extensions are the project's axis, not a rule's.** A project +that spells its interfaces `.ixx` declares `[build] module_extensions` +([04 — The mcpp.toml Project File](04-mcpp-toml.md)). No rule-package key adds +one, because a module interface is scanned for imports, produces a BMI and joins +the link — three engine behaviours rather than a command to run. + +**An extension in neither table is refused by name**, which is why a mistyped +`device_extensions` surfaces at once instead of dropping a source: + +``` +error: scanner errors: + .../orphan.zzz: 'orphan.zzz' is listed in [build] sources, and mcpp has no role + for the extension '.zzz'. + Its object would be compiled and then linked by nothing, so this is refused rather + than built. +``` + ## The definition of a rule package Three parts, and none of them is special to mcpp: diff --git a/docs/zh/03-examples.md b/docs/zh/03-examples.md index 062f092b..8a76cfe5 100644 --- a/docs/zh/03-examples.md +++ b/docs/zh/03-examples.md @@ -76,7 +76,7 @@ mcpp build && mcpp run | 示例 | 首次引入的内容 | |---|---| | [`08-build-rules`](../../examples/08-build-rules/) | 两个规则包与同时使用它们的工程;`host-module = true`、`role = "check"` 的 `mcpp::action` | -| [`12-a-new-device-language`](../../examples/12-a-new-device-language/) | `device_extensions` 与 `rule_module`:规则包教会 mcpp 一门引擎从未听说过的语言 | +| [`12-a-new-device-language`](../../examples/12-a-new-device-language/) | `device_extensions` 与 `rule_module`:规则包教会 mcpp 一门引擎从未听说过的语言,而它的编译器是一个经 `tools = [...]` 为构建机构建出来的包 | [31 —— 编写规则包](31-authoring-a-rule-package.md) 是这两个示例所演示内容的参考。 diff --git a/docs/zh/09-commands-by-scenario.md b/docs/zh/09-commands-by-scenario.md index 07f790cc..0de5ae28 100644 --- a/docs/zh/09-commands-by-scenario.md +++ b/docs/zh/09-commands-by-scenario.md @@ -26,7 +26,7 @@ | 存储 | 作用域 | 增长时机 | 清空方式 | |---|---|---|---| | `target/<三元组>/<指纹>/` | 单个工程 | 配置指纹变化,开出新目录 | `mcpp clean`、`mcpp clean --stale` | -| 构建缓存(`mcpp cache dir`) | 整台机器 | 任何工程编译依赖或 `std` 模块 | `mcpp cache gc`、`mcpp cache prune`、`mcpp cache clean` | +| 构建缓存(`mcpp cache dir`) | 整台机器 | 任何工程编译依赖、`std` 模块,或构建一个 host 工具 | `mcpp cache gc`、`mcpp cache prune`、`mcpp cache clean` | `mcpp clean` 整个删掉 `target/`,下次构建重编一切。`mcpp clean --stale` 只删已无构建 记录使用的指纹目录,在用的配置保留: diff --git a/docs/zh/30-build-mcpp.md b/docs/zh/30-build-mcpp.md index da2db95d..33d632ee 100644 --- a/docs/zh/30-build-mcpp.md +++ b/docs/zh/30-build-mcpp.md @@ -785,6 +785,14 @@ grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } - **全局缓存**,按 包版本 × host 工具链 × feature × 自身依赖闭包 键控 —— 每台机器 构建一次,而不是每个工程一次。 +**这个键里没有源码内容,而对 `path` 依赖这一点是看得见的。** 已发布的版本不可变, +所以对来自索引的工具,这个键是精确的。而正在旁边被编辑的工具,两次构建之间版本相同, +缓存里的二进制就留在原地:在 +[`examples/12-a-new-device-language`](../../examples/12-a-new-device-language/) +上实测,改动工具的 emitter 之后,`mcpp run` 报告 `Finished dev in 0.00s` 并打印上一次 +的答案。抬工具包的版本,或用 `mcpp cache clean` 清空构建缓存 —— tool store 就住在 +里面,路径是 `/tool//@/`。 + ### `[tools.overrides]` —— 使用已有的二进制 ```toml diff --git a/docs/zh/31-authoring-a-rule-package.md b/docs/zh/31-authoring-a-rule-package.md index c789bdcf..a291575e 100644 --- a/docs/zh/31-authoring-a-rule-package.md +++ b/docs/zh/31-authoring-a-rule-package.md @@ -12,6 +12,74 @@ 嵌入,[`12-a-new-device-language`](../../examples/12-a-new-device-language/) 新增 一门语言。 +## 扩展模型 + +mcpp 的构建表面由包扩展,而不是由发布扩展。做扩展的有五个点,而经由它们进来的任何 +名字,引擎都不持有。 + +| 扩展点 | 效果 | 声明位置 | +|---|---|---| +| `mcpp::action` | 构建图里的一条边:一条命令,带声明的输入与输出 | 构建程序,或它 import 的规则模块 | +| `device_extensions` | 一个扩展名被引擎归类为**设备源**,而不是被拒绝 | 包的某个 feature | +| `rule_module` | 消费者的构建程序 import 哪个模块来够到这条规则 | 同一个 feature | +| `tools = [...]` | 一个**从源码为构建机构建**的生成器或编译器,用 `mcpp::dep_bin` 取到 | 一条依赖边 | +| `[xlings]`、`[feature-xlings]` | 规则要运行的预建工具,按需安装 | 包本身,或它的某个 feature | + +生态用它们建出来的东西: + +| 扩展面 | 所在包 | 使用的扩展点 | +|---|---|---| +| CUDA、HIP、SYCL、Ascend C | `mcpp:plugins`,各一个 feature([42](42-heterogeneous-builds.md)) | 驱动厂商编译器的 action,加上按加速器设闸的载荷 | +| Slang | `mcpp:plugins` 的 `rules-slang` | `device_extensions = [".slang"]` —— 第一门无需引擎点名即被支持的语言 | +| GLSL 与 HLSL 到 SPIR-V,以及其上的模块 | `mcpp:plugins` 的 `rules-spirv` | 每个着色器一条 action,加上一个生成的模块 | +| 设备与 C++ 之间的岛边界 | `mcpp.tools.island` | 一个生成器,加上 `mcpp::generated` | +| 作为可链接对象的资源文件 | [`08-build-rules`](../../examples/08-build-rules/) | `role = "object"` | +| 能让构建失败的检查 | [`08-build-rules`](../../examples/08-build-rules/) | `role = "check"` | +| 引擎从未听说过的语言 | [`12-a-new-device-language`](../../examples/12-a-new-device-language/) | `device_extensions`,加上经 `tools = [...]` 构建出来的编译器 | + +### 这个模型能表达的形态 + +**一门新语言,不论由什么编译它。** 规则声明它认领的扩展名、为每个源提交一条 +action,并把编译器列进这条 action 的输入。这个编译器可以是厂商工具包、一个 LLVM +前端、一个发出设备二进制的解释器,也可以是规则包自己从源码构建出来的程序。它产出 +的是设备二进制、目标文件还是 C++,由 action 的 `role` 决定,别无其他。引擎始终不 +学习这门语言:它学到的是「某个扩展名是设备源」以及「某条 action 认领它」。 + +**预处理与代码生成。** `role = "source"` 的 action 产出 C++,由声明它的包随后编译, +而该包的每一条编译边都等它。输入可以是模板、接口定义、一张表,或另一条 action 的 +输出 —— 串联是常规做法,因为 action 之间由文件定序并计入指纹。 + +**一个一半是 C++、一半是另一种语言的文件。** 归类发生在任何规则运行之前,所以带着 +引擎自有扩展名的文件按 C++ 编译,永远到不了规则那里。因此一个携带外来代码块的源要 +用规则认领的扩展名,再由规则把它拆开:抽出来的 C++ 走 `role = "source"`,外来的那 +一半走规则自己的编译器,两者之间的缝就是 +[42 —— 异构构建](42-heterogeneous-builds.md) 里的 `extern "C"` 边界。本仓库今天没有 +任何包是这个形态。 + +### 边界 + +**声明不能重新归类引擎已经拥有的东西。** 依赖的 `device_extensions` 在内建角色 +**之后**才被查询,所以规则包认领不了 `.cpp`、`.cppm`、`.c` 或 `.S`。这些是引擎自己 +的词汇,一个包不得把文件从中挪走。认领它们不会被诊断,也不产生任何效果:实测把 +`".cpp"` 加进某条规则的 `device_extensions`,消费者的 `main.cpp` 仍按 C++ 编译, +构建成功。 + +**模块接口的扩展名是工程的轴,不是规则的轴。** 把接口写成 `.ixx` 的工程声明 +`[build] module_extensions`([04 —— mcpp.toml 工程文件指南](04-mcpp-toml.md))。没有 +任何规则包的键能新增一个,因为模块接口要被扫描 import、要产出 BMI、还要进链接 —— +这是三项引擎行为,而不是一条要跑的命令。 + +**两张表都不包含的扩展名会被点名拒绝**,所以写错的 `device_extensions` 会立刻显形, +而不是把一个源默默丢掉: + +``` +error: scanner errors: + .../orphan.zzz: 'orphan.zzz' is listed in [build] sources, and mcpp has no role + for the extension '.zzz'. + Its object would be compiled and then linked by nothing, so this is refused rather + than built. +``` + ## 规则包的定义 三部分,没有一部分是 mcpp 特有的: diff --git a/examples/12-a-new-device-language/README.md b/examples/12-a-new-device-language/README.md index ef26401d..634ef7a5 100644 --- a/examples/12-a-new-device-language/README.md +++ b/examples/12-a-new-device-language/README.md @@ -1,15 +1,114 @@ # 12 — A device language the engine has never heard of -A rule package teaches mcpp to compile `.toy`, and a project uses it. No mcpp -release is involved: `.toy` is not in the engine's built-in device-extension -table and will never be. +A rule package teaches mcpp to compile `.toy`, a small language with its own +compiler, and a project uses it. No mcpp release is involved: `.toy` is not in +the engine's built-in device-extension table and will never be. ``` cd examples/12-a-new-device-language/app -mcpp run # toy_answer() = 42 +mcpp run ``` -## The two keys +``` + Rules example.rules.toy (example:rules-toy) + Building host tool toyc:toyc from toyc v0.1.0 (once per package version × host toolchain) + Compiling toyapp v0.1.0 (.) + Finished dev [unoptimized + debuginfo] in 0.65s + +gcd(1071, 462) = 21 +scale(21, 2) = 42 +answer() = 42 +``` + +## The three packages + +| directory | what it is | who builds it | +|---|---|---| +| `toyc/` | the compiler for `.toy`: a lexer, a recursive-descent parser, semantic checks and a C++ emitter | mcpp, **for the build machine**, as a host tool | +| `rules-toy/` | the rule: it declares the extension, the module a consumer imports, and one action per `.toy` | the consumer's build program | +| `app/` | the project | mcpp, for the target | + +## The language + +`.toy` has integers, `let`, assignment, `if` / `else`, `while`, calls between +kernels, and the arithmetic and comparison operators. Every kernel takes and +returns an integer. + +``` +program := { kernel } +kernel := 'kernel' ident '(' [ ident { ',' ident } ] ')' block +block := '{' { stmt } '}' +stmt := 'let' ident '=' expr ';' + | ident '=' expr ';' + | 'return' expr ';' + | 'if' '(' expr ')' block [ 'else' block ] + | 'while' '(' expr ')' block +expr := cmp +cmp := sum { ('<' | '>' | '<=' | '>=' | '==' | '!=') sum } +sum := term { ('+' | '-') term } +term := unary { ('*' | '/' | '%') unary } +unary := [ '-' ] primary +primary := number | ident | ident '(' [ expr { ',' expr } ] ')' | '(' expr ')' +``` + +`src/kernels/answer.toy` is Euclid's algorithm and a caller: + +``` +kernel gcd(a, b) { + while (b != 0) { + let t = b; + b = a % b; + a = t; + } + return a; +} + +kernel answer() { + return scale(gcd(1071, 462), 2); +} +``` + +`toyc` emits one `extern "C"` function per kernel, and forward-declares them +all first so kernels may call each other in any order: + +```cpp +extern "C" int toy_gcd(int v_a, int v_b) { + while ((v_b != 0)) { + int v_t = v_b; + v_b = (v_a % v_b); + v_a = v_t; + } + return v_a; +} +``` + +Three things in that output are decisions rather than accidents. **`extern "C"`**, +because the two sides are produced by different compilers and share no C++ ABI +— the same reason a device island's boundary is. **`v_` on every local**, +because a kernel that names a variable `class` must not become a C++ file that +fails to compile for a reason the toy source cannot express. **Parentheses +around every binary expression**, because the AST already holds the grouping +and the emitter does not reproduce C++'s precedence table. + +The compiler rejects what it cannot compile, and says where. Each of these was +produced by running `toyc` on a file with that one defect: + +```console +$ toyc bad.toy -o bad.cpp +bad.toy:1:1: error: kernel `scale` can reach its end without a `return` +bad2.toy:2:12: error: `b` is not a kernel in this file +bad3.toy:2:12: error: `x` is not declared +bad4.toy:3:5: error: expected `;`, found `return` +``` + +The first is the one worth the code it takes. A kernel returns an integer on +every path, and a block satisfies that if it ends in a `return` or in an +`if`/`else` whose branches both do — `while` never counts, because the language +cannot state that a loop runs at all. Emitting `return 0;` at the end instead +would have compiled everything and given a wrong answer for the kernel whose +author forgot a branch. + +## The two manifest keys `rules-toy/mcpp.toml` declares them on the feature that selects the rule: @@ -25,7 +124,8 @@ device_extensions = [".toy"] | `device_extensions` | a consumer that activates the feature gets `.toy` classified as a **device source**: never scanned for imports, never producing a BMI, and refused if no action claims it | | `rule_module` | the module the consumer's build program imports. It implies `host-module = true`, so the consumer writes the feature and nothing else | -The consumer's whole declaration: +Device extensions are not in the default source glob. A `.toy` is compiled +because the manifest names it: ```toml [dependencies] @@ -35,43 +135,77 @@ rules-toy = { path = "../rules-toy", features = ["rules-toy"] } sources = ["src/*.cpp", "src/kernels/*.toy"] ``` -Device extensions are not in the default source glob. A `.toy` is compiled -because the manifest names it. +## The compiler is a package, built for the build machine + +The rule contains no compiler. `toyc` is an ordinary mcpp package with a +`kind = "bin"` target, and one line brings it into the graph: + +```toml +[feature-deps.rules-toy] +toyc = { path = "../toyc", tools = ["toyc"], reexport = true } +``` + +| part | what it does | +|---|---| +| `tools = ["toyc"]` | mcpp builds that target **for the build machine**, even when the project around it is cross-compiling | +| `reexport = true` | the tool reaches whoever activated the feature. Without it the tool stays with this package, which is the supply-chain default: an arbitrary transitive dependency must not put entries in a build program's tool namespace | +| on `[feature-deps]` | a project that depends on this package **without** activating the rule builds no compiler | + +The rule reads the path back with `mcpp::dep_bin("toyc", "toyc")`, under the +name of the manifest entry that **declared** the tool rather than the +consumer's spelling of anything. -## The compiler +That gating was measured. With `features = ["rules-toy"]` removed from the +consumer, the build stops before any tool is built: -`rules-toy/tools/toyc.sh` is the entire toolchain for `.toy`: a file is a list -of integers and the entry point returns their sum. A shell script rather than a -vendor toolkit, because the subject here is the build graph — how a language the -engine does not know reaches the link — and a real device compiler would only -add a download to it. +``` +error: build.mcpp imports 'example.rules.toy', and no dependency provides it as a host module. + declared without `host-module = true`: rules-toy (in [dependencies]) +``` -The generated C++ declares the entry point `extern "C"`, for the same reason a -device island's boundary is: the two sides are produced by different compilers -and do not share a C++ ABI. +and the tool store holds no `toyc` entry afterwards. ## What this example is the first to demonstrate Every other rule in this repository is built into `mcpp:plugins` and compiles an -extension the engine already knows. This one adds a language from outside, which -is the property the accelerator design is built on and which nothing in the tree -exercised. +extension the engine already knows. This one adds a language from outside — the +property the accelerator design is built on — and it is the tree's only example +of a **dependency that produces a host tool**, which is how a rule package ships +a real compiler rather than a script. ## Criteria -Three, each measured while this example was written, and each corresponding to -a defect the first version had: - | criterion | measured | |---|---| -| a `.toy` is compiled and its output joins the link, on an engine that does not know the extension | `toy_answer() = 42` | -| editing the `.toy` reaches the artifact | adding `1` to the file: `42` → `43` | -| **editing the compiler reaches the artifact** | changing `toyc.sh` to add 100: `42` → `142` | +| a `.toy` is compiled and its output joins the link, on an engine that does not know the extension | `answer() = 42` | +| the language is executed rather than pattern-matched | `gcd(1071, 462) = 21`, computed by the emitted loop | +| editing the `.toy` reaches the artifact | `scale(…, 2)` → `scale(…, 3)`: `42` → `63` | +| the compiler is built for the build machine, on demand, and only when the rule is active | the `Building host tool` line above; no store entry without the feature | + +## The boundary this example measured: a host tool is cached by version + +Editing `toyc`'s **source** does not reach the artifact. Measured: a change to +the emitter left `mcpp run` reporting `Finished dev in 0.00s` and printing the +previous answer. + +The tool store's key is the tool package's identity, version, host triple, +compiler identity, profile, features and the versions of its transitive +dependencies — not the content of its sources. For a package that arrives from +an index that key is exact, because a published version is immutable. For a +`path` dependency being edited it is not: + +| situation | effect | +|---|---| +| the tool's version changes | the tool is rebuilt, and the action re-runs because its declared input changed | +| the tool's sources change, its version does not | the cached binary stays, and the build is green over the previous compiler's output | + +Two ways out, and they are the same one at different sizes: bump the tool +package's version, or empty the build cache with `mcpp cache clean` — the tool +store lives inside it, at `/tool//@/`. -The third is the one that fails silently. An action whose only declared input is -its source leaves every edge clean when the compiler changes, so the artifact -keeps the bytes the previous compiler produced. `rules-toy` declares the script -as an input alongside the source. +The action itself is not the gap. `rules-toy` declares the compiler as an input +beside the source, so an action whose compiler binary changes does re-run. What +does not happen is the rebuild that would change those bytes. ## Three things the first version got wrong @@ -80,16 +214,15 @@ Recorded because each is a mistake a rule author will make once. **An action's command does not run from the package root.** `mcpp::device_sources()` answers with package-root-relative paths, and the command runs from the build directory. The relative path reached `toyc` -unchanged and the read failed there. The rule passes an absolute path. - -**A pipeline's exit status is its last command's.** The first `toyc` summed with -`sed … | grep … | paste -sd+ - | bc`. When an earlier stage produced nothing, -`bc` still exited 0, so `set -e` never fired and the script wrote a program that -compiled, linked, ran, and printed `0`. It sums with one `awk` now. - -**A rule finds its own files through `mcpp::dep_dir`, under the name the -consumer declared.** `dep_dir("rules-toy")` answers; `dep_dir("example.rules-toy")` -and `dep_dir("example:rules-toy")` return empty, because the key is the spelling -in the consumer's `[dependencies]`. The rule exposes `options::rule_dir` so a -consumer that declares the edge under another key can say so, and refuses with a -message naming what it looked for rather than running `sh /tools/toyc.sh`. +unchanged and the read failed there. The rule joins `mcpp::manifest_dir()` to +each path. + +**The compiler was a shell script, and its exit status lied.** The first `toyc` +summed with `sed … | grep … | paste -sd+ - | bc`. A pipeline's status is its +last command's: when an earlier stage produced nothing, `bc` still exited 0, so +`set -e` never fired and the script wrote a program that compiled, linked, ran +and printed `0`. Replacing the script with a compiled program removed the +class, not just the instance. + +**The strings must outlive the action.** `a.id = ("toy:" + stem).c_str()` hands +`submit()` a pointer into a temporary that is already gone. diff --git a/examples/12-a-new-device-language/app/mcpp.toml b/examples/12-a-new-device-language/app/mcpp.toml index 322f351b..009366e6 100644 --- a/examples/12-a-new-device-language/app/mcpp.toml +++ b/examples/12-a-new-device-language/app/mcpp.toml @@ -5,8 +5,9 @@ version = "0.1.0" [language] standard = "c++23" -# ONE EDGE. `rule_module` on the feature implies `host-module = true`, so the -# consumer names the feature and nothing else. +# ONE EDGE. `rule_module` on the feature implies `host-module = true`, and the +# feature's own `[feature-deps]` brings the compiler, so the consumer names the +# feature and nothing else. [dependencies] rules-toy = { path = "../rules-toy", features = ["rules-toy"] } diff --git a/examples/12-a-new-device-language/app/src/kernels/answer.toy b/examples/12-a-new-device-language/app/src/kernels/answer.toy index 76a6b520..8e5cefd6 100644 --- a/examples/12-a-new-device-language/app/src/kernels/answer.toy +++ b/examples/12-a-new-device-language/app/src/kernels/answer.toy @@ -1,3 +1,27 @@ -# A .toy file is a list of integers. The entry point returns their sum. -40 -2 +# answer.toy -- a kernel source in a language the engine has never heard of. +# +# `.toy` has integers, `let`, assignment, `if` / `else`, `while`, calls +# between kernels, and the arithmetic and comparison operators. Every kernel +# takes and returns an integer, and the file's kernels are what the C++ side +# can call. + +kernel gcd(a, b) { + while (b != 0) { + let t = b; + b = a % b; + a = t; + } + return a; +} + +kernel scale(v, k) { + if (k < 0) { + return -v * -k; + } else { + return v * k; + } +} + +kernel answer() { + return scale(gcd(1071, 462), 2); +} diff --git a/examples/12-a-new-device-language/app/src/main.cpp b/examples/12-a-new-device-language/app/src/main.cpp index a49debf5..628d7494 100644 --- a/examples/12-a-new-device-language/app/src/main.cpp +++ b/examples/12-a-new-device-language/app/src/main.cpp @@ -1,12 +1,16 @@ import std; -// Defined by C++ that `toyc` wrote from `src/kernels/answer.toy`. The -// declaration is `extern "C"` for the same reason a device island's is: the two -// sides are produced by different compilers and do not share a C++ ABI. +// Declared by hand here, and defined by C++ that `toyc` wrote from +// `src/kernels/answer.toy`. The declarations are `extern "C"` for the same +// reason a device island's boundary is: the two sides are produced by +// different compilers and do not share a C++ ABI. +extern "C" int toy_gcd(int a, int b); +extern "C" int toy_scale(int v, int k); extern "C" int toy_answer(); int main() { - const int v = toy_answer(); - std::println("toy_answer() = {}", v); - return v == 42 ? 0 : 1; + std::println("gcd(1071, 462) = {}", toy_gcd(1071, 462)); + std::println("scale(21, 2) = {}", toy_scale(21, 2)); + std::println("answer() = {}", toy_answer()); + return toy_answer() == 42 ? 0 : 1; } diff --git a/examples/12-a-new-device-language/rules-toy/mcpp.toml b/examples/12-a-new-device-language/rules-toy/mcpp.toml index 1b5fd874..d2a116bf 100644 --- a/examples/12-a-new-device-language/rules-toy/mcpp.toml +++ b/examples/12-a-new-device-language/rules-toy/mcpp.toml @@ -8,8 +8,9 @@ license = "Apache-2.0" # THE TWO KEYS THAT MAKE A NEW DEVICE LANGUAGE COST NO ENGINE RELEASE. # # `device_extensions` says which sources this rule compiles. A consumer that -# activates the feature gets `.toy` classified as a DEVICE source: never scanned -# for imports, never producing a BMI, and refused if no action claims it. +# activates the feature gets `.toy` classified as a DEVICE source: never +# scanned for imports, never producing a BMI, and refused if no action claims +# it. # # `rule_module` says which module the consumer's build program imports to reach # the rule. It also implies `host-module = true`, so a consumer writes the @@ -24,3 +25,16 @@ default = [] sources = ["src/rules-toy.cppm"] rule_module = "example.rules.toy" device_extensions = [".toy"] + +# THE COMPILER IS A PACKAGE, AND IT IS BUILT FOR THE BUILD MACHINE. +# +# `tools = ["toyc"]` asks the graph for that target as a host executable even +# when the project around it is cross-compiling; `reexport = true` hands it to +# whoever activated this feature. Without `reexport` the tool stays here, which +# is the supply-chain default: an arbitrary transitive dependency must not be +# able to put entries in a build program's tool namespace. +# +# The request hangs on the FEATURE, so a project that depends on this package +# without activating the rule builds no compiler. +[feature-deps.rules-toy] +toyc = { path = "../toyc", tools = ["toyc"], reexport = true } diff --git a/examples/12-a-new-device-language/rules-toy/src/rules-toy.cppm b/examples/12-a-new-device-language/rules-toy/src/rules-toy.cppm index 242cbf58..11fc85f6 100644 --- a/examples/12-a-new-device-language/rules-toy/src/rules-toy.cppm +++ b/examples/12-a-new-device-language/rules-toy/src/rules-toy.cppm @@ -1,10 +1,9 @@ -// A rule for `.toy`, a language whose entire definition is "one integer per -// line, and the file's name is the entry point". +// The rule for `.toy`. // -// The compiler is a shell script rather than a vendor toolkit, for the same -// reason `tests/e2e/607` uses `cat` as a device linker: the subject is the -// GRAPH -- how a language that the engine does not know reaches the link -- -// and a real device compiler would only add a download to it. +// It contains no compiler. `toyc` is an ordinary mcpp package next door, and +// this module's whole job is to turn "the consumer listed some `.toy` files" +// into edges in the build graph -- ordered, fingerprinted and incremental like +// every other edge. export module example.rules.toy; import std; @@ -15,16 +14,14 @@ export namespace example::rules::toy { struct options { std::string out_dir = std::string(mcpp::out_dir()); - // Where this rule's own files are. `mcpp::dep_dir` answers under the name - // the CONSUMER declared in `[dependencies]`, which is why the default is a - // string this package cannot verify -- a consumer that declares the edge - // under another key passes its own spelling here. - std::string rule_dir = std::string(mcpp::dep_dir("rules-toy")); + // The compiler, as the consumer's build program sees it. `dep_bin` answers + // under the name in the manifest that DECLARED the tool -- this package's + // `[feature-deps]` entry -- and the answer travels to the consumer because + // that entry says `reexport = true`. + std::string compiler = std::string(mcpp::dep_bin("toyc", "toyc")); }; -// Compile every `.toy` the consumer listed. `mcpp::device_sources()` returns -// the device sources of the package being built, so the rule reads the source -// set from the graph rather than asking the project to repeat it. +// Compile every `.toy` the consumer listed. inline bool compile(options opt = {}) { const std::string root = mcpp::manifest_dir(); if (root.empty()) { @@ -33,15 +30,13 @@ inline bool compile(options opt = {}) { return false; } - if (opt.rule_dir.empty()) { + if (opt.compiler.empty()) { std::println(std::cerr, - "example.rules.toy: cannot locate this rule package. `mcpp::dep_dir` " - "answers under the name the consumer declared; this rule looked for " - "`rules-toy`. Pass `options::rule_dir` if the edge is declared " - "under another key."); + "example.rules.toy: the `toyc` compiler was not built. Activate this " + "package's `rules-toy` feature, which is what asks the graph for it: " + "rules-toy = {{ path = \"...\", features = [\"rules-toy\"] }}"); return false; } - const std::string script = opt.rule_dir + "/tools/toyc.sh"; // `mcpp::device_sources()` is one string, one path per line, and it is the // package's WHOLE device set rather than this rule's share of it. A rule @@ -65,7 +60,7 @@ inline bool compile(options opt = {}) { // The strings outlive the action. `a.id = ("toy:" + stem).c_str()` // would hand it a pointer into a temporary that is gone by `submit`. const std::string id = "toy:" + stem; - const std::string desc = "compile " + src; + const std::string desc = "toyc " + src; // ABSOLUTE. `mcpp::device_sources()` answers package-root-relative, and // an action's command does not run from the package root -- it runs // from the build directory. Measured: the relative path reached `toyc` @@ -78,15 +73,13 @@ inline bool compile(options opt = {}) { // whose compiler emitted an object directly would use `object`. a.role = "source"; a.description = desc.c_str(); - a.arg("sh"); a.arg(script.c_str()); a.arg(abs.c_str()); a.arg(gen.c_str()); - a.arg(stem.c_str()); + a.arg(opt.compiler.c_str()).arg(abs.c_str()).arg("-o").arg(gen.c_str()); a.input(abs.c_str()); - // THE COMPILER IS AN INPUT TOO. Without this line, editing `toyc.sh` + // THE COMPILER IS AN INPUT TOO. Without this line, a change to `toyc` // leaves every edge clean and the artifact keeps the bytes the previous // compiler produced -- a green build over a stale result, which is the - // failure that is hardest to notice. Measured while writing this - // example: a fix to the summator did not reach the program. - a.input(script.c_str()); + // failure that is hardest to notice. + a.input(opt.compiler.c_str()); a.output(gen.c_str()); a.submit(); any = true; diff --git a/examples/12-a-new-device-language/rules-toy/tools/toyc.sh b/examples/12-a-new-device-language/rules-toy/tools/toyc.sh deleted file mode 100755 index add47b04..00000000 --- a/examples/12-a-new-device-language/rules-toy/tools/toyc.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -# toyc -- the whole compiler for `.toy`. -# -# toyc.sh -# -# A `.toy` file is a list of integers, one per line, with `#` comments. The -# entry point returns their sum. This is not a language; it is the smallest -# thing that is compiled by something mcpp does not drive. -# -# awk rather than `paste | bc`: bc is not installed on every runner this -# repository's CI uses, and a missing summator produced a program that built, -# ran, and printed the wrong number. -set -eu -in=$1; out=$2; name=$3 -sum=$(awk '{ sub(/#.*/, ""); gsub(/[ \t]/, "") } /^-?[0-9]+$/ { s += $0 } END { print s + 0 }' "$in") -mkdir -p "$(dirname "$out")" -cat > "$out" <' | '<=' | '>=' | '==' | '!=') sum } +// sum := term { ('+' | '-') term } +// term := unary { ('*' | '/' | '%') unary } +// unary := [ '-' ] primary +// primary := number | ident | ident '(' [ expr { ',' expr } ] ')' | '(' expr ')' +export module example.toyc.compile; + +import std; +import example.toyc.lexer; + +namespace toyc { + +// ── the AST ────────────────────────────────────────────────────────────── +struct expr; +using expr_ptr = std::unique_ptr; + +struct e_number { long long value; }; +struct e_name { std::string id; int line, col; }; +struct e_unary { std::string op; expr_ptr operand; }; +struct e_binary { std::string op; expr_ptr lhs, rhs; }; +struct e_call { std::string callee; std::vector args; int line, col; }; + +struct expr { std::variant node; }; + +struct stmt; +using stmt_ptr = std::unique_ptr; +struct block { std::vector body; }; + +struct s_let { std::string name; expr_ptr init; int line, col; }; +struct s_assign { std::string name; expr_ptr value; int line, col; }; +struct s_return { expr_ptr value; }; +struct s_if { expr_ptr cond; block then_branch; std::optional else_branch; }; +struct s_while { expr_ptr cond; block body; }; + +struct stmt { std::variant node; }; + +struct kernel { + std::string name; + std::vector params; + block body; + int line, col; +}; + +// ── the parser ─────────────────────────────────────────────────────────── +class parser { +public: + explicit parser(std::vector toks) : toks_(std::move(toks)) {} + + std::expected, diag> parse_program() { + std::vector out; + while (peek().kind != tok::end) { + auto k = parse_kernel(); + if (!k) return std::unexpected(k.error()); + out.push_back(std::move(*k)); + } + if (out.empty()) return std::unexpected(err("a source file declares at least one kernel")); + return out; + } + +private: + std::vector toks_; + std::size_t i_ = 0; + + const token& peek(std::size_t ahead = 0) const { + return toks_[std::min(i_ + ahead, toks_.size() - 1)]; + } + const token& take() { return toks_[i_ < toks_.size() - 1 ? i_++ : i_]; } + bool accept(tok k) { if (peek().kind == k) { ++i_; return true; } return false; } + + diag err(std::string m) const { return diag{std::move(m), peek().line, peek().col}; } + std::unexpected expected(tok k) const { + return std::unexpected(err(std::format("expected {}, found {}", + spelling(k), spelling(peek().kind)))); + } + + std::expected parse_kernel() { + const token head = peek(); + if (!accept(tok::kw_kernel)) return expected(tok::kw_kernel); + if (peek().kind != tok::ident) return expected(tok::ident); + kernel k{take().text, {}, {}, head.line, head.col}; + if (!accept(tok::lparen)) return expected(tok::lparen); + if (!accept(tok::rparen)) { + for (;;) { + if (peek().kind != tok::ident) return expected(tok::ident); + k.params.push_back(take().text); + if (accept(tok::comma)) continue; + if (accept(tok::rparen)) break; + return expected(tok::rparen); + } + } + auto b = parse_block(); + if (!b) return std::unexpected(b.error()); + k.body = std::move(*b); + return k; + } + + std::expected parse_block() { + if (!accept(tok::lbrace)) return expected(tok::lbrace); + block b; + while (!accept(tok::rbrace)) { + if (peek().kind == tok::end) return expected(tok::rbrace); + auto s = parse_stmt(); + if (!s) return std::unexpected(s.error()); + b.body.push_back(std::move(*s)); + } + return b; + } + + std::expected parse_stmt() { + const token head = peek(); + + if (accept(tok::kw_let)) { + if (peek().kind != tok::ident) return expected(tok::ident); + std::string name = take().text; + if (!accept(tok::assign)) return expected(tok::assign); + auto e = parse_expr(); + if (!e) return std::unexpected(e.error()); + if (!accept(tok::semi)) return expected(tok::semi); + return std::make_unique(stmt{s_let{std::move(name), std::move(*e), + head.line, head.col}}); + } + if (accept(tok::kw_return)) { + auto e = parse_expr(); + if (!e) return std::unexpected(e.error()); + if (!accept(tok::semi)) return expected(tok::semi); + return std::make_unique(stmt{s_return{std::move(*e)}}); + } + if (accept(tok::kw_if)) { + if (!accept(tok::lparen)) return expected(tok::lparen); + auto c = parse_expr(); + if (!c) return std::unexpected(c.error()); + if (!accept(tok::rparen)) return expected(tok::rparen); + auto t = parse_block(); + if (!t) return std::unexpected(t.error()); + std::optional e; + if (accept(tok::kw_else)) { + auto eb = parse_block(); + if (!eb) return std::unexpected(eb.error()); + e = std::move(*eb); + } + return std::make_unique(stmt{s_if{std::move(*c), std::move(*t), std::move(e)}}); + } + if (accept(tok::kw_while)) { + if (!accept(tok::lparen)) return expected(tok::lparen); + auto c = parse_expr(); + if (!c) return std::unexpected(c.error()); + if (!accept(tok::rparen)) return expected(tok::rparen); + auto b = parse_block(); + if (!b) return std::unexpected(b.error()); + return std::make_unique(stmt{s_while{std::move(*c), std::move(*b)}}); + } + if (peek().kind == tok::ident && peek(1).kind == tok::assign) { + std::string name = take().text; + take(); // `=` + auto e = parse_expr(); + if (!e) return std::unexpected(e.error()); + if (!accept(tok::semi)) return expected(tok::semi); + return std::make_unique(stmt{s_assign{std::move(name), std::move(*e), + head.line, head.col}}); + } + return std::unexpected(err(std::format( + "expected a statement, found {}", spelling(peek().kind)))); + } + + // Precedence is expressed by the call chain: cmp calls sum calls term. + std::expected parse_expr() { return parse_cmp(); } + + std::expected parse_cmp() { + auto lhs = parse_sum(); + if (!lhs) return lhs; + for (;;) { + std::string op; + switch (peek().kind) { + case tok::lt: op = "<"; break; + case tok::gt: op = ">"; break; + case tok::le: op = "<="; break; + case tok::ge: op = ">="; break; + case tok::eq: op = "=="; break; + case tok::ne: op = "!="; break; + default: return lhs; + } + take(); + auto rhs = parse_sum(); + if (!rhs) return rhs; + lhs = std::make_unique(expr{e_binary{op, std::move(*lhs), std::move(*rhs)}}); + } + } + + std::expected parse_sum() { + auto lhs = parse_term(); + if (!lhs) return lhs; + for (;;) { + std::string op; + if (peek().kind == tok::plus) op = "+"; + else if (peek().kind == tok::minus) op = "-"; + else return lhs; + take(); + auto rhs = parse_term(); + if (!rhs) return rhs; + lhs = std::make_unique(expr{e_binary{op, std::move(*lhs), std::move(*rhs)}}); + } + } + + std::expected parse_term() { + auto lhs = parse_unary(); + if (!lhs) return lhs; + for (;;) { + std::string op; + if (peek().kind == tok::star) op = "*"; + else if (peek().kind == tok::slash) op = "/"; + else if (peek().kind == tok::percent) op = "%"; + else return lhs; + take(); + auto rhs = parse_unary(); + if (!rhs) return rhs; + lhs = std::make_unique(expr{e_binary{op, std::move(*lhs), std::move(*rhs)}}); + } + } + + std::expected parse_unary() { + if (accept(tok::minus)) { + auto e = parse_unary(); + if (!e) return e; + return std::make_unique(expr{e_unary{"-", std::move(*e)}}); + } + return parse_primary(); + } + + std::expected parse_primary() { + const token head = peek(); + if (head.kind == tok::number) { + take(); + return std::make_unique(expr{e_number{head.value}}); + } + if (head.kind == tok::ident) { + take(); + if (!accept(tok::lparen)) + return std::make_unique(expr{e_name{head.text, head.line, head.col}}); + e_call c{head.text, {}, head.line, head.col}; + if (!accept(tok::rparen)) { + for (;;) { + auto a = parse_expr(); + if (!a) return a; + c.args.push_back(std::move(*a)); + if (accept(tok::comma)) continue; + if (accept(tok::rparen)) break; + return expected(tok::rparen); + } + } + return std::make_unique(expr{std::move(c)}); + } + if (accept(tok::lparen)) { + auto e = parse_expr(); + if (!e) return e; + if (!accept(tok::rparen)) return expected(tok::rparen); + return e; + } + return std::unexpected(err(std::format( + "expected an expression, found {}", spelling(head.kind)))); + } +}; + +// ── the emitter, with the semantic checks it needs to emit at all ──────── +class emitter { +public: + explicit emitter(const std::vector& ks) { + for (const auto& k : ks) arity_[k.name] = k.params.size(); + } + + std::expected emit(const std::vector& ks, + std::string_view source_name) { + std::string out = std::format( + "// Generated by toyc from {}. Do not edit.\n" + "//\n" + "// Every kernel is `extern \"C\"`: the C++ side and this file are\n" + "// produced by different compilers and share no C++ ABI.\n\n", + source_name); + + for (const auto& k : ks) out += declaration(k) + ";\n"; + out += "\n"; + + for (const auto& k : ks) { + scopes_.clear(); + scopes_.emplace_back(); + for (const auto& p : k.params) scopes_.back().insert(p); + + if (!always_returns(k.body)) + return std::unexpected(diag{ + std::format("kernel `{}` can reach its end without a `return`", k.name), + k.line, k.col}); + + out += declaration(k) + " {\n"; + auto body = emit_block(k.body, 1); + if (!body) return std::unexpected(body.error()); + out += *body; + out += "}\n\n"; + } + return out; + } + +private: + std::map arity_; + std::vector> scopes_; + + // `v_` on every local, `toy_` on every kernel. The target language has + // keywords of its own, and a kernel that names a variable `class` must not + // become a C++ file that does not compile for a reason the toy source + // cannot express. + static std::string local(std::string_view n) { return std::format("v_{}", n); } + static std::string global(std::string_view n) { return std::format("toy_{}", n); } + + static std::string declaration(const kernel& k) { + std::string params; + for (std::size_t i = 0; i < k.params.size(); ++i) + params += std::format("{}int {}", i ? ", " : "", local(k.params[i])); + return std::format("extern \"C\" int {}({})", global(k.name), + params.empty() ? "void" : params); + } + + // Every kernel returns an integer, so every path out of one is a `return`. + // A block satisfies that if its last statement is a `return`, or an + // `if`/`else` whose branches both do -- `while` never counts, because the + // language cannot say that a loop runs at all. + // + // Emitting `return 0;` at the end instead would have compiled everything + // and given a wrong answer for the kernel whose author forgot a branch. + static bool always_returns(const block& b) { + if (b.body.empty()) return false; + const stmt& last = *b.body.back(); + if (std::holds_alternative(last.node)) return true; + if (const auto* i = std::get_if(&last.node)) + return i->else_branch && always_returns(i->then_branch) + && always_returns(*i->else_branch); + return false; + } + + bool visible(const std::string& n) const { + return std::ranges::any_of(scopes_, [&](const auto& s) { return s.contains(n); }); + } + + std::expected emit_block(const block& b, int depth) { + scopes_.emplace_back(); + std::string out; + for (const auto& s : b.body) { + auto one = emit_stmt(*s, depth); + if (!one) return one; + out += *one; + } + scopes_.pop_back(); + return out; + } + + std::expected emit_stmt(const stmt& s, int depth) { + const std::string pad(static_cast(depth) * 4, ' '); + return std::visit([&](const auto& n) -> std::expected { + using T = std::decay_t; + if constexpr (std::is_same_v) { + if (scopes_.back().contains(n.name)) + return std::unexpected(diag{ + std::format("`{}` is already declared in this block", n.name), + n.line, n.col}); + auto e = emit_expr(*n.init); + if (!e) return std::unexpected(e.error()); + scopes_.back().insert(n.name); + return std::format("{}int {} = {};\n", pad, local(n.name), *e); + } else if constexpr (std::is_same_v) { + if (!visible(n.name)) + return std::unexpected(diag{ + std::format("`{}` is assigned before it is declared", n.name), + n.line, n.col}); + auto e = emit_expr(*n.value); + if (!e) return std::unexpected(e.error()); + return std::format("{}{} = {};\n", pad, local(n.name), *e); + } else if constexpr (std::is_same_v) { + auto e = emit_expr(*n.value); + if (!e) return std::unexpected(e.error()); + return std::format("{}return {};\n", pad, *e); + } else if constexpr (std::is_same_v) { + auto c = emit_expr(*n.cond); + if (!c) return std::unexpected(c.error()); + auto t = emit_block(n.then_branch, depth + 1); + if (!t) return t; + std::string out = std::format("{}if ({}) {{\n{}{}}}", pad, *c, *t, pad); + if (n.else_branch) { + auto e = emit_block(*n.else_branch, depth + 1); + if (!e) return e; + out += std::format(" else {{\n{}{}}}", *e, pad); + } + return out + "\n"; + } else { + auto c = emit_expr(*n.cond); + if (!c) return std::unexpected(c.error()); + auto b = emit_block(n.body, depth + 1); + if (!b) return b; + return std::format("{}while ({}) {{\n{}{}}}\n", pad, *c, *b, pad); + } + }, s.node); + } + + std::expected emit_expr(const expr& e) { + return std::visit([&](const auto& n) -> std::expected { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::format("{}", n.value); + } else if constexpr (std::is_same_v) { + if (!visible(n.id)) + return std::unexpected(diag{ + std::format("`{}` is not declared", n.id), n.line, n.col}); + return local(n.id); + } else if constexpr (std::is_same_v) { + auto o = emit_expr(*n.operand); + if (!o) return o; + return std::format("(-{})", *o); + } else if constexpr (std::is_same_v) { + auto l = emit_expr(*n.lhs); + if (!l) return l; + auto r = emit_expr(*n.rhs); + if (!r) return r; + // Parenthesised because the emitter does not reproduce C++'s + // precedence table: the AST already holds the grouping, and + // parentheses are how it survives into the target language. + return std::format("({} {} {})", *l, n.op, *r); + } else { + const auto it = arity_.find(n.callee); + if (it == arity_.end()) + return std::unexpected(diag{ + std::format("`{}` is not a kernel in this file", n.callee), + n.line, n.col}); + if (it->second != n.args.size()) + return std::unexpected(diag{ + std::format("kernel `{}` takes {} argument(s), {} given", + n.callee, it->second, n.args.size()), + n.line, n.col}); + std::string args; + for (std::size_t i = 0; i < n.args.size(); ++i) { + auto a = emit_expr(*n.args[i]); + if (!a) return a; + args += std::format("{}{}", i ? ", " : "", *a); + } + return std::format("{}({})", global(n.callee), args); + } + }, e.node); + } +}; + +} // namespace toyc + +export namespace toyc { + +// The whole compiler as one function: text in, C++ out, or one diagnostic +// with a position. +std::expected compile(std::string_view source, + std::string_view source_name) { + auto tokens = scan(source); + if (!tokens) return std::unexpected(tokens.error()); + parser p(std::move(*tokens)); + auto kernels = p.parse_program(); + if (!kernels) return std::unexpected(kernels.error()); + + std::set seen; + for (const auto& k : *kernels) + if (!seen.insert(k.name).second) + return std::unexpected(diag{std::format("kernel `{}` is declared twice", k.name), + k.line, k.col}); + + emitter e(*kernels); + return e.emit(*kernels, source_name); +} + +} // namespace toyc diff --git a/examples/12-a-new-device-language/toyc/src/lexer.cppm b/examples/12-a-new-device-language/toyc/src/lexer.cppm new file mode 100644 index 00000000..25fb5ddc --- /dev/null +++ b/examples/12-a-new-device-language/toyc/src/lexer.cppm @@ -0,0 +1,148 @@ +// The lexer for `.toy`. +// +// The interface exports one function and two aggregates. The parser's own +// types -- the variant-shaped AST -- stay inside `example.toyc.compile`, +// because a module interface that exports nested standard containers is the +// shape that has truncated BMIs on this repository's own toolchain before. +export module example.toyc.lexer; + +import std; + +export namespace toyc { + +enum class tok { + end, number, ident, + kw_kernel, kw_let, kw_return, kw_if, kw_else, kw_while, + lparen, rparen, lbrace, rbrace, comma, semi, + assign, plus, minus, star, slash, percent, + lt, gt, le, ge, eq, ne, +}; + +struct token { + tok kind = tok::end; + std::string text; // for ident, and for diagnostics + long long value = 0; // for number + int line = 1; + int col = 1; +}; + +// A diagnostic carries a position because a compiler that cannot say WHERE is +// a compiler its users debug by bisecting the input. +struct diag { + std::string message; + int line = 0; + int col = 0; +}; + +std::string spelling(tok k) { + switch (k) { + case tok::end: return "end of file"; + case tok::number: return "a number"; + case tok::ident: return "an identifier"; + case tok::kw_kernel: return "`kernel`"; + case tok::kw_let: return "`let`"; + case tok::kw_return: return "`return`"; + case tok::kw_if: return "`if`"; + case tok::kw_else: return "`else`"; + case tok::kw_while: return "`while`"; + case tok::lparen: return "`(`"; + case tok::rparen: return "`)`"; + case tok::lbrace: return "`{`"; + case tok::rbrace: return "`}`"; + case tok::comma: return "`,`"; + case tok::semi: return "`;`"; + case tok::assign: return "`=`"; + default: return "an operator"; + } +} + +std::expected, diag> scan(std::string_view src) { + std::vector out; + int line = 1, col = 1; + std::size_t i = 0; + + auto advance = [&](std::size_t n) { + for (std::size_t k = 0; k < n; ++k) { + if (src[i + k] == '\n') { ++line; col = 1; } else { ++col; } + } + i += n; + }; + auto push = [&](tok k, std::size_t n, std::string text = {}) { + out.push_back(token{k, std::move(text), 0, line, col}); + advance(n); + }; + + while (i < src.size()) { + const char c = src[i]; + + if (c == '#') { // a comment runs to the newline + std::size_t n = 0; + while (i + n < src.size() && src[i + n] != '\n') ++n; + advance(n); + continue; + } + if (std::isspace(static_cast(c))) { advance(1); continue; } + + if (std::isdigit(static_cast(c))) { + std::size_t n = 0; + long long v = 0; + while (i + n < src.size() && std::isdigit(static_cast(src[i + n]))) { + v = v * 10 + (src[i + n] - '0'); + ++n; + } + token t{tok::number, {}, v, line, col}; + out.push_back(t); + advance(n); + continue; + } + + if (std::isalpha(static_cast(c)) || c == '_') { + std::size_t n = 0; + while (i + n < src.size() && + (std::isalnum(static_cast(src[i + n])) || src[i + n] == '_')) ++n; + const std::string word(src.substr(i, n)); + const tok k = word == "kernel" ? tok::kw_kernel + : word == "let" ? tok::kw_let + : word == "return" ? tok::kw_return + : word == "if" ? tok::kw_if + : word == "else" ? tok::kw_else + : word == "while" ? tok::kw_while + : tok::ident; + push(k, n, word); + continue; + } + + // Two-character operators are tested first: `<` is a prefix of `<=`, + // and testing the one-character form first would lex `<=` as `<` `=` + // and report the error at the `=`, one column past the cause. + const std::string_view two = src.substr(i, 2); + if (two == "<=") { push(tok::le, 2); continue; } + if (two == ">=") { push(tok::ge, 2); continue; } + if (two == "==") { push(tok::eq, 2); continue; } + if (two == "!=") { push(tok::ne, 2); continue; } + + switch (c) { + case '(': push(tok::lparen, 1); continue; + case ')': push(tok::rparen, 1); continue; + case '{': push(tok::lbrace, 1); continue; + case '}': push(tok::rbrace, 1); continue; + case ',': push(tok::comma, 1); continue; + case ';': push(tok::semi, 1); continue; + case '=': push(tok::assign, 1); continue; + case '+': push(tok::plus, 1); continue; + case '-': push(tok::minus, 1); continue; + case '*': push(tok::star, 1); continue; + case '/': push(tok::slash, 1); continue; + case '%': push(tok::percent, 1); continue; + case '<': push(tok::lt, 1); continue; + case '>': push(tok::gt, 1); continue; + default: break; + } + return std::unexpected(diag{std::format("unexpected character `{}`", c), line, col}); + } + + out.push_back(token{tok::end, {}, 0, line, col}); + return out; +} + +} // namespace toyc diff --git a/examples/12-a-new-device-language/toyc/src/main.cpp b/examples/12-a-new-device-language/toyc/src/main.cpp new file mode 100644 index 00000000..386a07db --- /dev/null +++ b/examples/12-a-new-device-language/toyc/src/main.cpp @@ -0,0 +1,49 @@ +// toyc -- the driver. +// +// toyc -o +// +// Exit 0 and write the output, or exit 1 and print one diagnostic in the +// `file:line:col: error: message` form every editor already parses. +import std; +import example.toyc.compile; + +int main(int argc, char** argv) { + std::string in, out; + for (int i = 1; i < argc; ++i) { + const std::string a = argv[i]; + if (a == "-o" && i + 1 < argc) out = argv[++i]; + else if (a.starts_with("-o")) out = a.substr(2); + else if (a.starts_with("-")) { std::println(std::cerr, "toyc: unknown option `{}`", a); return 2; } + else if (in.empty()) in = a; + else { std::println(std::cerr, "toyc: more than one input file"); return 2; } + } + if (in.empty() || out.empty()) { + std::println(std::cerr, "usage: toyc -o "); + return 2; + } + + std::ifstream file(in, std::ios::binary); + if (!file) { + std::println(std::cerr, "toyc: cannot read {}", in); + return 1; + } + const std::string source((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + const auto cpp = toyc::compile(source, std::filesystem::path(in).filename().string()); + if (!cpp) { + std::println(std::cerr, "{}:{}:{}: error: {}", + in, cpp.error().line, cpp.error().col, cpp.error().message); + return 1; + } + + // The output directory is created by whoever declared the action; a + // compiler that creates it silently hides a rule that got out_dir wrong. + std::ofstream sink(out, std::ios::binary); + if (!sink) { + std::println(std::cerr, "toyc: cannot write {}", out); + return 1; + } + sink << *cpp; + return sink ? 0 : 1; +} From b65a64c2dd63ddf776bef87019c4b3d544990881 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:52:20 +0800 Subject: [PATCH 35/36] docs(design): record the two decisions of this batch Section 14: the register rules were written and then not enforced. The check had two independent defects, each a form recorded elsewhere -- the predicate tested a marker (`?`) rather than the property (an interrogative word), and the object set was enumerated syntactically (lines beginning with `#`) while a column header is a heading by every property that matters. Extending both reported 100 headings and 57 column headers. It also surfaced that the structure check discarded a link's fragment, so a link to a renamed section resolved to the file and passed. R5 is recorded with it: the scope of a background section is decided by the problem, not by the tool. Section 15: the extension model had no owner. Chapters 30 and 31 held every primitive between them and no section stated the model itself. Chapter 31 now opens with it, and its boundary is measured rather than asserted. The example had to become real before it could carry it -- the language, the compiler, and the tool-store boundary it measured. --- .../2026-09-08-the-documentation-as-a-book.md | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/.agents/docs/2026-09-08-the-documentation-as-a-book.md b/.agents/docs/2026-09-08-the-documentation-as-a-book.md index 9220779b..8ac607c7 100644 --- a/.agents/docs/2026-09-08-the-documentation-as-a-book.md +++ b/.agents/docs/2026-09-08-the-documentation-as-a-book.md @@ -538,3 +538,97 @@ question and exclusions; on the front door that block reads as machinery. `00` has no "not here" to declare because everything else *is* elsewhere, which its closing paragraph says in a sentence instead. Rule 11 exempts `00` by name, with that reason in the script. + +## 14. The register rules were written and then not enforced + +The user read chapter 00 and named three things: `背景:模块到了,工具链没跟上`, +`谁在为这个落差付账`, and the table header `大致相当于谁的活`. All three are +colloquial, all three violate §四 of the skill, and all three passed +`check_docs_style.sh`. + +Two independent defects in one check, and each is a form recorded elsewhere: + +**The predicate tested a marker rather than the property.** It matched `?`, 吗 +and 呢 — the punctuation a question usually carries. The property is the +interrogative *word*, and neither `谁在为这个落差付账` nor `打什么由谁决定` +carries the marker. The predicate is now the word set 谁 / 哪 / 什么 / 多少 / +为何 / 如何 / 怎样 / 怎么. + +**The object set was enumerated syntactically.** It read lines beginning with +`#`. The property a heading has — it names a topic, it is read out of order, it +is what a reader scans — is held equally by a table's header cells, which were +therefore never examined. Rule 4 adds them: the row above a `|---|---|` +separator, cell by cell. + +Extending both reported 100 headings and 57 header cells across 20 chapters, +in both languages. They are now noun phrases. The rename table is in the commit +rather than here, because it is a list of 157 strings and not a decision. + +**One consequence was not obvious.** Renaming 100 headings invalidates any link +that names a section by its anchor, and rule 9 of `check_docs_structure.sh` +discarded the fragment (`(?:#[^)]*)?`) — a link to a renamed section resolved to +the file and passed. It now computes GitHub's slug, including the `-1` / `-2` +suffix for repeated headings, and verifies the fragment. It immediately reported +two anchors in chapter 30 that had been wrong since they were written, before +any rename. + +### The rule that generalises + +R5 in the skill: **the scope of a background section is decided by the problem, +not by the tool.** Chapter 00's background was about modules, which is the part +of the problem mcpp is closest to. The problem a reader actually has is that a +C++ project needs four things at once — a build description, dependencies, a +compiler new enough, and an environment the result runs in — and no single tool +owns all four. CMake is the de facto standard for the first, and a de facto +standard is a statement about adoption rather than about the experience of use. +The environment is the layer that fails most often because it is the only one +nothing checks. Modules are the fourth constraint on that structure, not the +structure. + +## 15. The extension model had no owner, and the example that shows it was a toy + +The user asked where the plugin system is documented, and whether it reaches +preprocessing and languages of one's own. The tree had chapter 30 (build +programs) and chapter 31 (rule packages), and between them every primitive — +but no section stating the model: what the extension points are, what the +ecosystem has built from them, and where the boundary is. R1 says a topic has +exactly one owner, and this one had none. + +Chapter 31 now opens with it: five extension points with their effect and where +each is declared; seven things the ecosystem has built from them; three shapes +the model expresses (a new language whatever compiles it, preprocessing and +code generation, and a file that is partly C++ and partly another language); +and the boundary, which is measured rather than asserted — + +- a dependency's `device_extensions` is consulted *after* the built-in roles, + so a rule package cannot claim `.cpp`. Adding `".cpp"` to a rule's list is + not diagnosed and has no effect, measured; +- module-interface extensions are `[build] module_extensions`, a project axis, + because a module interface is scanned, produces a BMI and joins the link — + three engine behaviours rather than a command; +- an extension in neither table is refused by name, quoted from the run. + +### The example had to become real before it could carry this + +`examples/12-a-new-device-language` had a `.toy` that was "one integer per line, +and the entry point returns their sum", compiled by a shell script. It +demonstrated the two manifest keys and nothing else, and it was not a shape any +ecosystem author meets. + +`.toy` now has `let`, assignment, `if`/`else`, `while`, calls between kernels, +and the arithmetic and comparison operators. Its compiler is an ordinary mcpp +package — lexer, recursive-descent parser, semantic checks, C++ emitter — built +**for the build machine** through `tools = ["toyc"]` + `reexport = true` and +reached with `mcpp::dep_bin`. That makes it the tree's first example of a +dependency producing a host tool, a capability that until now existed only in +prose. + +**And it measured a boundary the documentation did not state.** The tool store's +key is the tool package's identity, version, host triple, compiler identity, +profile, features and the versions of its transitive dependencies. It holds no +source content, so editing a `path` tool's sources without changing its version +leaves the cached binary in place: `mcpp run` reported `Finished dev in 0.00s` +and printed the previous answer. Chapter 30 now states it, with the two ways +out. The action itself is not the gap — the rule declares the compiler binary +as an input, so a changed binary dirties the edge. What does not happen is the +rebuild that would change those bytes. From 11470f25a5bc862620ac199b7d492c5f6b6f9373 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 8 Sep 2026 20:01:24 +0800 Subject: [PATCH 36/36] ci(examples): the criterion for a rebuilt compiler had to be built to isolate it The device-language step still asserted the previous example: it grepped for `toy_answer() = 42` and edited `rules-toy/tools/toyc.sh`, a shell script that no longer exists. The job failed with "the .toy did not reach the link". Rewriting it turned up a claim I had written from reading the rule rather than from measuring it, and the measurement contradicts it. WHAT THE FIRST REWRITE ASSERTED. Bump the tool package's version, and the artifact follows. That is true, and it does not test what its message said. Falsified by removing `a.input(compiler)` from the rule: the version bump still reached the artifact and the step still passed. The tool's path is on the action's command line, so a new path re-runs the edge whether or not the compiler is also a declared input. WHAT ISOLATES IT. Different bytes at the same path: build a compiler that behaves differently and overwrite the cached binary in place, leaving the command line byte-identical. And both directions, because neither alone is the property -- with the input removed, the artifact followed the overwrite and then stopped following the restore. With the input present the step exits 0; with it removed, 1. The step now covers four things: the `.toy` reaches the link; the emitted loop runs (`gcd(1071, 462) = 21`, a value no constant in the tree holds); the compiler's bytes are tracked by the action; and the tool store holds no source content, with a version bump as the way out. Probe runs exit non-zero on purpose -- the program returns 1 when the answer is not 42 -- so they are judged by their output rather than their status. The README and chapter 30 said the action re-runs when the compiler binary changes. That is now measured rather than reasoned, and both state which case does not isolate it. --- .github/workflows/ci-linux.yml | 97 ++++++++++++++++++--- docs/30-build-mcpp.md | 13 ++- docs/zh/30-build-mcpp.md | 10 ++- examples/12-a-new-device-language/README.md | 61 ++++++++----- 4 files changed, 142 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 3151bd21..1f5a74c4 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -313,20 +313,95 @@ jobs: echo "ok: the optional package is absent without its feature" cd - >/dev/null - # 12-a-new-device-language. The `.toy` compiles on an engine that does - # not know the extension, AND editing the compiler reaches the - # artifact -- the half that fails silently when the tool is not a - # declared input of the action. + # 12-a-new-device-language. Three criteria, and a probe run exits + # non-zero on purpose -- the program returns 1 when the answer is not + # 42 -- so those runs are guarded with `|| true` and judged by their + # output rather than their status. cd examples/12-a-new-device-language/app "$MCPP" run | tee /tmp/toy.log - grep -q 'toy_answer() = 42' /tmp/toy.log || { + grep -q 'answer() = 42' /tmp/toy.log || { echo "FAIL: the .toy did not reach the link"; exit 1; } - sed -i 's/print s + 0 }/print s + 100 }/' ../rules-toy/tools/toyc.sh - "$MCPP" run | tee /tmp/toy2.log - sed -i 's/print s + 100 }/print s + 0 }/' ../rules-toy/tools/toyc.sh - grep -q 'toy_answer() = 142' /tmp/toy2.log || { - echo "FAIL: editing the compiler did not reach the artifact"; exit 1; } - echo "ok: the compiler is a declared input" + # THE LANGUAGE IS EXECUTED, NOT PATTERN-MATCHED: 21 is what the + # emitted `while` loop computes, and no constant in the tree holds it. + grep -q 'gcd(1071, 462) = 21' /tmp/toy.log || { + echo "FAIL: the emitted loop did not run"; exit 1; } + + # Editing the .toy reaches the artifact. + sed -i 's/scale(gcd(1071, 462), 2)/scale(gcd(1071, 462), 3)/' src/kernels/answer.toy + "$MCPP" run > /tmp/toy2.log 2>&1 || true + sed -i 's/scale(gcd(1071, 462), 3)/scale(gcd(1071, 462), 2)/' src/kernels/answer.toy + grep -q 'answer() = 63' /tmp/toy2.log || { + cat /tmp/toy2.log; echo "FAIL: editing the .toy did not reach the artifact"; exit 1; } + + # THE COMPILER IS A DECLARED INPUT OF THE ACTION -- the half that + # fails silently, and the one criterion here that has to be built to + # isolate it. Bumping the tool's version is NOT that criterion: the + # tool's path is on the action's command line, so a new path re-runs + # the edge whether or not it is also declared as an input. Measured: + # with `a.input(compiler)` removed, a version bump still reached the + # artifact, and this step passed. + # + # The isolating change is different BYTES AT THE SAME PATH. Build a + # compiler that behaves differently, overwrite the cached binary in + # place, and the command line is byte-identical. + # + # BOTH DIRECTIONS ARE THE CRITERION, not either one. Falsified by + # removing `a.input(compiler)`: the artifact followed the first + # overwrite anyway and stopped following the restore, so a check that + # asserted only the first direction would have passed on a rule that + # tracks nothing. + # + # `n.value * 2` keeps `0` at `0`: a probe that changed every literal + # would turn `while (b != 0)` into a loop that divides by zero, and + # this step would report a crash rather than an answer. + probe_on() { sed -i 's/return std::format("{}", n.value);/return std::format("{}", n.value * 2);/' ../toyc/src/compile.cppm; } + probe_off() { sed -i 's/return std::format("{}", n.value \* 2);/return std::format("{}", n.value);/' ../toyc/src/compile.cppm; } + install_toyc() { + ( cd ../toyc && "$MCPP" build >/dev/null ) + cp "$(find ../toyc/target -name toyc -type f -perm -u+x | head -1)" "$1" + } + + # `mcpp cache dir` prints a legacy-directory note on a second line. + store="$("$MCPP" cache dir | head -1)/tool" + cached="$(find "$store" -path '*toyc@0.1.0*/bin/toyc' | head -1)" + [ -n "$cached" ] || { + echo "FAIL: no toyc in the tool store under $store"; exit 1; } + + probe_on; install_toyc "$cached"; probe_off + "$MCPP" run > /tmp/toy3.log 2>&1 || true + grep -q 'answer() = 168' /tmp/toy3.log || { + cat /tmp/toy3.log + echo "FAIL: a changed compiler binary did not reach the artifact." + echo " rules-toy must declare the compiler among the action's inputs." + exit 1; } + install_toyc "$cached" + "$MCPP" run > /tmp/toy4.log 2>&1 || true + grep -q 'answer() = 42' /tmp/toy4.log || { + cat /tmp/toy4.log + echo "FAIL: restoring the compiler binary did not reach the artifact --" + echo " the action is not tracking the compiler's bytes." + echo " rules-toy must declare the compiler among the action's inputs." + exit 1; } + + # THE STORE HOLDS NO SOURCE CONTENT, which the example's README and + # docs/30 both state. Editing the compiler's sources at the same + # version changes nothing, because nothing rebuilds the tool. + probe_on + "$MCPP" run > /tmp/toy5.log 2>&1 || true + grep -q 'answer() = 42' /tmp/toy5.log || { + cat /tmp/toy5.log + echo "FAIL: the tool store now sees source content." + echo " The example's README and docs/30 state that it does not; update them." + exit 1; } + + # ... and bumping the version is the way out both of them offer. + sed -i 's/^version = "0.1.0"/version = "0.1.1"/' ../toyc/mcpp.toml + "$MCPP" run > /tmp/toy6.log 2>&1 || true + sed -i 's/^version = "0.1.1"/version = "0.1.0"/' ../toyc/mcpp.toml + probe_off + grep -q 'answer() = 168' /tmp/toy6.log || { + cat /tmp/toy6.log; echo "FAIL: bumping the tool version did not rebuild it"; exit 1; } + echo "ok: the compiler is a declared input, and the store is keyed on the version" - name: "Graphics example: render offscreen on lavapipe and assert the pixels" run: | diff --git a/docs/30-build-mcpp.md b/docs/30-build-mcpp.md index 7f901620..56e7b919 100644 --- a/docs/30-build-mcpp.md +++ b/docs/30-build-mcpp.md @@ -936,10 +936,15 @@ A published version is immutable, so for a tool that arrives from an index the key is exact. A tool being edited next door has the same version from one build to the next, and the cached binary stays: measured on [`examples/12-a-new-device-language`](../examples/12-a-new-device-language/), -a change to the tool's emitter left `mcpp run` reporting `Finished dev in 0.00s` -and printing the previous answer. Bump the tool package's version, or empty the -build cache with `mcpp cache clean` — the tool store lives inside it, at -`/tool//@/`. +a change to the tool's emitter left `mcpp run` printing the previous answer, +while bumping the tool package's version rebuilt it and changed the artifact. +Bump the version, or empty the build cache with `mcpp cache clean` — the tool +store lives inside it, at `/tool//@/`. + +This is a gap in the rebuild, not in the tracking. An action that declares the +tool among its inputs does re-run when that file's bytes change, measured by +overwriting the binary in the store: the artifact followed. What does not happen +is the rebuild that would change those bytes. ### `[tools.overrides]` — use an existing binary diff --git a/docs/zh/30-build-mcpp.md b/docs/zh/30-build-mcpp.md index 33d632ee..e4587085 100644 --- a/docs/zh/30-build-mcpp.md +++ b/docs/zh/30-build-mcpp.md @@ -789,9 +789,13 @@ grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] } 所以对来自索引的工具,这个键是精确的。而正在旁边被编辑的工具,两次构建之间版本相同, 缓存里的二进制就留在原地:在 [`examples/12-a-new-device-language`](../../examples/12-a-new-device-language/) -上实测,改动工具的 emitter 之后,`mcpp run` 报告 `Finished dev in 0.00s` 并打印上一次 -的答案。抬工具包的版本,或用 `mcpp cache clean` 清空构建缓存 —— tool store 就住在 -里面,路径是 `/tool//@/`。 +上实测,改动工具的 emitter 之后 `mcpp run` 打印的是上一次的答案,而抬高工具包的版本 +之后它被重建、产物随之改变。抬版本,或用 `mcpp cache clean` 清空构建缓存 —— +tool store 就住在里面,路径是 `/tool//@/`。 + +**缺口在重建,不在跟踪。** 把工具列进 action 输入的规则,确实会在那个文件的字节变化 +时重跑 —— 实测直接覆盖 store 里的二进制,产物随之改变。不发生的是「让这些字节变化」 +的那次重建。 ### `[tools.overrides]` —— 使用已有的二进制 diff --git a/examples/12-a-new-device-language/README.md b/examples/12-a-new-device-language/README.md index 634ef7a5..bcaba486 100644 --- a/examples/12-a-new-device-language/README.md +++ b/examples/12-a-new-device-language/README.md @@ -184,28 +184,47 @@ a real compiler rather than a script. ## The boundary this example measured: a host tool is cached by version -Editing `toyc`'s **source** does not reach the artifact. Measured: a change to -the emitter left `mcpp run` reporting `Finished dev in 0.00s` and printing the -previous answer. +Four changes were made one at a time, each from the same starting state: -The tool store's key is the tool package's identity, version, host triple, -compiler identity, profile, features and the versions of its transitive -dependencies — not the content of its sources. For a package that arrives from -an index that key is exact, because a published version is immutable. For a -`path` dependency being edited it is not: - -| situation | effect | -|---|---| -| the tool's version changes | the tool is rebuilt, and the action re-runs because its declared input changed | -| the tool's sources change, its version does not | the cached binary stays, and the build is green over the previous compiler's output | - -Two ways out, and they are the same one at different sizes: bump the tool -package's version, or empty the build cache with `mcpp cache clean` — the tool -store lives inside it, at `/tool//@/`. - -The action itself is not the gap. `rules-toy` declares the compiler as an input -beside the source, so an action whose compiler binary changes does re-run. What -does not happen is the rebuild that would change those bytes. +| what changed | the artifact | how it was changed | +|---|---|---| +| the `.toy` source | follows: `42` → `63` | `scale(…, 2)` → `scale(…, 3)` | +| the compiler's **bytes**, at the path the action names | follows: `42` → `168` | overwriting the binary in the tool store | +| the compiler's **sources**, its version unchanged | does not follow: the previous answer stands | editing the emitter | +| the compiler's **version** | follows: `42` → `168`, and the tool is rebuilt | `0.1.0` → `0.1.1` | + +Rows two and three are the whole finding, and they separate two things that are +easy to merge. **The action's input tracking works**: `rules-toy` declares the +compiler beside the source, and changing that file's bytes re-runs the edge. +**What does not happen is the rebuild that would change those bytes.** The tool +store's key is the tool package's identity, version, host triple, compiler +identity, profile, features and the versions of its transitive dependencies — +it holds no source content. For a package that arrives from an index the key is +exact, because a published version is immutable; for a `path` dependency being +edited it is not. + +`mcpp run` prints `Finished dev in 0.00s` in row three, and that line is mcpp's +own summary rather than evidence: row two prints it too, and the artifact +changed. + +**Row four does not test row two, which is why the difference is worth stating.** +The tool's path is on the action's command line, so a new version re-runs the +edge whether or not the compiler is also a declared input. Removing +`a.input(compiler)` from `rules-toy` and bumping the version left the artifact +following anyway. The isolating change is different bytes at the *same* path — +row two — and it takes both of its directions: with the input removed, the +artifact followed the overwrite and then stopped following the restore. CI runs +that pair. + +Two ways out: bump the tool package's version, or empty the build cache with +`mcpp cache clean` — the tool store lives inside it, at +`/tool//@/`. + +**One more trap sits behind them.** Going back from `0.1.1` to `0.1.0`, whose +clean tool was still in the store, left the artifact at `168`. The build program +did not re-run, so the plan still named the `0.1.1` binary, which had not +changed. `rm -rf target` cleared it. Iterating on a compiler means the version +goes forward only. ## Three things the first version got wrong