Skip to content

Commit 5c5aa70

Browse files
committed
ci: reuse finished builds from R2 and cache compiles with sccache
Builds were 2.5h per job with the compile phase at 95% of the wall clock, and the same inputs were rebuilt on every main->tag pair. - config now emits an inputs_hash over config.env, patches/, scripts/matrix/ and the workflow itself; jobs skip everything when artifacts/<hash>/ in R2 already holds their tarballs, and upload theirs there after a build. - builds compile through sccache backed by the same bucket under sccache/, so the two prefixes can carry separate lifecycle rules. Both scripts take the wrapper as an explicit --cc-wrapper flag. - fetch.sh gains --no-history, passed by the workflow, shallow-fetching the DEPS tree. All R2 steps are skipped when the secrets are absent (e.g. fork PRs) and the build then runs exactly as before.
1 parent 737e8bd commit 5c5aa70

4 files changed

Lines changed: 187 additions & 32 deletions

File tree

‎.github/workflows/build-matrix.yml‎

Lines changed: 141 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ on:
2323
permissions:
2424
contents: write
2525

26+
env:
27+
SCCACHE_VERSION: 0.10.0
28+
2629
# Nine jobs a run, several hours each -- superseded runs are not worth finishing.
2730
# Tags are excluded from cancellation so a release is never interrupted.
2831
concurrency:
@@ -35,13 +38,19 @@ jobs:
3538
outputs:
3639
v8_version: ${{ steps.cfg.outputs.v8_version }}
3740
ndk_release: ${{ steps.cfg.outputs.ndk_release }}
41+
inputs_hash: ${{ steps.cfg.outputs.inputs_hash }}
3842
steps:
3943
- uses: actions/checkout@v7
4044
- id: cfg
4145
run: |
4246
set -a; . ./config.env; set +a
4347
echo "v8_version=$V8_VERSION" >> "$GITHUB_OUTPUT"
4448
echo "ndk_release=$ANDROID_NDK_RELEASE" >> "$GITHUB_OUTPUT"
49+
# Everything the artifacts are a function of, hashed by blob id.
50+
# Commits with an unchanged hash produce identical artifacts, so a
51+
# finished build in R2 can be reused instead of repeated.
52+
echo "inputs_hash=$(git ls-files -s config.env patches scripts/matrix .github/workflows/build-matrix.yml \
53+
| git hash-object --stdin)" >> "$GITHUB_OUTPUT"
4554
4655
android:
4756
needs: config
@@ -52,61 +61,129 @@ jobs:
5261
fail-fast: false
5362
matrix:
5463
abi: [arm64-v8a, x86_64, armeabi-v7a, x86]
64+
# The R2 bucket has two prefixes with separate lifecycle rules:
65+
# artifacts/<inputs-hash>/ holds finished tarballs (expired quickly; a miss
66+
# just means building, as before) and sccache/ holds the compiler cache
67+
# (kept long). Every R2 step is skipped when the secrets are absent, e.g.
68+
# on a fork PR, and the build then runs exactly as it would without R2.
69+
env:
70+
R2_BUCKET: ${{ vars.R2_BUCKET }}
71+
R2_ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
72+
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
73+
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
74+
AWS_DEFAULT_REGION: auto
75+
# aws-cli >= 2.23 defaults to CRC checksums R2 rejects.
76+
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
77+
AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
78+
SCCACHE_BUCKET: ${{ vars.R2_BUCKET }}
79+
SCCACHE_ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
80+
SCCACHE_REGION: auto
81+
SCCACHE_S3_KEY_PREFIX: sccache
5582
steps:
5683
- uses: actions/checkout@v7
5784

