Skip to content

Commit 8e1dbd8

Browse files
committed
Verify a cross where the machine can run what it built
x86_64-apple-darwin became native in #161 because nothing ever executed what the cross produced: no toolchain contract, no bootstrap smoke test, so it could not be published. That was the right call for the evidence and it cost the time. In the first real nine-host publication it took 62 minutes against 14 for its arm64 sibling, and it is the pole every other host waits behind. "Only a native build can run its own output" is true of a canadian cross to Windows or FreeBSD and false of Apple: an arm64 Mac runs x86_64 Mach-O through Rosetta, so the machine that cross-builds the Intel SDK is the one that can check it. The gate now asks how to run this host's binaries here rather than whether the build is native, and passes the launcher to both the contract and the smoke test. Windows and FreeBSD answer "cannot" and are skipped exactly as before. Measured end to end in the probe repository before touching this: cross 650s, contract 24s, bootstrap smoke 30s, 12m18s for the whole job against 62m22s native -- same checks, same published host.
1 parent 79a9278 commit 8e1dbd8

4 files changed

Lines changed: 142 additions & 8 deletions

File tree

cmake/hosts.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
{"name": "x86_64-linux-gnu", "stage": 2, "runner": "ubuntu-24.04", "container": "ubuntu:20.04", "packaged": true},
55
{"name": "aarch64-linux-gnu", "stage": 2, "runner": "ubuntu-24.04-arm", "container": "ubuntu:20.04", "packaged": true},
66
{"name": "arm64-apple-darwin", "stage": 2, "runner": "macos-14", "container": null, "packaged": true},
7-
{"name": "x86_64-apple-darwin", "stage": 2, "runner": "macos-15-intel", "container": null, "packaged": true},
87
{"name": "x86_64-linux-musl", "stage": 2, "runner": "ubuntu-24.04", "container": null, "packaged": true},
98
{"name": "aarch64-linux-musl", "stage": 2, "runner": "ubuntu-24.04-arm", "container": null, "packaged": true},
109
{"name": "x86_64-w64-mingw32", "stage": 3, "runner": "ubuntu-24.04", "container": null, "packaged": true, "build_host": "x86_64-linux-gnu"},
10+
{"name": "x86_64-apple-darwin", "stage": 3, "runner": "macos-14", "container": null, "packaged": true, "build_host": "arm64-apple-darwin"},
1111
{"name": "i686-w64-mingw32", "stage": 3, "runner": "ubuntu-24.04", "container": null, "packaged": false, "build_host": "x86_64-linux-gnu"},
1212
{"name": "x86_64-unknown-freebsd", "stage": 3, "runner": "ubuntu-24.04", "container": null, "packaged": true, "build_host": "x86_64-linux-gnu"},
1313
{"name": "aarch64-unknown-freebsd", "stage": 3, "runner": "ubuntu-24.04", "container": null, "packaged": true, "build_host": "x86_64-linux-gnu"}

cmake/recipes/FinalizeSdk.cmake

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,17 @@ add_custom_target(finalize-sdk
126126
VERBATIM
127127
)
128128

