Repository navigation
227 lines (217 loc) · 11.3 KB
/
Copy pathci.yml
File metadata and controls
227 lines (217 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
# ⭐ THE GATE IS THAT THE BOARD BOOTS, NOT THAT IT COMPILES.
#
# A board package that builds and does not boot is the failure this ecosystem
# has already shipped once: a template whose generated project compiled for
# three machines and ran on none of them passed a check that stopped at
# `mcpp build`. Every assertion below runs the image.
jobs:
board:
name: the board boots
runs-on: ubuntu-24.04
timeout-minutes: 40
env:
# ⚠️⚠️ 2026.9.4.2, AND THE REASON IS THIS PACKAGE'S OWN CHANGE.
#
# The runner names its program by bare name, and mcpp searches the payload
# a manifest declared before it searches PATH. #544 shipped that search
# over the ROOT project's declarations only — so the bare name resolved
# when the CONSUMER declared the emulator and failed when THIS package
# did, which is backwards and is the duplication a board package exists to
# remove. 2026.9.4.2 extends both the search and the provisioning to every
# package in the graph.
#
# ⚠️ CI pins what the README tells people to use, and this package must
# not be PUBLISHED before that engine is: publishing first would leave the
# board broken on every engine that already has it.
MCPP_VERSION: 2026.9.4.2
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
steps:
- uses: actions/checkout@v4
- name: Install xlings
run: |
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \
| bash -s "$XLINGS_VERSION"
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
- name: Install mcpp
run: |
# A loop, because one `xlings update` can return a stale index without
# saying so: the index is published as an artifact behind a pointer,
# and that pointer propagates asynchronously after a merge.
for attempt in 1 2 3 4 5 6; do
xlings update > /dev/null 2>&1 || true
if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi
if [ "$attempt" = 6 ]; then
echo "::error::mcpp@$MCPP_VERSION never appeared in the index"
exit 1
fi
sleep 60
done
mcpp --version
mcpp self config --mirror GLOBAL
# ⭐⭐ THE ADVISORY IS GONE, AND ITS ABSENCE IS WHAT THIS STEP NOW
# ASSERTS.
#
# Until 0.2.0 this board computed the emulator's path from
# `mcpp::xpkg_dir` and printed a `mcpp::warning` when it answered empty —
# which it does for anyone building from a checkout, because a declaration
# is not an install. The board configured no runner, and the advisory
# existed to explain why.
#
# Naming the program deleted that failure mode rather than moving it. The
# engine searches the payloads the graph declared and then PATH, and when
# the program is nowhere it reports which directories it looked in — a
# better message than the board could write, from the only place that
# knows the answer.
#
# ⚠️ SO THE ASSERTION IS INVERTED: on a clean machine the build must still
# FINISH, and `mcpp run` must fail by NAMING WHERE IT LOOKED rather than
# by advising a `runner` key. A board that silently configured nothing
# would pass the first half and fail the second.
- name: A clean machine still builds, and says where it looked
working-directory: examples/blinky
run: |
set -euo pipefail
COLD=$(mktemp -d)
MCPP_HOME="$COLD" mcpp build --target aarch64-none-elf > cold.log 2>&1 || true
MCPP_HOME="$COLD" mcpp build --target aarch64-none-elf 2>&1 | tee cold.log
grep -q "Finished" cold.log \
|| { cat cold.log; echo "::error::the build did not finish without an emulator"; exit 1; }
if grep -q "is not installed" cold.log; then
cat cold.log
echo "::error::the board still prints the advisory the bare name removed"
exit 1
fi
# ⚠️ THE RUN'S OUTCOME IS NOT ASSERTED, AND PRETENDING IT COULD BE
# WOULD MAKE THIS STEP LIE. `MCPP_HOME` isolates mcpp's registry and
# not PATH, so whether an emulator is reachable here depends on the
# runner image — measured on a developer machine, where it was and the
# run succeeded. What IS guaranteed is the shape of the failure when
# there is one, so that is what is checked, conditionally.
set +e
MCPP_HOME="$COLD" mcpp run --target aarch64-none-elf > run.log 2>&1
rc=$?
set -e
cat run.log
if [ "$rc" != "0" ]; then
grep -qE "qemu-system-aarch64" run.log \
|| { echo "::error::the failure does not name the program it looked for"; exit 1; }
grep -qiE "searched|looked" run.log \
|| { echo "::error::the failure does not say where it looked"; exit 1; }
echo " ok the missing runner named the program and the directories"
else
echo " note: an emulator was reachable through PATH; the failure shape is unchecked here"
fi
rm -rf "$COLD" target
# THE EMULATOR IS A `run` TOOL, AND THE PAIR IS THE ASSERTION.
#
# "A build does not ask for xim:qemu-arm" is also true when the package is
# broken and asks for nothing at all, so it proves nothing on its own. The
# second half — a run asks for it BY NAME — is what makes the first half
# mean the tier is applied rather than that the whole declaration is
# ignored.
#
# A COLD `MCPP_HOME` IS WHAT MAKES THIS MEASURABLE. `MCPP_NO_AUTO_INSTALL`
# reports what WOULD be installed, so on a machine where the emulator is
# already present there is nothing to report and both halves read empty.
# Measured on a developer machine with it installed: the run half went
# silent and the step passed without testing anything. An isolated home
# has nothing installed, so what a verb needs is what it says.
#
# Nothing is downloaded here: `MCPP_NO_AUTO_INSTALL` refuses rather than
# fetches, which is why this can run in an ordinary build job.
- name: The emulator is asked for by a run and not by a build
working-directory: examples/blinky
run: |
set -euo pipefail
COLD=$(mktemp -d)
MCPP_HOME="$COLD" MCPP_NO_AUTO_INSTALL=1 mcpp build --target aarch64-none-elf > tier-build.log 2>&1 || true
MCPP_HOME="$COLD" MCPP_NO_AUTO_INSTALL=1 mcpp run --target aarch64-none-elf > tier-run.log 2>&1 || true
if grep -q 'xim:qemu-arm' tier-build.log; then
cat tier-build.log
echo "::error::a build asked for the emulator; the run tier is not applied"
exit 1
fi
grep -q 'xim:qemu-arm' tier-run.log || {
cat tier-run.log
echo "::error::a run did not ask for the emulator; the declaration is being ignored"
exit 1
}
echo " ok build asks for no emulator, run asks for xim:qemu-arm"
- name: Install the emulator
run: |
# BOTH homes: the shim on PATH dispatches against whichever home owns
# it, while `mcpp run` starts the runner through mcpp's own.
xlings install xim:qemu-arm -y
XLINGS_HOME="$HOME/.mcpp/registry" xlings install xim:qemu-arm -y
# ⚠️ THE `bss zeroed` LINE IS THE ONE THAT CATCHES A HALF-CORRECT BOOT.
#
# An entry point that set the stack and jumped to main would print the
# greeting and would leave `.bss` holding whatever the machine had there.
# That failure is data-dependent: it would not reproduce on the machine
# where the code was written.
- name: The example boots, and .bss was zeroed
working-directory: examples/blinky
run: |
set -euo pipefail
mcpp build --target aarch64-none-elf > /dev/null 2>&1 || true
mcpp run --target aarch64-none-elf 2>&1 | tee run.log
grep -q "hello from qemu aarch64 virt" run.log \
|| { cat run.log; echo "the board did not reach main"; exit 1; }
grep -q "bss zeroed" run.log \
|| { cat run.log; echo ".bss was not zeroed by the entry code"; exit 1; }
grep -q "counter 42" run.log \
|| { cat run.log; echo "a write to .bss did not survive"; exit 1; }
grep -q "blinky ok" run.log \
|| { cat run.log; echo "the example did not reach its end"; exit 1; }
# ⭐ THE CLAIM THIS PACKAGE WAS WRITTEN TO TEST.
#
# Both boards' build programs must emit the SAME SET of directives, with
# only the values differing. If this board needs one `riscv-virt-rt`
# cannot express, the board-package interface is incomplete — and that
# finding is worth more than this package is.
#
# Read from the engine's own cache record rather than from the source: the
# `d <tag>` lines ARE what the program emitted.
- name: The board-package interface is complete
working-directory: examples/blinky
run: |
set -euo pipefail
CACHE=$(find target -name build.mcpp.cache | head -1)
test -n "$CACHE" || { echo "no build.mcpp.cache — the program did not run"; exit 1; }
TAGS=$(grep '^d ' "$CACHE" | awk '{print $2}' | sort -u | tr '\n' ' ')
echo "emitted: $TAGS"
# ⚠️ Asserted against a literal rather than against riscv-virt-rt's
# tree, which this repository does not have. The literal is what that
# board was MEASURED to emit (2026-08-21); a change on either side has
# to come here and say so.
test "$TAGS" = "ldflag runner " \
|| { echo "this board emits '$TAGS', while riscv-virt-rt emits 'ldflag runner '."; \
echo "Either the board-package interface is incomplete, or riscv-virt-rt changed."; exit 1; }
# The template is rendered by hand: `mcpp new --template` resolves the
# package from the INDEX, so asking it for a template this commit ADDS
# would resolve the previously published version.
- name: The template's project boots
run: |
set -euo pipefail
T=templates/bare
V=$(grep -m1 '^version' mcpp.toml | cut -d'"' -f2)
D=$(mktemp -d); mkdir -p "$D/src"
for f in mcpp.toml README.md; do
sed -e "s/{{project\.name}}/k/g" -e "s/{{self\.version}}/$V/g" "$T/$f.in" > "$D/$f"
done
sed -e "s/{{project\.name}}/k/g" "$T/src/main.cpp.in" > "$D/src/main.cpp"
# The rendered manifest names this version, which is not published yet.
sed -i.bak "s|aarch64-virt-rt = \"$V\"|aarch64-virt-rt = { path = \"$PWD\" }|" "$D/mcpp.toml"
rm -f "$D/mcpp.toml.bak"
grep -q "path = " "$D/mcpp.toml" || { cat "$D/mcpp.toml"; exit 1; }
( cd "$D" && mcpp build --target aarch64-none-elf > /dev/null 2>&1 ) || true
( cd "$D" && mcpp run --target aarch64-none-elf 2>&1 | tee run.log )
grep -q "bss zeroed" "$D/run.log" \
|| { cat "$D/run.log"; echo "the generated project does not boot"; exit 1; }