85+
- name: Check R2 for prebuilt artifacts
86+
id: cache
87+
if: env.AWS_ACCESS_KEY_ID != ''
88+
run: |
89+
keys="v8-${{ needs.config.outputs.v8_version }}-android-${{ matrix.abi }}.tar.gz"
90+
if [ "${{ matrix.abi }}" = "arm64-v8a" ]; then
91+
keys="$keys v8-${{ needs.config.outputs.v8_version }}-src-headers.tar.gz"
92+
fi
93+
prefix="artifacts/${{ needs.config.outputs.inputs_hash }}"
94+
hit=true
95+
for k in $keys; do
96+
aws s3api head-object --endpoint-url "$R2_ENDPOINT" \
97+
--bucket "$R2_BUCKET" --key "$prefix/$k" >/dev/null 2>&1 || hit=false
98+
done
99+
if [ "$hit" = true ]; then
100+
for k in $keys; do
101+
aws s3 cp --endpoint-url "$R2_ENDPOINT" "s3://$R2_BUCKET/$prefix/$k" "$k"
102+
done
103+
fi
104+
echo "hit=$hit" >> "$GITHUB_OUTPUT"
105+
58106
# A 32-bit target builds mksnapshot and the bytecode-builtins generator
59107
# as 32-bit x86 host binaries. Without the i386 runtime they link and
60108
# then fail to execute, which surfaces only as a failed
61109
# generate_bytecode_builtins_list action.
62110
- name: Enable i386 multiarch
111+
if: steps.cache.outputs.hit != 'true'
63112
run: |
64113
sudo dpkg --add-architecture i386
65114
sudo apt-get update -qq
66115
sudo apt-get install -y -qq --no-install-recommends \
67116
libc6:i386 libstdc++6:i386 libatomic1:i386 zlib1g:i386 rsync
68117
69118
- name: Install the Android NDK ${{ needs.config.outputs.ndk_release }}
119+
if: steps.cache.outputs.hit != 'true'
70120
run: |
71121
curl -fsSL -o /tmp/ndk.zip \
72122
"https://dl.google.com/android/repository/android-ndk-${{ needs.config.outputs.ndk_release }}-linux.zip"
73123
unzip -q /tmp/ndk.zip -d "$HOME"
74124
echo "ANDROID_NDK_ROOT=$HOME/android-ndk-${{ needs.config.outputs.ndk_release }}" >> "$GITHUB_ENV"
75125
76126
- name: Install depot_tools
127+
if: steps.cache.outputs.hit != 'true'
77128
run: |
78129
git clone -q --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$HOME/depot_tools"
79130
echo "$HOME/depot_tools" >> "$GITHUB_PATH"
80131
132+
- name: Install sccache
133+
if: env.AWS_ACCESS_KEY_ID != '' && steps.cache.outputs.hit != 'true'
134+
run: |
135+
curl -fsSL "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
136+
| tar -xz -C /tmp
137+
mkdir -p "$HOME/.local/bin"
138+
install "/tmp/sccache-v${SCCACHE_VERSION}-x86_64-unknown-linux-musl/sccache" "$HOME/.local/bin/"
139+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
140+
81141
- name: Fetch and patch V8
82-
run: scripts/matrix/fetch.sh --platform android
142+
if: steps.cache.outputs.hit != 'true'
143+
run: scripts/matrix/fetch.sh --platform android --no-history
83144

84145
- name: Build ${{ matrix.abi }}
85-
run: scripts/matrix/build-android.sh --abi ${{ matrix.abi }}
146+
if: steps.cache.outputs.hit != 'true'
147+
run: >
148+
scripts/matrix/build-android.sh --abi ${{ matrix.abi }}
149+
${{ env.AWS_ACCESS_KEY_ID != '' && '--cc-wrapper sccache' || '' }}
150+
151+
- name: sccache stats
152+
if: env.AWS_ACCESS_KEY_ID != '' && steps.cache.outputs.hit != 'true'
153+
run: sccache --show-stats
86154

