Files
OpenShell/.github/workflows/rust-native-build.yml
dependabot[bot] e9ac0ee691 chore(deps): bump actions/checkout from 7.0.0 to 7.0.1 (#2381)
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 12:57:18 +00:00

308 lines
11 KiB
YAML

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Rust Image Binary Build (openshell-gateway / openshell-sandbox / openshell-cli)
# Build Rust binaries per Linux architecture before the Docker image build
# consumes them as prebuilt artifacts. Gateway images use GNU-linked binaries
# for the NVIDIA distroless C/C++ runtime; supervisor and cli images use musl/static
# binaries so the final image can remain scratch. Gateway GNU binaries are
# built with an explicit glibc 2.28 floor so image, package, and tarball
# artifacts share the same host portability contract.
on:
workflow_call:
inputs:
component:
description: "Binary component to build (gateway, sandbox, or cli)"
required: true
type: string
arch:
description: "Linux architecture to build (amd64 or arm64)"
required: true
type: string
cargo-version:
description: "Pre-computed cargo version (skips internal git-based computation)"
required: false
type: string
default: ""
features:
description: "Cargo features to enable"
required: false
type: string
default: ""
retention-days:
description: "Artifact retention period"
required: false
type: number
default: 5
artifact-name:
description: "Artifact name override"
required: false
type: string
default: ""
checkout-ref:
description: "Git ref to check out for build inputs (defaults to the workflow SHA)"
required: false
type: string
default: ""
image-tag:
description: "Supervisor image tag to bake into gateway binaries"
required: false
type: string
default: ""
permissions:
contents: read
packages: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: "0"
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Route sccache (already RUSTC_WRAPPER in mise.toml) to the GHA cache
# backend instead of the EKS memcached used by ARC.
SCCACHE_GHA_ENABLED: "true"
defaults:
run:
shell: bash
jobs:
rust-native-build:
name: ${{ inputs.component }} (${{ inputs.arch }})
runs-on: ${{ inputs.arch == 'arm64' && 'linux-arm64-cpu8' || 'linux-amd64-cpu8' }}
timeout-minutes: 60
env:
COMPONENT: ${{ inputs.component }}
ARCH: ${{ inputs.arch }}
FEATURES: ${{ inputs.features }}
# Partition the GHA sccache cache per (component, arch). Without this,
# concurrent jobs collide on the same cache key and later-starting
# writers hit 409 Conflict.
SCCACHE_GHA_VERSION: ${{ inputs.component }}-${{ inputs.arch }}
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs['checkout-ref'] || github.sha }}
fetch-depth: 0
- name: Mark workspace safe for git
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Fetch tags
run: git fetch --tags --force
- name: Configure GHA sccache backend
# Exposes ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN before `mise install`
# compiles cargo-installed tools through RUSTC_WRAPPER=sccache.
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
- name: Install tools
run: mise install --locked
- name: Resolve build target
id: target
run: |
set -euo pipefail
case "$COMPONENT" in
gateway)
crate=openshell-server
binary=openshell-gateway
zig_target=
;;
sandbox)
crate=openshell-sandbox
binary=openshell-sandbox
zig_target=
;;
cli)
crate=openshell-cli
binary=openshell
zig_target=
;;
*)
echo "unsupported component: $COMPONENT" >&2
exit 1
;;
esac
case "$ARCH" in
amd64)
if [[ "$COMPONENT" == "sandbox" || "$COMPONENT" == "cli" ]]; then
target=x86_64-unknown-linux-musl
zig_target=x86_64-linux-musl
else
target=x86_64-unknown-linux-gnu
zig_target=x86_64-unknown-linux-gnu.2.28
fi
;;
arm64)
if [[ "$COMPONENT" == "sandbox" || "$COMPONENT" == "cli" ]]; then
target=aarch64-unknown-linux-musl
zig_target=aarch64-linux-musl
else
target=aarch64-unknown-linux-gnu
zig_target=aarch64-unknown-linux-gnu.2.28
fi
;;
*)
echo "unsupported arch: $ARCH" >&2
exit 1
;;
esac
{
echo "crate=$crate"
echo "binary=$binary"
echo "target=$target"
echo "zig_target=$zig_target"
} >> "$GITHUB_OUTPUT"
- name: Cache Rust target and registry
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
with:
shared-key: rust-native-${{ inputs.component }}-${{ inputs.arch }}-zig-wrapper-${{ hashFiles('tasks/scripts/setup-zig-cc-wrapper.sh') }}
cache-directories: .cache/sccache
cache-targets: "true"
- name: Compute cargo version
id: version
run: |
set -euo pipefail
if [[ -n "${{ inputs['cargo-version'] }}" ]]; then
echo "cargo_version=${{ inputs['cargo-version'] }}" >> "$GITHUB_OUTPUT"
else
echo "cargo_version=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT"
fi
- name: Patch workspace version
if: steps.version.outputs.cargo_version != ''
run: |
set -euo pipefail
sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ steps.version.outputs.cargo_version }}"'"/}' Cargo.toml
- name: Set up zig musl wrappers
if: contains(steps.target.outputs.target, 'musl')
run: |
set -euo pipefail
ZIG="$(mise which zig)"
ZIG_TARGET="${{ steps.target.outputs.zig_target }}"
mkdir -p /tmp/zig-musl
# cc-rs injects --target=<rust-triple>, which zig does not parse.
# Strip caller-provided --target and use the wrapper's zig target.
for tool in cc c++; do
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' \
"$ZIG" "$tool" "$ZIG_TARGET" > "/tmp/zig-musl/${tool}"
chmod +x "/tmp/zig-musl/${tool}"
done
TARGET_ENV=$(echo "${{ steps.target.outputs.target }}" | tr '-' '_')
TARGET_ENV_UPPER=${TARGET_ENV^^}
echo "CC_${TARGET_ENV}=/tmp/zig-musl/cc" >> "$GITHUB_ENV"
echo "CXX_${TARGET_ENV}=/tmp/zig-musl/c++" >> "$GITHUB_ENV"
echo "CARGO_TARGET_${TARGET_ENV_UPPER}_LINKER=/tmp/zig-musl/cc" >> "$GITHUB_ENV"
echo "CARGO_TARGET_${TARGET_ENV_UPPER}_RUSTFLAGS=-Clink-self-contained=no" >> "$GITHUB_ENV"
- name: Set up zig glibc wrappers
if: inputs.component == 'gateway'
run: tasks/scripts/setup-zig-cc-wrapper.sh "${{ steps.target.outputs.zig_target }}" "${{ steps.target.outputs.zig_target }}" /tmp/zig-gnu
- name: Build ${{ steps.target.outputs.binary }} (${{ steps.target.outputs.zig_target || steps.target.outputs.target }})
env:
# Preserve the release-codegen setting used by the old Dockerfile
# Rust build path so image artifacts keep the same release profile.
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "1"
OPENSHELL_IMAGE_TAG: ${{ inputs['image-tag'] }}
run: |
set -euo pipefail
# z3 built with zig c++ uses libc++ symbols (std::__1::*).
# Override z3-sys default (stdc++) so Rust links the matching runtime.
if [[ "${{ inputs.component }}" == "cli" ]]; then
echo "CXXSTDLIB=c++" >> "$GITHUB_ENV"
fi
mise x -- rustup target add "${{ steps.target.outputs.target }}"
cargo_cmd=(cargo build)
build_target="${{ steps.target.outputs.target }}"
args=()
if [[ "${{ inputs.component }}" == "gateway" ]]; then
cargo_cmd=(cargo zigbuild)
build_target="${{ steps.target.outputs.zig_target }}"
args+=(--features bundled-z3)
fi
args+=(
--release
--target "$build_target"
-p "${{ steps.target.outputs.crate }}"
--bin "${{ steps.target.outputs.binary }}"
)
if [[ -n "$FEATURES" ]]; then
args+=(--features "$FEATURES")
fi
if [[ -n "${{ steps.version.outputs.cargo_version }}" ]]; then
export GIT_DIR=/nonexistent
fi
mise x -- "${cargo_cmd[@]}" "${args[@]}"
- name: Verify packaged binary
run: |
set -euo pipefail
BIN="target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}"
OUTPUT="$("$BIN" --version)"
echo "$OUTPUT"
grep -q "^${{ steps.target.outputs.binary }} " <<<"$OUTPUT"
# Record linkage so image runtime drift is visible in logs.
ldd --version
ldd "$BIN" || true
if [[ "${{ inputs.component }}" == "gateway" ]] && ldd "$BIN" | grep -q 'libz3'; then
echo "gateway binary must not depend on shared libz3; enable bundled-z3 for image artifacts" >&2
exit 1
fi
- name: Verify glibc symbol floor
if: inputs.component == 'gateway'
run: |
set -euo pipefail
BIN="target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}"
tasks/scripts/verify-glibc-symbols.sh 2.28 "$BIN"
- name: Stage binary for prebuilt layout
run: |
set -euo pipefail
STAGE="prebuilt-binaries/$ARCH"
mkdir -p "$STAGE"
install -m 0755 \
"target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}" \
"$STAGE/${{ steps.target.outputs.binary }}"
ls -lh "$STAGE/"
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs['artifact-name'] != '' && inputs['artifact-name'] || format('rust-binary-{0}-linux-{1}', inputs.component, inputs.arch) }}
path: prebuilt-binaries/${{ inputs.arch }}/${{ steps.target.outputs.binary }}
retention-days: ${{ inputs['retention-days'] }}
if-no-files-found: error
- name: sccache stats
if: always()
run: |
set +e
stats_bin="${SCCACHE_PATH:-sccache}"
"$stats_bin" --show-stats
status=$?
if [[ $status -ne 0 ]]; then
echo "::warning::sccache stats unavailable (exit $status)"
fi
exit 0