Skip to content

Commit 6626f2e

Browse files
committed
0.6.0 —— 干净机器上,构建程序现在说得出话
mcpp 2026.8.21.2 给了构建程序一条「成功但有话说」的通道。这个工程正是它的 动机:清单的 [xlings] deps 是声明不是安装触发器,干净机器上 xpkg_dir 返回空、 不配 runner,而 mcpp run 给出一条一般情况下对、在此处不对的建议。 - examples/switch 与 clock-study 的 else 分支改用 mcpp::warning(),说出确切 的那条安装命令 - CI 新增「A clean machine is told what is missing」,放在**装模拟器之前** —— 那是生态里唯一能观察到这条通知的地方 - .ci-identical-files 声明「示例与模板的构建程序自 The emulator 起逐字节相同」, 由 CI 断言。0.5.0 的缺陷正是这两份拷贝改了一份漏了另一份,而修法不是更仔细 - 模板 build.mcpp.in 已同步 ⚠️ 写那条一致性检查时先用了 sed 地址,而标记里含 /,sed 把它当模式结尾: 两边都空、diff 通过。改用 grep -F + tail,并断言两侧非空 —— 那正是这条规则 要防的形状,它先在自己身上出现了一次。
1 parent 3613005 commit 6626f2e

10 files changed

Lines changed: 136 additions & 30 deletions

File tree

.ci-identical-files

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Pairs that must stay byte-identical from a marker onward, checked by
2+
# mcpp's tools/lint-ci-assertions.sh (rule R2).
3+
#
4+
# ⚠️ THIS FILE EXISTS BECAUSE 0.5.0 SHIPPED A TEMPLATE WHOSE BUILD PROGRAM
5+
# DISAGREED WITH ITS OWN README. The example's build program was updated and
6+
# the template's copy was not; the fix is not "be more careful", which had
7+
# already been tried, but to assert sameness.
8+
#
9+
# Format: <a>TAB<b>TAB<marker>
10+
examples/switch/build.mcpp templates/three-machines/build.mcpp.in // The emulator.