87155
- name: Package
156+
if: steps.cache.outputs.hit != 'true'
88157
run: |
89158
tar -czf "v8-${{ needs.config.outputs.v8_version }}-android-${{ matrix.abi }}.tar.gz" \
90159
-C dist "android-${{ matrix.abi }}"
91160
92-
- uses: actions/upload-artifact@v7
93-
with:
94-
name: v8-android-${{ matrix.abi }}
95-
path: v8-*-android-${{ matrix.abi }}.tar.gz
96-
retention-days: 7
97-
98161
# The internal headers are architecture-independent, so one job produces
99162
# them. It piggybacks on a build rather than getting its own job because
100163
# src/inspector/protocol/*.h are generated into gen/ and only exist once
101164
# something has been built.
102165
- name: Package the internal headers
103-
if: matrix.abi == 'arm64-v8a'
166+
if: matrix.abi == 'arm64-v8a' && steps.cache.outputs.hit != 'true'
104167
run: |
105168
scripts/matrix/package-src-headers.sh \
106169
--gen-dir .v8/v8/out.gn/android-arm64-release/gen
107170
tar -czf "v8-${{ needs.config.outputs.v8_version }}-src-headers.tar.gz" \
108171
-C dist src-headers
109172
173+
- name: Upload the artifacts to R2
174+
if: env.AWS_ACCESS_KEY_ID != '' && steps.cache.outputs.hit != 'true'
175+
run: |
176+
prefix="artifacts/${{ needs.config.outputs.inputs_hash }}"
177+
for f in v8-*.tar.gz; do
178+
aws s3 cp --endpoint-url "$R2_ENDPOINT" "$f" "s3://$R2_BUCKET/$prefix/$f"
179+
done
180+
181+
- uses: actions/upload-artifact@v7
182+
with:
183+
name: v8-android-${{ matrix.abi }}
184+
path: v8-*-android-${{ matrix.abi }}.tar.gz
185+
retention-days: 7
186+
110187
- uses: actions/upload-artifact@v7
111188
if: matrix.abi == 'arm64-v8a'
112189
with:
@@ -123,25 +200,78 @@ jobs:
123200
fail-fast: false
124201
matrix:
125202
variant: [arm64-device, arm64-simulator, x64-simulator, arm64-catalyst, x64-catalyst]
203+
# See the android job for the R2 bucket layout and the missing-secrets
204+
# fallback; this job mirrors it.
205+
env:
206+
R2_BUCKET: ${{ vars.R2_BUCKET }}
207+
R2_ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
208+
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
209+
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
210+
AWS_DEFAULT_REGION: auto
211+
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
212+
AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
213+
SCCACHE_BUCKET: ${{ vars.R2_BUCKET }}
214+
SCCACHE_ENDPOINT: https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
215+
SCCACHE_REGION: auto
216+
SCCACHE_S3_KEY_PREFIX: sccache
126217
steps:
127218
- uses: actions/checkout@v7
128219

220+
- name: Check R2 for a prebuilt artifact
221+
id: cache
222+
if: env.AWS_ACCESS_KEY_ID != ''
223+
run: |
224+
key="artifacts/${{ needs.config.outputs.inputs_hash }}/v8-${{ needs.config.outputs.v8_version }}-ios-${{ matrix.variant }}.tar.gz"
225+
if aws s3api head-object --endpoint-url "$R2_ENDPOINT" \
226+
--bucket "$R2_BUCKET" --key "$key" >/dev/null 2>&1; then
227+
aws s3 cp --endpoint-url "$R2_ENDPOINT" "s3://$R2_BUCKET/$key" .
228+
echo "hit=true" >> "$GITHUB_OUTPUT"
229+
else
230+
echo "hit=false" >> "$GITHUB_OUTPUT"
231+
fi
232+
129233
- name: Install depot_tools
234+
if: steps.cache.outputs.hit != 'true'
130235
run: |
131236
git clone -q --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git "$HOME/depot_tools"
132237
echo "$HOME/depot_tools" >> "$GITHUB_PATH"
133238
239+
- name: Install sccache
240+
if: env.AWS_ACCESS_KEY_ID != '' && steps.cache.outputs.hit != 'true'
241+
run: |
242+
curl -fsSL "https://github.com/mozilla/sccache/releases/download/v${SCCACHE_VERSION}/sccache-v${SCCACHE_VERSION}-aarch64-apple-darwin.tar.gz" \
243+
| tar -xz -C /tmp
244+
mkdir -p "$HOME/.local/bin"
245+
install "/tmp/sccache-v${SCCACHE_VERSION}-aarch64-apple-darwin/sccache" "$HOME/.local/bin/"
246+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
247+
134248
- name: Fetch and patch V8
135-
run: scripts/matrix/fetch.sh --platform ios
249+
if: steps.cache.outputs.hit != 'true'
250+
run: scripts/matrix/fetch.sh --platform ios --no-history
136251

137252
- name: Build ${{ matrix.variant }}
138-
run: scripts/matrix/build-ios.sh --variant ${{ matrix.variant }}
253+
if: steps.cache.outputs.hit != 'true'
254+
run: >
255+
scripts/matrix/build-ios.sh --variant ${{ matrix.variant }}
256+
${{ env.AWS_ACCESS_KEY_ID != '' && '--cc-wrapper sccache' || '' }}
257+
258+
- name: sccache stats
259+
if: env.AWS_ACCESS_KEY_ID != '' && steps.cache.outputs.hit != 'true'
260+
run: sccache --show-stats
139261

