at master 9.9 kB view raw
1# Evaluates all the accessible paths in nixpkgs. 2# *This only builds on Linux* since it requires the Linux sandbox isolation to 3# be able to write in various places while evaluating inside the sandbox. 4# 5# This file is used by nixpkgs CI (see .github/workflows/eval.yml) as well as 6# being used directly as an entry point in Lix's CI (in `flake.nix` in the Lix 7# repo). 8# 9# If you know you are doing a breaking API change, please ping the nixpkgs CI 10# maintainers and the Lix maintainers (`nix eval -f . lib.teams.lix`). 11{ 12 callPackage, 13 lib, 14 runCommand, 15 writeShellScript, 16 symlinkJoin, 17 busybox, 18 jq, 19 nix, 20}: 21 22let 23 nixpkgs = 24 with lib.fileset; 25 toSource { 26 root = ../..; 27 fileset = unions ( 28 map (lib.path.append ../..) [ 29 ".version" 30 "ci/supportedSystems.json" 31 "ci/eval/attrpaths.nix" 32 "ci/eval/chunk.nix" 33 "ci/eval/outpaths.nix" 34 "default.nix" 35 "doc" 36 "lib" 37 "maintainers" 38 "modules" 39 "nixos" 40 "pkgs" 41 ] 42 ); 43 }; 44 45 supportedSystems = builtins.fromJSON (builtins.readFile ../supportedSystems.json); 46 47 attrpathsSuperset = 48 { 49 evalSystem, 50 }: 51 runCommand "attrpaths-superset.json" 52 { 53 src = nixpkgs; 54 # Don't depend on -dev outputs to reduce closure size for CI. 55 nativeBuildInputs = map lib.getBin [ 56 busybox 57 nix 58 ]; 59 } 60 '' 61 export NIX_STATE_DIR=$(mktemp -d) 62 mkdir $out 63 export GC_INITIAL_HEAP_SIZE=4g 64 command time -f "Attribute eval done [%MKB max resident, %Es elapsed] %C" \ 65 nix-instantiate --eval --strict --json --show-trace \ 66 "$src/ci/eval/attrpaths.nix" \ 67 -A paths \ 68 -I "$src" \ 69 --option restrict-eval true \ 70 --option allow-import-from-derivation false \ 71 --option eval-system "${evalSystem}" > $out/paths.json 72 ''; 73 74 singleSystem = 75 { 76 # The system to evaluate. 77 # Note that this is intentionally not called `system`, 78 # because `--argstr system` would only be passed to the ci/default.nix file! 79 evalSystem ? builtins.currentSystem, 80 # The path to the `paths.json` file from `attrpathsSuperset` 81 attrpathFile ? "${attrpathsSuperset { inherit evalSystem; }}/paths.json", 82 # The number of attributes per chunk, see ./README.md for more info. 83 chunkSize ? 5000, 84 85 # Don't try to eval packages marked as broken. 86 includeBroken ? false, 87 # Whether to just evaluate a single chunk for quick testing 88 quickTest ? false, 89 }: 90 let 91 singleChunk = writeShellScript "single-chunk" '' 92 set -euo pipefail 93 chunkSize=$1 94 myChunk=$2 95 system=$3 96 outputDir=$4 97 98 export NIX_SHOW_STATS=1 99 export NIX_SHOW_STATS_PATH="$outputDir/stats/$myChunk" 100 echo "Chunk $myChunk on $system start" 101 set +e 102 command time -o "$outputDir/timestats/$myChunk" \ 103 -f "Chunk $myChunk on $system done [%MKB max resident, %Es elapsed] %C" \ 104 nix-env -f "${nixpkgs}/ci/eval/chunk.nix" \ 105 --eval-system "$system" \ 106 --option restrict-eval true \ 107 --option allow-import-from-derivation false \ 108 --query --available \ 109 --out-path --json \ 110 --show-trace \ 111 --arg chunkSize "$chunkSize" \ 112 --arg myChunk "$myChunk" \ 113 --arg attrpathFile "${attrpathFile}" \ 114 --arg systems "[ \"$system\" ]" \ 115 --arg includeBroken ${lib.boolToString includeBroken} \ 116 -I ${nixpkgs} \ 117 -I ${attrpathFile} \ 118 > "$outputDir/result/$myChunk" \ 119 2> "$outputDir/stderr/$myChunk" 120 exitCode=$? 121 set -e 122 cat "$outputDir/stderr/$myChunk" 123 cat "$outputDir/timestats/$myChunk" 124 if (( exitCode != 0 )); then 125 echo "Evaluation failed with exit code $exitCode" 126 # This immediately halts all xargs processes 127 kill $PPID 128 elif [[ -s "$outputDir/stderr/$myChunk" ]]; then 129 echo "Nixpkgs on $system evaluated with warnings, aborting" 130 kill $PPID 131 fi 132 ''; 133 in 134 runCommand "nixpkgs-eval-${evalSystem}" 135 { 136 # Don't depend on -dev outputs to reduce closure size for CI. 137 nativeBuildInputs = map lib.getBin [ 138 busybox 139 jq 140 nix 141 ]; 142 env = { 143 inherit evalSystem chunkSize; 144 }; 145 __structuredAttrs = true; 146 unsafeDiscardReferences.out = true; 147 } 148 '' 149 export NIX_STATE_DIR=$(mktemp -d) 150 nix-store --init 151 152 echo "System: $evalSystem" 153 cores=$NIX_BUILD_CORES 154 echo "Cores: $cores" 155 attrCount=$(jq length "${attrpathFile}") 156 echo "Attribute count: $attrCount" 157 echo "Chunk size: $chunkSize" 158 # Same as `attrCount / chunkSize` but rounded up 159 chunkCount=$(( (attrCount - 1) / chunkSize + 1 )) 160 echo "Chunk count: $chunkCount" 161 162 mkdir -p $out/${evalSystem} 163 164 # Record and print stats on free memory and swap in the background 165 ( 166 while true; do 167 availMemory=$(free -m | grep Mem | awk '{print $7}') 168 freeSwap=$(free -m | grep Swap | awk '{print $4}') 169 echo "Available memory: $(( availMemory )) MiB, free swap: $(( freeSwap )) MiB" 170 171 if [[ ! -f "$out/${evalSystem}/min-avail-memory" ]] || (( availMemory < $(<$out/${evalSystem}/min-avail-memory) )); then 172 echo "$availMemory" > $out/${evalSystem}/min-avail-memory 173 fi 174 if [[ ! -f $out/${evalSystem}/min-free-swap ]] || (( freeSwap < $(<$out/${evalSystem}/min-free-swap) )); then 175 echo "$freeSwap" > $out/${evalSystem}/min-free-swap 176 fi 177 sleep 4 178 done 179 ) & 180 181 seq_end=$(( chunkCount - 1 )) 182 183 ${lib.optionalString quickTest '' 184 seq_end=0 185 ''} 186 187 chunkOutputDir=$(mktemp -d) 188 mkdir "$chunkOutputDir"/{result,stats,timestats,stderr} 189 190 seq -w 0 "$seq_end" | 191 command time -f "%e" -o "$out/${evalSystem}/total-time" \ 192 xargs -I{} -P"$cores" \ 193 ${singleChunk} "$chunkSize" {} "$evalSystem" "$chunkOutputDir" 194 195 cp -r "$chunkOutputDir"/stats $out/${evalSystem}/stats-by-chunk 196 197 if (( chunkSize * chunkCount != attrCount )); then 198 # A final incomplete chunk would mess up the stats, don't include it 199 rm "$chunkOutputDir"/stats/"$seq_end" 200 fi 201 202 cat "$chunkOutputDir"/result/* | jq -s 'add | map_values(.outputs)' > $out/${evalSystem}/paths.json 203 ''; 204 205 diff = callPackage ./diff.nix { }; 206 207 combine = 208 { 209 diffDir, 210 }: 211 runCommand "combined-eval" 212 { 213 # Don't depend on -dev outputs to reduce closure size for CI. 214 nativeBuildInputs = map lib.getBin [ 215 jq 216 ]; 217 } 218 '' 219 mkdir -p $out 220 221 # Combine output paths from all systems 222 cat ${diffDir}/*/diff.json | jq -s ' 223 reduce .[] as $item ({}; { 224 added: (.added + $item.added), 225 changed: (.changed + $item.changed), 226 removed: (.removed + $item.removed), 227 rebuilds: (.rebuilds + $item.rebuilds) 228 }) 229 ' > $out/combined-diff.json 230 231 mkdir -p $out/before/stats 232 for d in ${diffDir}/before/*; do 233 cp -r "$d"/stats-by-chunk $out/before/stats/$(basename "$d") 234 done 235 236 mkdir -p $out/after/stats 237 for d in ${diffDir}/after/*; do 238 cp -r "$d"/stats-by-chunk $out/after/stats/$(basename "$d") 239 done 240 ''; 241 242 compare = callPackage ./compare { }; 243 244 baseline = 245 { 246 # Whether to evaluate on a specific set of systems, by default all are evaluated 247 evalSystems ? if quickTest then [ "x86_64-linux" ] else supportedSystems, 248 # The number of attributes per chunk, see ./README.md for more info. 249 chunkSize ? 5000, 250 quickTest ? false, 251 }: 252 symlinkJoin { 253 name = "nixpkgs-eval-baseline"; 254 paths = map ( 255 evalSystem: 256 singleSystem { 257 inherit quickTest evalSystem chunkSize; 258 } 259 ) evalSystems; 260 }; 261 262 full = 263 { 264 # Whether to evaluate on a specific set of systems, by default all are evaluated 265 evalSystems ? if quickTest then [ "x86_64-linux" ] else supportedSystems, 266 # The number of attributes per chunk, see ./README.md for more info. 267 chunkSize ? 5000, 268 quickTest ? false, 269 baseline, 270 # Which maintainer should be considered the author? 271 # Defaults to nixpkgs-ci which is not a maintainer and skips the check. 272 githubAuthorId ? "nixpkgs-ci", 273 # What files have been touched? Defaults to none; use the expression below to calculate it. 274 # ``` 275 # git diff --name-only --merge-base master HEAD \ 276 # | jq --raw-input --slurp 'split("\n")[:-1]' > touched-files.json 277 # ``` 278 touchedFilesJson ? builtins.toFile "touched-files.json" "[ ]", 279 }: 280 let 281 diffs = symlinkJoin { 282 name = "nixpkgs-eval-diffs"; 283 paths = map ( 284 evalSystem: 285 diff { 286 inherit evalSystem; 287 beforeDir = baseline; 288 afterDir = singleSystem { 289 inherit quickTest evalSystem chunkSize; 290 }; 291 } 292 ) evalSystems; 293 }; 294 comparisonReport = compare { 295 combinedDir = combine { diffDir = diffs; }; 296 inherit touchedFilesJson githubAuthorId; 297 }; 298 in 299 comparisonReport; 300 301in 302{ 303 inherit 304 attrpathsSuperset 305 singleSystem 306 diff 307 combine 308 compare 309 # The above three are used by separate VMs in a GitHub workflow, 310 # while the below are intended for testing on a single local machine 311 baseline 312 full 313 ; 314}