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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion .agents/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
---
```

344 records.
345 records.

## By subject

Expand Down Expand Up @@ -88,6 +88,10 @@ Records that declare one. Everything else is listed by date below.

- [Two answers and two silences: the scanner's second grammar, and the manifest keys nothing reads](2026-09-09-two-answers-and-two-silences.md) — active

### openkal-performance

- [生态设计 part 3:ok 映像的性能对等——引擎路径、openkal 运行时与观测](2026-11-02-ecosystem-part3-ok-image-performance-parity-design.md) — active

### plan

- [Eight reports after 2026.9.27.1: implementation plan](2026-09-27-eight-reports-implementation-plan.md) — active
Expand Down Expand Up @@ -154,6 +158,9 @@ Records that declare one. Everything else is listed by date below.

## By date

### 2026-11

- [生态设计 part 3:ok 映像的性能对等——引擎路径、openkal 运行时与观测](2026-11-02-ecosystem-part3-ok-image-performance-parity-design.md) — active
### 2026-10

- [大型工作区的构建空间与复用(mcpp#874)](2026-10-31-workspace-space-and-reuse-design.md) — active
Expand Down
52 changes: 51 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@
> Each `## [<version>]` section is that release's notes. Entries are written in English
> from 2026.9.28.3 on; earlier entries remain as written.

## [2026.11.2.1] - 2026-11-02

A dependency's source is used without being copied: a dependency edge carries
patches, and a dependency is compiled into its consumer (design 2026-11-02,
part 3 §14). With them, mcpp#875 and mcpp#881.

### Added

- **`patches = [...]` on a dependency edge** (docs/05). Unified diffs, named
relative to the declaring manifest, applied in order to the verified source by
mcpp's own exact applier (no fuzz, no search, no conversion of line endings;
one leading directory of each path removed; renames, copies and binary changes
refused). A patched library is another library, a variant: its identity is
(package, version, patch set), its source a tree of its own under the mcpp
home (`cache/src/<package>/<version>+p<digest>`), and its build, BMIs and
cache entries its own. Version and git dependencies; a `path` dependency with
patches is refused. `mcpp.lock` records the variant and each patch's path and
SHA-256 (`variant`, `patches`); `mcpp emit sbom` states them as the component's
pedigree. A variant's tree is collected by `cache gc`, `prune` and after a
build as the cache's entries are. e2e 1001.
- **`embed = true` and `exclude = [...]` on a dependency edge** (docs/05). The
dependency's sources, less `exclude`, are compiled into the consumer with the
consumer's flags and, for those files, the dependency's include directories
and compile flags; the dependency builds nothing of its own in that
configuration, and a program that reaches it without its embedder is refused
naming both. `exclude` without `embed` is refused at the manifest. e2e 1002.
- **One program, one variant of a package**: two programs of one configuration
may each use their own patch set; a program that reaches two variants of a
package, or a variant and the package without patches, is refused naming
them. e2e 1003.

### Fixed

