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
10 changes: 5 additions & 5 deletions .github/tools/check_version_pins.sh
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ fi
# full index, and the mechanism is not yet understood; what is established
# is the pattern and its fix.
#
# The operational rule that satisfies both directions: after publishing
# release N, the post-release commit sets this pin to N. Never ahead
# (check (c) enforces that), and in practice never behind either. This is
# not checkable here — it needs the index — so it surfaces as the error
# above, and this note is where to look when it does.
# That job now checks the repository out last, so the pin no longer
# reaches it. The rule that stands: never ahead (check (c) enforces it),
# and behind for as long as the pinned mcpp builds the tree, which
# ci-bootstrap.yml and build.yml measure with `mcpp build --strict`
# (docs/92-release.md §4).

# (c) …and the bootstrap pin must never run AHEAD of the version being built.
# Four-key numeric sort, so the date scheme orders correctly (a plain
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ jobs:
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$XLINGS_BIN" config --mirror GLOBAL 2>/dev/null || true
"$MCPP" self config --mirror GLOBAL 2>/dev/null || true
"$MCPP" build
# --strict: a key this bootstrap mcpp does not know is an error, not
# a warning. A bootstrap older than the manifest used to drop such a
# key and build something else, and still exit 0 (2026.9.24.1 and
# `[test] windows_code_page`): the moment `.xlings.json` must move is
# the moment this fails.
"$MCPP" build --strict
# target/ is not restored from a cache, so the tree holds exactly the
# binary this build linked.
case "${{ inputs.host }}" in windows-*) exe=mcpp.exe ;; *) exe=mcpp ;; esac
Expand Down
143 changes: 143 additions & 0 deletions .github/workflows/ci-bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: ci-bootstrap

# Which released mcpp builds this tree, measured the way a developer builds it.
#
# `.xlings.json` names the mcpp a clone of this repository builds with: inside
# the checkout the `mcpp` shim refuses to run any other version
# ("mcpp@<pin> is the version this project asks for ... set this project up:
# xlings install"). The pin does not move with every release; it moves when the
# tree starts to need something the pinned mcpp does not have. Two jobs answer
# whether that moment has come:
#
# developer the pinned mcpp, installed by `xlings install` in the checkout
# (project scope, no -g) and run through the shim, builds the tree
# with --strict, on Linux, macOS and Windows. build.yml builds with
# the same version but installs it globally and runs it by path; a
# shim or project-scope defect, or a cold runner, is only seen here.
# latest the newest release builds the tree with --strict. It runs only
# while the pin lags the newest release; when they are equal the
# developer job has already answered.
#
# --strict is the assertion. A mcpp older than the manifest warns about a key it
# does not know, drops it and exits 0 (2026.9.24.1 against
# `[test] windows_code_page`); under --strict that is an error.
#
# No cache, on purpose: the subject is a fresh clone.

on:
workflow_call:

permissions:
contents: read

jobs:
developer:
name: the pinned mcpp builds the tree (${{ matrix.os }}, xlings install + mcpp build --strict)
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-15, windows-2025]
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '2026.10.10.2'
steps:
- uses: actions/checkout@v4

- name: Install xlings
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.10.10.2
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
echo "$HOME/.xlings/bin" >> "$GITHUB_PATH"

- name: Install xlings
if: runner.os == 'Windows'
shell: pwsh
run: |
irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8

- name: Install the mcpp .xlings.json names, in this project
shell: bash
run: |
set -euo pipefail
xlings install -y
pin=$(grep -oE '"mcpp"[[:space:]]*:[[:space:]]*"[^"]+"' .xlings.json \
| grep -oE '[0-9]+(\.[0-9]+)+' | head -1)
got=$(mcpp --version | head -1 | awk '{print $2}' | tr -d '\r')
[ -n "$pin" ] && [ "$got" = "$pin" ] || {
echo "::error::.xlings.json names mcpp ${pin:-?}; the shim in this checkout runs ${got:-nothing}"; exit 1; }
echo "the shim runs mcpp $got, the version .xlings.json names"

