Skip to content

Commit a267fcd

Browse files
committed
feat: a library states its default link form, and the root build program reads each dependency's form (#642 E1, E2)
- [targets.<n>] linkage and its row form state a library's default form; kind = "shared" stays the constraint. Precedence: the root edge, a written dependency_linkage, the package default, static. An explicit override is honoured with an information line and no degraded record. - The link-form resolution is computed once before the root build program (hasSources from the scanner's own package_source_files) and applied after the scan as before. - The root build program receives MCPP_DEP_<NAME>_LINKAGE under the names of MCPP_DEP_<NAME>_DIR; mcpp::dep_linkage() reads it. Dependency programs do not. - Form B descriptors read linkage the same way. - Unit tests (linkage_form, manifest, xpkg), e2e 692 and 693, docs 04, 22, 30 with their Chinese mirrors, SPEC-004 1.4.
1 parent 69fae26 commit a267fcd

21 files changed

Lines changed: 1132 additions & 151 deletions

‎docs/04-mcpp-toml.md‎

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ required_features = ["gui"] # only built when feature `gui` is
321321
| `required_features` | The target is emitted only when **every** listed feature is active in the build; otherwise it is silently skipped. A gate only — it does not activate features (use `--features` / `[features].default`). **One exception, and it is not a second rule:** when this target is requested as a host tool (`tools = [...]`, §2.14), the target is what was *asked for*, so its `required_features` become the sub-build's *inputs*. Same field, one meaning — the resolution just runs in the opposite direction. |
322322
| `windows_subsystem` *(2026.9.12.2+)* | The PE subsystem of an executable: `"console"` (the default) or `"windows"`, a GUI program that starts without a console. Reaches this target's link and no other, and renders nothing on a target that is not PE. See the section above. |
323323
| `windows_entry` *(2026.9.12.2+)* | The entry function the program defines: `"main"` (the default), `"wmain"`, `"WinMain"` or `"wWinMain"`. See the section above. |
324+
| `linkage` *(2026.9.15.2+)* | A library target's **default** link form, `"static"` or `"shared"`: the form a consumer that writes no `linkage` receives. Unlike `kind = "shared"` it is not a constraint, so a consumer's explicit statement is honoured. Refused beside `kind = "shared"` and on a program target. See [`dependency_linkage`](#dependency_linkage--static-or-shared-is-the-consumers-decision). |
324325

325326
> **Scope (important):** `defines` / `cxxflags` / `cflags` on a target apply **only to that
326327
> target's exclusive entry source** (its `main`) — never to shared module/impl objects, which
@@ -452,12 +453,48 @@ whose C library is linked statically — which is the **default for musl** —
452453
packages write it without choosing anything. Absence of a statement is not a
453454
statement.
454455

456+
**A package can also state a default** *(2026.9.15.2+)*, which is not a
457+
constraint:
458+
459+
```toml
460+
[targets.fw]
461+
kind = "lib"
462+
linkage = "shared" # the form a silent consumer receives
463+
464+
[target.'cfg(env = "android")'.targets.fw]
465+
linkage = "shared" # the same, on the rows the selector matches
466+
```
467+
468+
- The form is decided, most specific statement first, by the root's
469+
`linkage` on the dependency's edge, the root's `dependency_linkage` when it
470+
is written (in `[build]` or in the active profile), the package's
471+
`linkage`, and `static`.
472+
- An explicit statement that differs from the package's default is honoured.
473+
It is not a degradation, so `--strict` accepts it, and one information line
474+
(`Linkage`) names both statements.
475+
- `kind = "shared"` and `linkage` in one table are refused, because a
476+
constraint leaves no default to state; a row states one of `kind` and
477+
`linkage`, and the last matching statement replaces the earlier one, so a
478+
row's `linkage = "static"` returns a package the unconditional table
479+
constrains to `shared` to a form its consumers choose.
480+
- A default the target cannot honour (a fully static image, a freestanding
481+
target) falls back without a warning, because nobody asked for it.
482+
- An engine before 2026.9.15.2 reports `[targets.<n>] linkage` as an
483+
unsupported key (silently for a dependency) and links the package statically;
484+
engines from 2026.9.14.2 refuse a row table without `kind`. A package that
485+
relies on the key states that engine floor.
486+
487+
The root project's build program reads the form each dependency takes in the
488+
build through `mcpp::dep_linkage("name")` ([30 — build.mcpp](30-build-mcpp.md)),
489+
so a generated loader entry or import declaration follows the same decision.
490+
455491
A request the constraint refuses is linked in the form the package allows,
456492
with a warning that names the package's statement (`its manifest states
457493
[targets.fw] kind = "shared", ...`); `--strict` turns the warning into an
458494
error. `mcpp why deps` reports each dependency's form and the reason for it:
459-
`default`, `requested`, `package-kind`, `row-kind`, `packaged`, `no-sources`,
460-
`prebuilt-inputs`, `no-loader` or `static-libc` (2026.9.14.2+).
495+
`default`, `package-default` (2026.9.15.2+), `requested`, `package-kind`,
496+
`row-kind`, `packaged`, `no-sources`, `prebuilt-inputs`, `no-loader` or
497+
`static-libc` (2026.9.14.2+).
461498

462499
A per-dependency `linkage` is honoured **only in the root project's**
463500
`[dependencies]`. A package deep in the graph does not get to decide how the

‎docs/22-target-side.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,15 +691,21 @@ application on the desktop rows and must be one shared library on Android
691691
states it once, in its own manifest; every consumer keeps one unconditional
692692
dependency line.
693693

694-
- `kind` is the only key, and it chooses between the two library forms, `lib`
695-
and `shared`. A name that is not a library target of the package (declared
696-
or inferred), a program target, or another kind is refused.
694+
- A row states `kind`, which chooses between the two library forms, `lib`
695+
and `shared`, or *(2026.9.15.2+)* `linkage`, which states the library's
696+
default form on those rows without constraining it; the two in one row are
697+
refused, and a later matching statement replaces an earlier one, including
698+
the unconditional table's. A name that is not a library target of the package
699+
(declared or inferred), a program target, or another kind is refused.
697700
- On a matching row the package is constrained to the shared form exactly as
698701
`[targets.<name>] kind = "shared"` constrains it (`dependency_linkage` in
699702
[04 — mcpp.toml](04-mcpp-toml.md)): a consumer that writes no `linkage`
700703
receives the shared library, and a consumer that writes `linkage =
701704
"static"` receives a warning naming this line, which `--strict` turns into
702-
an error. `mcpp why deps` reports the form with the reason `row-kind`.
705+
an error. `mcpp why deps` reports the form with the reason `row-kind`. A
706+
row's `linkage = "shared"` gives a silent consumer the shared library with the
707+
reason `package-default`, and honours a consumer's `linkage = "static"` with an
708+
information line instead of a warning.
703709
- A selector that names a target-side layer cannot carry it; the table is
704710
reported and ignored, because a library's form is decided while the graph
705711
that answers the layer is resolved.

‎docs/30-build-mcpp.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,7 @@ The running program receives the build context as `MCPP_*` variables
885885
| `MCPP_FEATURE_<NAME>` | `mcpp::has_feature("name")` | set to `1` per active feature (same `<NAME>` sanitization as the `MCPP_FEATURE_` compile macro) |
886886
| `MCPP_FEATURES` | — | comma-separated active feature list |
887887
| `MCPP_DEP_<NAME>_DIR` | `mcpp::dep_dir("name")` | the resolved install dir of each declared dependency (canonical **and** namespace-stripped name spellings; same `<NAME>` sanitization as `MCPP_FEATURE_`). Received by dependencies' build.mcpp **and** the root project's (the root runs after dependency resolution, 0.0.100+) |
888+
| `MCPP_DEP_<NAME>_LINKAGE` *(2026.9.15.2+)* | `mcpp::dep_linkage("name")` | the link form each dependency takes in this build, `static` or `shared`, under the names `MCPP_DEP_<NAME>_DIR` uses; empty for a dependency with no library form. The value is the resolution that decides what is linked, so a generated loader entry or `dllimport` declaration follows it. Received by the **root project's** build.mcpp only: the root decides every dependency's form, and a dependency's program runs before packages discovered after it, whose programs supply facts the answer depends on, so `dep_linkage` is always empty there |
888889

889890
These values are folded into the re-run key **unconditionally** — changing the
890891
target, profile, or feature set re-runs the program without any

‎docs/specs/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
| [SPEC-001](package-identity.md) | 包身份(`package.namespace` / `package.name`)、`[dependencies]` 选择器与匹配机制 | 评审中 v1.1 | 2026-08-03 | mcpp >= 0.0.106 |
3434
| [SPEC-002](target-side.md) | 目标侧模型与能力声明(`mcpp:` 保留命名空间、五层、三条规则) | 评审中 v1.0 | 2026-08-24 | mcpp >= 2026.8.24.2 |
3535
| [SPEC-003](exit-codes.md) | 退出码契约(分类、语义、稳定性承诺) | 评审中 v1.0 | 2026-09-01 | mcpp >= 2026.9.1.1 |
36-
| [SPEC-004](manifest-semantics.md) | `mcpp.toml` 的平面划分、条件化形状、解析轴与命名规约 | 草案 v1.0 | 2026-09-07 | 条件化形状 mcpp >= 2026.8.29.1;目标轴未实现 |
36+
| [SPEC-004](manifest-semantics.md) | `mcpp.toml` 的平面划分、条件化形状、解析轴与命名规约 | 草案 v1.4 | 2026-09-15 | 条件化形状 mcpp >= 2026.8.29.1;目标轴 mcpp >= 2026.9.6.4;`linkage` 默认值 mcpp >= 2026.9.15.2 |
3737
| [SPEC-005](build-database.md) | 构建数据库:`mcpp emit build-database` 的内容、取值规则与不写工程目录的保证 | 评审中 v1.0 | 2026-09-15 | mcpp >= 2026.9.15.1 |
3838

3939
## 文档约定

‎docs/specs/manifest-semantics.md‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
| **规范编号** | SPEC-004 |
66
| **标题** | `mcpp.toml` 的平面划分、条件化形状、解析轴与命名规约 |
77
| **状态** | **草案(Draft)** |
8-
| **版本** | 1.3 |
9-
| **最后修改** | 2026-09-14 |
8+
| **版本** | 1.4 |
9+
| **最后修改** | 2026-09-15 |
1010
| **最低实现版本** | 条件化形状:mcpp **2026.8.29.1**(`[target.<selector>.build-dependencies]` 起齐备);目标轴:mcpp **2026.9.6.4** |
1111
| **作者/维护** | mcpp-community |
1212
| **相关设计文档** | `.agents/docs/2026-09-07-mcpp-toml-unified-semantics-design.md`<br>`.agents/docs/2026-06-04-manifest-schema-ownership.md`<br>`.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md` |
@@ -95,7 +95,15 @@ Principle)规定,本规范不重复它,只在 §6 引用并补充一条。
9595
只接受库目标,只在 `lib` 与 `shared` 之间选择,在命中的行上约束该包的链接形态,
9696
与无条件的 `kind = "shared"` 相同。
9797

98-
**状态:已实现**(mcpp 2026.9.14.2)。
98+
`linkage = "static" | "shared"` 与 `kind` 并列,可写在 `[targets.<name>]` 与其按行形式中,
99+
陈述库目标的**默认**链接形态:它不收窄可选形态的集合,只在消费者没有陈述时给出答案。
100+
形态按以下顺序决定,前者优先:根工程依赖边上的 `linkage`,根工程写下的
101+
`dependency_linkage`,包的 `linkage`,`static`。与包默认值不同的显式陈述**必须**被遵从,
102+
且不得记为降级;实现**必须**输出一条同时点名两条陈述的信息。同一张表中 `kind = "shared"`
103+
与 `linkage` 并存、一行同时陈述 `kind` 与 `linkage`、`linkage` 写在程序目标上,均**必须**
104+
被拒绝;按行合并时后命中的陈述替换先前的陈述,无论两者各是 `kind` 还是 `linkage`。
105+
106+
**状态:已实现**(mcpp 2026.9.14.2;`linkage` 为 2026.9.15.2)。
99107

100108
### 3.2 门可以嵌进条件
101109

@@ -315,6 +323,10 @@ feature-deps feature-xlings ← 限定词是门
315323
声明所在的表(`tests/e2e/677_a_conditional_dependency_replaces_the_unconditional_one.sh`);
316324
按行 `kind` 在命中行上给出共享库与原因 `row-kind`,不命中行为 `default`
317325
(`tests/e2e/678_a_row_states_a_library_form.sh`)。
326+
9. §3.1.1 `linkage` 的判据:包的默认值为 `shared` 时,不陈述的消费者得到共享库与原因
327+
`package-default`;边上的 `linkage = "static"` 与写下的 `dependency_linkage = "static"`
328+
都得到静态形态与原因 `requested`,且 `--strict` 下构建通过;按行 `linkage` 替换无条件的
329+
`kind = "shared"`(`tests/e2e/692_a_package_states_its_default_link_form.sh`)。
318330

319331
## 变更记录
320332

@@ -324,3 +336,4 @@ feature-deps feature-xlings ← 限定词是门
324336
| 1.1 | 2026-09-07 | 目标轴落地(mcpp 2026.9.6.4):§4.3.1 工具 selector 禁止命名目标侧层;`[target.<selector>.xlings…]` 与 `[target.<selector>.feature-xlings.<f>]` 转为已实现;§4.3 补两条轴同时命名一个包时的取舍与按包去重;§4.4 转为已实现;§7 补第 4 条判据。 |
325337
| 1.2 | 2026-09-07 | 一个包一个版本(mcpp 2026.9.6.6):新增 §4.5(身份=`(namespace, name)`,版本是约束;裁决与校验两步;范围必须双向可解且可被 `xpkg_dir` 回答);§4.3.1 改为「禁止命名**被解析的**层」,`accelerator` 明确被接受(2026.9.6.5);§7 补第 5 条的反向腿与第 6、7 条判据。 |
326338
| 1.3 | 2026-09-14 | 条件依赖声明替换同一身份的无条件声明,`targets.<name>` 成为可条件化的 section,不读取的 section 必须报出(mcpp 2026.9.14.2):新增 §3.1.1 与 §7 第 8 条判据。 |
339+
| 1.4 | 2026-09-15 | 库目标的默认链接形态 `linkage`(mcpp 2026.9.15.2):§3.1.1 补默认值的语义、优先顺序与拒绝条件;§7 补第 9 条判据。 |

‎docs/zh/04-mcpp-toml.md‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ required_features = ["gui"] # 仅当 feature `gui` 激活时
289289
| `required_features` | 仅当列出的 feature **全部**激活时才生成该目标,否则静默跳过。只是门禁——不激活 feature(用 `--features` / `[features].default`)。 |
290290
| `windows_subsystem` *(2026.9.12.2+)* | 可执行文件的 PE 子系统:`"console"`(默认)或 `"windows"`(启动时不带控制台的 GUI 程序)。只到达该目标的链接,在非 PE 目标上不产生任何标志。见上一节。 |
291291
| `windows_entry` *(2026.9.12.2+)* | 程序定义的入口函数:`"main"`(默认)、`"wmain"`、`"WinMain"` 或 `"wWinMain"`。见上一节。 |
292+
| `linkage` *(2026.9.15.2+)* | 库目标的**默认**链接形态,`"static"` 或 `"shared"`:不写 `linkage` 的消费者得到的形态。它不同于 `kind = "shared"`,不是约束,因此消费者的显式陈述会被遵从。与 `kind = "shared"` 同写或写在程序目标上会被拒绝。见 [`dependency_linkage`](#dependency_linkage--静态还是动态由消费者决定)。 |
292293

293294
> **作用域(重要):** 目标上的 `defines` / `cxxflags` / `cflags` **只作用于该目标独占的入口源**
294295
> (它的 `main`)——**绝不**作用于共享的模块/实现对象(那些只编译一次、被每个目标链接,即 mcpp 的
@@ -402,11 +403,39 @@ dependency_linkage = "shared" # 按 profile 覆盖
402403
`kind = "lib"` **不是**约束:它是默认值,大多数包写下它并没有做任何选择。
403404
**没有陈述不等于一条陈述。**
404405

406+
**包还可以陈述一个默认值** *(2026.9.15.2+)*,它不是约束:
407+
408+
```toml
409+
[targets.fw]
410+
kind = "lib"
411+
linkage = "shared" # 不写 linkage 的消费者得到的形态
412+
413+
[target.'cfg(env = "android")'.targets.fw]
414+
linkage = "shared" # 同上,只在选择器命中的行上
415+
```
416+
417+
- 形态按陈述从具体到一般的顺序决定:根工程在该依赖边上的 `linkage`,根工程
418+
写下的 `dependency_linkage`(`[build]` 或当前 profile),包的 `linkage`,
419+
最后是 `static`。
420+
- 与包的默认值不同的显式陈述会被遵从。它不是降级,`--strict` 接受它,并由一条
421+
信息行(`Linkage`)同时点名两条陈述。
422+
- 同一张表里同时写 `kind = "shared"` 与 `linkage` 会被拒绝,因为约束没有默认值可言;
423+
一行只陈述 `kind` 与 `linkage` 之一,后命中的陈述替换先前的陈述,因此某行的
424+
`linkage = "static"` 会把无条件表约束为 `shared` 的包恢复为由消费者选择的形态。
425+
- 目标无法遵从的默认值(完全静态的映像、freestanding 目标)静默回落,因为没有人
426+
要求它。
427+
- 2026.9.15.2 之前的引擎把 `[targets.<n>] linkage` 报为不支持的键(对依赖静默)
428+
并静态链接该包;2026.9.14.2 起的引擎拒绝不含 `kind` 的行表。依赖此键的包应陈述
429+
这一引擎下限。
430+
431+
根工程的构建程序通过 `mcpp::dep_linkage("name")` 读取每个依赖在本次构建中的形态
432+
([30 —— build.mcpp](30-build-mcpp.md)),生成的加载入口或导入声明因此跟随同一个决定。
433+
405434
约束拒绝的请求按包允许的形态链接,并给出一条点名包的陈述的警告
406435
(`its manifest states [targets.fw] kind = "shared", ...`);`--strict` 下该警告
407-
成为错误。`mcpp why deps` 报告每个依赖的形态及其原因:`default`、`requested`、
408-
`package-kind`、`row-kind`、`packaged`、`no-sources`、`prebuilt-inputs`、
409-
`no-loader` 或 `static-libc`(2026.9.14.2+)。
436+
成为错误。`mcpp why deps` 报告每个依赖的形态及其原因:`default`、
437+
`package-default`(2026.9.15.2+)、`requested`、`package-kind`、`row-kind`、
438+
`packaged`、`no-sources`、`prebuilt-inputs`、`no-loader` 或 `static-libc`(2026.9.14.2+)。
410439

411440
依赖边上的 `linkage` 只在**根工程**的 `[dependencies]` 里生效。依赖图深处的包
412441
无权决定最终程序的布局;真正必须只有一份共享副本的包,应当在自己的 target 上

‎docs/zh/22-target-side.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,12 +578,16 @@ kind = "shared"
578578
在桌面各行上链接进应用、在 Android 上必须是唯一一份共享库的框架,在它自己的清单里
579579
声明一次;每个消费者都只保留一行无条件依赖。
580580

581-
- `kind` 是唯一的键,只在两种库形态 `lib` 与 `shared` 之间选择。不是该包库目标
582-
(声明的或推断的)的名字、程序目标以及其他形态都被拒绝。
581+
- 一行陈述 `kind`,在两种库形态 `lib` 与 `shared` 之间选择;或者 *(2026.9.15.2+)*
582+
陈述 `linkage`,给出该库在这些行上的默认形态而不约束它。同一行同时写两者会被拒绝,
583+
后命中的陈述替换先前的陈述,包括无条件表中的陈述。不是该包库目标(声明的或推断的)
584+
的名字、程序目标以及其他形态都被拒绝。
583585
- 在命中的行上,该包被约束为共享形态,与 `[targets.<name>] kind = "shared"`
584586
的约束完全相同([04 —— mcpp.toml](04-mcpp-toml.md) 中的 `dependency_linkage`):
585587
不写 `linkage` 的消费者得到共享库,写 `linkage = "static"` 的消费者得到一条点名
586588
这一行的警告,`--strict` 下成为错误。`mcpp why deps` 以原因 `row-kind` 报告该形态。
589+
某行的 `linkage = "shared"` 让不写 `linkage` 的消费者得到共享库,原因为
590+
`package-default`;对消费者的 `linkage = "static"` 予以遵从,给出信息行而不是警告。
587591
- 点名目标侧层的选择器不能承载这张表;该表被报出并忽略,因为库的形态是在解析
588592
回答该层的那张图时决定的。
589593
- 2026.9.14.2 之前的引擎不读取这张表,也不报告。依赖它的包要写明这一引擎下限。

‎docs/zh/30-build-mcpp.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ mcpp 会把它自己构建时用的**同一份** std 模块暂存过来,缓存
761761
| `MCPP_FEATURE_<NAME>` | `mcpp::has_feature("name")` | 每个活跃 feature 置 `1`(`<NAME>` 消毒规则与 `MCPP_FEATURE_` 编译宏一致) |
762762
| `MCPP_FEATURES` | — | 活跃 feature 逗号列表 |
763763
| `MCPP_DEP_<NAME>_DIR` | `mcpp::dep_dir("name")` | 每个已声明依赖解析后的安装目录(canonical 名与去命名空间短名两种拼写都可用;`<NAME>` 消毒规则同 `MCPP_FEATURE_`)。依赖包的 build.mcpp **和**根工程的 build.mcpp 都能拿到(根工程的 build.mcpp 在依赖解析之后运行,0.0.100+) |
764+
| `MCPP_DEP_<NAME>_LINKAGE` *(2026.9.15.2+)* | `mcpp::dep_linkage("name")` | 每个依赖在本次构建中的链接形态,`static` 或 `shared`,名字与 `MCPP_DEP_<NAME>_DIR` 相同;没有库形态的依赖为空。该值就是决定链接内容的那次解析,生成的加载入口或 `dllimport` 声明因此与之一致。只有**根工程**的 build.mcpp 能拿到:根工程决定每个依赖的形态,而依赖包的程序运行在发现顺序更靠后的包之前,那些包的程序提供了答案所依赖的事实,所以在那里 `dep_linkage` 总是为空 |
764765

765766
这些契约值**无条件**折入重跑键——换 target、换 profile、开关 feature 都会触发重跑,
766767
不需要任何 `rerun-if-env-changed` 声明。

0 commit comments

Comments
 (0)