-
Notifications
You must be signed in to change notification settings - Fork 0
425 lines (406 loc) · 21.9 KB
/
Copy pathci.yml
File metadata and controls
425 lines (406 loc) · 21.9 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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
name: CI
# What this workflow asserts.
#
# conformance the suite in the specification package runs against this
# implementation and every observation holds, under all three
# of this environment's toolchains
# surface the exported names are exactly the fifty-one the
# specification lists
# independence the objects reference no C runtime symbol
#
# This environment has two application binary interfaces and three toolchains
# that reach them, and an implementation that built under one of the three would
# not be an implementation for this environment. So all three are built: the two
# that produce the environment's own ABI, and the one that produces the other.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
inputs:
mcpp_ref:
description: "Branch of mcpp-community/mcpp to build and test against (empty = the released pin)"
required: false
default: ""
env:
MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }}
MCPP_VERSION: 2026.8.27.1
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
jobs:
# ---------------------------------------------------------------------------
# On the system itself.
conformance:
name: conformance (${{ matrix.name }})
runs-on: windows-2022
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- { name: 'llvm, msvc ABI', toolchain: 'llvm@20.1.7', target: '' }
- { name: 'msvc', toolchain: 'msvc@system', target: '' }
- { name: 'gcc, gnu CRT', toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu' }
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
# The specification is checked out at the branch under test where it has
# one, so that this run asserts what it is for: that the specification as
# written there and this implementation as written here agree today.
- name: The specification
run: |
git clone --quiet https://github.com/mcpplibs/openkal.git .spec
branch='${{ github.head_ref || github.ref_name }}'
if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then
git -C .spec checkout --quiet "origin/$branch"
echo "the specification is at $branch"
else
echo "the specification has no $branch; its default branch is used"
fi
- name: Install xlings
shell: pwsh
run: |
irm https://d2learn.org/xlings-install.ps1.txt | iex
"$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install mcpp
run: |
xlings update
# ⚠️ THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does
# not exist yet — that is the whole point of MCPP_SOURCE_REF. Bootstrap
# from whatever the index has; the step below replaces it with the
# build under review, and the pin is what an ordinary run tests.
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
xlings install mcpp -y -g
else
xlings install "mcpp@$MCPP_VERSION" -y -g
fi
# ⚠️⚠️ TRANSITION: GIVE THE BOOTSTRAP THE glibc ITS BINDING NAMES.
#
# `xim:glibc`'s `latest` moved from `2.44` to `2.44.2`. A payload
# directory is named after the version a request RESOLVED to, while a
# RuntimeBinding carries the version that was DECLARED — and the xlings
# a released mcpp vendors into its own sandbox still declares `2.44`.
# So a clean machine installs `2.44.2`, the toolchain fixup asks for
# `2.44`, and the build stops before anything is compiled:
#
# error: selected RuntimeBinding glibc@2.44 requires payload
# '…/xpkgs/xim-x-glibc/2.44', but it is not installed
#
# ⚠️ On every NEW machine and on none that already existed, which is why
# it is invisible from a developer's own. Measured on `main` as readily
# as on any branch — the index records the same failure verbatim in
# `pkgs/g/glibc.lua` and states the rule it broke: "The index is DATA
# and the client is a PROGRAM: the consumer ships first."
#
# ⭐ REMOVE THIS once a released mcpp resolves it. `mcpp 2026.8.27.1`
# accepts an installed payload whose version REFINES the requested one
# (`payload_dir_for_version`), so a bootstrap from it needs nothing
# here. Until then the missing payload is simply installed.
if [ -x "$HOME/.mcpp/registry/bin/xlings" ]; then
XLINGS_HOME="$HOME/.mcpp/registry" XLINGS_NON_INTERACTIVE=1 \
"$HOME/.mcpp/registry/bin/xlings" install glibc@2.44 -y -g \
>/dev/null 2>&1 || true
echo "glibc payloads present: $(ls "$HOME/.mcpp/registry/data/xpkgs/xim-x-glibc" 2>/dev/null | tr '\n' ' ')"
fi
mcpp --version
mcpp self config --mirror GLOBAL
# ⭐⭐ CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE.
#
# Empty in the ordinary run, so this job keeps testing the RELEASED
# mcpp the pin above names. Set it — `workflow_dispatch` input, or the
# repository variable — and the same job runs against that source.
#
# ⚠️ THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp
# releases went out green and only then turned this ecosystem red: the
# engine's own CI cannot see a defect that appears only in a real
# dependency graph, and this repository could not see the engine until
# it had been published. Validating before the release closes that gap.
#
# The released mcpp installed just above is the bootstrap that compiles
# it; mcpp builds itself and there is no other compiler for it here.
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
src="$RUNNER_TEMP/mcpp-src"
[ -d "$src" ] || git clone --quiet --depth 1 \
--branch "$MCPP_SOURCE_REF" \
https://github.com/mcpp-community/mcpp.git "$src"
# ⚠️ THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp
# BUILDS IT. `.xlings.json` at mcpp's root pins the mcpp that
# compiles mcpp, and that pin does not move when mcpp is released —
# so a build inside the checkout obeys it and tries to install a
# version the index may no longer carry:
#
# [error] xlings: version '2026.8.17.1' not found for 'mcpp'
# available: 2026.8.25.1
#
# What is wanted here is the source compiled by the mcpp installed
# above, which is exactly what removing the file leaves.
rm -f "$src/.xlings.json"
( cd "$src" && mcpp build --release )
# ⚠️ BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# macOS runners too: on Windows the artefact is `mcpp.exe`, and
# `-perm -u+x` is not a question that filesystem answers the way this
# expects. Measured: `Finished release [optimized] in 173.44s`
# followed by "mcpp did not build" — the build had succeeded and the
# search was looking for the wrong name.
#
# `$src` is a FRESH clone each run, so `target/` holds exactly what
# this step just built; `-printf` would be the safer form on a cached
# tree and is a GNU extension this must not use.
built=$(find "$src/target" -type f \
\( -name mcpp -o -name mcpp.exe \) | head -1)
[ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; }
echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH"
# ⚠️ Reported, because a PATH entry that does not win looks exactly
# like one that does until something built with the wrong engine.
echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)"
fi
# EVERY NAME THE HEADER DECLARES IS EXPORTED BY ONE OF THE .def FILES.
#
# `port/*.def` is an explicit list, and an import library generated from it
# contains exactly those names. So a declaration added to `src/win32.h`
# without a matching line in a `.def` compiles, and fails at the link of a
# CONSUMER --- not of this package, which does not link.
#
# ⚠️ MEASURED. Three declarations were added for openkal 0.8 and the names
# were not, and this package's own CI stayed green: the failure appeared in
# openkal-llvm-runtime's cross-build, one repository away, as
#
# ld.lld: error: undefined symbol: __declspec(dllimport) CreatePipe
#
# which reads as a defect in the consumer. The check belongs here, where
# the two lists are.
- name: Every declared name is exported by a .def
run: |
python3 - <<'PY'
import glob, os, re, sys
# ⚠️ THE ENCODING IS NAMED. Python opens a file with the platform's
# default, which on this runner is cp1252, and these sources are UTF-8:
#
# UnicodeDecodeError: 'charmap' codec can't decode byte 0x90
#
# A check that reads source files must say what they are encoded in, or
# it reports on the runner's locale.
declared = set(re.findall(r'OKW_IMPORT\s+\w+\s+OKW_API\s+(\w+)\s*\(',
open("src/win32.h", encoding="utf-8").read()))
exported = set()
for f in glob.glob("port/*.def"):
body = open(f, encoding="utf-8").read().split("EXPORTS", 1)
if len(body) < 2: continue
exported |= {l.strip() for l in body[1].split("\n")
if l.strip() and not l.lstrip().startswith(';')}
# A denominator on both sides: with either list empty the difference is
# vacuously empty too.
if not declared or not exported:
print(f"::error::declared={len(declared)} exported={len(exported)}; nothing was compared")
sys.exit(1)
missing = sorted(declared - exported)
print(f" {len(declared)} declared, {len(exported)} exported across "
f"{len(glob.glob('port/*.def'))} .def files")
if missing:
print("::error::declared in src/win32.h and exported by no .def:")
for m in missing: print(f" {m}")
sys.exit(1)
print(" ok every declared name is exported")
PY
- name: Select the toolchain
run: |
spec='${{ matrix.toolchain }}'
case "$spec" in
msvc*) mcpp toolchain default msvc ;;
*) mcpp toolchain install "${spec%@*}" "${spec#*@}"
mcpp toolchain default "$spec" ;;
esac
mcpp toolchain list
- name: Every interface, every kind of examination
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
# ⚠️ THE SET IS ENUMERATED AND `optional` IS NOT NAMED, AND THE
# DIFFERENCE IS ONE INTERFACE.
#
# `optional` includes `space`, which this system does not provide:
# there is no primitive here that copies an address space and starts a
# context in the copy. A set demanding it would not report an
# observation that did not hold — it would fail to LINK, naming
# `kal_space_start`, which is clause 6.1's report arriving where a
# report was not wanted.
#
# So the six this implementation does provide are named, and the
# seventh is absent from the list for the same reason it is absent
# from the objects.
bash .spec/tools/run-conformance.sh openkal-windows . \
full,exec,random,terminal,net,datagram,timeout $extra
# ---------------------------------------------------------------------------
# From a system that is not this one.
#
# The same package, produced by a cross build and run under an implementation
# of this environment's interfaces rather than on the environment. It is here
# for two reasons: it is where the exported names can be examined with tools
# that read this environment's object format and are not on it, and a package
# that only builds on the system it targets is a package a contributor on any
# other system cannot work on.
cross:
name: cross build, run under wine, and examine the objects
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: The specification
run: |
git clone --quiet https://github.com/mcpplibs/openkal.git .spec
branch='${{ github.head_ref || github.ref_name }}'
if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then
git -C .spec checkout --quiet "origin/$branch"
fi
- name: Install wine
run: |
sudo dpkg --add-architecture i386
sudo apt-get update -qq
# Which package carries it and what the program is called differ
# between distributions and between releases of one, so both are
# tried and the name that exists is the one used. A script that names
# one of them fails with "command not found" after a successful
# install, which reads as a missing package rather than a renamed
# program.
sudo apt-get install -y --no-install-recommends wine wine64 > /dev/null 2>&1 \
|| sudo apt-get install -y --no-install-recommends wine > /dev/null
runner="$(command -v wine || command -v wine64)"
test -n "$runner" || { echo "no wine was installed" >&2; exit 1; }
"$runner" --version
echo "OPENKAL_CONFORMANCE_RUNNER=$runner" >> "$GITHUB_ENV"
- 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: |
xlings update
# ⚠️ THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does
# not exist yet — that is the whole point of MCPP_SOURCE_REF. Bootstrap
# from whatever the index has; the step below replaces it with the
# build under review, and the pin is what an ordinary run tests.
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
xlings install mcpp -y -g
else
xlings install "mcpp@$MCPP_VERSION" -y -g
fi
mcpp self config --mirror GLOBAL
# ⭐⭐ CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE.
#
# Empty in the ordinary run, so this job keeps testing the RELEASED
# mcpp the pin above names. Set it — `workflow_dispatch` input, or the
# repository variable — and the same job runs against that source.
#
# ⚠️ THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp
# releases went out green and only then turned this ecosystem red: the
# engine's own CI cannot see a defect that appears only in a real
# dependency graph, and this repository could not see the engine until
# it had been published. Validating before the release closes that gap.
#
# The released mcpp installed just above is the bootstrap that compiles
# it; mcpp builds itself and there is no other compiler for it here.
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
src="$RUNNER_TEMP/mcpp-src"
[ -d "$src" ] || git clone --quiet --depth 1 \
--branch "$MCPP_SOURCE_REF" \
https://github.com/mcpp-community/mcpp.git "$src"
# ⚠️ THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp
# BUILDS IT. `.xlings.json` at mcpp's root pins the mcpp that
# compiles mcpp, and that pin does not move when mcpp is released —
# so a build inside the checkout obeys it and tries to install a
# version the index may no longer carry:
#
# [error] xlings: version '2026.8.17.1' not found for 'mcpp'
# available: 2026.8.25.1
#
# What is wanted here is the source compiled by the mcpp installed
# above, which is exactly what removing the file leaves.
rm -f "$src/.xlings.json"
( cd "$src" && mcpp build --release )
# ⚠️ BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# macOS runners too: on Windows the artefact is `mcpp.exe`, and
# `-perm -u+x` is not a question that filesystem answers the way this
# expects. Measured: `Finished release [optimized] in 173.44s`
# followed by "mcpp did not build" — the build had succeeded and the
# search was looking for the wrong name.
#
# `$src` is a FRESH clone each run, so `target/` holds exactly what
# this step just built; `-printf` would be the safer form on a cached
# tree and is a GNU extension this must not use.
built=$(find "$src/target" -type f \
\( -name mcpp -o -name mcpp.exe \) | head -1)
[ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; }
echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH"
# ⚠️ Reported, because a PATH entry that does not win looks exactly
# like one that does until something built with the wrong engine.
echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)"
fi
- name: Every interface, every kind of examination
env:
WINEDEBUG: '-all'
run: |
bash .spec/tools/run-conformance.sh openkal-windows . \
full,exec,random,terminal,net,datagram,timeout \
--target x86_64-windows-gnu
- name: The exported surface is complete and contains nothing else
run: |
# Clause 9.3. The list and the checker come from the specification
# rather than from a copy kept here, so that the comparison has one
# source. --complete because this implementation claims every
# interface: a name it fails to export is a failure, not an interface
# it declines to provide.
rm -rf target && mcpp build --target x86_64-windows-gnu
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' | tr '\n' ' ')"
test -n "$objs" || { echo "no objects were found" >&2; exit 1; }
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt $objs
# The property this implementation exists to have. A program above openkal
# may supply a C runtime of its own; an implementation that reached for
# this environment's would have its calls resolve to the program's, and
# the program's would resolve back here.
- name: The objects reference no C runtime symbol
run: |
rm -rf target && mcpp build --features standalone --target x86_64-windows-gnu
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')"
test -n "$objs" || { echo "no objects were found; the check would pass vacuously" >&2; exit 1; }
# The permitted set, and why each entry is in it.
#
# __imp_* this environment's own interfaces, reached through
# the import table. They are the environment, not a C runtime.
# Nt*, Rtl* the object manager, likewise.
# WaitOnAddress, WakeByAddress* the suspension primitive, which
# this environment publishes without an import stub.
# memcpy, memmove, memset, memcmp a compiler emits calls to these
# from ordinary loops. They compute rather than call, so none of
# them can re-enter this implementation.
# __libc_start_main, main the hand-over, undefined here by
# construction.
# kal_*, _ZN3okw* the interface and this package.
# __chkstk_ms, __udivti3, ... the compiler's own support
# routines, which are in the compiler's library and not in a C
# runtime.
permitted='^(__imp_[A-Za-z0-9_]+|Nt[A-Za-z]+|Rtl[A-Za-z]+|WaitOnAddress|WakeByAddress(All|Single)|memcpy|memmove|memset|memcmp|__libc_start_main|main|kal_[a-z_]+|_ZN3okw.*|_*chkstk[a-z_]*|__udiv[a-z0-9]*|__umod[a-z0-9]*|__divti3|__modti3|_GLOBAL_OFFSET_TABLE_)$'
bad=0
for s in $(nm --undefined-only $objs | awk '{print $2}' | sort -u); do
[ -n "$s" ] || continue
printf '%s\n' "$s" | grep -qE "$permitted" || {
echo "the implementation references a symbol it must not: $s" >&2
bad=1
}
done
test "$bad" -eq 0
echo "the implementation references no C runtime symbol"
# A checker is only useful if it fails when it should.
- name: The independence check detects a dependence
run: |
printf 'extern "C" int puts(const char*);\nextern "C" void okw_probe(void) { puts("x"); }\n' > src/probe.cpp
rm -rf target && mcpp build --features standalone --target x86_64-windows-gnu
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')"
if ! nm --undefined-only $objs | awk '{print $2}' | grep -qx puts; then
echo "the probe did not produce the reference it was written to produce" >&2
rm -f src/probe.cpp; exit 1
fi
rm -f src/probe.cpp
echo "a dependence upon a C runtime is visible to the check"