- name: Build the tree with it (mcpp build --strict)
shell: bash
run: |
set -euo pipefail
mcpp self config --mirror GLOBAL
mcpp build --strict
case "$RUNNER_OS" in Windows) exe=mcpp.exe ;; *) exe=mcpp ;; esac
built=$(find target -type f -name "$exe" -path '*/bin/*' | grep -v '/dist/' || true)
[ "$(printf '%s\n' "$built" | grep -c .)" = 1 ] || {
echo "::error::expected one $exe under target/, found: ${built:-none}"; exit 1; }
want=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml)
got=$("$built" --version | head -1 | tr -d '\r')
[ "$got" = "mcpp $want" ] || {
echo "::error::the built binary says '$got', mcpp.toml says $want"; exit 1; }
echo "built $got"

latest:
name: the newest release builds the tree (linux, when the pin lags it)
runs-on: ubuntu-24.04
timeout-minutes: 45
env:
XLINGS_NON_INTERACTIVE: '1'
steps:
- uses: actions/checkout@v4

- name: Compare the pin with the newest release
id: cmp
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
pin=$(grep -oE '"mcpp"[[:space:]]*:[[:space:]]*"[^"]+"' .xlings.json \
| grep -oE '[0-9]+(\.[0-9]+)+' | head -1)
latest=$(gh api "repos/${{ github.repository }}/releases/latest" --jq .tag_name | sed 's/^v//')
[ -n "$pin" ] && [ -n "$latest" ] || {
echo "::error::could not read the pin (${pin:-?}) or the newest release (${latest:-?})"; exit 1; }
echo "pin=$pin latest=$latest"
echo "latest=$latest" >> "$GITHUB_OUTPUT"
if [ "$pin" = "$latest" ]; then
echo "run=false" >> "$GITHUB_OUTPUT"
echo "::notice::.xlings.json names the newest release ($latest); the developer job covers it"
else
echo "run=true" >> "$GITHUB_OUTPUT"
fi

- name: Install xlings
if: steps.cmp.outputs.run == 'true'
shell: bash
run: |
set -euo pipefail
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.10.10.2
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"

- name: Install the newest release and build the tree with it (mcpp build --strict)
if: steps.cmp.outputs.run == 'true'
shell: bash
run: |
set -euo pipefail
# Makes the released version the one the shim runs here, over the
# checkout's own pin, and asserts it.
bash .github/tools/install_released_mcpp.sh "${{ steps.cmp.outputs.latest }}" "$(pwd)"
mcpp self config --mirror GLOBAL
mcpp build --strict
built=$(find target -type f -name mcpp -path '*/bin/*' | grep -v '/dist/' || true)
[ "$(printf '%s\n' "$built" | grep -c .)" = 1 ] || {
echo "::error::expected one mcpp under target/, found: ${built:-none}"; exit 1; }
"$built" --version
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ jobs:
# The MinGW payload every Windows e2e shard installs.
prewarm: mingw 16.1.0

# The mcpp .xlings.json names builds the tree as a developer's clone does,
# and so does the newest release while the pin lags it (ci-bootstrap.yml).
bootstrap:
needs: changes
if: needs.changes.outputs.code == 'true'
uses: ./.github/workflows/ci-bootstrap.yml

linux:
needs: build-linux
uses: ./.github/workflows/ci-linux.yml
Expand Down
2 changes: 1 addition & 1 deletion .xlings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"workspace": {
"mcpp": "2026.10.8.1"
"mcpp": "2026.10.10.2"
}
}
26 changes: 21 additions & 5 deletions docs/92-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,17 +189,33 @@ up. `ci-fresh-install`'s `wait-index` job encodes exactly this with a bounded
## 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
mcpp from source. Its only requirement is that it can build the **current** tree.
the released mcpp that `xlings install` puts in the workspace so CI can build
mcpp from source. It is also what a developer builds with: inside a clone, the
`mcpp` shim runs this version and no other, and refuses to run until
`xlings install` has installed it. Its only requirement is that it can build the
**current** tree.

**It does not have to move with every release.** The index retains every
published version (105 entries at the time of writing, back to the 0.0.x series),
so an older pin keeps resolving indefinitely — verified by installing a
two-releases-old version against the current index.