.github/workflows/ci.yml

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
# carries it, and the row is now the same shape as the other two.
4242
- { arch: x86_64, triple: x86_64-none-elf, qemu: 'xim:qemu-x86' }
4343
env:
44-
MCPP_VERSION: 2026.8.21.1
44+
MCPP_VERSION: 2026.8.21.2
4545
XLINGS_VERSION: v2026.8.17.2
4646
XLINGS_NON_INTERACTIVE: '1'
4747
steps:
@@ -110,6 +110,33 @@ jobs:
110110
# The three packages are different builds, one per target family:
111111
# `qemu-riscv` carries the RISC-V emulators, `qemu-arm` the Arm ones and
112112
# `qemu-x86` the x86_64 one. Measured; no single package runs all three.
113+
# ⭐ BEFORE THE EMULATOR IS INSTALLED, AND THAT ORDER IS THE ASSERTION.
114+
#
115+
# `[xlings] deps` is a DECLARATION, not an install trigger, so on a clean
116+
# machine `xpkg_dir` answers empty and this project's build program
117+
# configures no runner. That is correct and it used to be SILENT: mcpp
118+
# prints a build program's output only on a non-zero exit, so a
119+
# `std::cerr` note printed nothing on exactly the builds that needed it.
120+
#
121+
# mcpp 2026.8.21.2's `mcpp::warning()` is the channel. This step is the
122+
# only place the ecosystem can observe it, because every later step has
123+
# the emulator — so it runs here, once, and asserts the sentence a first
124+
# -time user sees.
125+
- name: A clean machine is told what is missing
126+
working-directory: examples/switch
127+
run: |
128+
set -euo pipefail
129+
COLD=$(mktemp -d)
130+
MCPP_HOME="$COLD" mcpp build --target ${{ matrix.triple }} 2>&1 | tee cold.log
131+
grep -q "is not installed" cold.log \
132+
|| { cat cold.log; echo "the build program said nothing about the missing emulator"; exit 1; }
133+
grep -q "xlings install" cold.log \
134+
|| { cat cold.log; echo "the advisory does not name the command that fixes it"; exit 1; }
135+
# An advisory is not an error: the build still succeeded.
136+
grep -q "Finished" cold.log \
137+
|| { cat cold.log; echo "the build did not finish"; exit 1; }
138+
rm -rf "$COLD" target
139+
113140
- name: Install the emulator
114141
run: |
115142
xlings install ${{ matrix.qemu }} -y
@@ -188,7 +215,10 @@ jobs:
188215
# while mcpp wants a native path — a mismatch this repository has been
189216
# bitten by before.
190217
- name: The template generates a project that builds for all three machines
191-
if: matrix.arch == 'riscv64'
218+
# The claim here is host-independent — the template's CONTENT builds for
219+
# all three machines — so one row is the right place for it. The per-row
220+
# claim, that it RUNS, is the separate step below.
221+
if: matrix.arch == 'riscv64' # ci-lint: allow-r1: host-independent claim
192222
run: |
193223
set -euo pipefail
194224
T=templates/three-machines
@@ -255,6 +285,39 @@ jobs:
255285
# The same source produced that output. Asserted rather than trusted: a
256286
# probe that had quietly grown a per-architecture branch would still pass
257287
# every line above, and the gate would be measuring two programs.
288+
# ⚠️ ONE PIECE OF LOGIC LIVING IN TWO FILES IS WHAT SHIPPED 0.5.0's DEFECT:
289+
# the example's build program was updated and the template's copy was not,
290+
# so the template's README claimed something its own program could not do.
291+
#
292+
# The fix is not "be more careful" — that had already been tried once. The
293+
# pairs are declared in `.ci-identical-files` and asserted here.
294+
- name: Files declared identical are identical
295+
run: |
296+
set -euo pipefail
297+
test -s .ci-identical-files
298+
n=0
299+
while IFS=$'\t' read -r a b marker; do
300+
case "${a:-}" in ''|'#'*) continue ;; esac
301+
test -f "$a" && test -f "$b" || { echo "declared pair missing a side: $a / $b"; exit 1; }
302+
# ⚠️ `grep -F` + `tail`, NOT a sed address. The marker is a line of
303+
# C++ and contains `/`, which sed reads as the end of its pattern —
304+
# measured: `unknown command: '/'`, both sides empty, and the diff
305+
# would then have PASSED. That is the emptiness-as-success shape
306+
# this repository has already been bitten by once.
307+
la=$(grep -nF -m1 "$marker" "$a" | cut -d: -f1)
308+
lb=$(grep -nF -m1 "$marker" "$b" | cut -d: -f1)
309+
test -n "$la" && test -n "$lb" \
310+
|| { echo "marker '$marker' not found in $a or $b"; exit 1; }
311+
tail -n +"$la" "$a" > /tmp/ia
312+
tail -n +"$lb" "$b" > /tmp/ib
313+
test -s /tmp/ia && test -s /tmp/ib \
314+
|| { echo "marker '$marker' matched an empty tail in $a or $b"; exit 1; }
315+
diff -u /tmp/ia /tmp/ib || { echo "$a and $b diverge after '$marker'"; exit 1; }
316+
n=$((n+1))
317+
done < .ci-identical-files
318+
test "$n" -ge 1 || { echo ".ci-identical-files declared no pairs"; exit 1; }
319+
echo "$n declared pair(s) identical"
320+
258321
- name: The probe is one source, and the layering is a directory tree
259322
run: |
260323
set -euo pipefail
@@ -347,7 +410,7 @@ jobs:
347410
run:
348411
shell: bash
349412
env:
350-
MCPP_VERSION: 2026.8.21.1
413+
MCPP_VERSION: 2026.8.21.2
351414
XLINGS_VERSION: v2026.8.17.2
352415
XLINGS_NON_INTERACTIVE: '1'
353416
steps:
@@ -437,7 +500,7 @@ jobs:
437500
run:
438501
shell: bash
439502
env:
440-
MCPP_VERSION: 2026.8.21.1
503+
MCPP_VERSION: 2026.8.21.2
441504
XLINGS_VERSION: v2026.8.17.2
442505
XLINGS_NON_INTERACTIVE: '1'
443506
steps:

abi/mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
[package]
2525
namespace = "mcpplibs"
2626
name = "openarch-abi"
27-
version = "0.5.1"
27+
version = "0.6.0"
2828
description = "openarch's C ABI: the contract between the interface and an instruction set's backend"
2929
license = "Apache-2.0"
3030
authors = ["mcpplibs"]

backends/aarch64/mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
[package]
1515
namespace = "mcpplibs"
1616
name = "openarch-aarch64"
17-
version = "0.5.1"
17+
version = "0.6.0"
1818
description = "openarch's aarch64 backend: the instructions behind the ABI"
1919
license = "Apache-2.0"
2020
authors = ["mcpplibs"]

