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
929 changes: 929 additions & 0 deletions .agents/docs/2026-08-30-cross-repo-fix-plan-532-533-534.md

Large diffs are not rendered by default.

443 changes: 443 additions & 0 deletions .agents/docs/2026-08-30-issues-532-533-534-analysis.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/actions/bootstrap-mcpp/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ inputs:
# `package.name`, so one of the two was simply unreachable — and which one
# depended on the machine, which is why CI failed on `compat:lua` on
# Windows and `mcpplibs.capi:lua` on Linux. Never pin below that.
default: '2026.8.27.5'
default: '2026.8.30.2'
cache-target:
description: also restore/save target/ (build artifacts + BMIs)
required: false
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-macos-llvm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ inputs:
# Floor imposed by the index, not a routine bump — see
# .github/actions/bootstrap-mcpp/action.yml for why 0.4.69 is required
# (two packages named `lua` in one repo need openxlings/xlings#381).
default: '2026.8.27.5'
default: '2026.8.30.2'

runs:
using: composite
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/bootstrap-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
# Dormant (workflow_dispatch only), but kept in step with the rest —
# check_version_pins.sh holds it there. Floor: 0.4.69, below which the
# index cannot resolve two packages that share a short name.
XLINGS_VERSION: '2026.8.27.5'
XLINGS_VERSION: '2026.8.30.2'
steps:
- uses: actions/checkout@v4

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci-fresh-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ jobs:
env:
XLINGS_NON_INTERACTIVE: '1'
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.27.5
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.30.2
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install mcpp and config mirror
Expand Down Expand Up @@ -293,7 +293,7 @@ jobs:

- name: Install xlings + mcpp
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.27.5
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.30.2
# Deliberately NOT writing to $GITHUB_PATH here. On container
# images that declare no PATH in their config (opensuse/
# tumbleweed), appending a single dir to GITHUB_PATH makes the
Expand Down Expand Up @@ -364,7 +364,7 @@ jobs:
# (older ones carry minos=15 and refuse to start).
# v0.4.51+: in-process sha256 — this image has no sha256sum
# binary, so pinned fetches failed before it.
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.27.5
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.30.2
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install mcpp and config mirror
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-linux-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ jobs:

- name: Bootstrap xlings + released mcpp
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.27.5
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.30.2
export PATH="$HOME/.xlings/subos/current/bin:$PATH"
xlings update
xlings install mcpp -y -g
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/cross-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:
# release assets were uploaded in a broken state (records present,
# blobs missing → 404 on GET); re-uploaded clean. The stale-INDEX
# half is handled by the marker-clear below.
XLINGS_VERSION: '2026.8.27.5'
XLINGS_VERSION: '2026.8.30.2'
run: |
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
bash "$GITHUB_WORKSPACE/.github/tools/fetch_release.sh" \
Expand Down Expand Up @@ -263,7 +263,7 @@ jobs:
- name: Bootstrap mcpp via xlings
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.8.27.5'
XLINGS_VERSION: '2026.8.30.2'
run: |
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
bash "$GITHUB_WORKSPACE/.github/tools/fetch_release.sh" \
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
# Pin xlings to a known-good version. The upstream install
# script always grabs `latest` (no version override), so we
# download + self-install manually to avoid broken releases.
XLINGS_VERSION: '2026.8.27.5'
XLINGS_VERSION: '2026.8.30.2'
run: |
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
Expand Down Expand Up @@ -289,7 +289,7 @@ jobs:
- name: Bootstrap mcpp via xlings
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.8.27.5'
XLINGS_VERSION: '2026.8.30.2'
run: |
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
bash "$GITHUB_WORKSPACE/.github/tools/fetch_release.sh" \
Expand Down Expand Up @@ -360,7 +360,7 @@ jobs:
# below are pinned to the same version as XLINGS_VERSION; they are
# NOT interpolated from it, so check_version_pins.sh scans for them
# explicitly (they were absent from the old lock-step comment).
XLA="xlings-2026.8.27.5-linux-aarch64.tar.gz"
XLA="xlings-2026.8.30.2-linux-aarch64.tar.gz"
# NOT fetch_release.sh: this asset is OPTIONAL and the `if` is the
# point — an arch with no prebuilt xlings must fall through quietly,
# while the helper retries a 404 five times before giving up. The one
Expand All @@ -369,9 +369,9 @@ jobs:
# cover it.
if curl -fsSL --retry 3 --retry-delay 2 --retry-all-errors \
--connect-timeout 20 --max-time 600 -o "/tmp/$XLA" \
"https://github.com/openxlings/xlings/releases/download/v2026.8.27.5/$XLA"; then
"https://github.com/openxlings/xlings/releases/download/v2026.8.30.2/$XLA"; then
tar -xzf "/tmp/$XLA" -C /tmp
XLBIN=$(find /tmp/xlings-2026.8.27.5-linux-aarch64 -path '*/bin/xlings' -type f | head -1)
XLBIN=$(find /tmp/xlings-2026.8.30.2-linux-aarch64 -path '*/bin/xlings' -type f | head -1)
if [ -n "$XLBIN" ]; then
mkdir -p "$STAGING/$WRAPPER/registry/bin"
cp "$XLBIN" "$STAGING/$WRAPPER/registry/bin/xlings"
Expand Down Expand Up @@ -449,7 +449,7 @@ jobs:
- name: Bootstrap mcpp via xlings
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.8.27.5'
XLINGS_VERSION: '2026.8.30.2'
run: |
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
WORK=$(mktemp -d)
Expand Down Expand Up @@ -632,7 +632,7 @@ jobs:
shell: bash
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.8.27.5'
XLINGS_VERSION: '2026.8.30.2'
run: |
# Captured before the `cd` below, in POSIX form: this step never
# returns to the workspace, and GITHUB_WORKSPACE is a backslash
Expand Down
23 changes: 18 additions & 5 deletions docs/07-build-mcpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,27 @@ attach:

| `role` | Outputs | Ordering | Typical |
|---|---|---|---|
| `source` | join the compile set | the compile edge consumes them | protoc, a transpiler |
| `check` | a stamp file, written by mcpp | runs **alongside** compilation (set `blocking = true` to gate it) | clang-tidy, a format or ABI check |
| `source` | compilable ones join the compile set; the rest are produced but not compiled | **every compile edge of the declaring package waits for them** | protoc, a transpiler, a protocol/IDL generator |
| `check` | a stamp file, written by mcpp | runs **alongside** compilation; `blocking = true` makes the package's compile edges wait for it | clang-tidy, a format or ABI check |
| `object` | join the **link** set | the link edge consumes them | a resource compiler, `objcopy` embedding a blob, a generated `.def`, a pre-built `.o` |
| `artifact` | a new file | its *inputs* are link outputs, so it runs after the link | codesign, packaging, size budgets |

No phase machinery is involved: ninja's own file dependencies do the
sequencing, which is also why an `artifact` action cannot double-apply itself
the way a naive "post-build hook" would.
No phase machinery is involved. `object` and `artifact` are sequenced by
ninja's own file dependencies — which is also why an `artifact` action cannot
double-apply itself the way a naive "post-build hook" would. `source` and a
blocking `check` are sequenced by an order-only edge from the declaring
package's compile edges to that package's action outputs.

> **Why `source` needs the edge (mcpp 2026.8.30.2+).** A generated `.cpp`
> becomes an input of the edge that compiles it, so it was ordered for free. A
> generated **header** never does: it is reached through `-I`, and the depfile
> that would record it does not exist until a compile has already succeeded.
> Before this, an action whose outputs were all headers had a node in
> `build.ninja` that nothing could reach — not `default`, not the goal set, no
> consuming edge — so it never ran, and what the compiler read was the empty
> placeholder mcpp writes for a declared output. The ordering is **per
> package**, because `include_dir` colours only the declaring package's own
> translation units.

