diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml new file mode 100644 index 0000000000000..62facba96b72e --- /dev/null +++ b/.github/workflows/eval.yml @@ -0,0 +1,53 @@ +name: Eval + +on: pull_request_target + +permissions: + contents: read + +jobs: + tests: + name: eval-check + runs-on: ubuntu-latest + strategy: + matrix: + system: [x86_64-linux, aarch64-linux, aarch64-darwin, x86_64-darwin] + steps: + # Important: Because of `pull_request_target`, this doesn't check out the PR, + # but rather the base branch of the PR, which is needed so we don't run untrusted code + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: base + sparse-checkout: ci + - name: Resolving the merge commit + env: + GH_TOKEN: ${{ github.token }} + run: | + if mergedSha=$(base/ci/get-merge-commit.sh ${{ github.repository }} ${{ github.event.number }}); then + echo "Checking the merge commit $mergedSha" + echo "mergedSha=$mergedSha" >> "$GITHUB_ENV" + else + # Skipping so that no notifications are sent + echo "Skipping the rest..." + fi + rm -rf base + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # Add this to _all_ subsequent steps to skip them + if: env.mergedSha + with: + ref: ${{ env.mergedSha }} + + - uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30 + if: env.mergedSha + + - name: Enable swap + if: env.mergedSha + run: | + sudo fallocate -l 10G /swapfile + sudo chmod 600 /swapfile + sudo mkswap /swapfile + sudo swapon /swapfile + + - name: Check eval + if: env.mergedSha + run: ./ci/eval-nixpkgs.sh --system "${{ matrix.system }}" diff --git a/ci/eval-nixpkgs.sh b/ci/eval-nixpkgs.sh new file mode 100755 index 0000000000000..f6f841ed25f08 --- /dev/null +++ b/ci/eval-nixpkgs.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p moreutils + +set -euxo pipefail + +system="x86_64-linux" +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +NIXPKGS_PATH="$(readlink -f "$SCRIPT_DIR"/..)" + +parseArgs() { + while [[ $# -gt 0 ]]; do + case $1 in + --system) + system=$2 + shift 2 + ;; + *) + echo "Unknown argument: $1" + exit 1 + ;; + esac + done +} + +main() { + parseArgs "$@" + tmpdir=$(mktemp -d) + trap 'rm -rf "$tmpdir"' EXIT + + nix-instantiate --eval --strict --json --arg enableWarnings false "$NIXPKGS_PATH"/pkgs/top-level/release-attrpaths-superset.nix -A paths > "$tmpdir/paths.json" + + CORES=$(nproc) + # Originally @amjoseph: note that the number of processes spawned is four times + # the number of cores -- this helps in two ways: + # 1. Keeping cores busy while I/O operations are in flight + # 2. Since the amount of time needed for the jobs is *not* balanced + # this minimizes the "tail latency" for the very last job to finish + # (on one core) by making the job size smaller. + NUM_CHUNKS=$(( 4 * CORES )) + + + ( + set +e + parallel -j "$CORES" \ + nix-env -qaP --no-name --out-path --arg checkMeta true --arg includeBroken true \ + --arg systems "[\"$system\"]" \ + -f "$NIXPKGS_PATH"/ci/parallel.nix --arg attrPathFile "$tmpdir"/paths.json \ + --arg numChunks "$NUM_CHUNKS" --show-trace --arg myChunk \ + -- $(seq 0 $(( NUM_CHUNKS - 1 ))) > "$tmpdir/paths" + echo $? > "$tmpdir/exit-code" + ) & + pid=$! + while kill -0 "$pid"; do + free -g >&2 + sleep 20 + done + jq --raw-input --slurp 'split("\n") | map(select(. != "") | split(" ") | map(select(. != "")) | { key: .[0], value: .[1] }) | from_entries' "$tmpdir/paths" + exit "$(cat "$tmpdir/exit-code")" +} + +main "$@" diff --git a/ci/parallel.nix b/ci/parallel.nix new file mode 100644 index 0000000000000..8c4a4f3928c25 --- /dev/null +++ b/ci/parallel.nix @@ -0,0 +1,61 @@ +/* +Invocation: + + +Invocation; note that the number of processes spawned is four times +the number of cores -- this helps in two ways: + +1. Keeping cores busy while I/O operations are in flight + +2. Since the amount of time needed for the jobs is *not* balanced + this minimizes the "tail latency" for the very last job to finish + (on one core) by making the job size smaller. + +*/ +# see pkgs/top-level/nohydra +{ lib ? import ../lib +, checkMeta +, includeBroken ? true +, path ? ./.. +, systems +, myChunk +, numChunks +, attrPathFile +}: + +let + attrPaths = builtins.fromJSON (builtins.readFile attrPathFile); + chunkSize = (lib.length attrPaths) / numChunks; + myPaths = + let + dropped = lib.drop (chunkSize*myChunk) attrPaths; + in + if myChunk == numChunks - 1 + then dropped + else lib.take chunkSize dropped; + + unfiltered = import ../pkgs/top-level/release-outpaths.nix { + inherit checkMeta path includeBroken systems; + }; + + f = i: m: a: + lib.mapAttrs (name: values: + if a ? ${name} then + if lib.any (value: lib.length value <= i + 1) values then + a.${name} + else + f (i + 1) values a.${name} + else + null + ) (lib.groupBy (a: lib.elemAt a i) m); + + filtered = f 0 myPaths unfiltered; + + recurseEverywhere = val: + if lib.isDerivation val || !(lib.isAttrs val) + then val + else (builtins.mapAttrs (_: v: recurseEverywhere v) val) + // { recurseForDerivations = true; }; + +in + recurseEverywhere filtered