backends/riscv64/mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
[package]
1515
namespace = "mcpplibs"
1616
name = "openarch-riscv64"
17-
version = "0.5.1"
17+
version = "0.6.0"
1818
description = "openarch's riscv64 backend: the instructions behind the ABI"
1919
license = "Apache-2.0"
2020
authors = ["mcpplibs"]

backends/x86_64/mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
[package]
2323
namespace = "mcpplibs"
2424
name = "openarch-x86-64"
25-
version = "0.5.1"
25+
version = "0.6.0"
2626
description = "openarch's x86_64 backend: the instructions behind the ABI"
2727
license = "Apache-2.0"
2828
authors = ["mcpplibs"]

examples/clock-study/build.mcpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,19 @@ int main() {
6969
// those ARE installed with the package. This project has no board package —
7070
// none serves three machines — so it declares them itself.
7171
//
72-
// ⚠️ AND IT CANNOT WARN. A build program's output reaches the user only
73-
// when it FAILS: mcpp captures the process and prints what it captured
74-
// solely on a non-zero exit. A `std::cerr` note here was written, measured,
75-
// and removed — it printed nothing on the very builds that needed it, which
76-
// is worse than no note at all because it looks like a fix. Failing instead
77-
// would be wrong too: `mcpp build` has no need of an emulator.
72+
// ⚠️ IT USED NOT TO BE ABLE TO SAY SO, AND THAT GAP IS NOW CLOSED.
7873
//
79-
// So the README carries it, and this comment records why it has to.
74+
// A build program's output reaches the user only when it FAILS: mcpp
75+
// captures the process and prints what it captured solely on a non-zero
76+
// exit. A `std::cerr` note here was written, measured, and removed — it
77+
// printed nothing on the very builds that needed it, which is worse than no
78+
// note at all because it looks like a fix. Failing instead would be wrong
79+
// too: `mcpp build` has no need of an emulator.
80+
//
81+
// mcpp 2026.8.21.2 added `mcpp::warning()` for exactly this: a program that
82+
// handled a condition correctly and still has something to say. The `else`
83+
// branch below is what that channel exists for, and it survives the build
84+
// cache, so it does not appear once and then look resolved.
8085
if (const char* dir = mcpp::xpkg_dir("xim", pkg); dir && *dir) {
8186
mcpp::runner(std::format("{}/bin/qemu-system-{}", dir, sys).c_str());
8287
// ⚠️ THE MACHINE TYPE IS NOT UNIVERSAL. `virt` is the para-virtual
@@ -99,6 +104,12 @@ int main() {
99104
// instruction: with `-kernel` and no `-bios` it runs the image directly.
100105
if (arch == "riscv64") { mcpp::runner("-bios"); mcpp::runner("none"); }
101106
mcpp::runner("-kernel");
107+
} else {
108+
mcpp::warning(std::format(
109+
"{} is not installed, so `mcpp run --target {}-none-elf` has no "
110+
"runner. `mcpp build` is unaffected. Install the three emulators "
111+
"once: xlings install qemu-riscv qemu-arm qemu-x86 -y",
112+
pkg, arch).c_str());
102113
}
103114

104115
mcpp::rerun_if_env_changed("MCPP_TARGET_ARCH");

examples/switch/build.mcpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,19 @@ int main() {
6969
// those ARE installed with the package. This project has no board package —
7070
// none serves three machines — so it declares them itself.
7171
//
72-
// ⚠️ AND IT CANNOT WARN. A build program's output reaches the user only
73-
// when it FAILS: mcpp captures the process and prints what it captured
74-
// solely on a non-zero exit. A `std::cerr` note here was written, measured,
75-
// and removed — it printed nothing on the very builds that needed it, which
76-
// is worse than no note at all because it looks like a fix. Failing instead
77-
// would be wrong too: `mcpp build` has no need of an emulator.
72+
// ⚠️ IT USED NOT TO BE ABLE TO SAY SO, AND THAT GAP IS NOW CLOSED.
7873
//
79-
// So the README carries it, and this comment records why it has to.
74+
// A build program's output reaches the user only when it FAILS: mcpp
75+
// captures the process and prints what it captured solely on a non-zero
76+
// exit. A `std::cerr` note here was written, measured, and removed — it
77+
// printed nothing on the very builds that needed it, which is worse than no
78+
// note at all because it looks like a fix. Failing instead would be wrong
79+
// too: `mcpp build` has no need of an emulator.
80+
//
81+
// mcpp 2026.8.21.2 added `mcpp::warning()` for exactly this: a program that
82+
// handled a condition correctly and still has something to say. The `else`
83+
// branch below is what that channel exists for, and it survives the build
84+
// cache, so it does not appear once and then look resolved.
8085
if (const char* dir = mcpp::xpkg_dir("xim", pkg); dir && *dir) {
8186
mcpp::runner(std::format("{}/bin/qemu-system-{}", dir, sys).c_str());
8287
// ⚠️ THE MACHINE TYPE IS NOT UNIVERSAL. `virt` is the para-virtual
@@ -99,6 +104,12 @@ int main() {
99104
// instruction: with `-kernel` and no `-bios` it runs the image directly.
100105
if (arch == "riscv64") { mcpp::runner("-bios"); mcpp::runner("none"); }
101106
mcpp::runner("-kernel");
107+
} else {
108+
mcpp::warning(std::format(
109+
"{} is not installed, so `mcpp run --target {}-none-elf` has no "
110+
"runner. `mcpp build` is unaffected. Install the three emulators "
111+
"once: xlings install qemu-riscv qemu-arm qemu-x86 -y",
112+
pkg, arch).c_str());
102113
}
103114

104115
mcpp::rerun_if_env_changed("MCPP_TARGET_ARCH");

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
[package]
3434
namespace = "mcpplibs"
3535
name = "openarch"
36-
version = "0.5.1"
36+
version = "0.6.0"
3737
description = "openarch: the architecture-mechanism layer — execution contexts, traps, per-CPU state and address spaces, as one interface over several instruction sets"
3838
license = "Apache-2.0"
3939
authors = ["mcpplibs"]

templates/three-machines/build.mcpp.in

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,19 @@ int main() {
6969
// those ARE installed with the package. This project has no board package —
7070
// none serves three machines — so it declares them itself.
7171
//
72-
// ⚠️ AND IT CANNOT WARN. A build program's output reaches the user only
73-
// when it FAILS: mcpp captures the process and prints what it captured
74-
// solely on a non-zero exit. A `std::cerr` note here was written, measured,
75-
// and removed — it printed nothing on the very builds that needed it, which
76-
// is worse than no note at all because it looks like a fix. Failing instead
77-
// would be wrong too: `mcpp build` has no need of an emulator.
72+
// ⚠️ IT USED NOT TO BE ABLE TO SAY SO, AND THAT GAP IS NOW CLOSED.
7873
//
79-
// So the README carries it, and this comment records why it has to.
74+
// A build program's output reaches the user only when it FAILS: mcpp
75+
// captures the process and prints what it captured solely on a non-zero
76+
// exit. A `std::cerr` note here was written, measured, and removed — it
77+
// printed nothing on the very builds that needed it, which is worse than no
78+
// note at all because it looks like a fix. Failing instead would be wrong
79+
// too: `mcpp build` has no need of an emulator.
80+
//
81+
// mcpp 2026.8.21.2 added `mcpp::warning()` for exactly this: a program that
82+
// handled a condition correctly and still has something to say. The `else`
83+
// branch below is what that channel exists for, and it survives the build
84+
// cache, so it does not appear once and then look resolved.
8085
if (const char* dir = mcpp::xpkg_dir("xim", pkg); dir && *dir) {
8186
mcpp::runner(std::format("{}/bin/qemu-system-{}", dir, sys).c_str());
8287
// ⚠️ THE MACHINE TYPE IS NOT UNIVERSAL. `virt` is the para-virtual
@@ -99,6 +104,12 @@ int main() {
99104
// instruction: with `-kernel` and no `-bios` it runs the image directly.
100105
if (arch == "riscv64") { mcpp::runner("-bios"); mcpp::runner("none"); }
101106
mcpp::runner("-kernel");
107+
} else {
108+
mcpp::warning(std::format(
109+
"{} is not installed, so `mcpp run --target {}-none-elf` has no "
110+
"runner. `mcpp build` is unaffected. Install the three emulators "
111+
"once: xlings install qemu-riscv qemu-arm qemu-x86 -y",
112+
pkg, arch).c_str());
102113
}
103114

104115
mcpp::rerun_if_env_changed("MCPP_TARGET_ARCH");

0 commit comments

Comments
 (0)