140262
- name: Package
263+
if: steps.cache.outputs.hit != 'true'
141264
run: |
142265
tar -czf "v8-${{ needs.config.outputs.v8_version }}-ios-${{ matrix.variant }}.tar.gz" \
143266
-C dist "ios-${{ matrix.variant }}"
144267
268+
- name: Upload the artifact to R2
269+
if: env.AWS_ACCESS_KEY_ID != '' && steps.cache.outputs.hit != 'true'
270+
run: |
271+
f="v8-${{ needs.config.outputs.v8_version }}-ios-${{ matrix.variant }}.tar.gz"
272+
aws s3 cp --endpoint-url "$R2_ENDPOINT" "$f" \
273+
"s3://$R2_BUCKET/artifacts/${{ needs.config.outputs.inputs_hash }}/$f"
274+
145275
- uses: actions/upload-artifact@v7
146276
with:
147277
name: v8-ios-${{ matrix.variant }}

‎scripts/matrix/build-android.sh‎

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,33 @@ source "$ROOT_DIR/config.env"
1313
V8_DIR="$ROOT_DIR/.v8/v8"
1414
NDK_ROOT=""
1515
ABI=""
16+
CC_WRAPPER=""
1617
NINJA_ARGS=()
1718

1819
usage() {
1920
cat <<EOF
20-
Usage: $(basename "$0") --abi <abi> [--v8-dir <path>] [--ndk-root <path>] [-- <ninja args>]
21-
22-
--abi <abi> armeabi-v7a | arm64-v8a | x86 | x86_64
23-
--v8-dir <path> V8 sources (default: $V8_DIR)
24-
--ndk-root <path> NDK to build against; must be the same one the runtime is
25-
built with. Defaults to \$ANDROID_NDK_ROOT.
21+
Usage: $(basename "$0") --abi <abi> [--v8-dir <path>] [--ndk-root <path>]
22+
[--cc-wrapper <bin>] [-- <ninja args>]
23+
24+
--abi <abi> armeabi-v7a | arm64-v8a | x86 | x86_64
25+
--v8-dir <path> V8 sources (default: $V8_DIR)
26+
--ndk-root <path> NDK to build against; must be the same one the runtime is
27+
built with. Defaults to \$ANDROID_NDK_ROOT.
28+
--cc-wrapper <bin> Compiler launcher (e.g. sccache), passed to gn as
29+
cc_wrapper. Must be on PATH.
2630
EOF
2731
}
2832

2933
while [ $# -gt 0 ]; do
3034
case "$1" in
31-
--abi) ABI="$2"; shift 2 ;;
32-
--abi=*) ABI="${1#*=}"; shift ;;
33-
--v8-dir) V8_DIR="$2"; shift 2 ;;
34-
--v8-dir=*) V8_DIR="${1#*=}"; shift ;;
35-
--ndk-root) NDK_ROOT="$2"; shift 2 ;;
36-
--ndk-root=*) NDK_ROOT="${1#*=}"; shift ;;
35+
--abi) ABI="$2"; shift 2 ;;
36+
--abi=*) ABI="${1#*=}"; shift ;;
37+
--v8-dir) V8_DIR="$2"; shift 2 ;;
38+
--v8-dir=*) V8_DIR="${1#*=}"; shift ;;
39+
--ndk-root) NDK_ROOT="$2"; shift 2 ;;
40+
--ndk-root=*) NDK_ROOT="${1#*=}"; shift ;;
41+
--cc-wrapper) CC_WRAPPER="$2"; shift 2 ;;
42+
--cc-wrapper=*) CC_WRAPPER="${1#*=}"; shift ;;
3743
--) shift; NINJA_ARGS=("$@"); break ;;
3844
-h|--help) usage; exit 0 ;;
3945
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
@@ -116,6 +122,10 @@ GN_ARGS="
116122
v8_control_flow_integrity=false
117123
"
118124

125+
if [ -n "$CC_WRAPPER" ]; then
126+
GN_ARGS="$GN_ARGS cc_wrapper=\"$CC_WRAPPER\""
127+
fi
128+
119129
OUTFOLDER="out.gn/android-$CPU-release"
120130
DIST="$ROOT_DIR/dist/android-$ABI"
121131

‎scripts/matrix/build-ios.sh‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,29 @@ source "$ROOT_DIR/config.env"
1919

