-
Notifications
You must be signed in to change notification settings - Fork 0
543 lines (504 loc) · 26.9 KB
/
Copy pathci.yml
File metadata and controls
543 lines (504 loc) · 26.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
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
name: CI
# What this workflow asserts.
#
# The claim this package makes is that a C library ported once onto openkal runs
# above every implementation of the specification, so the claim is tested by
# running the same two programs on three systems and comparing what they produce
# against what the system's own tools produce.
#
# posix a program that opens a file by an absolute name, reads a
# variable, measures an interval, starts another program and waits
# for it --- thirty-two observations, none of which the program
# contains an implementation of
# wordcount the same counts as the system's own `wc'
#
# Two compiler families, gcc and llvm. The third of the three mcpp offers is
# absent from this package and the reason is recorded in the manifest: musl's
# four remaining assembly definitions are in an object format that toolchain
# does not assemble, so the question that toolchain would answer is not one this
# package can ask.
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.26.2
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
jobs:
programs:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- { name: 'linux, gcc', os: ubuntu-24.04, toolchain: 'gcc@16.1.0', target: '' }
- { name: 'linux, llvm', os: ubuntu-24.04, toolchain: 'llvm@22.1.8', target: '' }
- { name: 'macos, llvm', os: macos-14, toolchain: 'llvm@20.1.7', target: '' }
- { name: 'windows, gcc', os: windows-2022, toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu' }
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: The specification and the implementation for this system
run: |
bash tools/working-trees.sh '${{ github.head_ref || github.ref_name }}' \
'${{ matrix.target }}'
- name: Install xlings (Unix)
if: runner.os != 'Windows'
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 xlings (Windows)
if: runner.os == 'Windows'
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
- name: Select the toolchain
run: |
spec='${{ matrix.toolchain }}'
mcpp toolchain install "${spec%@*}" "${spec#*@}"
mcpp toolchain default "$spec"
mcpp toolchain list
- name: The library builds
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp build $extra
# ⭐ AN OPTIONAL INTERFACE IS REFERENCED WEAKLY, WHICH IS A PROPERTY OF
# THE OBJECT AND NOT OF ANY ONE LINK.
#
# `openkal.random` is optional, and clause 6.1 expresses an
# implementation that does not provide it as the absence of a link-time
# definition. This dispatcher is linked into every program, so a strong
# reference to `kal_random_fill` makes an optional interface mandatory:
# every program over a backend without it fails to link, whether or not
# it wanted a random byte.
#
# ⚠️ THAT DEFECT SHIPPED, AND NOTHING HERE SAW IT. It was found one
# repository downstream, by a bare-metal program in openkal-llvm-runtime
# linking over openkal-opensbi:
#
# ld.lld: error: undefined symbol: kal_random_fill
#
# Reproducing it here would need a bare-metal toolchain and a backend
# that declines the interface. The symbol class needs neither, and states
# the invariant more directly than a link would: `w` for the optional
# one, `U` for a required one beside it.
- name: An optional interface is referenced weakly
if: runner.os == 'Linux'
run: |
obj="$(find target -name okm_syscall.o | head -1)"
test -n "$obj" || { echo "::error::okm_syscall.o was not built"; exit 1; }
nm="$(command -v llvm-nm || command -v nm)"
"$nm" "$obj" > syms.txt
# The control: a required interface must still be a strong reference,
# so that a backend failing to provide one is still a link error.
grep -qE '^ *U kal_time_sleep$' syms.txt \
|| { echo "::error::kal_time_sleep is not an undefined strong reference — the check below proves nothing"
grep kal_time syms.txt; exit 1; }
grep -qE '^ *w kal_random_fill$' syms.txt \
|| { echo "::error::kal_random_fill is not a weak reference; an optional interface has been made mandatory"
grep kal_random syms.txt; exit 1; }
echo " ok kal_random_fill is weak, kal_time_sleep is strong"
# ⭐⭐ ASKING WHETHER A STREAM IS A TERMINAL GETS THE RIGHT ANSWER.
#
# musl's `isatty' asks with TIOCGWINSZ; this port answered only TCGETS,
# so every `isatty' returned 0 — for a real terminal as readily as for a
# pipe. Nothing failed: `std::print' simply never took its terminal path,
# and a program deciding on colour or on line buffering decided wrongly
# and in silence.
#
# ⭐ THE CRITERION IS A RELATION, NOT A VALUE. `isatty` under a pipe and
# under a pseudo-terminal must DIFFER, and must differ the same way the
# system's own C library does. A test asserting "0 in a pipe" alone would
# have passed throughout the defect.
- name: Asking whether a stream is a terminal is answered, not refused
if: runner.os == 'Linux' && matrix.target == ''
run: |
d="$(mktemp -d)"; mkdir -p "$d/src"
cat > "$d/mcpp.toml" <<TOML
[package]
name = "isattyprobe"
version = "0.1.0"
[dependencies]
openkal-musl = { path = "$PWD" }
[targets.isattyprobe]
kind = "bin"
main = "src/main.c"
[build]
cxx_runtime = "host-coupled"
TOML
sed -i 's/^ //' "$d/mcpp.toml"
printf '#include <unistd.h>\n#include <stdio.h>\nint main(void){ printf("%%d\\n", isatty(1)); return 0; }\n' > "$d/src/main.c"
( cd "$d" && mcpp build --toolchain '${{ matrix.toolchain }}' )
bin="$(find "$d/target" -name isattyprobe -type f | head -1)"
test -n "$bin" || { echo "::error::the probe did not build"; exit 1; }
# The control: the system's own C library, through the same harness.
# Without it a `script` that fails to allocate a pty would make the
# port look broken.
printf '#include <unistd.h>\n#include <stdio.h>\nint main(void){ printf("%%d\\n", isatty(1)); return 0; }\n' > "$d/ctrl.c"
cc "$d/ctrl.c" -o "$d/ctrl"
ctrl_pipe="$("$d/ctrl" | cat | tr -d '\r')"
ctrl_tty="$(script -qec "$d/ctrl" /dev/null | tr -d '\r' | head -1)"
[ "$ctrl_pipe" = 0 ] && [ "$ctrl_tty" = 1 ] \
|| { echo "::error::the harness cannot tell a pty from a pipe (control gave $ctrl_pipe/$ctrl_tty) — this check would prove nothing"
exit 1; }
port_pipe="$("$bin" | cat | tr -d '\r')"
port_tty="$(script -qec "$bin" /dev/null | tr -d '\r' | head -1)"
echo " control: pipe=$ctrl_pipe tty=$ctrl_tty port: pipe=$port_pipe tty=$port_tty"
[ "$port_pipe" = "$ctrl_pipe" ] && [ "$port_tty" = "$ctrl_tty" ] \
|| { echo "::error::isatty over this port disagrees with the system's own C library"; exit 1; }
echo " ok isatty answers the same as the system's own C library"
# ⭐⭐ THE INTERNAL OVERLAY STOPS AT THIS PACKAGE'S BOUNDARY.
#
# musl reaches its own declarations through `src/include`, whose headers
# define `hidden`, `weak` and `weak_alias` — names that mean something
# only to musl's own sources. This package publishes the path it is built
# from, so every consumer used to see them too, and which consumer broke
# on which name was found one at a time (openkal-musl#13).
#
# `[build] private_include_dirs` (mcpp 2026.8.27.1) says which entries of
# `include_dirs` stop here. This asserts the DIRECTORY is absent from a
# consumer's command line — not that one macro no longer collides, which
# would go green again the moment the package patched that macro while
# the leak stayed.
- name: What this package is built from is not what it publishes
working-directory: examples/cross-hello
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp build --toolchain '${{ matrix.toolchain }}' $extra
test -s compile_commands.json \
|| { echo "::error::no compile_commands.json — nothing to check"; exit 1; }
# ⚠️⚠️ PER ROW, NOT OVER THE FILE. This example depends on the
# package by path, so THE PROVIDER'S OWN ROWS ARE IN THIS SAME FILE
# and they carry the overlay legitimately — that is what "private,
# not unused" means. A `grep` over the whole file cannot tell the two
# apart and would call the package's own build a leak.
#
# ⚠️ Separators are normalised because the Windows runner writes
# `…\musl\src\include`, and a check that silently stops matching on
# one platform is a check that platform does not have.
norm='(.arguments // (.command | split(" "))) | join(" ") | gsub("\\\\"; "/")'
jq -r ".[] | select((.file | gsub(\"\\\\\\\\\"; \"/\")) | test(\"examples/cross-hello\")) | $norm" \
compile_commands.json > consumer.txt
jq -r ".[] | select((.file | gsub(\"\\\\\\\\\"; \"/\")) | test(\"examples/cross-hello\") | not) | $norm" \
compile_commands.json > provider.txt
# ⚠️ DENOMINATORS ON BOTH SIDES. With no consumer row every absence
# below is vacuously true; with no provider row the control is.
cons="$(wc -l < consumer.txt)"; prov="$(wc -l < provider.txt)"
echo " rows: consumer=$cons provider=$prov"
[ "$cons" -ge 1 ] && [ "$prov" -ge 1 ] \
|| { echo "::error::consumer=$cons provider=$prov — nothing was checked"; exit 1; }
bad=0
for d in musl/src/include musl/src/internal musl-generated/internal; do
if grep -q -- "$d" consumer.txt; then
echo "::error::the internal overlay '$d' reached a consumer"
bad=1
fi
# The other half of the same key: private is not the same as
# dropped. musl's own sources must still reach their declarations.
grep -q -- "$d" provider.txt \
|| { echo "::error::'$d' is on nobody's command line — private_include_dirs withheld it from this package too"
bad=1; }
done
# And something PUBLIC must still cross the boundary, or this check
# would pass for a build that published nothing at all.
grep -q -- 'port/include' consumer.txt \
|| { echo "::error::no public include directory reached the consumer — the check above proves nothing"
exit 1; }
[ "$bad" = 0 ] || exit 1
echo " ok the internal overlay stops here; the public headers do not"
# A program above this package names one package. It does not name
# openkal, it does not name an implementation, and it says nothing about
# the platform.
- name: The posix probe
working-directory: examples/posix
run: |
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp build $extra
binary="$(find target -type f \( -name 'posix' -o -name 'posix.exe' \) | head -1)"
# A watchdog, because a program that does not return is as much a
# failure as one that returns wrongly, and the job would otherwise
# spend its whole timeout finding that out. Written out rather than
# taken from `timeout', which two of the three systems have and one
# does not.
watch() { # watch <seconds> <command>...
local seconds="$1"; shift
"$@" & local pid=$!
( sleep "$seconds"; kill -9 "$pid" 2> /dev/null ) & local guard=$!
wait "$pid"; local status=$?
kill "$guard" 2> /dev/null || true
return $status
}
if watch 120 sh -c "\"$binary\" > run.log 2>&1"; then
cat run.log
else
status=$?
echo "--- what the program printed before it stopped (status $status) ---"
cat run.log
# A program that stopped and a program that did not return need
# different questions asked of them, and "exit code 139" and "the
# job timed out" answer neither. The debugger is for the first; a
# stack sample of a program that is still running is for the second,
# and a debugger asked to run a program that hangs hangs with it.
if [ "$status" -eq 137 ]; then
echo "--- it did not return; where it was ---"
"$binary" > /dev/null 2>&1 & hung=$!
sleep 5
if command -v sample > /dev/null 2>&1; then
sample "$hung" 3 -mayDie 2>&1 | head -80 || true
elif command -v eu-stack > /dev/null 2>&1; then
eu-stack -p "$hung" 2>&1 | head -60 || true
fi
kill -9 "$hung" 2> /dev/null || true
elif command -v lldb > /dev/null 2>&1; then
watch 90 lldb --batch -o run \
-k 'thread backtrace all' -k 'register read' -k quit \
-- "$binary" > crash.log 2>&1 || true
cat crash.log
elif command -v gdb > /dev/null 2>&1; then
watch 90 gdb -batch -ex run -ex 'bt' --args "$binary" > crash.log 2>&1 || true
cat crash.log
fi
exit 1
fi
# Both directions: that the program reported, and that nothing it
# observed failed to hold. Asserting only the first would pass for a
# program that printed its failures.
grep -qE '^-- failures: 0 --$' run.log
! grep -q '^FAIL:' run.log
- name: The same program, built the ordinary way, as a control
run: |
# ⚠️ THIS STEP EXISTS BECAUSE IT ALREADY EARNED ITS PLACE.
#
# The cross-built copy failed on a machine of the other system, and the
# obvious reading was a defect in this port or in the way it was
# cross-built. A native build of the SAME source reproduced it here ---
# which said it was neither, and the defect was in the program's own
# assertion macro, which evaluated its condition twice and so closed a
# stream that was already closed.
#
# Two failures that look alike are told apart by building the same
# source two ways, and that is cheap enough to do every time.
# ⚠️ The target has to be named here as it is everywhere else in this
# job: one system needs it, and a step that omitted it built for that
# system's other toolchain and failed at the link with names kernel32
# supplies. Measured, on the first run of this step.
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
cd examples/cross-hello
mcpp build $extra
binary="$(find target -type f \( -name 'cross-hello' -o -name 'cross-hello.exe' \) | head -1)"
"./$binary" 2>&1 | tee out.log
grep -q -- '-- failures: 0 --' out.log
- name: The counts agree with the system's own
working-directory: examples/wordcount
run: |
printf 'alpha beta\ngamma\n' > sample.txt
expected="$(wc < sample.txt | tr -s ' ' | sed 's/^ //')"
extra=''
[ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}'
mcpp build $extra
binary="$(find target -type f \( -name 'wordcount' -o -name 'wordcount.exe' \) | head -1)"
WORDCOUNT_VERBOSE=1 "$binary" sample.txt 2>&1 | tee run.log
counts="$(sed -n 's/^lines \([0-9]*\) words \([0-9]*\) bytes \([0-9]*\)$/\1 \2 \3/p' run.log)"
test -n "$counts" || { echo "the program printed no count" >&2; exit 1; }
echo "wordcount: $counts"
echo "wc: $expected"
test "$counts" = "$expected"
# The variable was consulted, and the interval was measured.
grep -q 'elapsed .* nanoseconds' run.log
# ---------------------------------------------------------------------------
# The measurement musl/PATCHES.md records, made from a machine that is not the
# system it is about.
#
# It is a job rather than a note because the claim it supports --- that a
# program for that system can be built, and linked, without any file that
# system supplies --- is one that a change to this port could silently undo.
# The two numbers it asserts are zero indirect symbols and exactly two names.
cross-macos:
name: cross-link for the other system, from this one
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: The implementation for that system
run: |
branch='${{ github.head_ref || github.ref_name }}'
git clone --quiet https://github.com/mcpplibs/openkal-macos.git ../openkal-macos
git -C ../openkal-macos checkout --quiet "origin/$branch" 2>/dev/null || true
git clone --quiet https://github.com/mcpplibs/openkal.git ../openkal
git -C ../openkal checkout --quiet "origin/$branch" 2>/dev/null || true
- name: Install LLVM
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq clang lld llvm
# ld64.lld is the Mach-O driver of the same binary; Ubuntu's package
# installs it under a version suffix and without the plain name.
v=$(ls /usr/lib/llvm-*/bin/ld64.lld 2>/dev/null | sort -V | tail -1)
test -n "$v" || { echo "::error::this image has no ld64.lld"; exit 1; }
d=$(dirname "$v")
echo "LD64=$v" >> "$GITHUB_ENV"
# The tools are installed under a version suffix and the probe asks for
# them by their plain names, so the directory that has both spellings
# is named rather than assumed.
echo "NM=$d/llvm-nm" >> "$GITHUB_ENV"
echo "$d" >> "$GITHUB_PATH"
"$d/llvm-nm" --version | head -1
clang --version | head -1
- name: Zero indirect symbols, and two names
run: |
bash tools/probe-cross-macos.sh aarch64
bash tools/probe-cross-macos.sh x86_64
# ⚠️ LINKING IS NOT RUNNING, AND THIS IS WHERE THAT IS ADMITTED.
#
# Everything above happens on a machine that is not the system in
# question, and everything above is about the objects and the link. Whether
# the result STARTS is a different question, and it cannot be asked here:
# that system's 64-bit ARM variant refuses to start an image that is not
# signed, and the signature can only be made on a machine of that system.
#
# So the artifact is carried there. The job that receives it is the only
# place in this workflow where the claim becomes "a program built here
# runs there" rather than "a program built here links".
- name: A program for that system, built here
run: |
bash tools/cross-build-macos.sh aarch64 examples/cross-hello/src/main.c cross-hello-arm64
file cross-hello-arm64
- uses: actions/upload-artifact@v4
with:
name: cross-hello-arm64
path: cross-hello-arm64
retention-days: 1
# ---------------------------------------------------------------------------
# The other half of the claim, on a machine of that system.
cross-macos-run:
name: what was cross-built starts, on that system
needs: cross-macos
runs-on: macos-14
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: cross-hello-arm64
- name: Sign it, because this system will not start it otherwise
run: |
chmod +x cross-hello-arm64
# Ad hoc: the identity is `-', which asserts nothing about who made
# the program and is exactly what this system requires before it will
# start one on this architecture. A program that is distributed needs
# more; a program that is checked needs this.
codesign -s - --force cross-hello-arm64
codesign -dv cross-hello-arm64 2>&1 | head -3
- name: It starts, and the C library beneath it came up
run: |
./cross-hello-arm64 | tee run.log
grep -q -- '-- failures: 0 --' run.log