From 7c35e38c8103783fec80b192c333fbc6c7f28087 Mon Sep 17 00:00:00 2001 From: zackees Date: Mon, 22 Jun 2026 06:45:51 -0700 Subject: [PATCH 1/6] ci(release): cross-compile every target from one ubuntu-24.04 runner family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collapses the 4-OS-per-target release matrix down to a single linux-x86 runner family. Every triple now compiles via soldr's cargo front door: * Linux musl + macOS targets -> `soldr cargo zigbuild --target ` * Windows-MSVC targets -> `soldr cargo xwin build --target ` The template was already half-way there for Linux (cargo-zigbuild for musl + manylinux PyO3 extension). This finishes the job for macOS and Windows lanes too, so every artifact comes off the same compiler with the same flags and the same linker semantics. Changes: * template_native_build.yml: drop the `runner`, `linux_cross`, `macos_cross` inputs (single ubuntu-24.04 runner). Switch runner-based branches to target-triple-based branches. Use taiki-e/install-action for cargo-zigbuild / cargo-xwin so soldr's cargo front door takes its PATH-first deferral branch. mlugg/setup-zig for zig on zigbuild lanes. llvm-strip universally. * release-auto.yml: trim the matrix to target + binary_ext only. Add a `dry-run` workflow_dispatch input that forces build but suppresses GitHub Release + PyPI publish — lets us validate without burning a version bump. * build.yml: same matrix trim, re-enables x86_64-apple-darwin since zigbuild handles the ring-crate cross-compile that previously required (perpetually-unavailable) macos-13 runners. Why: * Cheaper: linux runners cost ~1/2 of macOS, ~1/10 of Windows. * Faster cold-start: linux runners spin up in ~5s vs macos ~30s, windows ~60s. * Cross-target consistency: same compiler + linker semantics across every artifact. * Dogfoods soldr's cargo front door / cargo-xwin / cargo-zigbuild. Pattern proven on zackees/ai-tools#11 (8 targets, all green in dry-run). Two recipe sharp edges already addressed here: * `linker: platform-default` on setup-soldr (otherwise SOLDR_LINKER=fast injects host-arch rust-lld and aarch64 lanes fail to link). * Explicit `build` subverb for cargo-xwin (`cargo xwin build --release`, not `cargo xwin --release`). Part of zackees/soldr#922. Closes #754. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build.yml | 28 +--- .github/workflows/release-auto.yml | 35 ++-- .github/workflows/template_native_build.yml | 175 +++++++++----------- 3 files changed, 99 insertions(+), 139 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1fd9446..7da37e4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,39 +20,19 @@ jobs: matrix: include: - target: x86_64-unknown-linux-musl - runner: ubuntu-latest binary_ext: "" - linux_cross: false - macos_cross: false - - target: aarch64-unknown-linux-musl - runner: ubuntu-latest binary_ext: "" - linux_cross: true - macos_cross: false - - # x86_64-apple-darwin removed: Intel Macs use ARM wheel via Rosetta. - # Cross-compiling ring crate ARM→x86 fails; native macos-13 runners - # are perpetually unavailable. Re-enable when ring supports cross or - # GitHub restores Intel runner capacity. - + # x86_64-apple-darwin: was previously blocked on cross-compiling + # `ring` ARM->x86 from a macos-arm runner. zigbuild from a + # linux-x86 runner handles this cleanly via `zig cc`, so we can + # re-enable it whenever the release matrix wants the extra wheel. - target: aarch64-apple-darwin - runner: macos-latest binary_ext: "" - linux_cross: false - macos_cross: false - - target: x86_64-pc-windows-msvc - runner: windows-latest binary_ext: ".exe" - linux_cross: false - macos_cross: false - uses: ./.github/workflows/template_native_build.yml with: target: ${{ matrix.target }} - runner: ${{ matrix.runner }} binary_ext: ${{ matrix.binary_ext }} - linux_cross: ${{ matrix.linux_cross }} - macos_cross: ${{ matrix.macos_cross }} ref: ${{ github.event.inputs.ref || 'main' }} diff --git a/.github/workflows/release-auto.yml b/.github/workflows/release-auto.yml index 15c0cb3d..9c80b10d 100644 --- a/.github/workflows/release-auto.yml +++ b/.github/workflows/release-auto.yml @@ -7,6 +7,12 @@ on: - Cargo.toml - pyproject.toml workflow_dispatch: + inputs: + dry-run: + description: "Build all targets but skip GitHub Release + PyPI publish" + required: false + type: boolean + default: false permissions: contents: write @@ -40,6 +46,8 @@ jobs: - name: Check release eligibility id: prepare shell: bash + env: + DRY_RUN: ${{ inputs.dry-run }} run: | set -euo pipefail @@ -97,6 +105,14 @@ jobs: should_publish_pypi="true" fi + # Dry-run override: force build, suppress publish. + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${DRY_RUN:-false}" = "true" ]; then + should_build="true" + should_publish_github="false" + should_publish_pypi="false" + echo "DRY-RUN: build forced on, publish suppressed" + fi + { echo "version=${version}" echo "project_version=${cargo_version}" @@ -126,36 +142,17 @@ jobs: matrix: include: - target: x86_64-unknown-linux-musl - runner: ubuntu-latest binary_ext: "" - linux_cross: false - macos_cross: false - - target: aarch64-unknown-linux-musl - runner: ubuntu-latest binary_ext: "" - linux_cross: true - macos_cross: false - - target: aarch64-apple-darwin - runner: macos-latest binary_ext: "" - linux_cross: false - macos_cross: false - - target: x86_64-pc-windows-msvc - runner: windows-latest binary_ext: ".exe" - linux_cross: false - macos_cross: false - uses: ./.github/workflows/template_native_build.yml with: target: ${{ matrix.target }} - runner: ${{ matrix.runner }} binary_ext: ${{ matrix.binary_ext }} - linux_cross: ${{ matrix.linux_cross }} - macos_cross: ${{ matrix.macos_cross }} ref: ${{ needs.prepare.outputs.release_ref }} publish: diff --git a/.github/workflows/template_native_build.yml b/.github/workflows/template_native_build.yml index acf3b6cd..2c25c9b1 100644 --- a/.github/workflows/template_native_build.yml +++ b/.github/workflows/template_native_build.yml @@ -1,4 +1,7 @@ -name: Native Build Template +name: Native Build Template (cross-compiled from linux-x86) +# Single linux-x86 runner family. zigbuild handles Linux/macOS targets; +# xwin handles Windows-MSVC. Per-OS native runners are gone — same compiler, +# same flags, same linker semantics across every artifact. on: workflow_call: @@ -7,25 +10,11 @@ on: 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" ref: required: false type: string @@ -45,13 +34,19 @@ permissions: jobs: build: name: Build (${{ inputs.target }}) - runs-on: ${{ inputs.runner }} - + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.sha }} + # `linker: platform-default` opts out of setup-soldr's `fast` linker + # override. SOLDR_LINKER=fast injects rust-lld as the cargo target + # linker which defaults to the host arch (x86_64) and fails to link + # aarch64 object files (incompatible with elf64-x86-64). zigbuild's + # `zig cc` wrapper needs to own the toolchain end-to-end on cross + # lanes; the xwin lane gets its MSVC linker from soldr's cargo + # front door regardless. - name: Setup soldr id: setup-soldr uses: zackees/setup-soldr@v0.9.62 @@ -60,16 +55,10 @@ jobs: 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). + # 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 }} @@ -77,40 +66,50 @@ jobs: with: python-version: "3.13" - # rust-toolchain.toml pins 1.94.1 which overrides the above; - # ensure the target stdlib is installed for the pinned toolchain too + # rust-toolchain.toml pins 1.94.1; ensure the target stdlib is + # installed for the pinned toolchain. - name: Add Rust target run: 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 + # Pre-install cargo-zigbuild / cargo-xwin via taiki-e/install-action + # so soldr's cargo front door takes its PATH-first deferral branch + # (avoids fanning out unauthenticated GitHub API calls across the + # release matrix and getting 403'd). + - name: Install cross-compile cargo subcommand + uses: taiki-e/install-action@v2 + with: + tool: ${{ contains(inputs.target, 'pc-windows-msvc') && 'cargo-xwin' || 'cargo-zigbuild' }} + + # zig is needed for cargo-zigbuild lanes (Linux + macOS). xwin lanes + # don't need zig — cargo-xwin uses cargo's built-in MSVC support + # plus a soldr-managed LLVM toolchain bootstrap. + - name: Install zig (zigbuild lanes only) + if: ${{ !contains(inputs.target, 'pc-windows-msvc') }} + uses: mlugg/setup-zig@v2 + with: + version: 0.14.0 + + # llvm-strip is universal (ELF + Mach-O); zip is needed for the + # windows-msvc archive path downstream. + - name: Install llvm + zip run: | sudo apt-get update - sudo apt-get install -y musl-tools pkg-config - - # cargo-zigbuild — used for two reasons on Linux: - # 1. musl cross-compilation (linux_cross targets) - # 2. Building the PyO3 extension with a pinned glibc symbol version - # so manylinux_2_17 wheels actually run on glibc 2.17. The native - # ubuntu-latest linker would otherwise pull in glibc 2.39 symbols. - - name: Install cross-compilation tools - if: runner.os == 'Linux' - run: pip install cargo-zigbuild + sudo apt-get install -y --no-install-recommends llvm zip + # CLI binaries: + # * windows-msvc -> `soldr cargo xwin build --release` + # * linux-musl -> `soldr cargo zigbuild --release` (static, no glibc dep) + # * macos/other -> `soldr cargo zigbuild --release` - name: Build release binaries shell: bash run: | - if [[ "${{ inputs.target }}" == *-unknown-linux-musl ]]; then - # Direct `cargo zigbuild` (pip-installed wrapper) bypasses - # soldr's bin cache, which has been serving a corrupted - # cargo-zigbuild binary (`Syntax error: ")" unexpected` at - # line 10) and blocking PyPI publishes. See #331. - cargo zigbuild --release --target ${{ inputs.target }} \ + set -euo pipefail + if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then + soldr cargo xwin build --release --target ${{ inputs.target }} \ -p fbuild-cli \ -p fbuild-daemon else - soldr cargo build --release --target ${{ inputs.target }} \ + soldr cargo zigbuild --release --target ${{ inputs.target }} \ -p fbuild-cli \ -p fbuild-daemon fi @@ -119,93 +118,77 @@ jobs: # Notes: # - PYO3_NO_PYTHON=1 + abi3-py310 feature lets pyo3-build-config skip # the host interpreter lookup on cross builds. - # - All Linux builds (native AND cross) use cargo-zigbuild against - # -unknown-linux-gnu.2.17 — manylinux wheels are glibc-based - # and must work on glibc >= 2.17 (the manylinux_2_17 floor). Native - # ubuntu-latest's system linker would otherwise pull in glibc 2.39 - # symbols, breaking the wheel on every distro older than ubuntu-24.04. - # The CLI binaries stay on musl for static-link portability. - # - macOS cross: cargo rustc with -undefined dynamic_lookup works the - # same as native; just add --target. + # - For Linux: build against `-unknown-linux-gnu.2.17` so the + # manylinux_2_17 wheel actually runs on glibc 2.17+. The CLI + # binaries stay on musl for static-link portability. + # - For macOS: zigbuild against the target directly. + # - For Windows MSVC: xwin against the target directly. - name: Build Python extension shell: bash run: | + set -euo pipefail PYTHON_TARGET_DIR="target/python-extension" - if [ "${{ runner.os }}" = "Linux" ]; then + if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then + PYO3_NO_PYTHON=1 soldr cargo xwin build --release \ + --target-dir "${PYTHON_TARGET_DIR}" \ + --target ${{ inputs.target }} -p fbuild-python \ + --features extension-module + elif [[ "${{ inputs.target }}" == *-unknown-linux-* ]]; then TARGET="${{ inputs.target }}" ARCH="${TARGET%%-*}" PYO3_TARGET="${ARCH}-unknown-linux-gnu" rustup target add "$PYO3_TARGET" - # Direct `cargo zigbuild` (pip-installed wrapper) — see #331. - PYO3_NO_PYTHON=1 cargo zigbuild --release \ + PYO3_NO_PYTHON=1 soldr cargo zigbuild --release \ --target-dir "${PYTHON_TARGET_DIR}" \ --target "${PYO3_TARGET}.2.17" -p fbuild-python \ --features extension-module - elif [ "${{ inputs.macos_cross }}" = "true" ]; then - PYO3_NO_PYTHON=1 soldr cargo build --release \ + else + PYO3_NO_PYTHON=1 soldr cargo zigbuild --release \ --target-dir "${PYTHON_TARGET_DIR}" \ --target ${{ inputs.target }} -p fbuild-python \ --features extension-module - elif [ "${{ runner.os }}" = "macOS" ]; then - soldr cargo build --release --target-dir "${PYTHON_TARGET_DIR}" \ - --target ${{ inputs.target }} \ - -p fbuild-python \ - --features extension-module - else - soldr cargo build --release --target-dir "${PYTHON_TARGET_DIR}" \ - --target ${{ inputs.target }} \ - -p fbuild-python \ - --features extension-module fi - name: Stage artifacts shell: bash run: | + set -euo pipefail 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 [ "${{ runner.os }}" = "Linux" ]; then + # 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.target }}" == *-unknown-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 + elif [[ "${{ inputs.target }}" == *-apple-darwin ]]; then ext_src="${PYTHON_TARGET_DIR}/${{ inputs.target }}/release/lib_native.dylib" [ -f "$ext_src" ] && cp "$ext_src" staging/_native.abi3.so - elif [ "${{ inputs.binary_ext }}" = ".exe" ]; 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 - 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 + # Fail fast if the extension is missing — we expect it on all targets. + if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; 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 + # Strip with llvm-strip — universal across ELF + Mach-O. Skip + # Windows PE (cross-compiled, debug info already split). + if [[ "${{ inputs.target }}" != *-pc-windows-msvc ]]; then + llvm-strip staging/fbuild 2>/dev/null || true + llvm-strip staging/fbuild-daemon 2>/dev/null || true llvm-strip staging/_native.abi3.so 2>/dev/null || 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 From 2757ffab83be2e69b06697e212d9d0c938168e9d Mon Sep 17 00:00:00 2001 From: zackees Date: Mon, 22 Jun 2026 07:19:58 -0700 Subject: [PATCH 2/6] fixup(ci): direct `cargo zigbuild` / `cargo xwin` invocation (avoid soldr#331 bin-cache bug) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dry-run #1 (https://github.com/FastLED/fbuild/actions/runs/27957484231) hit the same corrupted-cargo-zigbuild-binary failure the original template was already working around: /home/runner/work/_temp/setup-soldr-soldr/bin/cargo-zigbuild-0.23.0/cargo-zigbuild: 4: Syntax error: ")" unexpected ##[error]Process completed with exit code 2. soldr's cargo front door routes through its own bin cache for cargo-zigbuild / cargo-xwin lookup. That cache has been serving a corrupted cargo-zigbuild — see soldr#331 and the matching note in the pre-conversion template at line 105-108. The conversion lost the workaround by routing through `soldr cargo zigbuild`; pre-installing via taiki-e/install-action doesn't help because soldr prefers its own bin cache here, not the PATH entry. Fix: call `cargo zigbuild` / `cargo xwin build` directly. Cargo's built-in `cargo-` resolution finds the taiki-e binary on PATH. setup-soldr still exports RUSTC_WRAPPER=zccache, so per-rustc caching engages on every invocation regardless of whether soldr's cargo front door is in the loop. The only thing bypassed is the buggy bin-cache lookup. Also adds `clang lld` to the apt install list: soldr's cargo front door normally auto-bootstraps these for windows-msvc xwin lanes via `ensure_llvm_toolchain`. Bypassing the front door means we install them ourselves. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/template_native_build.yml | 51 +++++++++++++++------ 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/.github/workflows/template_native_build.yml b/.github/workflows/template_native_build.yml index 2c25c9b1..b1e97de8 100644 --- a/.github/workflows/template_native_build.yml +++ b/.github/workflows/template_native_build.yml @@ -89,27 +89,49 @@ jobs: with: version: 0.14.0 - # llvm-strip is universal (ELF + Mach-O); zip is needed for the - # windows-msvc archive path downstream. - - name: Install llvm + zip + # System tools: + # * llvm-strip: universal (ELF + Mach-O) strip + # * zip: needed for the windows-msvc archive path downstream + # * clang/lld: needed for cargo-xwin's linker step on the windows-msvc + # lanes. soldr's cargo front door auto-bootstraps these, but we + # invoke cargo directly (see "Build release binaries" rationale) + # so we install them via apt instead. + - name: Install llvm + lld + zip run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends llvm zip - - # CLI binaries: - # * windows-msvc -> `soldr cargo xwin build --release` - # * linux-musl -> `soldr cargo zigbuild --release` (static, no glibc dep) - # * macos/other -> `soldr cargo zigbuild --release` + sudo apt-get install -y --no-install-recommends llvm lld clang zip + + # IMPORTANT — direct `cargo` invocation (not `soldr cargo`): + # + # soldr's cargo front door routes through its own bin cache for + # cargo-zigbuild / cargo-xwin lookup. That cache has been serving a + # corrupted cargo-zigbuild binary (`Syntax error: ")" unexpected` + # at line 4) — see soldr#331 and the original fbuild template's + # workaround note. Going through `soldr cargo zigbuild` reproduces + # the failure even when a working cargo-zigbuild is on PATH via + # taiki-e/install-action. + # + # Calling cargo directly here lets cargo's built-in `cargo-` + # resolution find the taiki-e binary on PATH. setup-soldr still + # exports RUSTC_WRAPPER=zccache, so per-rustc caching engages on + # every invocation regardless of whether soldr's cargo front door + # is in the loop. + # + # Targets: + # * windows-msvc -> `cargo xwin build --release` (cargo-xwin uses + # clang/lld for the linker, MSVC CRT/SDK auto-fetched by xwin) + # * linux-musl -> `cargo zigbuild --release` (static, no glibc dep) + # * macos/other -> `cargo zigbuild --release` - name: Build release binaries shell: bash run: | set -euo pipefail if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then - soldr cargo xwin build --release --target ${{ inputs.target }} \ + cargo xwin build --release --target ${{ inputs.target }} \ -p fbuild-cli \ -p fbuild-daemon else - soldr cargo zigbuild --release --target ${{ inputs.target }} \ + cargo zigbuild --release --target ${{ inputs.target }} \ -p fbuild-cli \ -p fbuild-daemon fi @@ -123,13 +145,14 @@ jobs: # binaries stay on musl for static-link portability. # - For macOS: zigbuild against the target directly. # - For Windows MSVC: xwin against the target directly. + # Same direct-cargo invocation rationale as above. - name: Build Python extension shell: bash run: | set -euo pipefail PYTHON_TARGET_DIR="target/python-extension" if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then - PYO3_NO_PYTHON=1 soldr cargo xwin build --release \ + PYO3_NO_PYTHON=1 cargo xwin build --release \ --target-dir "${PYTHON_TARGET_DIR}" \ --target ${{ inputs.target }} -p fbuild-python \ --features extension-module @@ -138,12 +161,12 @@ jobs: ARCH="${TARGET%%-*}" PYO3_TARGET="${ARCH}-unknown-linux-gnu" rustup target add "$PYO3_TARGET" - PYO3_NO_PYTHON=1 soldr cargo zigbuild --release \ + PYO3_NO_PYTHON=1 cargo zigbuild --release \ --target-dir "${PYTHON_TARGET_DIR}" \ --target "${PYO3_TARGET}.2.17" -p fbuild-python \ --features extension-module else - PYO3_NO_PYTHON=1 soldr cargo zigbuild --release \ + PYO3_NO_PYTHON=1 cargo zigbuild --release \ --target-dir "${PYTHON_TARGET_DIR}" \ --target ${{ inputs.target }} -p fbuild-python \ --features extension-module From a738a66b27482c2df9024b4e0fce234b0d71db4d Mon Sep 17 00:00:00 2001 From: zackees Date: Mon, 22 Jun 2026 07:52:51 -0700 Subject: [PATCH 3/6] fixup(ci): add macOS SDK + Windows python3.lib for cross-compile (workaround dry-run #2 blockers) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dry-run #2 (https://github.com/FastLED/fbuild/actions/runs/27959614790) got linux musl lanes green but exposed two cross-compile-from-linux dep-chain blockers on the remaining lanes: * **macOS aarch64**: libmimalloc-sys -> CommonCrypto/CommonCryptoError.h not found. zigbuild's bundled SDK doesn't ship Apple framework headers — only the libc subset. * **Windows MSVC**: PyO3 extension -> lld-link: could not open 'python3.lib'. PyO3 extensions link against the Python import lib at the end; on a linux runner there's no Windows Python install. Both are now addressed with documented patterns: * `joseluisq/setup-macos-sdk@v1` downloads a complete macOS 14 SDK and exports SDKROOT — gated on darwin lanes only. Fixes the CommonCrypto / Apple framework header lookup. * NuGet `python` package gives us the official Windows `python3.lib` import library. Extract, set `PYO3_CROSS_LIB_DIR` + `PYO3_CROSS_PYTHON_VERSION` + `PYO3_CROSS_PYTHON_IMPLEMENTATION` — gated on windows-msvc lanes only. Fixes the PyO3 link step. Uses `pythonarm64` for the aarch64 windows lane (currently out-of-matrix but future-proofed). These are one-time setup steps, ~30 lines of yaml total. The runner-cost win is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/template_native_build.yml | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/.github/workflows/template_native_build.yml b/.github/workflows/template_native_build.yml index b1e97de8..6a4002bd 100644 --- a/.github/workflows/template_native_build.yml +++ b/.github/workflows/template_native_build.yml @@ -101,6 +101,49 @@ jobs: sudo apt-get update sudo apt-get install -y --no-install-recommends llvm lld clang zip + # macOS SDK for darwin lanes — zigbuild's bundled SDK lacks Apple + # framework headers (CommonCrypto, Security, etc.) that + # libmimalloc-sys and other native deps require. joseluisq/setup-macos-sdk + # downloads a complete SDK and exports SDKROOT for the build. + - name: Setup macOS SDK (darwin lanes only) + if: ${{ contains(inputs.target, 'apple-darwin') }} + uses: joseluisq/setup-macos-sdk@v1 + with: + sdk: "14" + + # Windows Python lib for PyO3 cross-compile — PyO3 extension on + # MSVC links against python3.lib at the end. python3.lib isn't + # available on a linux runner, so we fetch a Windows Python + # distribution via NuGet and point PYO3_CROSS_LIB_DIR at its libs + # directory. The python.nuget package is the simplest authoritative + # source for the official Windows python3.lib import library. + - name: Fetch Windows Python lib for PyO3 cross-compile (windows-msvc lanes only) + if: ${{ contains(inputs.target, 'pc-windows-msvc') }} + shell: bash + run: | + set -euo pipefail + PYTHON_VERSION="3.13.0" + # NuGet packages for python use arch-suffixed names. + if [[ "${{ inputs.target }}" == aarch64-* ]]; then + PKG="pythonarm64" + else + PKG="python" + fi + curl -fsSL -o python.zip "https://www.nuget.org/api/v2/package/${PKG}/${PYTHON_VERSION}" + mkdir -p python-windows + unzip -q python.zip -d python-windows + # NuGet layout: tools/libs/{python3.lib, python313.lib, ...} + libs_dir="$(pwd)/python-windows/tools/libs" + if [ ! -f "$libs_dir/python3.lib" ]; then + echo "ERROR: python3.lib not found in extracted NuGet package" >&2 + find python-windows -name "python3*.lib" || true + exit 1 + fi + echo "PYO3_CROSS_LIB_DIR=$libs_dir" >> "$GITHUB_ENV" + echo "PYO3_CROSS_PYTHON_VERSION=3.13" >> "$GITHUB_ENV" + echo "PYO3_CROSS_PYTHON_IMPLEMENTATION=CPython" >> "$GITHUB_ENV" + echo "Configured PYO3 cross for Windows: PYO3_CROSS_LIB_DIR=$libs_dir" + # IMPORTANT — direct `cargo` invocation (not `soldr cargo`): # # soldr's cargo front door routes through its own bin cache for From 8fe85c74835c90b1810dc5e872d87d2e8112e094 Mon Sep 17 00:00:00 2001 From: zackees Date: Mon, 22 Jun 2026 07:58:21 -0700 Subject: [PATCH 4/6] fixup(ci): direct curl for macOS SDK (joseluisq/setup-macos-sdk doesn't exist) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dry-run #3 (https://github.com/FastLED/fbuild/actions/runs/27961768951) failed before any step ran: Unable to resolve action joseluisq/setup-macos-sdk, repository not found GitHub Actions resolves all `uses:` references at job start regardless of `if:` conditions on the step, so the unresolvable action killed every matrix lane — including the linux musl lanes that don't use it. Switch to direct curl from `phracker/MacOSX-SDKs` releases (the canonical mirror of Xcode-shipped SDKs). 11.3 is the latest published release on that mirror and includes the CommonCrypto + Security framework headers libmimalloc-sys needs. Added a sanity check that verifies `CommonCryptoError.h` actually landed in the extracted tree. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/template_native_build.yml | 28 +++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/template_native_build.yml b/.github/workflows/template_native_build.yml index 6a4002bd..657a6d21 100644 --- a/.github/workflows/template_native_build.yml +++ b/.github/workflows/template_native_build.yml @@ -103,13 +103,31 @@ jobs: # macOS SDK for darwin lanes — zigbuild's bundled SDK lacks Apple # framework headers (CommonCrypto, Security, etc.) that - # libmimalloc-sys and other native deps require. joseluisq/setup-macos-sdk - # downloads a complete SDK and exports SDKROOT for the build. + # libmimalloc-sys and other native deps require. Fetched directly + # from phracker/MacOSX-SDKs releases (the canonical mirror of the + # Xcode-shipped SDKs used by cross-compile toolchains). 11.3 + # is the most recent release on that mirror and includes the + # CommonCrypto + Security headers libmimalloc-sys needs. - name: Setup macOS SDK (darwin lanes only) if: ${{ contains(inputs.target, 'apple-darwin') }} - uses: joseluisq/setup-macos-sdk@v1 - with: - sdk: "14" + shell: bash + run: | + set -euo pipefail + SDK_VERSION="11.3" + SDK_URL="https://github.com/phracker/MacOSX-SDKs/releases/download/${SDK_VERSION}/MacOSX${SDK_VERSION}.sdk.tar.xz" + curl -fsSL -o /tmp/macosx-sdk.tar.xz "$SDK_URL" + sudo mkdir -p /opt/macosx-sdk + sudo tar -xJf /tmp/macosx-sdk.tar.xz -C /opt/macosx-sdk + SDK_PATH="/opt/macosx-sdk/MacOSX${SDK_VERSION}.sdk" + if [ ! -d "$SDK_PATH" ]; then + SDK_PATH="$(sudo find /opt/macosx-sdk -maxdepth 2 -type d -name 'MacOSX*.sdk' | head -1)" + fi + [ -d "$SDK_PATH" ] || { echo "ERROR: macOS SDK not found after extract" >&2; sudo ls -la /opt/macosx-sdk; exit 1; } + # Confirm the headers libmimalloc-sys actually needs. + [ -f "$SDK_PATH/usr/include/CommonCrypto/CommonCryptoError.h" ] \ + || { echo "ERROR: CommonCrypto headers missing from extracted SDK" >&2; exit 1; } + echo "SDKROOT=$SDK_PATH" >> "$GITHUB_ENV" + echo "Configured macOS SDK: $SDK_PATH" # Windows Python lib for PyO3 cross-compile — PyO3 extension on # MSVC links against python3.lib at the end. python3.lib isn't From 09bfedcdf202f0f8bd3ad2c4ccb907399a55f427 Mon Sep 17 00:00:00 2001 From: zackees Date: Mon, 22 Jun 2026 12:58:28 -0700 Subject: [PATCH 5/6] ci: revert native release cross-compilation --- .github/workflows/build.yml | 28 ++- .github/workflows/release-auto.yml | 35 +-- .github/workflows/template_native_build.yml | 255 ++++++++------------ 3 files changed, 137 insertions(+), 181 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7da37e4c..c1fd9446 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,19 +20,39 @@ jobs: matrix: include: - target: x86_64-unknown-linux-musl + runner: ubuntu-latest binary_ext: "" + linux_cross: false + macos_cross: false + - target: aarch64-unknown-linux-musl + runner: ubuntu-latest binary_ext: "" - # x86_64-apple-darwin: was previously blocked on cross-compiling - # `ring` ARM->x86 from a macos-arm runner. zigbuild from a - # linux-x86 runner handles this cleanly via `zig cc`, so we can - # re-enable it whenever the release matrix wants the extra wheel. + linux_cross: true + macos_cross: false + + # x86_64-apple-darwin removed: Intel Macs use ARM wheel via Rosetta. + # Cross-compiling ring crate ARM→x86 fails; native macos-13 runners + # are perpetually unavailable. Re-enable when ring supports cross or + # GitHub restores Intel runner capacity. + - target: aarch64-apple-darwin + runner: macos-latest binary_ext: "" + linux_cross: false + macos_cross: false + - target: x86_64-pc-windows-msvc + runner: windows-latest binary_ext: ".exe" + linux_cross: false + macos_cross: false + uses: ./.github/workflows/template_native_build.yml with: target: ${{ matrix.target }} + runner: ${{ matrix.runner }} binary_ext: ${{ matrix.binary_ext }} + linux_cross: ${{ matrix.linux_cross }} + macos_cross: ${{ matrix.macos_cross }} ref: ${{ github.event.inputs.ref || 'main' }} diff --git a/.github/workflows/release-auto.yml b/.github/workflows/release-auto.yml index 9c80b10d..15c0cb3d 100644 --- a/.github/workflows/release-auto.yml +++ b/.github/workflows/release-auto.yml @@ -7,12 +7,6 @@ on: - Cargo.toml - pyproject.toml workflow_dispatch: - inputs: - dry-run: - description: "Build all targets but skip GitHub Release + PyPI publish" - required: false - type: boolean - default: false permissions: contents: write @@ -46,8 +40,6 @@ jobs: - name: Check release eligibility id: prepare shell: bash - env: - DRY_RUN: ${{ inputs.dry-run }} run: | set -euo pipefail @@ -105,14 +97,6 @@ jobs: should_publish_pypi="true" fi - # Dry-run override: force build, suppress publish. - if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${DRY_RUN:-false}" = "true" ]; then - should_build="true" - should_publish_github="false" - should_publish_pypi="false" - echo "DRY-RUN: build forced on, publish suppressed" - fi - { echo "version=${version}" echo "project_version=${cargo_version}" @@ -142,17 +126,36 @@ jobs: matrix: include: - target: x86_64-unknown-linux-musl + runner: ubuntu-latest binary_ext: "" + linux_cross: false + macos_cross: false + - target: aarch64-unknown-linux-musl + runner: ubuntu-latest binary_ext: "" + linux_cross: true + macos_cross: false + - target: aarch64-apple-darwin + runner: macos-latest binary_ext: "" + linux_cross: false + macos_cross: false + - target: x86_64-pc-windows-msvc + runner: windows-latest binary_ext: ".exe" + linux_cross: false + macos_cross: false + uses: ./.github/workflows/template_native_build.yml with: target: ${{ matrix.target }} + runner: ${{ matrix.runner }} binary_ext: ${{ matrix.binary_ext }} + linux_cross: ${{ matrix.linux_cross }} + macos_cross: ${{ matrix.macos_cross }} ref: ${{ needs.prepare.outputs.release_ref }} publish: diff --git a/.github/workflows/template_native_build.yml b/.github/workflows/template_native_build.yml index 657a6d21..acf3b6cd 100644 --- a/.github/workflows/template_native_build.yml +++ b/.github/workflows/template_native_build.yml @@ -1,7 +1,4 @@ -name: Native Build Template (cross-compiled from linux-x86) -# Single linux-x86 runner family. zigbuild handles Linux/macOS targets; -# xwin handles Windows-MSVC. Per-OS native runners are gone — same compiler, -# same flags, same linker semantics across every artifact. +name: Native Build Template on: workflow_call: @@ -10,11 +7,25 @@ on: 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" ref: required: false type: string @@ -34,19 +45,13 @@ permissions: jobs: build: name: Build (${{ inputs.target }}) - runs-on: ubuntu-24.04 + runs-on: ${{ inputs.runner }} + steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.sha }} - # `linker: platform-default` opts out of setup-soldr's `fast` linker - # override. SOLDR_LINKER=fast injects rust-lld as the cargo target - # linker which defaults to the host arch (x86_64) and fails to link - # aarch64 object files (incompatible with elf64-x86-64). zigbuild's - # `zig cc` wrapper needs to own the toolchain end-to-end on cross - # lanes; the xwin lane gets its MSVC linker from soldr's cargo - # front door regardless. - name: Setup soldr id: setup-soldr uses: zackees/setup-soldr@v0.9.62 @@ -55,10 +60,16 @@ jobs: 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). + # 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 }} @@ -66,133 +77,40 @@ jobs: with: python-version: "3.13" - # rust-toolchain.toml pins 1.94.1; ensure the target stdlib is - # installed for the pinned toolchain. + # rust-toolchain.toml pins 1.94.1 which overrides the above; + # ensure the target stdlib is installed for the pinned toolchain too - name: Add Rust target run: rustup target add ${{ inputs.target }} - # Pre-install cargo-zigbuild / cargo-xwin via taiki-e/install-action - # so soldr's cargo front door takes its PATH-first deferral branch - # (avoids fanning out unauthenticated GitHub API calls across the - # release matrix and getting 403'd). - - name: Install cross-compile cargo subcommand - uses: taiki-e/install-action@v2 - with: - tool: ${{ contains(inputs.target, 'pc-windows-msvc') && 'cargo-xwin' || 'cargo-zigbuild' }} - - # zig is needed for cargo-zigbuild lanes (Linux + macOS). xwin lanes - # don't need zig — cargo-xwin uses cargo's built-in MSVC support - # plus a soldr-managed LLVM toolchain bootstrap. - - name: Install zig (zigbuild lanes only) - if: ${{ !contains(inputs.target, 'pc-windows-msvc') }} - uses: mlugg/setup-zig@v2 - with: - version: 0.14.0 - - # System tools: - # * llvm-strip: universal (ELF + Mach-O) strip - # * zip: needed for the windows-msvc archive path downstream - # * clang/lld: needed for cargo-xwin's linker step on the windows-msvc - # lanes. soldr's cargo front door auto-bootstraps these, but we - # invoke cargo directly (see "Build release binaries" rationale) - # so we install them via apt instead. - - name: Install llvm + lld + zip + # Linux musl toolchain (libudev is auto-excluded for musl targets by serialport) + - name: Install Linux dependencies + if: runner.os == 'Linux' && !inputs.linux_cross run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends llvm lld clang zip + sudo apt-get install -y musl-tools pkg-config - # macOS SDK for darwin lanes — zigbuild's bundled SDK lacks Apple - # framework headers (CommonCrypto, Security, etc.) that - # libmimalloc-sys and other native deps require. Fetched directly - # from phracker/MacOSX-SDKs releases (the canonical mirror of the - # Xcode-shipped SDKs used by cross-compile toolchains). 11.3 - # is the most recent release on that mirror and includes the - # CommonCrypto + Security headers libmimalloc-sys needs. - - name: Setup macOS SDK (darwin lanes only) - if: ${{ contains(inputs.target, 'apple-darwin') }} - shell: bash - run: | - set -euo pipefail - SDK_VERSION="11.3" - SDK_URL="https://github.com/phracker/MacOSX-SDKs/releases/download/${SDK_VERSION}/MacOSX${SDK_VERSION}.sdk.tar.xz" - curl -fsSL -o /tmp/macosx-sdk.tar.xz "$SDK_URL" - sudo mkdir -p /opt/macosx-sdk - sudo tar -xJf /tmp/macosx-sdk.tar.xz -C /opt/macosx-sdk - SDK_PATH="/opt/macosx-sdk/MacOSX${SDK_VERSION}.sdk" - if [ ! -d "$SDK_PATH" ]; then - SDK_PATH="$(sudo find /opt/macosx-sdk -maxdepth 2 -type d -name 'MacOSX*.sdk' | head -1)" - fi - [ -d "$SDK_PATH" ] || { echo "ERROR: macOS SDK not found after extract" >&2; sudo ls -la /opt/macosx-sdk; exit 1; } - # Confirm the headers libmimalloc-sys actually needs. - [ -f "$SDK_PATH/usr/include/CommonCrypto/CommonCryptoError.h" ] \ - || { echo "ERROR: CommonCrypto headers missing from extracted SDK" >&2; exit 1; } - echo "SDKROOT=$SDK_PATH" >> "$GITHUB_ENV" - echo "Configured macOS SDK: $SDK_PATH" + # cargo-zigbuild — used for two reasons on Linux: + # 1. musl cross-compilation (linux_cross targets) + # 2. Building the PyO3 extension with a pinned glibc symbol version + # so manylinux_2_17 wheels actually run on glibc 2.17. The native + # ubuntu-latest linker would otherwise pull in glibc 2.39 symbols. + - name: Install cross-compilation tools + if: runner.os == 'Linux' + run: pip install cargo-zigbuild - # Windows Python lib for PyO3 cross-compile — PyO3 extension on - # MSVC links against python3.lib at the end. python3.lib isn't - # available on a linux runner, so we fetch a Windows Python - # distribution via NuGet and point PYO3_CROSS_LIB_DIR at its libs - # directory. The python.nuget package is the simplest authoritative - # source for the official Windows python3.lib import library. - - name: Fetch Windows Python lib for PyO3 cross-compile (windows-msvc lanes only) - if: ${{ contains(inputs.target, 'pc-windows-msvc') }} - shell: bash - run: | - set -euo pipefail - PYTHON_VERSION="3.13.0" - # NuGet packages for python use arch-suffixed names. - if [[ "${{ inputs.target }}" == aarch64-* ]]; then - PKG="pythonarm64" - else - PKG="python" - fi - curl -fsSL -o python.zip "https://www.nuget.org/api/v2/package/${PKG}/${PYTHON_VERSION}" - mkdir -p python-windows - unzip -q python.zip -d python-windows - # NuGet layout: tools/libs/{python3.lib, python313.lib, ...} - libs_dir="$(pwd)/python-windows/tools/libs" - if [ ! -f "$libs_dir/python3.lib" ]; then - echo "ERROR: python3.lib not found in extracted NuGet package" >&2 - find python-windows -name "python3*.lib" || true - exit 1 - fi - echo "PYO3_CROSS_LIB_DIR=$libs_dir" >> "$GITHUB_ENV" - echo "PYO3_CROSS_PYTHON_VERSION=3.13" >> "$GITHUB_ENV" - echo "PYO3_CROSS_PYTHON_IMPLEMENTATION=CPython" >> "$GITHUB_ENV" - echo "Configured PYO3 cross for Windows: PYO3_CROSS_LIB_DIR=$libs_dir" - - # IMPORTANT — direct `cargo` invocation (not `soldr cargo`): - # - # soldr's cargo front door routes through its own bin cache for - # cargo-zigbuild / cargo-xwin lookup. That cache has been serving a - # corrupted cargo-zigbuild binary (`Syntax error: ")" unexpected` - # at line 4) — see soldr#331 and the original fbuild template's - # workaround note. Going through `soldr cargo zigbuild` reproduces - # the failure even when a working cargo-zigbuild is on PATH via - # taiki-e/install-action. - # - # Calling cargo directly here lets cargo's built-in `cargo-` - # resolution find the taiki-e binary on PATH. setup-soldr still - # exports RUSTC_WRAPPER=zccache, so per-rustc caching engages on - # every invocation regardless of whether soldr's cargo front door - # is in the loop. - # - # Targets: - # * windows-msvc -> `cargo xwin build --release` (cargo-xwin uses - # clang/lld for the linker, MSVC CRT/SDK auto-fetched by xwin) - # * linux-musl -> `cargo zigbuild --release` (static, no glibc dep) - # * macos/other -> `cargo zigbuild --release` - name: Build release binaries shell: bash run: | - set -euo pipefail - if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then - cargo xwin build --release --target ${{ inputs.target }} \ + if [[ "${{ inputs.target }}" == *-unknown-linux-musl ]]; then + # Direct `cargo zigbuild` (pip-installed wrapper) bypasses + # soldr's bin cache, which has been serving a corrupted + # cargo-zigbuild binary (`Syntax error: ")" unexpected` at + # line 10) and blocking PyPI publishes. See #331. + cargo zigbuild --release --target ${{ inputs.target }} \ -p fbuild-cli \ -p fbuild-daemon else - cargo zigbuild --release --target ${{ inputs.target }} \ + soldr cargo build --release --target ${{ inputs.target }} \ -p fbuild-cli \ -p fbuild-daemon fi @@ -201,78 +119,93 @@ jobs: # Notes: # - PYO3_NO_PYTHON=1 + abi3-py310 feature lets pyo3-build-config skip # the host interpreter lookup on cross builds. - # - For Linux: build against `-unknown-linux-gnu.2.17` so the - # manylinux_2_17 wheel actually runs on glibc 2.17+. The CLI - # binaries stay on musl for static-link portability. - # - For macOS: zigbuild against the target directly. - # - For Windows MSVC: xwin against the target directly. - # Same direct-cargo invocation rationale as above. + # - All Linux builds (native AND cross) use cargo-zigbuild against + # -unknown-linux-gnu.2.17 — manylinux wheels are glibc-based + # and must work on glibc >= 2.17 (the manylinux_2_17 floor). Native + # ubuntu-latest's system linker would otherwise pull in glibc 2.39 + # symbols, breaking the wheel on every distro older than ubuntu-24.04. + # The CLI binaries stay on musl for static-link portability. + # - macOS cross: cargo rustc with -undefined dynamic_lookup works the + # same as native; just add --target. - name: Build Python extension shell: bash run: | - set -euo pipefail PYTHON_TARGET_DIR="target/python-extension" - if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then - PYO3_NO_PYTHON=1 cargo xwin build --release \ - --target-dir "${PYTHON_TARGET_DIR}" \ - --target ${{ inputs.target }} -p fbuild-python \ - --features extension-module - elif [[ "${{ inputs.target }}" == *-unknown-linux-* ]]; then + if [ "${{ runner.os }}" = "Linux" ]; then TARGET="${{ inputs.target }}" ARCH="${TARGET%%-*}" PYO3_TARGET="${ARCH}-unknown-linux-gnu" rustup target add "$PYO3_TARGET" + # Direct `cargo zigbuild` (pip-installed wrapper) — see #331. PYO3_NO_PYTHON=1 cargo zigbuild --release \ --target-dir "${PYTHON_TARGET_DIR}" \ --target "${PYO3_TARGET}.2.17" -p fbuild-python \ --features extension-module - else - PYO3_NO_PYTHON=1 cargo zigbuild --release \ + elif [ "${{ inputs.macos_cross }}" = "true" ]; then + PYO3_NO_PYTHON=1 soldr cargo build --release \ --target-dir "${PYTHON_TARGET_DIR}" \ --target ${{ inputs.target }} -p fbuild-python \ --features extension-module + elif [ "${{ runner.os }}" = "macOS" ]; then + soldr cargo build --release --target-dir "${PYTHON_TARGET_DIR}" \ + --target ${{ inputs.target }} \ + -p fbuild-python \ + --features extension-module + else + soldr cargo build --release --target-dir "${PYTHON_TARGET_DIR}" \ + --target ${{ inputs.target }} \ + -p fbuild-python \ + --features extension-module fi - name: Stage artifacts shell: bash run: | - set -euo pipefail 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.target }}" == *-unknown-linux-* ]]; then + # Stage Python extension — location depends on how it was built + if [ "${{ 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 [[ "${{ inputs.target }}" == *-apple-darwin ]]; then + 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 + elif [ "${{ inputs.binary_ext }}" = ".exe" ]; 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 + 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.target }}" == *-pc-windows-msvc ]]; then + # 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 with llvm-strip — universal across ELF + Mach-O. Skip - # Windows PE (cross-compiled, debug info already split). - if [[ "${{ inputs.target }}" != *-pc-windows-msvc ]]; then - llvm-strip staging/fbuild 2>/dev/null || true - llvm-strip staging/fbuild-daemon 2>/dev/null || true + # 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 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 From f1c6c8edf3d9c605114f46381635beac8aa80a77 Mon Sep 17 00:00:00 2001 From: zackees Date: Mon, 22 Jun 2026 13:01:05 -0700 Subject: [PATCH 6/6] ci: keep Windows release cross-build on Linux --- .github/workflows/build.yml | 2 +- .github/workflows/release-auto.yml | 2 +- .github/workflows/template_native_build.yml | 70 +++++++++++++++++---- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1fd9446..57da24f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: macos_cross: false - target: x86_64-pc-windows-msvc - runner: windows-latest + runner: ubuntu-24.04 binary_ext: ".exe" linux_cross: false macos_cross: false diff --git a/.github/workflows/release-auto.yml b/.github/workflows/release-auto.yml index 15c0cb3d..02956b0c 100644 --- a/.github/workflows/release-auto.yml +++ b/.github/workflows/release-auto.yml @@ -144,7 +144,7 @@ jobs: macos_cross: false - target: x86_64-pc-windows-msvc - runner: windows-latest + runner: ubuntu-24.04 binary_ext: ".exe" linux_cross: false macos_cross: false diff --git a/.github/workflows/template_native_build.yml b/.github/workflows/template_native_build.yml index acf3b6cd..16f0b28d 100644 --- a/.github/workflows/template_native_build.yml +++ b/.github/workflows/template_native_build.yml @@ -84,24 +84,65 @@ jobs: # Linux musl toolchain (libudev is auto-excluded for musl targets by serialport) - name: Install Linux dependencies - if: runner.os == 'Linux' && !inputs.linux_cross + if: runner.os == 'Linux' && !inputs.linux_cross && !contains(inputs.target, 'pc-windows-msvc') run: | sudo apt-get update sudo apt-get install -y musl-tools pkg-config + # Windows MSVC release artifacts are built from Linux with cargo-xwin. + - name: Install cargo-xwin + if: contains(inputs.target, 'pc-windows-msvc') + uses: taiki-e/install-action@v2 + with: + tool: cargo-xwin + + - name: Install xwin system dependencies + if: contains(inputs.target, 'pc-windows-msvc') + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends clang lld zip + # cargo-zigbuild — used for two reasons on Linux: # 1. musl cross-compilation (linux_cross targets) # 2. Building the PyO3 extension with a pinned glibc symbol version # so manylinux_2_17 wheels actually run on glibc 2.17. The native # ubuntu-latest linker would otherwise pull in glibc 2.39 symbols. - name: Install cross-compilation tools - if: runner.os == 'Linux' + if: runner.os == 'Linux' && !contains(inputs.target, 'pc-windows-msvc') run: pip install cargo-zigbuild + - name: Fetch Windows Python lib for PyO3 cross-compile + if: contains(inputs.target, 'pc-windows-msvc') + shell: bash + run: | + set -euo pipefail + PYTHON_VERSION="3.13.0" + if [[ "${{ inputs.target }}" == aarch64-* ]]; then + PKG="pythonarm64" + else + PKG="python" + fi + curl -fsSL -o python.zip "https://www.nuget.org/api/v2/package/${PKG}/${PYTHON_VERSION}" + mkdir -p python-windows + unzip -q python.zip -d python-windows + libs_dir="$(pwd)/python-windows/tools/libs" + if [ ! -f "$libs_dir/python3.lib" ]; then + echo "ERROR: python3.lib not found in extracted NuGet package" >&2 + find python-windows -name "python3*.lib" || true + exit 1 + fi + echo "PYO3_CROSS_LIB_DIR=$libs_dir" >> "$GITHUB_ENV" + echo "PYO3_CROSS_PYTHON_VERSION=3.13" >> "$GITHUB_ENV" + echo "PYO3_CROSS_PYTHON_IMPLEMENTATION=CPython" >> "$GITHUB_ENV" + - name: Build release binaries shell: bash run: | - if [[ "${{ inputs.target }}" == *-unknown-linux-musl ]]; then + if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then + cargo xwin build --release --target ${{ inputs.target }} \ + -p fbuild-cli \ + -p fbuild-daemon + elif [[ "${{ inputs.target }}" == *-unknown-linux-musl ]]; then # Direct `cargo zigbuild` (pip-installed wrapper) bypasses # soldr's bin cache, which has been serving a corrupted # cargo-zigbuild binary (`Syntax error: ")" unexpected` at @@ -131,7 +172,12 @@ jobs: shell: bash run: | PYTHON_TARGET_DIR="target/python-extension" - if [ "${{ runner.os }}" = "Linux" ]; then + if [[ "${{ inputs.target }}" == *-pc-windows-msvc ]]; then + PYO3_NO_PYTHON=1 cargo xwin build --release \ + --target-dir "${PYTHON_TARGET_DIR}" \ + --target ${{ inputs.target }} -p fbuild-python \ + --features extension-module + elif [ "${{ runner.os }}" = "Linux" ]; then TARGET="${{ inputs.target }}" ARCH="${TARGET%%-*}" PYO3_TARGET="${ARCH}-unknown-linux-gnu" @@ -167,7 +213,13 @@ jobs: cp target/${{ inputs.target }}/release/fbuild-daemon${{ inputs.binary_ext }} staging/ # Stage Python extension — location depends on how it was built - if [ "${{ runner.os }}" = "Linux" ]; then + 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 [ "${{ runner.os }}" = "Linux" ]; then TARGET="${{ inputs.target }}" ARCH="${TARGET%%-*}" ext_src="${PYTHON_TARGET_DIR}/${ARCH}-unknown-linux-gnu.2.17/release/lib_native.so" @@ -176,12 +228,6 @@ jobs: 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 - elif [ "${{ inputs.binary_ext }}" = ".exe" ]; 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 else for ext in so dylib; do src="${PYTHON_TARGET_DIR}/release/lib_native.$ext" @@ -201,6 +247,8 @@ jobs: llvm-strip staging/fbuild || true llvm-strip staging/fbuild-daemon || true llvm-strip staging/_native.abi3.so 2>/dev/null || 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