diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f88664fb53..361a93d49a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,6 +102,11 @@ jobs: full: ${{ steps.paths.outputs.full }} # Code changed on a pull request that has no approved-for-ci label yet: only Quick compile. quick: ${{ steps.paths.outputs.quick }} + # An approved fork pull request whose approval is this very run: its heavy jobs run on the + # trusted self-hosted-fast runners, in private copies of the warm directories (fork-scratch.sh). + # Only the `labeled` event qualifies, so a later push to the fork's branch (unreviewed code) + # falls back to the fork routing above until a maintainer labels it again. `local` stays false. + fork_heavy: ${{ github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'approved-for-ci' && github.event.pull_request.head.repo.full_name != github.repository }} packages: ${{ steps.paths.outputs.packages }} oracle: ${{ steps.paths.outputs.oracle }} # Whether the other Linux jobs use the self-hosted runners too. @@ -282,10 +287,13 @@ jobs: name: Native Linux Clippy needs: changes if: needs.changes.outputs.native == 'true' - runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }} + runs-on: ${{ needs.changes.outputs.fork_heavy == 'true' && 'self-hosted-fast' || needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }} timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Private build, Cargo and rustup directories (approved fork pull request) + if: needs.changes.outputs.fork_heavy == 'true' + run: scripts/ci/fork-scratch.sh clippy - name: Warm build directory (self-hosted runner) if: runner.environment == 'self-hosted' && !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) run: scripts/ci/warm-target.sh clippy @@ -308,12 +316,15 @@ jobs: scope=(--workspace) if [ "$FULL" != true ]; then scope=(); for p in $PACKAGES; do scope+=(-p "$p"); done; fi cargo clippy "${scope[@]}" --all-targets --profile ci --locked -- -D warnings + - name: Delete the private directories + if: always() && needs.changes.outputs.fork_heavy == 'true' + run: scripts/ci/fork-scratch.sh --clean build-tests: name: Build tests needs: changes if: needs.changes.outputs.native == 'true' - runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 'self-hosted-untrusted' || 'ubuntu-24.04' }} + runs-on: ${{ needs.changes.outputs.fork_heavy == 'true' && 'self-hosted-fast' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 'self-hosted-untrusted' || 'ubuntu-24.04' }} # Fork builds compile inside gVisor on shared boxes; give them room to finish and upload. timeout-minutes: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 90 || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 60 || 45 }} env: @@ -323,6 +334,9 @@ jobs: SCCACHE_IGNORE_SERVER_IO_ERROR: "1" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Private build, Cargo and rustup directories (approved fork pull request) + if: needs.changes.outputs.fork_heavy == 'true' + run: scripts/ci/fork-scratch.sh - name: Size parallel compiles to the job's memory run: scripts/ci/build-jobs.sh - uses: dtolnay/rust-toolchain@stable @@ -355,11 +369,14 @@ jobs: - name: Compiler cache statistics if: always() run: sccache --show-stats + - name: Delete the private directories + if: always() && needs.changes.outputs.fork_heavy == 'true' + run: scripts/ci/fork-scratch.sh --clean tests: name: Native Linux tests (${{ matrix.shard }}/4) needs: [changes, build-tests] - runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 'self-hosted-untrusted' || 'ubuntu-24.04') }} + runs-on: ${{ needs.changes.outputs.fork_heavy == 'true' && 'self-hosted-fast' || needs.changes.outputs.local == 'true' && 'self-hosted-fast' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && (endsWith(format('{0}', github.event.pull_request.number), '7') || endsWith(format('{0}', github.event.pull_request.number), '8') || endsWith(format('{0}', github.event.pull_request.number), '9'))) && 'self-hosted-untrusted' || 'ubuntu-24.04') }} timeout-minutes: 60 strategy: fail-fast: false @@ -374,6 +391,9 @@ jobs: SOLIDCRAFT_REQUIRE_GPU: "1" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Private build, Cargo and rustup directories (approved fork pull request) + if: needs.changes.outputs.fork_heavy == 'true' + run: scripts/ci/fork-scratch.sh - name: Install a software Vulkan driver (lavapipe) # Preinstalled on the self-hosted runner, which has no sudo. if: runner.environment == 'github-hosted' @@ -425,6 +445,9 @@ jobs: name: junit-${{ matrix.shard }} path: target/nextest/ci/junit.xml retention-days: 7 + - name: Delete the private directories + if: always() && needs.changes.outputs.fork_heavy == 'true' + run: scripts/ci/fork-scratch.sh --clean action-matrix: # Every ordinary action on every face, edge, vertex and body of the design corpus, against @@ -522,7 +545,7 @@ jobs: name: Doc tests needs: changes if: needs.changes.outputs.native == 'true' - runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }} + runs-on: ${{ needs.changes.outputs.fork_heavy == 'true' && 'self-hosted-fast' || needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }} timeout-minutes: 45 env: CARGO_INCREMENTAL: "0" @@ -531,6 +554,9 @@ jobs: SCCACHE_IGNORE_SERVER_IO_ERROR: "1" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Private build, Cargo and rustup directories (approved fork pull request) + if: needs.changes.outputs.fork_heavy == 'true' + run: scripts/ci/fork-scratch.sh doc-tests - name: Size parallel compiles to the job's memory run: scripts/ci/build-jobs.sh - name: Warm build directory (self-hosted runner) @@ -570,6 +596,9 @@ jobs: exit 0 fi cargo test "${scope[@]}" --profile ci --locked --doc + - name: Delete the private directories + if: always() && needs.changes.outputs.fork_heavy == 'true' + run: scripts/ci/fork-scratch.sh --clean oracle: name: Reference tests @@ -671,6 +700,9 @@ jobs: # Preserve the existing check name for branch protection, including docs-only PRs. ci: + # On the skipped run another label starts, this job is named differently, so that skipped job + # can never stand in for the required `ci` check of the pull request's real run. + name: ${{ github.event.action == 'labeled' && github.event.label.name != 'approved-for-ci' && 'ci (ignored label)' || 'ci' }} needs: [changes, checks, quick-compile, clippy, build-tests, tests, doc-tests, oracle, windows-fxc, windows-startup] if: always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci') runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-light' || 'ubuntu-24.04' }} diff --git a/scripts/ci/fork-scratch.sh b/scripts/ci/fork-scratch.sh new file mode 100755 index 0000000000..a430d551e2 --- /dev/null +++ b/scripts/ci/fork-scratch.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Self-hosted runner, approved fork pull requests only: give the job private copies of the build +# directory, Cargo home and rustup home, so the job never writes to the shared warm directories +# that main and merge-queue runs reuse (a build script or test from a fork could otherwise leave +# files there). The copies start as the warm state (reflinks where the filesystem has them) and +# `fork-scratch.sh --clean` deletes them at the end of the job. +# Usage: fork-scratch.sh [NAME] NAME = the warm-target.sh directory to start the build from +# fork-scratch.sh --clean +# This keeps shared state out of reach of ordinary writes. It is not a sandbox: fork code still +# runs as the runner's user, which is why the workflow starts it only from a `labeled` event a +# maintainer triggered, never from a push. +set -euo pipefail +base=${RUNNER_TEMP:?}/fork-scratch +if [ "${1:-}" = --clean ]; then + rm -rf "$base" + exit 0 +fi +if [ "${GITHUB_EVENT_NAME:-}" != pull_request ]; then + echo "fork-scratch: only for pull request runs" >&2 + exit 1 +fi +name=${1:-} +rm -rf "$base" +mkdir -p "$base" +if [ -n "$name" ]; then + warm=/home/runner/work/cargo-target/$name + if [ -d "$warm" ]; then + cp -a --reflink=auto "$warm" "$base/target" + else + mkdir -p "$base/target" + fi + echo "CARGO_TARGET_DIR=$base/target" >> "$GITHUB_ENV" +fi +cargo_home=${CARGO_HOME:-$HOME/.cargo} +rustup_home=${RUSTUP_HOME:-$HOME/.rustup} +mkdir -p "$base/cargo-home" "$base/rustup-home" +[ -d "$cargo_home" ] && cp -a --reflink=auto "$cargo_home/." "$base/cargo-home/" +[ -d "$rustup_home" ] && cp -a --reflink=auto "$rustup_home/." "$base/rustup-home/" +{ + echo "CARGO_HOME=$base/cargo-home" + echo "RUSTUP_HOME=$base/rustup-home" +} >> "$GITHUB_ENV" +echo "$base/cargo-home/bin" >> "$GITHUB_PATH" +echo "fork-scratch: private copies in $base ($(du -sh "$base" | cut -f1))"