Blacksmith Testbox #230
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Blacksmith Testbox | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| testbox_id: | |
| description: Blacksmith Testbox session ID | |
| required: false | |
| default: "" | |
| type: string | |
| warm_target: | |
| description: Pre-build the workspace so testbox shells start with a warm target dir | |
| required: false | |
| default: true | |
| type: boolean | |
| build_command: | |
| description: >- | |
| Build/test command to run on the Blacksmith box (empty = just warm). Runs from the | |
| repo root in a login shell, verbatim -- $?, ${PIPESTATUS[0]}, $(...) and $((...)) | |
| are evaluated there, not by the workflow. A non-zero exit fails this run. | |
| required: false | |
| default: "" | |
| type: string | |
| permissions: | |
| contents: read | |
| actions: read | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| light-checks-testbox: | |
| name: codex-rs Testbox | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 60 | |
| env: | |
| # Keep this list in sync with ~/.codewith-testbox-env below: any build-affecting | |
| # variable set here but missing there changes the cargo fingerprint, which would | |
| # make a testbox shell rebuild the workspace from scratch and defeat the warm-up. | |
| # (Notably: do NOT set RUSTFLAGS here — repo CI does not, and an unpersisted | |
| # RUSTFLAGS invalidates the warmed target dir.) | |
| CARGO_NET_GIT_FETCH_WITH_CLI: "true" | |
| CARGO_TERM_COLOR: always | |
| RUST_MIN_STACK: "8388608" | |
| steps: | |
| - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # pin: v1 | |
| - uses: useblacksmith/begin-testbox@d0e04585c26905fdd92c94a09c159544c7ee1b67 # pin: v2 | |
| with: | |
| testbox_id: ${{ inputs.testbox_id }} | |
| # codex-bwrap's build.rs needs libcap.pc; the Blacksmith image does not ship it, | |
| # so without this every codex-rs build fails in ~1 min at the linker/pkg-config step. | |
| # Mirrors what rust-ci-full.yml installs for the same reason. | |
| - name: Install system build deps | |
| run: | | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ | |
| pkg-config libcap-dev bubblewrap | |
| - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 | |
| with: | |
| components: clippy,rustfmt | |
| - uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2.83.2 | |
| with: | |
| tool: just,cargo-nextest | |
| # Testbox shells are fresh logins: they do NOT inherit this job's env or PATH. | |
| # Persist the Rust toolchain + build env so `blacksmith testbox run` works out of the box. | |
| - name: Persist toolchain for Testbox shells | |
| run: | | |
| CARGO_BIN="$(dirname "$(command -v cargo)")" | |
| cat > "$HOME/.codewith-testbox-env" <<EOF | |
| export PATH="$CARGO_BIN:\$PATH" | |
| export CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}" | |
| export RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}" | |
| export CARGO_TARGET_DIR="$GITHUB_WORKSPACE/codex-rs/target" | |
| export CARGO_NET_GIT_FETCH_WITH_CLI="true" | |
| export CARGO_TERM_COLOR="always" | |
| export RUST_MIN_STACK="8388608" | |
| EOF | |
| for rc in "$HOME/.bashrc" "$HOME/.profile"; do | |
| grep -q 'codewith-testbox-env' "$rc" 2>/dev/null || \ | |
| printf '\n. "$HOME/.codewith-testbox-env"\n' >> "$rc" | |
| done | |
| sudo ln -sf "$CARGO_BIN/cargo" /usr/local/bin/cargo | |
| sudo ln -sf "$CARGO_BIN/rustc" /usr/local/bin/rustc | |
| sudo ln -sf "$(command -v just)" /usr/local/bin/just | |
| sudo ln -sf "$(command -v cargo-nextest)" /usr/local/bin/cargo-nextest | |
| # These run from `codex-rs` on purpose: rustup resolves an override from the | |
| # rust-toolchain.toml in the *current directory*, not from --manifest-path. Warming | |
| # from the repo root would build with the bootstrap toolchain instead of the pinned | |
| # one, so every testbox command (which runs under codex-rs) would recompile. | |
| - name: Prime toolchain | |
| working-directory: codex-rs | |
| run: | | |
| . "$HOME/.codewith-testbox-env" | |
| # Go through the rustup proxies first: they materialise the toolchain named by | |
| # codex-rs/rust-toolchain.toml (rustup >= 1.28 no longer installs it from | |
| # `rustup show`). This is the same path rust-ci.yml relies on. | |
| rustc --version | |
| cargo --version | |
| rustup show active-toolchain | |
| just --version | |
| cargo nextest --version | |
| # Fetch deps (and optionally pre-build) so an interactive testbox is immediately usable | |
| # instead of spending the first 10+ minutes cold-compiling the workspace. | |
| - name: Warm cargo registry | |
| working-directory: codex-rs | |
| run: | | |
| . "$HOME/.codewith-testbox-env" | |
| cargo fetch --locked | |
| - name: Warm target dir | |
| if: ${{ inputs.warm_target }} | |
| working-directory: codex-rs | |
| run: | | |
| . "$HOME/.codewith-testbox-env" | |
| cargo check --workspace --all-targets | |
| # This step is a build GATE: if the operator's command fails, the run MUST be red. | |
| # | |
| # The command is handed to the shell through `env:` and referenced as "$BUILD_COMMAND". | |
| # It is never interpolated into the script text. Two separate reasons, both load-bearing: | |
| # | |
| # 1. Correctness. `bash -lc "${{ inputs.build_command }}"` pastes the operator's text | |
| # inside a double-quoted string in THIS script, so this shell expands $?, | |
| # ${PIPESTATUS[0]}, $(...) and $((...)) before the inner shell ever sees them. | |
| # A submitted `...; rc1=$?; ...; rc2=${PIPESTATUS[0]}; exit $(( rc1 + rc2 ))` | |
| # degraded to `...; rc1=0; ...; rc2=0; exit 0`, so a clippy error[E0004] was | |
| # reported as a green required check (run 30190166288). Through `env:` the value | |
| # is data, not script text, and the inner shell parses it exactly as typed. | |
| # 2. Security. Interpolating an input into a `run:` body is a script-injection sink | |
| # (the input becomes part of the program). workflow_dispatch needs write access so | |
| # the blast radius is limited, but the env-var form removes the sink outright -- | |
| # the same one-line change fixes both problems. Every other workflow in this repo | |
| # already passes inputs via `env:`; this step was the only exception. | |
| # | |
| # The inner `bash -lc` is deliberate: the operator gets a plain login shell (toolchain | |
| # env, no -e/-o pipefail), so idioms like `cmd; rc=$?` collect statuses as written | |
| # instead of aborting at the first failure. Its exit status is captured and re-raised | |
| # explicitly below, which is what makes the gate honest. | |
| - name: Run build command on Blacksmith box | |
| if: ${{ inputs.build_command != '' }} | |
| env: | |
| BUILD_COMMAND: ${{ inputs.build_command }} | |
| run: | | |
| set -euo pipefail | |
| . "$HOME/.codewith-testbox-env" | |
| echo "::group::build command" | |
| printf '%s\n' "$BUILD_COMMAND" | |
| echo "::endgroup::" | |
| # `|| status=$?` keeps the failure ours to report and is safe under `set -e`. | |
| # Output is intentionally NOT wrapped in a log group: a collapsed group hides the | |
| # compiler errors that explain why the gate went red. | |
| status=0 | |
| bash -lc "$BUILD_COMMAND" || status=$? | |
| if [ "$status" -ne 0 ]; then | |
| echo "::error title=Testbox build command failed::exit status ${status}" | |
| exit "$status" | |
| fi | |
| echo "build command exited 0" | |
| # Must be the LAST step: hands the box over and holds it open for | |
| # `blacksmith testbox run --id <id> "<cmd>"`. | |
| - uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc # pin: v2 |