@@ -89,14 +89,70 @@ jobs:
8989 "${REGISTRY}/${component}:${{ github.sha }}"
9090 done
9191
92- build-python-wheels :
93- name : Stage Python Wheels
92+ build-python-wheels-linux :
93+ name : Build Python Wheels (Linux ${{ matrix.arch }})
94+ needs : [compute-versions]
95+ strategy :
96+ matrix :
97+ include :
98+ - arch : amd64
99+ runner : build-amd64
100+ artifact : linux-amd64
101+ task : python:build:linux:amd64
102+ output_path : target/wheels/linux-amd64/*.whl
103+ - arch : arm64
104+ runner : build-arm64
105+ artifact : linux-arm64
106+ task : python:build:linux:arm64
107+ output_path : target/wheels/linux-arm64/*.whl
108+ runs-on : ${{ matrix.runner }}
109+ timeout-minutes : 120
110+ container :
111+ image : ghcr.io/nvidia/openshell/ci:latest
112+ credentials :
113+ username : ${{ github.actor }}
114+ password : ${{ secrets.GITHUB_TOKEN }}
115+ options : --privileged
116+ env :
117+ MISE_GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
118+ SCCACHE_MEMCACHED_ENDPOINT : ${{ vars.SCCACHE_MEMCACHED_ENDPOINT }}
119+ OPENSHELL_IMAGE_TAG : dev
120+ steps :
121+ - uses : actions/checkout@v4
122+ with :
123+ fetch-depth : 0
124+
125+ - name : Mark workspace safe for git
126+ run : git config --global --add safe.directory "$GITHUB_WORKSPACE"
127+
128+ - name : Sync Python dependencies
129+ run : uv sync
130+
131+ - name : Cache Rust target and registry
132+ uses : Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
133+ with :
134+ shared-key : python-wheel-linux-${{ matrix.arch }}
135+ cache-directories : .cache/sccache
136+ cache-targets : " true"
137+
138+ - name : Build Python wheels
139+ run : |
140+ set -euo pipefail
141+ OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run ${{ matrix.task }}
142+ ls -la ${{ matrix.output_path }}
143+
144+ - name : Upload wheel artifacts
145+ uses : actions/upload-artifact@v4
146+ with :
147+ name : python-wheels-${{ matrix.artifact }}
148+ path : ${{ matrix.output_path }}
149+ retention-days : 5
150+
151+ build-python-wheel-macos :
152+ name : Build Python Wheel (macOS)
94153 needs : [compute-versions]
95154 runs-on : build-amd64
96155 timeout-minutes : 120
97- outputs :
98- wheel_version : ${{ needs.compute-versions.outputs.python_version }}
99- wheel_filenames : ${{ steps.filenames.outputs.wheel_filenames }}
100156 container :
101157 image : ghcr.io/nvidia/openshell/ci:latest
102158 credentials :
@@ -126,29 +182,24 @@ jobs:
126182 - name : Sync Python dependencies
127183 run : uv sync
128184
129- - name : Build Python wheels
185+ - name : Build Python wheel
130186 run : |
131187 set -euo pipefail
132- OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:multiarch
133188 OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" mise run python:build:macos
134189 ls -la target/wheels/*.whl
135190
136- - name : Capture wheel filenames
137- id : filenames
138- run : |
139- set -euo pipefail
140- WHEEL_FILENAMES=$(ls target/wheels/*.whl | xargs -n1 basename | paste -sd, -)
141- echo "wheel_filenames=${WHEEL_FILENAMES}" >> "$GITHUB_OUTPUT"
142-
143191 - name : Upload wheel artifacts
144192 uses : actions/upload-artifact@v4
145193 with :
146- name : python-wheels
194+ name : python-wheels-macos
147195 path : target/wheels/*.whl
148196 retention-days : 5
149197
150198 # ---------------------------------------------------------------------------
151199 # Build CLI binaries (Linux musl — static, native on each arch)
200+ #
201+ # Builds run directly on the CI host (glibc Ubuntu). Zig provides musl
202+ # C/C++ toolchains for bundled-z3 and ring, and is also used as the linker.
152203 # ---------------------------------------------------------------------------
153204 build-cli-linux :
154205 name : Build CLI (Linux ${{ matrix.arch }})
@@ -159,9 +210,11 @@ jobs:
159210 - arch : amd64
160211 runner : build-amd64
161212 target : x86_64-unknown-linux-musl
213+ zig_target : x86_64-linux-musl
162214 - arch : arm64
163215 runner : build-arm64
164216 target : aarch64-unknown-linux-musl
217+ zig_target : aarch64-linux-musl
165218 runs-on : ${{ matrix.runner }}
166219 timeout-minutes : 60
167220 container :
@@ -195,23 +248,44 @@ jobs:
195248 cache-directories : .cache/sccache
196249 cache-targets : " true"
197250
198- - name : Install musl toolchain
251+ - name : Add Rust musl target
252+ run : mise x -- rustup target add ${{ matrix.target }}
253+
254+ - name : Set up zig musl wrappers
199255 run : |
200256 set -euo pipefail
201- apt-get update
202- apt-get install -y --no-install-recommends musl-tools
203- rm -rf /var/lib/apt/lists/*
257+ ZIG="$(mise which zig)"
258+ ZIG_TARGET="${{ matrix.zig_target }}"
259+ mkdir -p /tmp/zig-musl
260+
261+ # cc-rs injects --target=<rust-triple> (for example
262+ # aarch64-unknown-linux-musl), which zig does not parse. Strip any
263+ # caller-provided --target and use the wrapper's zig-native target.
264+ for tool in cc c++; do
265+ printf '#!/bin/bash\nargs=()\nfor arg in "$@"; do\n case "$arg" in\n --target=*) ;;\n *) args+=("$arg") ;;\n esac\ndone\nexec "%s" %s --target=%s "${args[@]}"\n' \
266+ "$ZIG" "$tool" "$ZIG_TARGET" > "/tmp/zig-musl/${tool}"
267+ chmod +x "/tmp/zig-musl/${tool}"
268+ done
204269
205- - name : Add Rust musl target
206- run : mise x -- rustup target add ${{ matrix.target }}
270+ TARGET_ENV=$(echo "${{ matrix.target }}" | tr '-' '_')
271+ TARGET_ENV_UPPER=${TARGET_ENV^^}
272+
273+ # Use zig for C/C++ compilation and final linking.
274+ echo "CC_${TARGET_ENV}=/tmp/zig-musl/cc" >> "$GITHUB_ENV"
275+ echo "CXX_${TARGET_ENV}=/tmp/zig-musl/c++" >> "$GITHUB_ENV"
276+ echo "CARGO_TARGET_${TARGET_ENV_UPPER}_LINKER=/tmp/zig-musl/cc" >> "$GITHUB_ENV"
277+
278+ # Let zig own CRT/startfiles to avoid duplicate _start symbols.
279+ echo "CARGO_TARGET_${TARGET_ENV_UPPER}_RUSTFLAGS=-Clink-self-contained=no" >> "$GITHUB_ENV"
280+
281+ # z3 built with zig c++ uses libc++ symbols (std::__1::*).
282+ # Override z3-sys default (stdc++) so Rust links the matching runtime.
283+ echo "CXXSTDLIB=c++" >> "$GITHUB_ENV"
207284
208285 - name : Scope workspace to CLI crates
209286 run : |
210287 set -euo pipefail
211- # Remove workspace members that are not needed for openshell-cli.
212- # This avoids Cargo feature-unification pulling in aws-lc-sys (via
213- # russh in openshell-sandbox / openshell-server).
214- sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-cli", "crates/openshell-core", "crates/openshell-bootstrap", "crates/openshell-policy", "crates/openshell-providers", "crates/openshell-tui"]|' Cargo.toml
288+ sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-cli", "crates/openshell-core", "crates/openshell-bootstrap", "crates/openshell-policy", "crates/openshell-prover", "crates/openshell-providers", "crates/openshell-tui"]|' Cargo.toml
215289
216290 - name : Patch workspace version
217291 if : needs.compute-versions.outputs.cargo_version != ''
@@ -220,7 +294,7 @@ jobs:
220294 sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml
221295
222296 - name : Build ${{ matrix.target }}
223- run : mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-cli
297+ run : mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-cli --features bundled-z3
224298
225299 - name : sccache stats
226300 if : always()
@@ -309,9 +383,11 @@ jobs:
309383 # ---------------------------------------------------------------------------
310384 release-dev :
311385 name : Release Dev
312- needs : [build-cli-linux, build-cli-macos, build-python-wheels]
386+ needs : [compute-versions, build-cli-linux, build-cli-macos, build-python-wheels-linux, build-python-wheel-macos ]
313387 runs-on : build-amd64
314388 timeout-minutes : 10
389+ outputs :
390+ wheel_filenames : ${{ steps.wheel_filenames.outputs.wheel_filenames }}
315391 steps :
316392 - uses : actions/checkout@v4
317393
@@ -325,8 +401,17 @@ jobs:
325401 - name : Download wheel artifacts
326402 uses : actions/download-artifact@v4
327403 with :
328- name : python-wheels
404+ pattern : python-wheels-*
329405 path : release/
406+ merge-multiple : true
407+
408+ - name : Capture wheel filenames
409+ id : wheel_filenames
410+ run : |
411+ set -euo pipefail
412+ ls -la release/*.whl
413+ WHEEL_FILENAMES=$(ls release/*.whl | xargs -n1 basename | sort | paste -sd, -)
414+ echo "wheel_filenames=${WHEEL_FILENAMES}" >> "$GITHUB_OUTPUT"
330415
331416 - name : Generate checksums
332417 run : |
@@ -338,7 +423,7 @@ jobs:
338423 - name : Prune stale wheel assets from dev release
339424 uses : actions/github-script@v7
340425 env :
341- WHEEL_VERSION : ${{ needs.build-python-wheels .outputs.wheel_version }}
426+ WHEEL_VERSION : ${{ needs.compute-versions .outputs.python_version }}
342427 with :
343428 script : |
344429 const wheelVersion = process.env.WHEEL_VERSION;
@@ -416,7 +501,7 @@ jobs:
416501
417502 trigger-wheel-publish :
418503 name : Trigger Wheel Publish
419- needs : [compute-versions, build-python-wheels, release-dev]
504+ needs : [compute-versions, release-dev]
420505 runs-on : [self-hosted, nv]
421506 timeout-minutes : 10
422507 steps :
@@ -425,7 +510,7 @@ jobs:
425510 GITLAB_CI_TRIGGER_TOKEN : ${{ secrets.GITLAB_CI_TRIGGER_TOKEN }}
426511 GITLAB_CI_TRIGGER_URL : ${{ secrets.GITLAB_CI_TRIGGER_URL }}
427512 RELEASE_VERSION : ${{ needs.compute-versions.outputs.python_version }}
428- WHEEL_FILENAMES : ${{ needs.build-python-wheels .outputs.wheel_filenames }}
513+ WHEEL_FILENAMES : ${{ needs.release-dev .outputs.wheel_filenames }}
429514 run : |
430515 set -euo pipefail
431516 if [ -z "${WHEEL_FILENAMES}" ]; then
0 commit comments