|
| 1 | +name: Validate DevX containers (PR) |
| 2 | + |
| 3 | +# Validate devcontainer closures on pull requests by fetching |
| 4 | +# Hydra-cached build outputs and smoke-testing them. This replaces |
| 5 | +# the GHCR upload pipeline for PRs — actual uploads only happen |
| 6 | +# from the main branch (gated in main.yml). |
| 7 | +on: |
| 8 | + pull_request: |
| 9 | + |
| 10 | +env: |
| 11 | + GH_TOKEN: ${{ github.token }} |
| 12 | + |
| 13 | +jobs: |
| 14 | + wait-for-hydra: |
| 15 | + name: Wait for Hydra status |
| 16 | + runs-on: ubuntu-latest |
| 17 | + steps: |
| 18 | + - uses: input-output-hk/actions/wait-for-hydra@latest |
| 19 | + with: |
| 20 | + check: required |
| 21 | + |
| 22 | + discover: |
| 23 | + needs: wait-for-hydra |
| 24 | + name: Discover -env closures |
| 25 | + runs-on: ubuntu-latest |
| 26 | + outputs: |
| 27 | + matrix: ${{ steps.set-matrix.outputs.matrix }} |
| 28 | + steps: |
| 29 | + - name: Discover Hydra -env check-runs |
| 30 | + id: set-matrix |
| 31 | + run: | |
| 32 | + # Fetch all check-runs for this commit from Hydra. |
| 33 | + # --paginate returns multiple JSON objects (one per page), |
| 34 | + # so we pipe through jq --slurp to merge them before filtering. |
| 35 | + RUNS=$(gh api "repos/$GITHUB_REPOSITORY/commits/${{ github.event.pull_request.head.sha }}/check-runs" --paginate) |
| 36 | +
|
| 37 | + # Pick a representative subset: one -env per platform to keep |
| 38 | + # the matrix small (avoids 80+ jobs on every PR push). |
| 39 | + # Only include successfully completed builds — Hydra posts |
| 40 | + # check-runs at eval time before builds finish, so in-progress |
| 41 | + # or failed builds would have store paths not yet in caches. |
| 42 | + FILTERED=$(echo "$RUNS" | jq -s -c ' |
| 43 | + [.[].check_runs[] |
| 44 | + | select(.name | endswith("-env")) |
| 45 | + | select(.conclusion == "success") |
| 46 | + | select(.output.summary | . != null and startswith("/nix/store/")) |
| 47 | + | { "config": .name, |
| 48 | + "build_path": .output.summary, |
| 49 | + "short_name": (.name | sub("^[^.]+\\."; "")) }] |
| 50 | + | group_by(.short_name) |
| 51 | + | map(.[0]) |
| 52 | + | sort_by(.config) |
| 53 | + | .[0:8] |
| 54 | + ') |
| 55 | +
|
| 56 | + echo "Selected closures for validation:" |
| 57 | + echo "$FILTERED" | jq . |
| 58 | + echo "matrix=$FILTERED" >> $GITHUB_OUTPUT |
| 59 | +
|
| 60 | + validate: |
| 61 | + needs: discover |
| 62 | + if: needs.discover.outputs.matrix != '[]' && needs.discover.outputs.matrix != '' |
| 63 | + runs-on: ubuntu-latest |
| 64 | + strategy: |
| 65 | + fail-fast: false |
| 66 | + matrix: |
| 67 | + job: ${{ fromJson(needs.discover.outputs.matrix) }} |
| 68 | + name: Validate ${{ matrix.job.short_name }} |
| 69 | + steps: |
| 70 | + - name: Install Nix |
| 71 | + uses: cachix/install-nix-action@v31 |
| 72 | + with: |
| 73 | + extra_nix_config: | |
| 74 | + trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= loony-tools:pr9m4BkM/5/eSTZlkQyRt57Jz7OMBxNSUiMC4FkcNfk= |
| 75 | + substituters = https://cache.iog.io/ https://cache.zw3rk.com/ https://cache.nixos.org/ |
| 76 | +
|
| 77 | + - name: Validate closure |
| 78 | + env: |
| 79 | + SHELL_NIX_PATH: ${{ matrix.job.build_path }} |
| 80 | + run: | |
| 81 | + set -euo pipefail |
| 82 | +
|
| 83 | + echo "::group::Realize closure from Hydra cache" |
| 84 | + nix-store -r "$SHELL_NIX_PATH" |
| 85 | + echo "::endgroup::" |
| 86 | +
|
| 87 | + CLOSURE_PATHS=$(nix-store -qR "$SHELL_NIX_PATH") |
| 88 | +
|
| 89 | + # Verify the "devx" wrapper script is in the closure |
| 90 | + if ! echo "$CLOSURE_PATHS" | grep -q "devx$"; then |
| 91 | + echo "::error::No 'devx' wrapper found in closure" |
| 92 | + exit 1 |
| 93 | + fi |
| 94 | +
|
| 95 | + DEVX_PATH=$(echo "$CLOSURE_PATHS" | grep "devx$" | head -1) |
| 96 | + echo "Found devx wrapper: $DEVX_PATH" |
| 97 | +
|
| 98 | + echo "::group::Smoke test" |
| 99 | + # Write smoke test commands to a temporary file — the devx |
| 100 | + # wrapper sources $1 as a script, it doesn't support -c. |
| 101 | + SMOKE_TEST=$(mktemp) |
| 102 | + cat > "$SMOKE_TEST" << 'TESTEOF' |
| 103 | + echo "GHC: $(ghc --version 2>/dev/null || echo N/A)" |
| 104 | + echo "Cabal: $(cabal --version 2>/dev/null | head -1 || echo N/A)" |
| 105 | + TESTEOF |
| 106 | +
|
| 107 | + # stdenv's setup.sh expects Nix builder runtime variables |
| 108 | + # (NIX_BUILD_TOP, out, etc.) that only exist inside nix build. |
| 109 | + # Provide them so the devx wrapper can source setup.sh. |
| 110 | + export NIX_BUILD_TOP="$(mktemp -d)" |
| 111 | + export TMPDIR="$NIX_BUILD_TOP" |
| 112 | + export TMP="$NIX_BUILD_TOP" |
| 113 | + export TEMP="$NIX_BUILD_TOP" |
| 114 | + export TEMPDIR="$NIX_BUILD_TOP" |
| 115 | + export NIX_STORE="/nix/store" |
| 116 | + export out="$NIX_BUILD_TOP/out" |
| 117 | + mkdir -p "$out" |
| 118 | + "$DEVX_PATH" "$SMOKE_TEST" |
| 119 | + echo "::endgroup::" |
| 120 | +
|
| 121 | + echo "Validation passed for ${{ matrix.job.short_name }}" |
0 commit comments