diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml new file mode 100644 index 0000000000000..d75985f419c52 --- /dev/null +++ b/.github/workflows/eval.yml @@ -0,0 +1,110 @@ +name: Eval + +on: pull_request_target + +permissions: + contents: read + +jobs: + attrs: + name: Attrs + runs-on: ubuntu-latest + outputs: + systems: ${{ steps.systems.outputs.systems }} + mergedSha: ${{ steps.merged.outputs.mergedSha }} + 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 + id: merged + 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_OUTPUT" + 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: steps.merged.outputs.mergedSha + with: + ref: ${{ env.mergedSha }} + path: nixpkgs + + - uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30 + if: steps.merged.outputs.mergedSha + + - id: systems + if: steps.merged.outputs.mergedSha + run: | + nix-build nixpkgs/ci -A eval.attrpathsSuperset + echo "systems=$(> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + if: steps.merged.outputs.mergedSha + with: + name: paths + path: result/paths.json + + eval: + name: Eval + runs-on: ubuntu-latest + needs: attrs + if: needs.attrs.outputs.mergedSha + strategy: + matrix: + system: ${{ fromJSON(needs.attrs.outputs.systems) }} + steps: + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: paths + path: paths + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.attrs.outputs.mergedSha }} + path: nixpkgs + + - uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30 + + - name: Check eval + run: nix-build nixpkgs/ci -A eval.singleSystem --argstr evalSystem ${{ matrix.system }} --arg attrpathFile ./paths/paths.json + + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + if: needs.attrs.outputs.mergedSha + with: + name: result-${{ matrix.system }} + path: result/paths + + combine: + name: Combined + runs-on: ubuntu-latest + needs: eval + steps: + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + pattern: result-* + path: results + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.attrs.outputs.mergedSha }} + path: nixpkgs + + - uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30 + + - name: Check eval + run: nix-build nixpkgs/ci -A eval.combine --arg resultsDir ./results + + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: outpaths + path: result/outpaths.json diff --git a/ci/default.nix b/ci/default.nix index 02b2e948d17b8..f5a6cf7d3dd0e 100644 --- a/ci/default.nix +++ b/ci/default.nix @@ -26,4 +26,5 @@ in inherit pkgs; requestReviews = pkgs.callPackage ./request-reviews { }; codeownersValidator = pkgs.callPackage ./codeowners-validator { }; + eval = pkgs.callPackage ./eval { }; } diff --git a/ci/eval/default.nix b/ci/eval/default.nix new file mode 100644 index 0000000000000..55abd0284801e --- /dev/null +++ b/ci/eval/default.nix @@ -0,0 +1,196 @@ +{ + lib, + runCommand, + writeShellScript, + linkFarm, + time, + procps, + nix, + jq, +}: + +let + nixpkgs = + with lib.fileset; + toSource { + root = ../..; + fileset = unions ( + map (lib.path.append ../..) [ + "default.nix" + "doc" + "lib" + "maintainers" + "nixos" + "pkgs" + ".version" + "ci/eval/parallel.nix" + ] + ); + }; + + supportedSystems = import ../supportedSystems.nix; + + attrpathsSuperset = + runCommand "attrpaths-superset.json" + { + src = nixpkgs; + nativeBuildInputs = [ + nix + ]; + env.supportedSystems = builtins.toJSON supportedSystems; + passAsFile = [ "supportedSystems" ]; + } + '' + export NIX_STATE_DIR=$(mktemp -d) + mkdir $out + nix-instantiate --eval --strict --json --arg enableWarnings false $src/pkgs/top-level/release-attrpaths-superset.nix -A paths > $out/paths.json + mv "$supportedSystemsPath" $out/systems.json + ''; + + singleSystem = + { + evalSystem, + attrpathFile, + checkMeta ? true, + includeBroken ? true, + # How many attributes to be evaluating at any single time. + # This effectively limits the maximum memory usage. + # Decrease this if too much memory is used + simultaneousAttrsPerSystem ? 100000, + quickTest ? false, + }: + let + singleChunk = writeShellScript "chunk" '' + set -euo pipefail + chunkSize=$1 + myChunk=$2 + outputDir=$3 + system=$4 + + nix-env -f "${nixpkgs}/ci/eval/parallel.nix" \ + --query --available \ + --no-name --attr-path --out-path \ + --show-trace \ + --arg chunkSize "$chunkSize" \ + --arg myChunk "$myChunk" \ + --arg attrpathFile "${attrpathFile}" \ + --arg systems "[ \"$system\" ]" \ + --arg checkMeta ${lib.boolToString checkMeta} \ + --arg includeBroken ${lib.boolToString includeBroken} \ + > "$outputDir/$myChunk" + ''; + in + runCommand "nixpkgs-eval-${evalSystem}" + { + nativeBuildInputs = [ + nix + time + procps + jq + ]; + env = { + inherit evalSystem; + }; + } + '' + set -x + export NIX_STATE_DIR=$(mktemp -d) + nix-store --init + + echo "System: $evalSystem" + cores=$NIX_BUILD_CORES + echo "Cores: $cores" + num_attrs=$(jq length "${attrpathFile}") + echo "Attribute count: $num_attrs" + chunk_size=$(( ${toString simultaneousAttrsPerSystem} / cores )) + echo "Chunk size: $chunk_size" + # Same as `num_attrs / chunk_size` but rounded up + num_chunks=$(( (num_attrs - 1) / chunk_size + 1 )) + echo "Chunk count: $num_chunks" + + ( + while true; do + free -g + sleep 20 + done + ) & + + seq_end=$(( num_chunks - 1 )) + + ${lib.optionalString quickTest '' + seq_end=0 + ''} + + chunkOutputDir=$(mktemp -d) + seq -w 0 "$seq_end" | + command time -v xargs -t -I{} -P"$cores" \ + ${singleChunk} "$chunk_size" {} "$chunkOutputDir" "$evalSystem" + + mkdir $out + cat "$chunkOutputDir"/* > $out/paths + ''; + + combine = + { + resultsDir, + }: + runCommand "combined-result" + { + nativeBuildInputs = [ + jq + ]; + passAsFile = [ "jqScript" ]; + jqScript = # jq + '' + split("\n") | + map(select(. != "") | split(" ") | map(select(. != ""))) | + map( + { + key: .[0], + value: .[1] | split(";") | map(split("=") | + if length == 1 then + { key: "out", value: .[0] } + else + { key: .[0], value: .[1] } + end) | from_entries} + ) | from_entries + ''; + } + '' + mkdir -p $out + cat ${resultsDir}/*/paths | + jq --sort-keys --raw-input --slurp -f "$jqScriptPath" \ + > $out/outpaths.json + ''; + + together = + { + quickTest ? false, + }: + let + systems = if quickTest then [ "x86_64-linux" ] else supportedSystems; + results = linkFarm "results" ( + map (system: { + name = system; + path = singleSystem { + system = system; + attrpathFile = attrpathsSuperset + "/paths.json"; + inherit quickTest; + }; + }) systems + ); + final = combine { + resultsDir = results; + }; + in + final; + +in +{ + inherit + attrpathsSuperset + singleSystem + combine + together + ; +} diff --git a/ci/eval/parallel.nix b/ci/eval/parallel.nix new file mode 100644 index 0000000000000..cee0115aa8a2e --- /dev/null +++ b/ci/eval/parallel.nix @@ -0,0 +1,46 @@ +{ + lib ? import ../../lib, + path ? ../.., + attrpathFile, + chunkSize, + myChunk, + checkMeta, + includeBroken, + systems, +}: + +let + attrpaths = lib.importJSON attrpathFile; + myAttrpaths = lib.sublist (chunkSize * myChunk) chunkSize attrpaths; + + unfiltered = import ../../pkgs/top-level/release-outpaths.nix { + inherit path; + inherit checkMeta includeBroken systems; + }; + + filtered = + let + recurse = + index: paths: attrs: + lib.mapAttrs ( + name: values: + if attrs ? ${name} then + if lib.any (value: lib.length value <= index + 1) values then + attrs.${name} + else + recurse (index + 1) values attrs.${name} + else + null + ) (lib.groupBy (a: lib.elemAt a index) paths); + in + recurse 0 myAttrpaths unfiltered; + + recurseEverywhere = + val: + if lib.isDerivation val || !(lib.isAttrs val) then + val + else + (lib.mapAttrs (_: v: recurseEverywhere v) val) // { recurseForDerivations = true; }; + +in +recurseEverywhere filtered diff --git a/ci/supportedSystems.nix b/ci/supportedSystems.nix new file mode 100644 index 0000000000000..0036f02f6b134 --- /dev/null +++ b/ci/supportedSystems.nix @@ -0,0 +1,7 @@ +[ + "aarch64-linux" + "aarch64-darwin" + #"i686-linux" # !!! + "x86_64-linux" + "x86_64-darwin" +] diff --git a/lib/tests/release.nix b/lib/tests/release.nix index 084fbd94d34c2..5334498d08449 100644 --- a/lib/tests/release.nix +++ b/lib/tests/release.nix @@ -14,19 +14,5 @@ let in pkgs.symlinkJoin { name = "nixpkgs-lib-tests"; - paths = map testWithNix nixVersions ++ - - # - # TEMPORARY MIGRATION MECHANISM - # - # This comment and the expression which follows it should be - # removed as part of resolving this issue: - # - # https://github.com/NixOS/nixpkgs/issues/272591 - # - [(import ../../pkgs/test/release { - inherit pkgs lib nix; - })] - ; - + paths = map testWithNix nixVersions; } diff --git a/pkgs/test/release/default.nix b/pkgs/test/release/default.nix deleted file mode 100644 index 2fdc6d0dc38c5..0000000000000 --- a/pkgs/test/release/default.nix +++ /dev/null @@ -1,52 +0,0 @@ -# Adapted from lib/tests/release.nix -{ pkgs-path ? ../../.. -, pkgs ? import pkgs-path {} -, lib ? pkgs.lib -, nix ? pkgs.nix -}: - -# -# This verifies that release-attrpaths-superset.nix does not encounter -# infinite recursion or non-tryEval-able failures. -# -pkgs.runCommand "all-attrs-eval-under-tryEval" { - nativeBuildInputs = [ - nix - pkgs.gitMinimal - ] ++ lib.optional pkgs.stdenv.hostPlatform.isLinux pkgs.inotify-tools; - strictDeps = true; - - src = with lib.fileset; toSource { - root = pkgs-path; - fileset = unions [ - ../../../default.nix - ../../../doc - ../../../lib - ../../../maintainers - ../../../nixos - ../../../pkgs - ../../../.version - ]; - }; -} -'' - datadir="${nix}/share" - export TEST_ROOT=$(pwd)/test-tmp - export HOME=$(mktemp -d) - export NIX_BUILD_HOOK= - export NIX_CONF_DIR=$TEST_ROOT/etc - export NIX_LOCALSTATE_DIR=$TEST_ROOT/var - export NIX_LOG_DIR=$TEST_ROOT/var/log/nix - export NIX_STATE_DIR=$TEST_ROOT/var/nix - export NIX_STORE_DIR=$TEST_ROOT/store - export PAGER=cat - cacheDir=$TEST_ROOT/binary-cache - - nix-store --init - - echo "Running pkgs/top-level/release-attrpaths-superset.nix" - nix-instantiate --eval --strict --json $src/pkgs/top-level/release-attrpaths-superset.nix -A names > /dev/null - - mkdir $out - echo success > $out/${nix.version} -'' diff --git a/pkgs/top-level/release-haskell.nix b/pkgs/top-level/release-haskell.nix index 7a5a87ccf42e4..7df396f4327fc 100644 --- a/pkgs/top-level/release-haskell.nix +++ b/pkgs/top-level/release-haskell.nix @@ -9,7 +9,7 @@ $ hydra-eval-jobs -I . pkgs/top-level/release-haskell.nix */ -{ supportedSystems ? [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ] }: +{ supportedSystems ? import ../../ci/supportedSystems.nix }: let diff --git a/pkgs/top-level/release-outpaths.nix b/pkgs/top-level/release-outpaths.nix index 5c433fa542e0c..fd54609efa51b 100644 --- a/pkgs/top-level/release-outpaths.nix +++ b/pkgs/top-level/release-outpaths.nix @@ -12,13 +12,7 @@ , attrNamesOnly ? false # Set this to `null` to build for builtins.currentSystem only -, systems ? [ - "aarch64-linux" - "aarch64-darwin" - #"i686-linux" # !!! - "x86_64-linux" - "x86_64-darwin" - ] +, systems ? import ../../ci/supportedSystems.nix }: let lib = import (path + "/lib"); diff --git a/pkgs/top-level/release.nix b/pkgs/top-level/release.nix index 3d9de8660282b..25c28e8ff57aa 100644 --- a/pkgs/top-level/release.nix +++ b/pkgs/top-level/release.nix @@ -12,7 +12,7 @@ , system ? builtins.currentSystem , officialRelease ? false # The platform doubles for which we build Nixpkgs. -, supportedSystems ? [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ] +, supportedSystems ? import ../../ci/supportedSystems.nix # The platform triples for which we build bootstrap tools. , bootstrapConfigs ? [ "aarch64-apple-darwin"