-
Notifications
You must be signed in to change notification settings - Fork 2
336 lines (317 loc) · 15.3 KB
/
Copy pathtemplate_native_build.yml
File metadata and controls
336 lines (317 loc) · 15.3 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
name: Native Build Template
on:
workflow_call:
inputs:
target:
required: true
type: string
description: "Rust target triple (e.g. x86_64-unknown-linux-musl)"
runner:
required: true
type: string
description: "GitHub Actions runner label (e.g. ubuntu-latest)"
binary_ext:
required: false
type: string
default: ""
description: "Binary extension (.exe for Windows, empty otherwise)"
linux_cross:
required: false
type: boolean
default: false
description: "Linux aarch64 cross-compilation"
macos_cross:
required: false
type: boolean
default: false
description: "macOS x86_64 cross-compilation on ARM runner"
mac_cross_linux:
required: false
type: boolean
default: false
description: "Cross-compile a macOS target from Linux via soldr's blessed path + managed Apple SDK"
ref:
required: false
type: string
default: ""
description: "Git ref to build (branch, tag, or SHA)"
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
# cc-rs env vars for the xwin lanes. ring's build.rs uses cc-rs;
# without these, cc-rs invokes plain `clang` and the curve25519.c
# compile fails with `error: no such file or directory: '/imsvc'`
# (clang-cl-style include flags on a clang driver). The vars are
# ignored by non-MSVC targets, so it's safe to set them globally.
# AR_<target>=llvm-lib mirrors cargo-xwin's expectation so cc-rs
# archiving matches the linker side.
CC_x86_64_pc_windows_msvc: clang-cl
CXX_x86_64_pc_windows_msvc: clang-cl
AR_x86_64_pc_windows_msvc: llvm-lib
CC_aarch64_pc_windows_msvc: clang-cl
CXX_aarch64_pc_windows_msvc: clang-cl
AR_aarch64_pc_windows_msvc: llvm-lib
permissions:
contents: read
attestations: write
id-token: write
jobs:
build:
name: Build (${{ inputs.target }})
runs-on: ${{ inputs.runner }}
timeout-minutes: 90
env:
# Native Windows release builds can be quiet for longer than soldr's
# default 30 minute cargo diagnostic watchdog while rustc/link.exe are
# still active. Match the release-binary step's 45 minute timeout so
# GitHub Actions owns the outer deadline instead of soldr killing the
# build early.
SOLDR_CARGO_WAIT_TIMEOUT_SECS: "2700"
# Six cold release lanes run concurrently on two-core hosted runners.
# Bound rustc and soldr concurrency so cross-target codegen stays within
# the runner memory limit instead of being killed by the host.
CARGO_BUILD_JOBS: "1"
SOLDR_JOBS: "1"
# cargo-zigbuild clears generic CFLAGS while constructing a cross-target
# environment. Use cc-rs's target-specific variables so the vendored
# mimalloc-pprof build keeps this narrow Zig diagnostic demotion.
CFLAGS_x86_64_unknown_linux_musl: "-Wno-error=date-time"
CFLAGS_aarch64_unknown_linux_musl: "-Wno-error=date-time"
CFLAGS_x86_64_apple_darwin: "-Wno-error=date-time"
CFLAGS_aarch64_apple_darwin: "-Wno-error=date-time"
# Keep every soldr invocation in this reusable job on the same catalogue.
# soldr >= 0.9.5 consumes its multipart v2 assets, including Apple SDKs
# whose legacy direct-LFS URLs are no longer anonymously downloadable.
SOLDR_TOOLCHAIN_ORIGIN: https://zackees.github.io/soldr-toolchain
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || github.sha }}
- name: Setup soldr
id: setup-soldr
timeout-minutes: 10
uses: zackees/setup-soldr@v0
with:
cache: true
build-cache: true
target-cache: true
prebuild-deps: none
# `fast` (the default) injects mold-or-rust-lld via SOLDR_LINKER,
# which clobbers the per-target linker config that
# `cargo zigbuild` needs for aarch64-unknown-linux-musl
# cross-compile (clang tries to use host /usr/bin/ld and fails
# with "file in wrong format" on the cross crt1.o). Disable the
# injection and let cargo + the toolchain decide.
linker: platform-default
# Native release builds produce a payload larger than the
# 512 MiB action default (issue #400). Raise the soft warn
# threshold; hard cap stays at the action default (6 GiB).
cache-payload-warn-bytes: 2GiB
cache-key-suffix: native-${{ inputs.target }}
- uses: actions/setup-python@v6
with:
# The extension's public ABI baseline is abi3-py310. Keep release
# tooling on that version so a newer interpreter cannot constrain
# the repository's dependency lock (FastLED/fbuild#1451).
python-version: "3.10"
# rust-toolchain.toml pins 1.95.0 which overrides the above;
# ensure the target stdlib is installed for the pinned toolchain too.
#
# The remove + add dance is required for aarch64-pc-windows-msvc
# (and a known-good safety belt for other targets): setup-soldr's
# The thin-mode build cache + setup-soldr's solo-toolchain cache
# restore-key fallback can land an inconsistent state where the
# rustup component manifest claims `rust-std-<target>` is
# installed but the actual .rlib files have been trimmed from
# the toolchain dir. `rustup target add` then says "is up to
# date" while the files stay missing, and the build fails with
# E0463 'can't find crate for `core`'. `rustup target remove`
# itself fails (No such file or directory on the rust-std
# manifest) and silently rolls back, leaving the manifest
# entry intact.
#
# Brute-force fix: delete the cached host toolchain dir if it
# exists, then `rustup toolchain install 1.95.0` reinstalls
# cleanly from scratch and `rustup target add <triple>` works
# against a fresh manifest. This is idempotent — if RUSTUP_HOME
# is already healthy, the install is a no-op refresh and the
# target add succeeds.
- name: Add Rust target
shell: bash
run: |
# On native Windows runner the toolchain dir lives outside
# $RUSTUP_HOME's `toolchains/1.95.0-x86_64-unknown-linux-gnu`
# path used by setup-soldr's Linux solo-cache. Skip the
# stale-cache reinstall there; rustup is already healthy.
if [ "${{ runner.os }}" != "Linux" ]; then
rustup target add ${{ inputs.target }}
exit 0
fi
TOOLCHAIN_DIR="$RUSTUP_HOME/toolchains/1.95.0-x86_64-unknown-linux-gnu"
if [ -d "$TOOLCHAIN_DIR" ] && [ ! -f "$TOOLCHAIN_DIR/lib/rustlib/manifest-rust-std-${{ inputs.target }}" ]; then
# Cache restored an older toolchain manifest that doesn't
# know about this target's rust-std component. The reinstall
# below recreates the manifest from scratch, but only do it
# when we have evidence of the inconsistency so cache-hit
# warm-paths stay fast.
echo "Detected stale toolchain state for ${{ inputs.target }} — reinstalling 1.95.0"
rm -rf "$TOOLCHAIN_DIR"
rustup toolchain install 1.95.0 --profile minimal --component rustfmt --component clippy
fi
rustup target add ${{ inputs.target }}
# Linux musl toolchain (libudev is auto-excluded for musl targets by serialport)
- name: Install Linux dependencies
if: runner.os == 'Linux' && !inputs.linux_cross && !contains(inputs.target, 'pc-windows-msvc')
timeout-minutes: 5
run: |
sudo apt-get -o Acquire::http::Timeout=30 -o Acquire::Retries=3 update
sudo apt-get -o Acquire::http::Timeout=30 -o Acquire::Retries=3 install -y musl-tools pkg-config
# soldr's blessed cross path, step 1 of 2. `soldr prepare` installs the
# target stdlib and materializes the compiler, linker and SDK/sysroot,
# then exports the target-scoped environment that `soldr build`
# consumes. Run for EVERY target, native included: it is a no-op where
# nothing needs fetching, and running it unconditionally means one code
# path instead of a per-target branch.
#
# Kept as its own step so a slow or failing SDK/sysroot download is
# diagnosable separately from the compile.
#
# SOLDR_TOOLCHAIN_ORIGIN is job-scoped above so this step and both
# build steps resolve the exact same catalogue.
- name: Prepare target toolchain
timeout-minutes: 15
shell: bash
run: soldr prepare --target ${{ inputs.target }}
- name: Build release binaries
timeout-minutes: 45
shell: bash
run: |
# One invocation for every target, native and cross alike.
# `soldr build` is soldr's blessed-default surface and consumes
# exactly what `soldr prepare` set up above.
#
# This replaced four hand-rolled branches: `soldr cargo zigbuild`
# for the apple-darwin lanes, bare `cargo zigbuild` for musl,
# and for x86_64-pc-windows-msvc a cargo-xwin install plus ~40
# lines of shell that hunted through the xwin cache and created
# lower/upper-case symlinks for msvcrt.lib and oldnames.lib so a
# case-sensitive filesystem would satisfy MSVC's import
# libraries. soldr owns the xwin cache now; none of that is ours
# to carry. See agents/docs/cross-compilation.md.
soldr build --release --target ${{ inputs.target }} \
-p fbuild-cli \
-p fbuild-daemon
# PyO3 extension — built for ALL targets, including cross-compiled,
# through the same blessed path as the binaries.
#
# manylinux wheels are glibc-based and must run on glibc >= 2.17. soldr's
# catalogue sysroot holds that floor on its own: the extension built via
# `soldr build --target x86_64-unknown-linux-gnu` tops out at GLIBC_2.16,
# measured 2026-09-03 with
# objdump -T lib_native.so | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail
# That is *below* the floor, and below the 2.17 the retired
# `cargo zigbuild --target ...-gnu.2.17` lane produced.
#
# Do NOT pass zigbuild's `.2.17` target suffix to soldr. soldr has no
# such target, logs the miss as a warning, falls back to the bare host
# toolchain and still exits 0 — which yields a GLIBC_2.39 .so wearing a
# manylinux_2_17 tag. The plain triple is correct and is what holds the
# floor. See agents/docs/cross-compilation.md.
#
# PYO3_NO_PYTHON=1 + the abi3-py310 feature let pyo3-build-config skip
# the host-interpreter lookup on cross builds.
- name: Build Python extension
timeout-minutes: 30
shell: bash
run: |
PYTHON_TARGET_DIR="target/python-extension"
PYO3_TARGET="${{ inputs.target }}"
if [ "${{ runner.os }}" = "Linux" ] \
&& [[ "${{ inputs.target }}" == *-unknown-linux-musl ]]; then
# The CLI binaries stay on musl for static-link portability, but a
# manylinux wheel is glibc-based -- build the extension for gnu.
ARCH="${PYO3_TARGET%%-*}"
PYO3_TARGET="${ARCH}-unknown-linux-gnu"
soldr prepare --target "$PYO3_TARGET"
fi
PYO3_NO_PYTHON=1 soldr build --release \
--target-dir "${PYTHON_TARGET_DIR}" \
--target "$PYO3_TARGET" -p fbuild-python \
--features extension-module
- name: Stage artifacts
shell: bash
run: |
PYTHON_TARGET_DIR="target/python-extension"
mkdir -p staging
cp target/${{ inputs.target }}/release/fbuild${{ inputs.binary_ext }} staging/
cp target/${{ inputs.target }}/release/fbuild-daemon${{ inputs.binary_ext }} staging/
# Stage Python extension — location depends on how it was built
if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then
for ext_src in \
"${PYTHON_TARGET_DIR}/${{ inputs.target }}/release/_native.dll" \
"${PYTHON_TARGET_DIR}/release/_native.dll"; do
[ -f "$ext_src" ] && cp "$ext_src" staging/_native.pyd && break
done
elif [ "${{ inputs.mac_cross_linux }}" = "true" ]; then
# Linux → mac cross: dylib lands under the apple-darwin
# target dir (no manylinux floor, unlike the Linux PyO3 lane).
ext_src="${PYTHON_TARGET_DIR}/${{ inputs.target }}/release/lib_native.dylib"
[ -f "$ext_src" ] && cp "$ext_src" staging/_native.abi3.so
elif [ "${{ runner.os }}" = "Linux" ]; then
TARGET="${{ inputs.target }}"
ARCH="${TARGET%%-*}"
ext_src="${PYTHON_TARGET_DIR}/${ARCH}-unknown-linux-gnu.2.17/release/lib_native.so"
[ -f "$ext_src" ] || ext_src="${PYTHON_TARGET_DIR}/${ARCH}-unknown-linux-gnu/release/lib_native.so"
[ -f "$ext_src" ] && cp "$ext_src" staging/_native.abi3.so
elif [ "${{ runner.os }}" = "macOS" ]; then
ext_src="${PYTHON_TARGET_DIR}/${{ inputs.target }}/release/lib_native.dylib"
[ -f "$ext_src" ] && cp "$ext_src" staging/_native.abi3.so
else
for ext in so dylib; do
src="${PYTHON_TARGET_DIR}/release/lib_native.$ext"
[ -f "$src" ] && cp "$src" staging/_native.abi3.so && break
done
fi
# Fail fast if the extension is missing — we expect it on all targets
if [ "${{ inputs.binary_ext }}" = ".exe" ]; then
[ -f staging/_native.pyd ] || { echo "ERROR: _native.pyd missing"; exit 1; }
else
[ -f staging/_native.abi3.so ] || { echo "ERROR: _native.abi3.so missing"; exit 1; }
fi
# Strip binaries (reduces size significantly)
if [ "${{ inputs.linux_cross }}" = "true" ]; then
llvm-strip staging/fbuild || true
llvm-strip staging/fbuild-daemon || true
llvm-strip staging/_native.abi3.so 2>/dev/null || true
elif [ "${{ inputs.mac_cross_linux }}" = "true" ]; then
# Linux host `strip` is binutils — it would silently corrupt
# a Mach-O. Skip stripping; ~30 MB is acceptable for release
# artifacts. A future iteration can wire `llvm-strip` from
# ziglang's bundled tools.
true
elif [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then
true
elif command -v strip &> /dev/null; then
strip staging/fbuild${{ inputs.binary_ext }} || true
strip staging/fbuild-daemon${{ inputs.binary_ext }} || true
strip staging/_native.abi3.so 2>/dev/null || true
strip staging/_native.pyd 2>/dev/null || true
fi
- name: Generate checksums
shell: bash
run: |
set -euo pipefail
cd staging
sha256sum * > SHA256SUMS.txt
cat SHA256SUMS.txt
- name: Attest native artifacts
uses: actions/attest-build-provenance@v4
with:
subject-checksums: staging/SHA256SUMS.txt
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: binaries-${{ inputs.target }}
path: staging/
if-no-files-found: error