-
-
Notifications
You must be signed in to change notification settings - Fork 159
155 lines (143 loc) · 6.74 KB
/
Copy pathext-link.yml
File metadata and controls
155 lines (143 loc) · 6.74 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
# ---------------------------------------------------------------------------
# ext-link — per-PR LINK check for the `perry-ext-*` crates (#7656)
#
# A `perry-runtime` change broke the link of five `perry-ext-*` crates and no
# per-PR gate could have caught it (#7650, fixed in #7655); it surfaced at the
# next tag, days later.
#
# Why `cargo-test`'s scope cannot see this: `ci_test_scope.py` selects a
# reverse-dependency closure, and `_is_fanout_leaf` deliberately keeps
# `perry-ext-*` / `perry-stdlib` OUT of the fan-out. That is correct on its own
# terms — their unit tests are self-contained pure-Rust logic, and re-running
# ~40 crates on every foundational change is the cost the scoping exists to
# avoid. What it misses is that for these crates the coupling is the LINK, not
# the test: they pull in a feature-stripped runtime through `perry-ffi`'s
# `runtime-link` built with `-Wl,-dead_strip`, so a new reference edge inside
# `perry-runtime` can keep alive a chain the stripper had been removing. In
# #7650 that edge was one added call (`pin_object` -> `arena::classify_heap_space`)
# in code that had previously done a raw flag write, and the symptom was
# `Undefined symbols for architecture arm64`.
#
# So this job BUILDS and does not RUN: `cargo test --no-run` links the test
# binaries and stops. Running them would add time and check nothing this does
# not already.
#
# `--release` deliberately: `-dead_strip` is what makes the failure, and it is
# a release-profile behaviour. A dev-profile build links a different set and
# would be green through exactly the regression this exists to catch.
# ---------------------------------------------------------------------------
name: ext-link
on:
pull_request:
workflow_dispatch:
concurrency:
# PR runs supersede each other; a manual dispatch is never cancelled.
group: ext-link-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
ext-link:
runs-on: ubuntu-latest
# Measured on an arm64 dev Mac. COLD release target, the five crates from
# #7650 only: 7:24, 7 test binaries. All 38 ext crates with perry-runtime
# already built: 4:10, 216 test binaries. The shared `perry-runtime` release
# build dominates, so covering every ext crate costs LESS than building the
# runtime once — which is why the scope is all of them rather than the five
# that happened to fail in #7650.
#
# The bound is a backstop, not a budget: it has to cover a cold sccache
# building perry-runtime in release from scratch on a shared runner, which
# is far slower than the numbers above, while still cutting a true hang.
timeout-minutes: 120
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "false"
SCCACHE_DIR: ${{ github.workspace }}/.sccache
SCCACHE_CACHE_SIZE: "8G"
CARGO_INCREMENTAL: "0"
steps:
# This job compiles PR-controlled build scripts; don't leave the workflow
# token in .git/config for them to read.
- uses: actions/checkout@v7
with:
persist-credentials: false
# Cheap gate: no toolchain, no cargo, no cache restore. Every step below
# is skipped when the diff cannot change what the archives link.
- name: Compute ext-link scope
id: scope
env:
GH_TOKEN: ${{ github.token }}
run: |
python3 scripts/ci_ext_link_scope.py --self-test
if [ "${{ github.event_name }}" = "pull_request" ]; then
changed_files="$(gh pr view "${{ github.event.pull_request.number }}" \
--json files --jq '.files[].path')"
else
# Manual dispatch: check everything.
changed_files="crates/perry-runtime/src/lib.rs"
fi
pkgs="$(printf '%s\n' "$changed_files" | python3 scripts/ci_ext_link_scope.py)"
if [ -z "$pkgs" ]; then
echo "Diff cannot change what the ext archives link — nothing to do."
echo "pkgs=" >> "$GITHUB_OUTPUT"
else
echo "Linking $(printf '%s\n' "$pkgs" | wc -l) perry-ext-* crates:"
printf '%s\n' "$pkgs"
{
echo 'pkgs<<PERRY_EOF'
printf '%s\n' "$pkgs"
echo 'PERRY_EOF'
} >> "$GITHUB_OUTPUT"
fi
- name: Install Rust toolchain
if: steps.scope.outputs.pkgs != ''
uses: dtolnay/rust-toolchain@stable
- if: steps.scope.outputs.pkgs != ''
uses: ./.github/actions/setup-llvm22
- name: Install sccache
if: steps.scope.outputs.pkgs != ''
uses: mozilla-actions/sccache-action@v0.0.11
- name: Cache sccache objects
if: steps.scope.outputs.pkgs != ''
uses: actions/cache@v6
with:
path: ${{ github.workspace }}/.sccache
key: sccache-${{ runner.os }}-perry-${{ github.job }}-${{ github.run_id }}
restore-keys: |
sccache-${{ runner.os }}-perry-
- uses: Swatinem/rust-cache@v2
if: steps.scope.outputs.pkgs != ''
with:
shared-key: "${{ runner.os }}-perry"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Link the ext crates
if: steps.scope.outputs.pkgs != ''
env:
# lld has repeatedly SIGBUS'd large links on the shared runner.
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS: "-C linker-features=-lld"
CARGO_PROFILE_TEST_DEBUG: "0"
CARGO_BUILD_JOBS: "1"
PKGS: ${{ steps.scope.outputs.pkgs }}
run: |
# Explicit rather than relying on the runner's default `bash -e`: a
# failing `cargo test --no-run` IS the regression this job exists to
# catch, so its exit status must abort the step under any shell.
set -euo pipefail
args=""
while read -r pkg; do
[ -n "$pkg" ] || continue
args="$args -p $pkg"
done <<< "$PKGS"
# `--message-format=json` so the count below comes from cargo rather
# than being inferred from exit status. The counting lives in the
# script (`--count-linked`) so this file carries no inline Python —
# a heredoc at column 0 silently breaks the YAML block scalar.
# shellcheck disable=SC2086
cargo test --release --no-run --message-format=json $args > /tmp/ext-link.json
linked="$(python3 scripts/ci_ext_link_scope.py --count-linked /tmp/ext-link.json)"
echo "linked test binaries: $linked"
if [ "$linked" -eq 0 ]; then
echo "::error::ext-link linked ZERO test binaries. The gate ran but had no subject — either the scope selected no packages or cargo built nothing. Fix the scope rather than trusting this green."
exit 1
fi