2020
V8_DIR="$ROOT_DIR/.v8/v8"
2121
VARIANT=""
22+
CC_WRAPPER=""
2223
NINJA_ARGS=()
2324

2425
usage() {
2526
cat <<EOF
26-
Usage: $(basename "$0") --variant <variant> [--v8-dir <path>] [-- <ninja args>]
27+
Usage: $(basename "$0") --variant <variant> [--v8-dir <path>]
28+
[--cc-wrapper <bin>] [-- <ninja args>]
2729
28-
--variant arm64-device | arm64-simulator | x64-simulator
29-
| arm64-catalyst | x64-catalyst
30+
--variant arm64-device | arm64-simulator | x64-simulator
31+
| arm64-catalyst | x64-catalyst
32+
--cc-wrapper <bin> Compiler launcher (e.g. sccache), passed to gn as
33+
cc_wrapper. Must be on PATH.
3034
EOF
3135
}
3236

3337
while [ $# -gt 0 ]; do
3438
case "$1" in
35-
--variant) VARIANT="$2"; shift 2 ;;
36-
--variant=*) VARIANT="${1#*=}"; shift ;;
37-
--v8-dir) V8_DIR="$2"; shift 2 ;;
38-
--v8-dir=*) V8_DIR="${1#*=}"; shift ;;
39+
--variant) VARIANT="$2"; shift 2 ;;
40+
--variant=*) VARIANT="${1#*=}"; shift ;;
41+
--v8-dir) V8_DIR="$2"; shift 2 ;;
42+
--v8-dir=*) V8_DIR="${1#*=}"; shift ;;
43+
--cc-wrapper) CC_WRAPPER="$2"; shift 2 ;;
44+
--cc-wrapper=*) CC_WRAPPER="${1#*=}"; shift ;;
3945
--) shift; NINJA_ARGS=("$@"); break ;;
4046
-h|--help) usage; exit 0 ;;
4147
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
@@ -126,6 +132,10 @@ else
126132
ios_deployment_target=\"$IOS_DEPLOYMENT_TARGET\""
127133
fi
128134

135+
if [ -n "$CC_WRAPPER" ]; then
136+
GN_ARGS="$GN_ARGS cc_wrapper=\"$CC_WRAPPER\""
137+
fi
138+
129139
OUTFOLDER="out.gn/$VARIANT-release"
130140
DIST="$ROOT_DIR/dist/ios-$VARIANT"
131141

‎scripts/matrix/fetch.sh‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@ source "$ROOT_DIR/config.env"
1414

1515
PLATFORM=""
1616
V8_PARENT="$ROOT_DIR/.v8"
17+
NO_HISTORY=""
1718

1819
usage() {
1920
cat <<EOF
20-
Usage: $(basename "$0") --platform <android|ios> [--v8-dir <path>]
21+
Usage: $(basename "$0") --platform <android|ios> [--v8-dir <path>] [--no-history]
2122
2223
--platform Which DEPS set to sync and which patches to apply.
2324
--v8-dir <path> Directory to hold the checkout (default: $V8_PARENT).
2425
Sources land in <path>/v8.
26+
--no-history Shallow-fetch every repo in the DEPS tree. Leaves a checkout
27+
that later syncs to a different version may have to re-fetch,
28+
so best suited to throwaway checkouts.
2529
EOF
2630
}
2731

@@ -31,6 +35,7 @@ while [ $# -gt 0 ]; do
3135
--platform=*) PLATFORM="${1#*=}"; shift ;;
3236
--v8-dir) V8_PARENT="$2"; shift 2 ;;
3337
--v8-dir=*) V8_PARENT="${1#*=}"; shift ;;
38+
--no-history) NO_HISTORY="--no-history"; shift ;;
3439
-h|--help) usage; exit 0 ;;
3540
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
3641
esac
@@ -69,7 +74,7 @@ checkpoint "Syncing"
6974
# (//build/linux/debian_bullseye_amd64-sysroot)" for the host toolchain that
7075
# torque and mksnapshot are built with. target_os in .gclient already selects
7176
# the target deps, and additionally keeping the host ones is what we want.
72-
gclient sync --reset --with_branch_head \
77+
gclient sync --reset --with_branch_head $NO_HISTORY \
7378
--revision "$V8_VERSION" --delete_unversioned_trees
7479

7580
checkpoint "Patching"

0 commit comments

Comments
 (0)