Skip to content
Draft
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.

4 changes: 3 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
---
```

323 records.
324 records.

## By subject

Expand All @@ -30,6 +30,7 @@ Records that declare one. Everything else is listed by date below.

### design

- [PR CI acceleration and the toolchain specification (#756, #757, #669)](2026-10-02-pr-ci-acceleration-and-the-toolchain-specification-design.md) — active
- [工具与工具链的来源:声明、编程决定、可观察](2026-10-01-tool-and-toolchain-sources-design.md) — landed
- [A pack's build reported as a build, and a unit's compile independent of the member selection: triage and design (#753, #751)](2026-10-01-pack-drive-and-selection-independent-compile-design.md) — landed
- [Member selection, build programs prepared once, a pack over several members, and the output streams of `mcpp run`: the plan for the release after 2026.9.30.2 (#748, #749, #750)](2026-09-30-member-selection-and-build-program-cost-plan.md) — landed
Expand Down Expand Up @@ -114,6 +115,7 @@ Records that declare one. Everything else is listed by date below.

### 2026-10

- [PR CI acceleration and the toolchain specification (#756, #757, #669)](2026-10-02-pr-ci-acceleration-and-the-toolchain-specification-design.md) — active
- [工具与工具链的来源:声明、编程决定、可观察](2026-10-01-tool-and-toolchain-sources-design.md) — landed
- [A pack's build reported as a build, and a unit's compile independent of the member selection: triage and design (#753, #751)](2026-10-01-pack-drive-and-selection-independent-compile-design.md) — landed
### 2026-09
Expand Down
18 changes: 10 additions & 8 deletions .agents/skills/mcpp-contributing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,16 @@ gh pr checks <pr-number> # 查看状态
gh run view <run-id> --log-failed # 查看失败日志
```

CI 由分平台的基础构建/单元集成检查与独立 E2E 检查组成:
| Workflow | 平台 | 内容 |
|----------|------|------|
| `ci-linux` / `ci-linux-e2e` | Linux x86_64 | 自举构建、unit/integration / 分片 E2E |
| `ci-macos` / `ci-macos-e2e` | macOS ARM64 | 自举构建、unit/integration / E2E |
| `ci-windows` / `ci-windows-e2e` | Windows x86_64 | 自举构建、toolchain 回归 / E2E |
| `cross-build-test` | Linux/Windows cross targets | 交叉构建、产物运行与 MinGW/Wine 检查 |
| `ci-aarch64-fresh-install` | Linux ARM64 native | path-filtered fresh install、原生自举与 musl `build.mcpp` host-helper 回归 |
一次提交的 CI 是 `ci.yml` 的一次运行,分段执行(设计见 `.agents/docs/2026-10-02-pr-ci-acceleration-and-the-toolchain-specification-design.md` 第二部分):
| 阶段 | 内容 |
|------|------|
| `changes` | 按改动路径分类;只改了没有任何脚本、测试或源码读取的文档时,只跑 `docs` |
| `docs` | 不需要二进制的检查(版本钉、文档风格与结构、工作流断言等) |
| `build-*` | 每个宿主构建一次 mcpp(`build.yml`),上传为 `mcpp-built-<host>` |
| `linux` / `linux-e2e`、`macos` / `macos-e2e` / `macos-ios`、`windows` / `windows-e2e` / `windows-msvc-xlings`、`cross`、`target-matrix`、`openkal` | 各领域的可复用工作流(原来的 `ci-*.yml`),通过 `.github/actions/use-built-mcpp` 使用上面那次构建,不再各自构建 |
| `e2e-coverage` | 每个 e2e 测试都在某个宿主上运行、由专门 job 运行,或在 `tests/e2e/coverage-exceptions.tsv` 中写明原因 |

`ci-aarch64-fresh-install`、`measure-windows-tool-crt` 与 `pypi-publish` 仍是按路径触发的独立工作流。缓存只在 main 上由一个 job 保存;PR 只恢复。