129+
# Empty when the host's binaries run here as they are. A cross whose output
130+
# this machine can still execute -- x86_64 macOS on arm64, through Rosetta --
131+
# sets it to the launcher that makes that true, so the contract runs against
132+
# the SDK that was actually built rather than being skipped.
133+
set(VITASDK_HOST_RUNNER "" CACHE STRING
134+
"Launcher prefix for running this host's binaries, if one is needed")
135+
129136
add_custom_target(check-toolchain-contract
130137
COMMAND ${CMAKE_COMMAND} -E env
131138
VITASDK=${CMAKE_INSTALL_PREFIX}
139+
${VITASDK_HOST_RUNNER}
132140
${CMAKE_SOURCE_DIR}/tests/toolchain-contract/run.sh
133141
DEPENDS finalize-sdk
134142
VERBATIM

scripts/ci/build-host.sh

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,25 @@ trap dump_configure_logs EXIT
105105
# The matrix's packaged flag, not the host name, gates core-package treatment.
106106
is_packaged_host() { [[ $packaged == true ]]; }
107107

108+
# How to run this host's binaries on this machine, if at all. Prints the
109+
# launcher prefix and succeeds; fails when there is none.
110+
#
111+
# What this replaces was "only a native build can run its own output", which
112+
# is true of a canadian cross to Windows or FreeBSD and false of Apple: an
113+
# arm64 Mac runs x86_64 Mach-O through Rosetta, so the machine that
114+
# cross-builds the Intel SDK is also the one that can check it.
115+
host_runner() {
116+
if [[ -z $build_host ]]; then
117+
printf ''
118+
return 0
119+
fi
120+
if [[ $host == x86_64-apple-darwin && $build_host == arm64-apple-darwin ]]; then
121+
printf 'arch -x86_64'
122+
return 0
123+
fi
124+
return 1
125+
}
126+
108127
vdpm_tag() {
109128
sed -n 's/^set(VDPM_TAG \([^ )]*\).*/\1/p' "$repo_root/cmake/Components.cmake"
110129
}
@@ -133,14 +152,17 @@ download_vdpm_bundle() {
133152
# Same-runner smoke test only; a canadian cross cannot run what it built.
134153
smoke_test_bootstrap() {
135154
local bootstrap_archive=$1 digest install_root
155+
# Empty for a host that runs here directly; a launcher for one that needs
156+
# it. Unquoted on purpose, so an empty prefix contributes no argument.
157+
local -a run=(${2:-})
136158
digest=$(awk '{print $1}' "$bootstrap_archive.sha256")
137159
install_root="$PWD/bootstrap-installed"
138160
VITASDK_BOOTSTRAP_ARCHIVE="$bootstrap_archive" VITASDK_BOOTSTRAP_SHA256="$digest" \
139-
build/vitasdk/share/vdpm/bootstrap-vitasdk.sh --install-dir "$install_root"
140-
VITASDK="$install_root" "$install_root/bin/vdpm" --help >/dev/null
161+
"${run[@]}" build/vitasdk/share/vdpm/bootstrap-vitasdk.sh --install-dir "$install_root"
162+
VITASDK="$install_root" "${run[@]}" "$install_root/bin/vdpm" --help >/dev/null
141163
# vdpm ships pacman under libexec/vdpm, not bin/.
142-
"$install_root/libexec/vdpm/pacman" --version >/dev/null
143-
"$install_root/bin/arm-vita-eabi-gcc" --version
164+
"${run[@]}" "$install_root/libexec/vdpm/pacman" --version >/dev/null
165+
"${run[@]}" "$install_root/bin/arm-vita-eabi-gcc" --version
144166
}
145167

146168
# Builds against $stage1_dir if set, then stages outputs plus provenance.
@@ -155,6 +177,7 @@ build_and_stage() {
155177
# The lock names the host; artifacts published under any other name
156178
# would not match what the caller asked to be built.
157179
-DVITASDK_HOST_NAME="$host"
180+
-DVITASDK_HOST_RUNNER="$(host_runner || true)"
158181
)
159182
[[ -n ${stage1_dir:-} ]] && cmake_args+=(-DVITASDK_STAGE1_DIR="$stage1_dir")
160183
local -a targets=(tarball)
@@ -175,12 +198,14 @@ build_and_stage() {
175198
cmake --build build --target "${targets[@]}" --parallel "$(ci_nproc)"
176199
report_ccache_statistics
177200

178-
# Only a native build can run its own output.
179-
if [[ -z $build_host ]]; then
201+
# Verified wherever it can be run, which is not the same as natively.
202+
if runner=$(host_runner); then
180203
cmake --build build --target check-toolchain-contract
181204
if is_packaged_host; then
182-
smoke_test_bootstrap "build/bootstraps/vitasdk-bootstrap-$host.tar.bz2"
205+
smoke_test_bootstrap "build/bootstraps/vitasdk-bootstrap-$host.tar.bz2" "$runner"
183206
fi
207+
else
208+
printf 'no way to run %s binaries here; contract and smoke test skipped\n' "$host"
184209
fi
185210

186211
stage_and_write_provenance

tests/ci/test-host-runner.sh

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env bash
2+
# Which hosts get their output verified, and how it is run.
3+
#
4+
# build-host.sh used to ask "is this native?", which skipped the toolchain
5+
# contract and the bootstrap smoke test for every cross. That is right for a
6+
# canadian cross to Windows or FreeBSD and wrong for Apple: an arm64 Mac runs
7+
# x86_64 Mach-O through Rosetta, so the machine that cross-builds the Intel
8+
# SDK is the one that can check it. Getting this wrong is silent -- the host
9+
# publishes either way, just unverified.
10+
11+
set -euo pipefail
12+
13+
repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)
14+
script="$repository_root/scripts/ci/build-host.sh"
15+
16+
failures=0
17+
18+
# host_runner() reads $host and $build_host from the script's scope, so it is
19+
# exercised the way the script calls it rather than through the CLI, which
20+
# would need a whole toolchain to reach the same line.
21+
runner_for()
22+
{
23+
host=$1 build_host=$2 bash -c '
24+
host=$host
25+
build_host=$build_host
26+
'"$(sed -n '/^host_runner()/,/^}/p' "$script")"'
27+
if runner=$(host_runner); then
28+
printf "runs:%s" "$runner"
29+
else
30+
printf "cannot"
31+
fi
32+
'
33+
}
34+
35+
check()
36+
{
37+
description=$1
38+
actual=$(runner_for "$2" "$3")
39+
if [[ $actual != "$4" ]]; then
40+
printf 'FAIL: %s\n expected: %s\n actual: %s\n' \
41+
"$description" "$4" "$actual" >&2
42+
failures=$((failures + 1))
43+
fi
44+
}
45+
46+
# Native: runs directly, no launcher.
47+
check "a native host runs its own output" \
48+
x86_64-linux-gnu "" "runs:"
49+
check "a native Mac runs its own output" \
50+
arm64-apple-darwin "" "runs:"
51+
52+
# The case this exists for.
53+
check "an arm64 Mac runs the Intel SDK it cross-built" \
54+
x86_64-apple-darwin arm64-apple-darwin "runs:arch -x86_64"
55+
56+
# The cases where the old comment was right and must stay right.
57+
check "a Linux box cannot run Windows binaries" \
58+
x86_64-w64-mingw32 x86_64-linux-gnu "cannot"
59+
check "a Linux box cannot run FreeBSD binaries" \
60+
x86_64-unknown-freebsd x86_64-linux-gnu "cannot"
61+
check "a Linux box cannot run the Intel Mac SDK either" \
62+
x86_64-apple-darwin x86_64-linux-gnu "cannot"
63+
64+
# Rosetta goes one way only: an Intel Mac does not run arm64.
65+
check "an Intel Mac cannot run an arm64 Mac SDK" \
66+
arm64-apple-darwin x86_64-apple-darwin "cannot"
67+
68+
# Which packaged hosts this script cannot run, pinned so that adding one
69+
# passes somebody's eyes. Windows is on the list and is still verified: a
70+
# dedicated job smoke-tests its bootstrap on a real Windows runner. The two
71+
# FreeBSD hosts are on it and are verified nowhere, which is a gap this test
72+
# records rather than fixes -- closing it needs a FreeBSD VM, not Rosetta.
73+
unverified=$(python3 - "$repository_root/cmake/hosts.json" <<'PYTHON'
74+
import json, sys
75+
hosts = json.load(open(sys.argv[1]))["hosts"]
76+
out = []
77+
for h in hosts:
78+
if not h["packaged"]:
79+
continue
80+
build_host = h.get("build_host")
81+
if not build_host:
82+
continue
83+
if h["name"] == "x86_64-apple-darwin" and build_host == "arm64-apple-darwin":
84+
continue
85+
out.append(h["name"])
86+
print(" ".join(sorted(out)))
87+
PYTHON
88+
)
89+
expected="aarch64-unknown-freebsd x86_64-unknown-freebsd x86_64-w64-mingw32"
90+
if [[ $unverified != "$expected" ]]; then
91+
printf 'FAIL: the set of hosts this script cannot run changed\n expected: %s\n actual: %s\n' \
92+
"$expected" "$unverified" >&2
93+
failures=$((failures + 1))
94+
fi
95+
96+
if (( failures )); then
97+
printf '%d host-runner check(s) failed\n' "$failures" >&2
98+
exit 1
99+
fi
100+
101+
printf 'host runner contract tests passed\n'

0 commit comments

Comments
 (0)