**A check's command does not have to write its stamp** (mcpp 2026.8.29.1+).
The verdict is the exit code; the stamp is bookkeeping the graph needs, and
Expand Down
17 changes: 13 additions & 4 deletions docs/zh/07-build-mcpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,22 @@ int main() {

| `role` | 输出 | 顺序 | 典型 |
|---|---|---|---|
| `source` | 进编译集 | 编译边消费它们 | protoc、转译器 |
| `check` | 一个 stamp 文件,由 mcpp 写入 | **与编译并行**(`blocking = true` 才前置) | clang-tidy、格式/ABI 检查 |
| `source` | 可编译的进编译集,其余只产出、不编译 | **声明它的那个包的每条编译边都等它** | protoc、转译器、协议/IDL 生成器 |
| `check` | 一个 stamp 文件,由 mcpp 写入 | 与编译并行;`blocking = true` 让该包的编译边等它 | clang-tidy、格式/ABI 检查 |
| `object` | 进**链接**集 | 链接边消费它们 | 资源编译器、`objcopy` 嵌 blob、生成的 `.def`、预编译 `.o` |
| `artifact` | 一个新文件 | 它的**输入**是链接产物,所以在链接之后跑 | 签名、打包、size budget |

全程不涉及任何 phase 机制:顺序由 ninja 自己的文件依赖决定 —— 这也是为什么
`artifact` 不会像朴素的「post 构建钩子」那样把自己重复施加一遍。
全程不涉及任何 phase 机制。`object` 与 `artifact` 由 ninja 自己的文件依赖定序 ——
这也是为什么 `artifact` 不会像朴素的「post 构建钩子」那样把自己重复施加一遍。
`source` 与 blocking 的 `check` 则由一条 order-only 边定序:从声明它的那个包的
编译边,指向该包的 action 产物。

> **`source` 为什么需要这条边(mcpp 2026.8.30.2+)。** 生成的 `.cpp` 会成为编译它
> 那条边的输入,所以顺序是白得的。生成的**头文件**永远不会:它是通过 `-I` 找到的,
> 而能记录它的 depfile 要等到某次编译成功之后才存在。在此之前,一个产物全是头文件的
> action 在 `build.ninja` 里有节点却无人可达 —— 不在 `default`、不在 goal 集、没有
> 任何边消费它 —— 于是它从不执行,而编译器读到的是 mcpp 为已声明产物写下的那个空占位
> 文件。这条边**按包**划分,因为 `include_dir` 只染色声明它的那个包自己的 TU。

**check 的命令不必自己写 stamp**(mcpp 2026.8.29.1+)。判定是退出码,stamp 是**构建图**
需要的记账;命令成功时由 mcpp 创建它。在此之前每个 check 都需要一个包装脚本去 touch
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.8.29.1"
version = "2026.8.30.1"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
19 changes: 17 additions & 2 deletions modules/buildmcpp/src/directives.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,17 @@ std::string action_error(const Directives& d);
// Never truncates an existing file: after the first build the real content is
// there, and rewriting it would make ninja think the input changed on every
// prepare.
//
// ⚠️ ONLY FOR OUTPUTS THAT ARE TRANSLATION UNITS, which is why this needs the
// table. A placeholder exists so the SCAN has something to read, and the scan
// never reads a header — but writing one anyway turned "the generator did not
// run" into "the header is empty", and mcpp#534 was diagnosed as a race for
// exactly that reason: the file was on disk, so the action looked like it had
// run. A missing file is the honest report, and after the ordering fix the
// generator runs before anything reads it either way.
void prepare_actions(std::vector<mcpp::manifest::BuildAction>& actions,
const std::filesystem::path& pkgRoot);
const std::filesystem::path& pkgRoot,
const mcpp::ExtensionTable& extensions);

// Does this action output belong in the COMPILE set?
//
Expand Down Expand Up @@ -773,7 +782,8 @@ bool is_compilable_output(const fs::path& p, const mcpp::ExtensionTable& t) {
}

void prepare_actions(std::vector<mcpp::manifest::BuildAction>& actions,
const fs::path& pkgRoot) {
const fs::path& pkgRoot,
const mcpp::ExtensionTable& extensions) {
for (auto& a : actions) {
auto absolutize = [&](std::vector<std::string>& v) {
for (auto& p : v) {
Expand All @@ -788,6 +798,11 @@ void prepare_actions(std::vector<mcpp::manifest::BuildAction>& actions,
if (a.role != mcpp::manifest::BuildAction::Role::Source) continue;
for (auto const& o : a.outputs) {
if (o.find("${mcpp.") != std::string::npos) continue;
// A placeholder exists so the scan has a translation unit to read.
// A header is not one — nothing scans it, and the empty file it
// used to leave behind is what made a generator that never ran
// look like one that had (mcpp#534).
if (!is_compilable_output(o, extensions)) continue;
std::error_code ec;
fs::path p(o);
if (fs::exists(p, ec)) continue; // real content already there
Expand Down
12 changes: 12 additions & 0 deletions modules/manifest/src/types.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,18 @@ struct BuildAction {
enum class Role { Source, Check, Object, Artifact };

std::string id; // diagnostics + edge naming
// Which package's `build.mcpp` declared this. Filled by the engine when
// actions are collected into the plan, NOT by the build program — the
// program does not know, and the engine already does.
//
// Load-bearing, not bookkeeping: the ordering edge an action needs is
// scoped to the declaring package, because `include_dir` colours only that
// package's own translation units. A build-wide ordering would express a
// dependency that does not exist and put it on the critical path of a
// build whose wall clock is dominated by one. Spelled the same way
// `CompileUnit::packageName` is (`qualified_package_name`), because the
// two are matched against each other.
std::string packageName;
Role role = Role::Source;
std::vector<std::string> inputs; // absolute or package-relative
std::vector<std::string> outputs; // ditto; declared, see INV-D
Expand Down
2 changes: 1 addition & 1 deletion modules/versioning/src/version.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ import std;

export namespace mcpp {

inline constexpr std::string_view MCPP_VERSION = "2026.8.29.1";
inline constexpr std::string_view MCPP_VERSION = "2026.8.30.1";

} // namespace mcpp
Loading
Loading