**以 PR 实际 required checks 为准,所有未跳过的 required checks 必须通过。** 如果某个平台失败:
1. 下载日志分析原因
Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/mcpp-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ gh run list --branch main --limit 3
```

以分支保护和 `gh pr checks <pr-number>` 显示的 actual required checks 为准。
在 main 上监控当前运行时,检查 `ci-linux`、`ci-linux-e2e`、`ci-macos`、
`ci-macos-e2e`、`ci-windows`、`ci-windows-e2e` 与 `cross-build-test` 的结果;
跳过或非 required 的 workflow 不是合入 gate。不要在 required CI 红的时候发版。
在 main 上监控当前运行时,检查 `ci` 这一个工作流的运行(它包含各平台、e2e 分片、
交叉构建与 `e2e-coverage`);known-red 的腿(名字里带 issue 号)允许失败。跳过或
非 required 的 workflow 不是合入 gate。不要在 `ci` 红的时候发版。

### 2. bump 版本号(第一组两处,单个 commit,走 PR)

Expand Down
131 changes: 101 additions & 30 deletions .github/actions/bootstrap-mcpp/action.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
name: bootstrap-mcpp
description: >
Restore the shared CI cache lineage (mcpp sandbox + xlings + target/) and
bootstrap a released mcpp via xlings. Exports MCPP and XLINGS_BIN.
Restore the shared CI cache lineage (mcpp sandbox and xlings) and bootstrap
a released mcpp via xlings. Exports MCPP and XLINGS_BIN.

Extracted so the split CI jobs (build / toolchain legs / e2e shards /
integration) share ONE definition instead of copy-pasting a 40-line
preamble per job. Every job that uses it lands on the same cache keys,
which is what makes splitting cheap: each job restores a warm sandbox
and only pays one incremental `mcpp build`.
The caches are RESTORED here and never saved. One job per host writes them,
the build job of .github/workflows/build.yml, and only on a push to main
(rule R3 of .agents/docs/2026-10-02-pr-ci-acceleration-and-the-toolchain-
specification-design.md). Before that rule every job that used this action
saved the same key on success: thirty-four to forty-two saves per pull
request against a 10 GB repository limit, parallel saves of one key racing
each other, and the main lineage evicted within forty minutes (measured
2026-10-01). The keys are outputs so that the one writer saves exactly what
was restored.

`target/` is no longer cached. A restored `target/` made no build
incremental: on an exact hit ninja still ran 830 of 830 edges, while the
caches themselves were up to 3.3 GB each.

inputs:
xlings-version:
Expand All @@ -26,10 +34,20 @@ inputs:
# 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.9.30.1'
cache-target:
description: also restore/save target/ (build artifacts + BMIs)
required: false
default: 'true'

outputs:
sandbox-key:
description: the exact key of the mcpp sandbox cache
value: ${{ steps.sandbox.outputs.cache-primary-key }}
sandbox-hit:
description: "'true' when the sandbox was restored by its exact key"
value: ${{ steps.sandbox.outputs.cache-hit }}
xlings-key:
description: the exact key of the xlings cache
value: ${{ steps.xlings.outputs.cache-primary-key }}
xlings-hit:
description: "'true' when xlings was restored by its exact key"
value: ${{ steps.xlings.outputs.cache-hit }}

runs:
using: composite
Expand All @@ -38,8 +56,9 @@ runs:
# "-release-" caches. A bare "mcpp-sandbox-<os>-" restore prefix used to
# match the release sandbox too, silently swapping in a differently
# populated registry (issue #120).
- name: Cache mcpp sandbox
uses: actions/cache@v4
- name: Restore the mcpp sandbox
id: sandbox
uses: actions/cache/restore@v4
with:
path: ~/.mcpp
# `runner.arch` IS PART OF EVERY KEY, AND WAS NOT.
Expand All @@ -66,12 +85,16 @@ runs:
# sandbox — which is what actually resolves dependencies — would
# silently stay behind (observed: a 0.4.30 sandbox surviving under a
# 0.4.69 bootstrap for weeks).
key: mcpp-sandbox-${{ runner.os }}-${{ runner.arch }}-ci-xl${{ inputs.xlings-version }}-${{ hashFiles('mcpp.toml', '.xlings.json') }}
# ci.yml is part of the key because it names the toolchains the build
# job installs before it saves (`prewarm`): a sandbox saved under an
# unchanged key would never gain one added there.
key: mcpp-sandbox-${{ runner.os }}-${{ runner.arch }}-ci-xl${{ inputs.xlings-version }}-${{ hashFiles('mcpp.toml', '.xlings.json', '.github/workflows/ci.yml') }}
restore-keys: |
mcpp-sandbox-${{ runner.os }}-${{ runner.arch }}-ci-xl${{ inputs.xlings-version }}-

- name: Cache xlings
uses: actions/cache@v4
- name: Restore xlings
id: xlings
uses: actions/cache/restore@v4
with:
path: ~/.xlings
key: xlings-${{ runner.os }}-${{ runner.arch }}-v2-xl${{ inputs.xlings-version }}-${{ hashFiles('.xlings.json') }}
Expand Down Expand Up @@ -109,6 +132,37 @@ runs:
esac
tarball="xlings-${XLINGS_VERSION}-linux-${xa}.tar.gz" ;;
esac
# FAST PATH: the xlings cache already holds the pinned version, so the
# tarball fetch + extract + `self install` are skipped. Without this
# guard every job of every CI run paid the download and the extract,
# measured at 5 to 30 seconds per job on Linux and ~30 seconds on
# Windows, across thirty jobs per run — about half the bootstrap-mcpp
# step on Windows, more the 7 seconds the unix leg pays. The cache key
# is `xl$VER` already; the check is the one case this guard would
# otherwise miss: a stale `xlings` cache from BEFORE the pin was
# bumped (a partial restore-key match hands the same OS/ARCH cache
# back, but with the previous release), or a binary that no longer runs
# because its dynamic loader is gone.
XL_BIN_PATH="$HOME/.xlings/subos/default/bin/xlings"
if [ -x "$XL_BIN_PATH" ]; then
xl_ver="$("$XL_BIN_PATH" --version 2>/dev/null | head -1 || true)"
if [ -n "$xl_ver" ] && echo "$xl_ver" | grep -qF "$XLINGS_VERSION"; then
export PATH="$HOME/.xlings/subos/default/bin:$PATH"
echo "$HOME/.xlings/subos/default/bin" >> "$GITHUB_PATH"
"$XL_BIN_PATH" --version
MCPP=$(bash "$REPO_DIR/.github/tools/install_pinned_mcpp.sh" "$REPO_DIR")
echo "system xlings: $("$XL_BIN_PATH" --version 2>/dev/null | head -1)"
if [ -x "$HOME/.mcpp/registry/bin/xlings" ]; then
echo "sandbox xlings: $("$HOME/.mcpp/registry/bin/xlings" --version 2>/dev/null | head -1)"
else
echo "sandbox xlings: (not initialised yet)"
fi
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
echo "XLINGS_BIN=$XL_BIN_PATH" >> "$GITHUB_ENV"
exit 0
fi
fi

WORK=$(mktemp -d)
# Retried and verified — see .github/tools/fetch_release.sh. A bare curl
# here was the single largest source of unexplained CI red on this repo
Expand Down Expand Up @@ -179,6 +233,37 @@ runs:
XLINGS_VERSION: ${{ inputs.xlings-version }}
run: |
REPO_DIR="$(pwd)"
# FAST PATH: see the unix leg for the reasoning. The cost on Windows is
# larger (the zip is bigger and the runner's network path to
# github.com is slower), measured at ~30 s per job.
#
# The fast path addresses xlings by ABSOLUTE PATH, not by bare
# `xlings.exe`. The cold path runs `xlings self install` first, which
# writes the dir into Windows PATH via `[Environment]::SetEnvironmentVariable`;
# without that step a bare `xlings.exe` call depends on the bash
# export PATH, which Git Bash re-derives from Windows on every child
# shell and drops the mixed-separator entry. install_pinned_mcpp.sh
# carries the same note.
XL_BIN_PATH="$USERPROFILE/.xlings/subos/default/bin/xlings.exe"
if [ -x "$XL_BIN_PATH" ]; then
xl_ver="$("$XL_BIN_PATH" --version 2>/dev/null | head -1 || true)"
if [ -n "$xl_ver" ] && echo "$xl_ver" | grep -qF "$XLINGS_VERSION"; then
export PATH="$USERPROFILE/.xlings/subos/default/bin:$PATH"
echo "$USERPROFILE/.xlings/subos/default/bin" >> "$GITHUB_PATH"
"$XL_BIN_PATH" --version
MCPP=$(bash "$REPO_DIR/.github/tools/install_pinned_mcpp.sh" "$REPO_DIR")
echo "system xlings: $("$XL_BIN_PATH" --version 2>/dev/null | head -1)"
if [ -x "$USERPROFILE/.mcpp/registry/bin/xlings.exe" ]; then
echo "sandbox xlings: $("$USERPROFILE/.mcpp/registry/bin/xlings.exe" --version 2>/dev/null | head -1)"
else
echo "sandbox xlings: (not initialised yet)"
fi
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
echo "XLINGS_BIN=$(cygpath -w "$XL_BIN_PATH")" >> "$GITHUB_ENV"
exit 0
fi
fi

WORK=$(mktemp -d)
zipfile="xlings-${XLINGS_VERSION}-windows-x86_64.zip"
# Same helper as the unix leg. This is the leg that kept failing, and a
Expand Down Expand Up @@ -207,17 +292,3 @@ runs:
# Precise key on src/ + manifest so a no-source-change run lands on a full
# hit; layered restore-keys let partial hits keep BMI/dyndep state for a
# proper incremental build.
- name: Cache target/ (build artifacts + BMIs)
if: inputs.cache-target == 'true'
uses: actions/cache@v4
with:
path: target
# `modules/**` belongs here as much as `src/**` does. mcpp's own
# source lives in both since the subsystem split, and a key that hashed
# only one of them would restore a target/ built from different sources
# and report success — the failure mode a cache key exists to prevent,
# arriving silently.
key: mcpp-target-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ hashFiles('src/**', 'modules/**', 'tests/**', 'mcpp.toml', 'mcpp.lock') }}
restore-keys: |
mcpp-target-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-
mcpp-target-${{ runner.os }}-${{ runner.arch }}-
15 changes: 13 additions & 2 deletions .github/actions/setup-macos-llvm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,22 @@ inputs:
required: false
default: 'macos-15'

outputs:
xlings-key:
description: the exact key of the xlings cache
value: ${{ steps.xlings.outputs.cache-primary-key }}
xlings-hit:
description: "'true' when xlings was restored by its exact key"
value: ${{ steps.xlings.outputs.cache-hit }}

runs:
using: composite
steps:
- name: Cache xlings
uses: actions/cache@v4
# Restored, never saved here: the macOS build job of
# .github/workflows/build.yml is the one writer, on a push to main (rule R3).
- name: Restore xlings
id: xlings
uses: actions/cache/restore@v4
with:
path: ~/.xlings
key: xlings-${{ inputs.image }}-arm64-v3-xl${{ inputs.xlings-version }}-${{ hashFiles('.xlings.json') }}
Expand Down
44 changes: 44 additions & 0 deletions .github/actions/use-built-mcpp/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: use-built-mcpp
description: >
Put this commit's mcpp, built once per host by .github/workflows/build.yml,
in place of a build of the job's own (rule R1 of
.agents/docs/2026-10-02-pr-ci-acceleration-and-the-toolchain-specification-design.md).

Run it after bootstrap-mcpp or setup-macos-llvm, which restore the sandbox
and install the released bootstrap. It exports MCPP_BOOT (that bootstrap),
MCPP and MCPP_FRESH (this commit's binary, at an absolute path), and
MCPP_VENDORED_XLINGS, and sets the given mirror on xlings and on the binary.

The binary is the one the build job produced, not a repackaging of it, so
every consumer tests what a self-host build makes. On Linux that binary's
interpreter and runtime libraries live in payloads of the sandbox (glibc and
the GCC runtime of the toolchain mcpp.toml names). A restored sandbox holds
them; when it does not, the bootstrap installs that toolchain and the binary
is run again. A binary that still does not run fails this step.

The toolchain mcpp.toml names for the host is then installed with the
binary, as a job that built mcpp used to install it as a side effect.

inputs:
host:
description: >
The host the artifact was built on: linux-x86_64, linux-aarch64,
macos-arm64 or windows-x86_64.
required: true
mirror:
description: The mirror xlings and mcpp use in this job.
required: false
default: GLOBAL

runs:
using: composite
steps:
- name: Download this commit's mcpp (${{ inputs.host }})
uses: actions/download-artifact@v4
with:
name: mcpp-built-${{ inputs.host }}
path: ${{ runner.temp }}/mcpp-built

- name: Use this commit's mcpp
shell: bash
run: bash "$GITHUB_ACTION_PATH/use.sh" "${{ inputs.host }}" "${{ inputs.mirror }}"
82 changes: 82 additions & 0 deletions .github/actions/use-built-mcpp/use.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# The body of the use-built-mcpp action; see action.yml for what it provides.
#
# Usage: use.sh <host> <mirror>
set -euo pipefail

host="$1"
mirror="$2"

dir="$RUNNER_TEMP/mcpp-built"
case "$host" in
windows-*) exe=mcpp.exe; dir="$(cygpath -u "$dir")" ;;
*) exe=mcpp ;;
esac
bin="$dir/$exe"
if [ ! -f "$bin" ]; then
echo "::error::the artifact mcpp-built-$host holds no $exe"
ls -la "$dir" || true
exit 1
fi
chmod +x "$bin"

boot="${MCPP:-}"
if [ -z "$boot" ]; then
echo "::error::MCPP is unset: run bootstrap-mcpp or setup-macos-llvm before use-built-mcpp"
exit 1
fi

# The toolchain mcpp.toml names for this host is the one the build used, so it
# is the one whose payloads hold the binary's runtime.
manifest_toolchain() {
local key
case "$host" in
macos-*) key=macos ;;
windows-*) key=windows ;;
*) key=default ;;
esac
awk -v k="$key" '
/^\[/ { in_tc = ($0 == "[toolchain]") ; next }
in_tc && $1 == k { gsub(/"/, "", $3); print $3; exit }
' mcpp.toml
}

if ! out=$("$bin" --version 2>&1); then
tc="$(manifest_toolchain)"
echo "this commit's mcpp does not run yet ($out); installing ${tc:-the default toolchain} with the bootstrap"
if [ -n "$tc" ]; then
"$boot" toolchain install "${tc%@*}" "${tc#*@}"
fi
if ! out=$("$bin" --version 2>&1); then
echo "::error::this commit's mcpp does not run on this runner: $out"
exit 1
fi
fi
echo "this commit's mcpp: $out ($bin)"

# The mirror first: the runners are outside CN, and the install below and
# every later download read it.
if [ -n "${XLINGS_BIN:-}" ]; then
"$XLINGS_BIN" config --mirror "$mirror" 2>/dev/null || true
fi
MCPP_VENDORED_XLINGS="${XLINGS_BIN:-}" "$bin" self config --mirror "$mirror"

# THE TOOLCHAIN THE BUILD USED IS INSTALLED, AS IT WAS WHEN EVERY JOB BUILT.
# A job that built mcpp itself installed this toolchain as a side effect, and
# the steps after the build relied on it without saying so: measured on the
# first run of this action, the aarch64 leg of the target matrix restored no
# sandbox, its binary ran without any payload, and the invariants that list the
# host's toolchains found none ("gcc is not installed here"). Installing it here
# keeps every consumer's environment what it was. It is a lookup when the
# toolchain is present.
tc="$(manifest_toolchain)"
if [ -n "$tc" ]; then
MCPP_VENDORED_XLINGS="${XLINGS_BIN:-}" "$bin" toolchain install "${tc%@*}" "${tc#*@}"
fi

{
echo "MCPP_BOOT=$boot"
echo "MCPP=$bin"
echo "MCPP_FRESH=$bin"
if [ -n "${XLINGS_BIN:-}" ]; then echo "MCPP_VENDORED_XLINGS=$XLINGS_BIN"; fi
} >> "$GITHUB_ENV"
Loading
Loading