- **A build with a toolchain outside every provider found no ninja** (mcpp#875):
the ninja mcpp installed into its own home is used after the one beside the
compiler's provider and before PATH, and `MCPP_NINJA` names the same one. With
none, the build is refused naming where it looked, instead of "cannot start
'ninja'". e2e 1004.
- **`clean --stale` said every directory was current after keeping one**
(mcpp#881): directories kept for being newer than the window are counted, and
the summary says how many and within what window. The default window of
`--older-than` is `[build] keep_days` (3 days), the window of the sweep after a
build, instead of one day. The SubOS remedies name the home:
`XLINGS_HOME=<registry> xlings self doctor --fix`. e2e 1005.

### Design

- Design 2026-11-02 part 3 (ok image performance parity), approved; part 2
rev 11 and 12 corrections.

## [2026.10.31.3] - 2026-10-31

A large workspace keeps its bytes once and builds each output once (mcpp#874;
Expand Down Expand Up @@ -2906,7 +2956,7 @@ C 库来自依赖图的链接此前没有 `--sysroot`,clang 会从宿主推导 `

### 成员清单只有一种读法

`mcpp publish`、`mcpp pack`、`mcpp emit xpkg`、`mcpp toolchain list`、`mcpp sbom`、`mcpp index list`/`update`、
`mcpp publish`、`mcpp pack`、`mcpp emit xpkg`、`mcpp toolchain list`、`mcpp emit sbom`、`mcpp index list`/`update`、
`mcpp update` 的索引刷新、快路径的身份判定与测试发现,此前直接读取成员目录中的原始清单:省略
`version` 的成员被拒绝,未写 `[toolchain]` 的成员在 `toolchain list` 中显示全局默认工具链,而同一
目录下的 `mcpp build` 用的是工作空间的工具链。现在它们与 `prepare_build` 经同一个函数读取继承后的
Expand Down
55 changes: 55 additions & 0 deletions docs/05-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,61 @@ each of them may have its own module of one name.
A module that several programs share is best provided by one package that the
others depend on; it is then compiled once.

### A dependency's patches, and a dependency embedded in its consumer (mcpp 2026.11+)

Two fields of a dependency edge use a package's source without copying it.

```toml
[dependencies]
"compat.zlib" = { version = "1.3.2", patches = ["patches/0001-fix.patch"] }
"compat.mimalloc" = { version = "3.6.1", embed = true, exclude = ["*/src/prim/prim.c"] }
```

**`patches`** are unified diffs (`diff -u`, `git format-patch`), named relative
to the manifest that declares them and applied in order to the package's
verified source.

- A patched library is another library, a **variant**: its identity is the
package, its version and the patch set, named by the SHA-256 over each
patch's SHA-256 in order (not by the files' names). Its source is a tree of
its own, `cache/src/<package>/<version>+p<digest>` under the mcpp home, and
its build, its BMIs and its cache entries are its own. One patch set carried
by two packages is one variant.
- The applier is mcpp's and is exact: a hunk applies where its header says,
every line of context and every line it removes byte for byte, or the build
is refused naming the patch, the file, the hunk and the line. Nothing is
searched for elsewhere and no line ending is converted. Paths in a patch
carry one leading directory (`a/`, `b/`), which is removed. Renames, copies
and binary changes are refused.
- Version and git dependencies may carry patches; a `path` dependency is a tree
its consumer owns and is refused with them.
- `mcpp.lock` names the variant and each patch by its path and SHA-256;
`mcpp emit sbom` states them as the component's pedigree.
- One program holds one variant of a package. Two programs of one
configuration (members of a workspace) may each use their own; a program
that reaches two variants of one package, or a variant and the package
without patches, is refused, naming them.
- The variant's tree is collected as the cache's entries are: by `mcpp cache
gc` and `prune`, and after a build once unused for `[cache] unused_days`.

**`embed = true`** compiles the dependency into its consumer.

- The dependency's sources, as its description selects them and less
`exclude` (globs relative to the dependency's root), are compiled as the
consumer's, with the consumer's flags and, for those files, the
dependency's include directories and compile flags.
- The dependency builds nothing of its own in that configuration: every other
edge to it takes its interface, and the embedder supplies its definitions.
A program that reaches it without its embedder is refused, naming both.
- `exclude` is allowed only with `embed`: a library built on its own that
compiled fewer files would change for every consumer, which is a patch's to
state.

A C library uses `embed` to carry an allocator or another dependency that would
otherwise need the C library itself to be built. A package that uses either
field states `mcpp = ">=2026.11"` in `[package]`, so that an older mcpp refuses
it rather than building it without its patches.

## Current limitations

- **Two things need the network, and only two:** resolving a branch that has no
Expand Down
9 changes: 6 additions & 3 deletions docs/09-commands-by-scenario.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ the fast paths read. Three consequences follow from that definition:

- A directory no record names is not deleted merely for that. `mcpp test`
builds through a path that writes no record, and so does a `--no-cache`
build. An unrecorded directory written within `--older-than` (one day by
default) is kept; older than that it goes, and the cost of being wrong is one
rebuild of a configuration nothing has touched since.
build. An unrecorded directory written within `--older-than` is kept; older
than that it goes, and the cost of being wrong is one rebuild of a
configuration nothing has touched since. The default window is `[build]
keep_days` of `config.toml` (3 days), the window the sweep after a build
uses, so the two agree on what is recent (2026.11). When it kept a
directory, the summary says how many and within what window.
- With no record at all the command refuses rather than guess. One
`mcpp build` establishes what is current.
- Directories under `target/` that are not fingerprint directories — `dist/`
Expand Down
39 changes: 39 additions & 0 deletions docs/zh/05-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,45 @@ updater = { path = "../updater", artifacts = ["updater"] }

几个程序共用的模块,最好由一个包提供、其余的包依赖它;这样它只编译一次。

### 依赖的补丁,以及嵌入使用者的依赖(mcpp 2026.11+)

依赖边上的两个字段让一个包使用另一个包的源码而不拷贝它。

```toml
[dependencies]
"compat.zlib" = { version = "1.3.2", patches = ["patches/0001-fix.patch"] }
"compat.mimalloc" = { version = "3.6.1", embed = true, exclude = ["*/src/prim/prim.c"] }
```

**`patches`** 是 unified diff(`diff -u`、`git format-patch` 的输出),路径相对于声明它们的清单,
按顺序应用到该包已校验的源码上。

- 打过补丁的库是另一个库,即**变体**:它的身份是包、版本与补丁集;补丁集以各补丁 SHA-256 依序
再取的 SHA-256 命名,与文件名无关。变体有自己的源码树(mcpp home 下
`cache/src/<包>/<版本>+p<摘要>`),构建、BMI 与缓存条目都是自己的。两个包携带同一组补丁,得到
同一个变体。
- 应用器是 mcpp 自己的,而且是精确的:一个 hunk 只在其头部所写的位置应用,每一行上下文与每一行
删除都必须逐字节相符,否则构建被拒绝,并指出补丁、文件、hunk 与行号。不在别处查找,不转换换行。
补丁中的路径带一级前导目录(`a/`、`b/`),应用时去掉。改名、复制与二进制改动被拒绝。
- 版本依赖与 git 依赖可以带补丁;`path` 依赖是使用者自己拥有的源码树,带补丁时被拒绝。
- `mcpp.lock` 记录变体,以及每个补丁的路径与 SHA-256;`mcpp emit sbom` 把它们写作组件的 pedigree。
- 一个程序只持有一个包的一个变体。同一配置中的两个程序(工作区的成员)可以各用自己的变体;一个
程序同时到达同一个包的两个变体,或一个变体与不带补丁的该包时被拒绝,消息点名两者。
- 变体的源码树与缓存条目一样回收:`mcpp cache gc`、`prune`,以及构建之后超过
`[cache] unused_days` 未用的。

**`embed = true`** 把依赖编进使用者。

- 依赖的源文件(按其描述选出,再减去 `exclude`,glob 相对于依赖的根目录)作为使用者的源文件编译:
使用使用者的编译参数,并且对这些文件另加依赖的 include 目录与编译参数。
- 在这一配置中,该依赖自己不编译任何东西:指向它的其他边只取它的接口,定义由嵌入者提供。一个程序
到达它而不含其嵌入者时被拒绝,消息点名两者。
- `exclude` 只能与 `embed` 一起使用:一个单独构建的库若少编译一些文件,它对所有使用者都变了,
这应当由补丁陈述。

C 库用 `embed` 携带分配器之类本身需要 C 库才能构建的依赖。使用其中任一字段的包在 `[package]` 中写
`mcpp = ">=2026.11"`,使旧版 mcpp 拒绝它,而不是不带补丁地构建它。

## 当前边界

- **只有两件事需要网络,而且只有这两件:** 解析一个在 lock 里没有 commit 的
Expand Down
8 changes: 5 additions & 3 deletions docs/zh/09-commands-by-scenario.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ Would remove 1 directory (0.0 B)
路径读取。这个定义带来三个推论:

- 一个没有记录的目录,不会仅因此就被删除。`mcpp test` 走的构建路径不写
记录,`--no-cache` 构建同样不写。未被记录、但写在 `--older-than`(默认
一天)之内的目录会被保留;更旧的会被删,判断出错的代价是重编一个此后
无人碰过的配置。
记录,`--no-cache` 构建同样不写。未被记录、但写在 `--older-than` 之内的
目录会被保留;更旧的会被删,判断出错的代价是重编一个此后无人碰过的配置。
默认窗口是 `config.toml` 的 `[build] keep_days`(3 天),即构建后清理所用
的同一个窗口,两者对"新"的定义一致(2026.11)。保留了目录时,摘要给出
保留的个数与窗口。
- 完全没有记录时,命令拒绝执行,而不是去猜。跑一次 `mcpp build` 就能确立
什么是当前的。
- `target/` 下不是指纹目录的东西 —— 例如 `mcpp pack` 的 `dist/` —— 从不
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mcpp"
version = "2026.10.31.3"
version = "2026.11.2.1"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
20 changes: 20 additions & 0 deletions modules/manifest/src/dep_spec.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ struct DependencySpec {
// into publicUsage unconditionally. Only build.mcpp-INJECTED include dirs
// are private-only.)
bool reexport = false;
// design 2026-11-02 (part 3) §14: a patched dependency is another
// library. `patches` are unified diffs applied in order to the verified
// source; the edge's dependency becomes the VARIANT (package, version,
// patch set), with a source tree, a build and a cache of its own. Each path
// is absolute here --- the reader resolves it against the directory of the
// manifest that declared it, so the patch belongs to the package that
// carries it --- and `patchesDigest` names the set: the SHA-256 over each
// patch's own SHA-256, in order (empty for no patches). Version and git
// edges only: a `path` dependency is a tree its consumer already owns.
std::vector<std::string> patches;
std::string patchesDigest;
// `embed = true`: the dependency's sources (as its description selects
// them, less `exclude`) are compiled INTO this consumer with the
// consumer's flags and the dependency's include directories and macros,
// and the dependency is not built as a library of its own. The embedder is
// the dependency's supplier in every program it is in; other edges to the
// same package there take its interface only. `exclude` (globs relative to
// the dependency's root) is allowed only with `embed`.
bool embed = false;
std::vector<std::string> exclude;
bool defaultFeatures = true; // consumer opt-out: `default-features = false`
bool defaultFeaturesDeclared = false;
// A member edge (workspace design 2026-09-29 §15): the edge from a
Expand Down
12 changes: 12 additions & 0 deletions modules/manifest/src/glob.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,18 @@ bool path_matches_glob(const std::filesystem::path& candidate,
const std::filesystem::path& root,
std::string_view glob)
{
// AN ABSOLUTE GLOB NAMES FILES WHEREVER THEY ARE: the flags an embedded
// dependency's own files are compiled with name that dependency's tree,
// which is not beneath the consumer's root (design 2026-11-02 §14.1).
if (glob.starts_with('/') || (glob.size() > 2 && glob[1] == ':' && (glob[2] == '/' || glob[2] == '\\'))) {
auto whole = try_narrow(candidate.lexically_normal());
if (!whole) { note_unnarrowable_path(candidate); return false; }
std::string generic = *whole;
std::ranges::replace(generic, '\\', '/');
std::string pattern(glob);
std::ranges::replace(pattern, '\\', '/');
return relative_path_matches_glob(generic, pattern);
}
// lexically_relative is pure path arithmetic and cannot throw; the
// narrowing is the part that can, so it is the part that goes through
// try_narrow.
Expand Down
Loading
Loading