Bumping it anyway is reasonable and is what this repository does in practice: a
green CI round on the bumped pin is a direct proof that the new release can build
mcpp itself on every platform. Treat it as a *useful check*, not a prerequisite.
**It moves when the tree needs it**: when `mcpp.toml` or the sources use something
the pinned mcpp does not have — a key, a table, a toolchain line. CI tells when
that moment has come, because the pinned mcpp builds the tree with `--strict`,
which makes a key it does not know an error. Without it the pinned mcpp warns,
drops the key, builds something other than the manifest describes, and exits 0
(2026.9.24.1 against the `[test] windows_code_page` of 2026.10.5.2). Three jobs build
the tree on every pull request that changes code:

| Job | mcpp | Installation and build |
| --- | --- | --- |
| `build.yml` (four hosts) | the pin | installed globally by `install_pinned_mcpp.sh`, `mcpp build --strict` |
| `ci-bootstrap.yml` `developer` (Linux, macOS, Windows) | the pin | a cold clone: `xlings install` in the checkout, then `mcpp build --strict` through the shim |
| `ci-bootstrap.yml` `latest` (Linux) | the newest release | only while the pin lags it |

A pull request that needs a newer pin fails there, and moves the pin in the same
pull request.

**The one hard constraint is direction**: the pin must never name a version that
is not yet installable. Bump it only after the release is published, mirrored,
Expand Down
22 changes: 17 additions & 5 deletions docs/zh/92-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,28 @@ $(find "$XLINGS_HOME" -name mcpp -type f -path '*/bin/*' | head -1) --version
## 4. 自举 pin 的定义与更新条件

`.xlings.json` 的 `[workspace].mcpp` 是**自举的起点**——那个由
`xlings install mcpp` 装进 workspace、供 CI 从源码构建 mcpp 的已发布
mcpp。它唯一的要求是:能构建**当前**这棵源码树。
`xlings install` 装进 workspace、供 CI 从源码构建 mcpp 的已发布 mcpp。
它也是开发者构建所用的版本:在克隆的仓库里,`mcpp` shim 只运行这个版本,
未经 `xlings install` 安装时拒绝运行。它唯一的要求是:能构建**当前**这棵
源码树。

**它不必每次发布都跟着动。** 索引保留每一个已发布版本(撰写时 105 个
条目,一直回溯到 0.0.x 系列),旧 pin 可以无限期继续解析——这一点用
「在当前索引下安装一个隔了两个版本的旧版」实测验证过。

跟着 bump 仍然是合理的,也是本仓库的实际做法:bump 后 CI 一轮全绿,直接
证明了新发布能在每个平台上构建 mcpp 自己。把它当作**一项有用的检查**,
而不是前置条件。
**树需要时它才动**:`mcpp.toml` 或源码用到了固定版本没有的东西——一个键、
一张表、一条工具链线。何时到了这一刻由 CI 判定:固定版本以 `--strict` 构建
这棵树,它不认识的键是错误。不加 `--strict` 时,固定版本对不认识的键只给
警告、丢弃该键、构建出与清单不同的结果,并以 0 退出(2026.9.24.1 遇到
2026.10.5.2 的 `[test] windows_code_page`)。每个改动代码的 PR 上有三组构建:

| 作业 | mcpp | 方式 |
| --- | --- | --- |
| `build.yml`(四个主机) | 固定版本 | `install_pinned_mcpp.sh` 全局安装,`mcpp build --strict` |
| `ci-bootstrap.yml` `developer`(Linux、macOS、Windows) | 固定版本 | 冷克隆:在仓库内 `xlings install`,经 shim 执行 `mcpp build --strict` |
| `ci-bootstrap.yml` `latest`(Linux) | 最新发布版 | 仅在固定版本落后于最新发布版时运行 |

需要更新固定版本的 PR 在这里失败,并在同一个 PR 中更新它。

**唯一的硬约束是方向**:pin 绝不能指向一个尚不可安装的版本。只在发布
已完成、已镜像、**且已合入 xim-pkgindex 之后**再 bump——否则所有 CI 会
Expand Down
Loading