nixpkgs/ci/eval/default.nix

254 lines
7.2 KiB
Nix
Raw Normal View History

{
callPackage,
lib,
runCommand,
writeShellScript,
2024-12-07 16:31:33 +01:00
writeText,
symlinkJoin,
time,
procps,
2024-11-21 09:08:25 +01:00
nixVersions,
jq,
python3,
}:
let
nixpkgs =
with lib.fileset;
toSource {
root = ../..;
fileset = unions (
map (lib.path.append ../..) [
"default.nix"
"doc"
"lib"
"maintainers"
"nixos"
"pkgs"
".version"
"ci/supportedSystems.json"
]
);
};
2025-05-06 11:04:47 +02:00
nix = nixVersions.latest;
2024-11-21 09:08:25 +01:00
supportedSystems = builtins.fromJSON (builtins.readFile ../supportedSystems.json);
attrpathsSuperset =
{
evalSystem,
}:
runCommand "attrpaths-superset.json"
{
src = nixpkgs;
nativeBuildInputs = [
nix
time
];
}
''
export NIX_STATE_DIR=$(mktemp -d)
mkdir $out
export GC_INITIAL_HEAP_SIZE=4g
command time -f "Attribute eval done [%MKB max resident, %Es elapsed] %C" \
nix-instantiate --eval --strict --json --show-trace \
2024-11-29 21:31:24 +01:00
"$src/pkgs/top-level/release-attrpaths-superset.nix" \
-A paths \
-I "$src" \
--option restrict-eval true \
--option allow-import-from-derivation false \
--option eval-system "${evalSystem}" \
2024-11-29 21:31:24 +01:00
--arg enableWarnings false > $out/paths.json
'';
singleSystem =
{
# The system to evaluate.
# Note that this is intentionally not called `system`,
# because `--argstr system` would only be passed to the ci/default.nix file!
evalSystem,
# The path to the `paths.json` file from `attrpathsSuperset`
attrpathFile ? "${attrpathsSuperset { inherit evalSystem; }}/paths.json",
# The number of attributes per chunk, see ./README.md for more info.
chunkSize,
checkMeta ? true,
includeBroken ? true,
# Whether to just evaluate a single chunk for quick testing
quickTest ? false,
}:
let
singleChunk = writeShellScript "single-chunk" ''
set -euo pipefail
chunkSize=$1
myChunk=$2
system=$3
outputDir=$4
export NIX_SHOW_STATS=1
export NIX_SHOW_STATS_PATH="$outputDir/stats/$myChunk"
echo "Chunk $myChunk on $system start"
set +e
command time -o "$outputDir/timestats/$myChunk" \
-f "Chunk $myChunk on $system done [%MKB max resident, %Es elapsed] %C" \
nix-env -f "${nixpkgs}/pkgs/top-level/release-attrpaths-parallel.nix" \
ci/eval: make eval for non-native platforms less incorrect We commonly use platform-dependent conditional patterns like `lib.meta.availableOn stdenv.hostPlatform` and `stdenv.hostPlatform.isLinux` to enable different features in a given derivation or to evaluate completely different derivations based on the platform. For example, source builds of a given derivation may only be available on linux but not on darwin. The use of such conditionals allow us to fall back to patched binaries on darwin instead. In `chromedriver` (pkgs/development/tools/selenium/chromedriver/default.nix), we use ~~~nix if lib.meta.availableOn stdenv.hostPlatform chromium then callPackage ./source.nix { } else callPackage ./binary.nix { } ~~~ To provide some context, `chromedriver` source builds are based on `chromium.mkDerivation` and `chromium` is limited to `lib.platforms.linux`. Based on the same `chromium.mkDerivation`, we also do source builds for `electron` (pkgs/top-level/all-packages.nix): ~~~nix electron_33 = if lib.meta.availableOn stdenv.hostPlatform electron-source.electron_33 then electron-source.electron_33 else electron_33-bin; electron_34 = electron_34-bin; electron = electron_34; ~~~ And finally, the top-level `jdk` (Java) attribute has a lot of indirection, but eventually also boils down to `stdenv.hostPlatform.isLinux` for source builds and binaries for x86_64-darwin and aarch64-darwin. A surprising amount of electron and jdk consumers use variations of `meta.platforms = electron.meta.platforms` in their own meta block. Due to internal implementation details, the conditionals in those top-level attributes like `chromedriver`, `electron` and `jdk` are evaluated based on the value from `builtins.currentSystem` and not the system passed to `import <nixpkgs> { }`. This then causes `chromedriver`, `electron`, `jdk` and all dependents that inherit those `meta.platforms` to appear only available on linux despite also being available on darwin. Hydra is affected similarly, but it's a lot more nuanced and in practice not actually *that* bad. The addition of `--eval-system` ensures that `builtins.currentSystem` matches the requested platform. As a bonus, this also fixes the store paths of an impure test that should probably be made pure: ~~~diff @@ -885069,13 +886119,13 @@ "out": "/nix/store/lb2500hc69czy4sfga9mbh2k679cr1rp-test-compressDrv" }, "tests.config.allowPkgsInPermittedInsecurePackages.aarch64-darwin": { - "out": "/nix/store/0l5h8svrpzwymq35mnpvx82gyc7nf8s4-hello-2.12.1" + "out": "/nix/store/v1zjb688mp4y2132b6chii43d5kkxnpa-hello-2.12.1" }, "tests.config.allowPkgsInPermittedInsecurePackages.aarch64-linux": { - "out": "/nix/store/0l5h8svrpzwymq35mnpvx82gyc7nf8s4-hello-2.12.1" + "out": "/nix/store/hb21z2zdk03dwygsw5lvpa8zc3fbr500-hello-2.12.1" }, "tests.config.allowPkgsInPermittedInsecurePackages.x86_64-darwin": { - "out": "/nix/store/0l5h8svrpzwymq35mnpvx82gyc7nf8s4-hello-2.12.1" + "out": "/nix/store/gljdqsf0mxv1j8zb04phx9ws09pp7z3l-hello-2.12.1" }, "tests.config.allowPkgsInPermittedInsecurePackages.x86_64-linux": { "out": "/nix/store/0l5h8svrpzwymq35mnpvx82gyc7nf8s4-hello-2.12.1" ~~~ Diff stats between two full evals based on 75c8548d8118c31509b89ffd7df6c322b94017dd with and without this fix on x86_64-linux: ~~~bash # git diff --no-index --stat /nix/store/659l3xp78255wx7abbahggsnrlj3a1la-combined-result/outpaths.json /nix/store/4fhlq4g5qa65cxbibskq9pma40zigrx7-combined-result/outpaths.json /nix/store/{659l3xp78255wx7abbahggsnrlj3a1la-combined-result => 4fhlq4g5qa65cxbibskq9pma40zigrx7-combined-result}/outpaths.json | 1416 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1405 insertions(+), 11 deletions(-) ~~~ The full diff is available as a gist at <https://gist.github.com/emilylange/d40c50031fc332bbcca133ad56d224f6>. When we added `electron_34` only as binary instead of the usual source on linux with binary fallback in cfed9a19cbc98d12c0167f69238673f4ed89f798 and made the unversioned `electron` top-level point to the newly added `electron_34` instead of `electron_33`, the GitHub workflow suddenly reported 20 new packages. Of those 20 reported packages, 17 where false-positives caused by dropping the wrongly evaluated conditional.
2025-02-02 21:04:08 +01:00
--eval-system "$system" \
2024-11-29 21:31:24 +01:00
--option restrict-eval true \
--option allow-import-from-derivation false \
--query --available \
--out-path --json \
--show-trace \
--arg chunkSize "$chunkSize" \
--arg myChunk "$myChunk" \
--arg attrpathFile "${attrpathFile}" \
--arg systems "[ \"$system\" ]" \
--arg checkMeta ${lib.boolToString checkMeta} \
--arg includeBroken ${lib.boolToString includeBroken} \
2024-11-29 21:31:24 +01:00
-I ${nixpkgs} \
-I ${attrpathFile} \
> "$outputDir/result/$myChunk" \
2> "$outputDir/stderr/$myChunk"
exitCode=$?
set -e
cat "$outputDir/stderr/$myChunk"
cat "$outputDir/timestats/$myChunk"
if (( exitCode != 0 )); then
echo "Evaluation failed with exit code $exitCode"
# This immediately halts all xargs processes
kill $PPID
elif [[ -s "$outputDir/stderr/$myChunk" ]]; then
echo "Nixpkgs on $system evaluated with warnings, aborting"
kill $PPID
fi
'';
in
runCommand "nixpkgs-eval-${evalSystem}"
{
nativeBuildInputs = [
nix
time
procps
jq
];
env = {
inherit evalSystem chunkSize;
};
}
''
export NIX_STATE_DIR=$(mktemp -d)
nix-store --init
echo "System: $evalSystem"
cores=$NIX_BUILD_CORES
echo "Cores: $cores"
attrCount=$(jq length "${attrpathFile}")
echo "Attribute count: $attrCount"
echo "Chunk size: $chunkSize"
# Same as `attrCount / chunkSize` but rounded up
chunkCount=$(( (attrCount - 1) / chunkSize + 1 ))
echo "Chunk count: $chunkCount"
mkdir -p $out/${evalSystem}
# Record and print stats on free memory and swap in the background
(
while true; do
availMemory=$(free -b | grep Mem | awk '{print $7}')
freeSwap=$(free -b | grep Swap | awk '{print $4}')
echo "Available memory: $(( availMemory / 1024 / 1024 )) MiB, free swap: $(( freeSwap / 1024 / 1024 )) MiB"
if [[ ! -f "$out/${evalSystem}/min-avail-memory" ]] || (( availMemory < $(<$out/${evalSystem}/min-avail-memory) )); then
echo "$availMemory" > $out/${evalSystem}/min-avail-memory
fi
if [[ ! -f $out/${evalSystem}/min-free-swap ]] || (( availMemory < $(<$out/${evalSystem}/min-free-swap) )); then
echo "$freeSwap" > $out/${evalSystem}/min-free-swap
fi
sleep 4
done
) &
seq_end=$(( chunkCount - 1 ))
${lib.optionalString quickTest ''
seq_end=0
''}
chunkOutputDir=$(mktemp -d)
mkdir "$chunkOutputDir"/{result,stats,timestats,stderr}
seq -w 0 "$seq_end" |
command time -f "%e" -o "$out/${evalSystem}/total-time" \
xargs -I{} -P"$cores" \
${singleChunk} "$chunkSize" {} "$evalSystem" "$chunkOutputDir"
cp -r "$chunkOutputDir"/stats $out/${evalSystem}/stats-by-chunk
2025-04-18 13:22:09 +02:00
if (( chunkSize * chunkCount != attrCount )); then
# A final incomplete chunk would mess up the stats, don't include it
rm "$chunkOutputDir"/stats/"$seq_end"
fi
cat "$chunkOutputDir"/result/* | jq -s 'add | map_values(.outputs)' > $out/${evalSystem}/paths.json
'';
combine =
{
evalDir,
}:
runCommand "combined-eval"
{
nativeBuildInputs = [
jq
];
}
''
mkdir -p $out
# Combine output paths from all systems
cat ${evalDir}/*/paths.json | jq -s add > $out/outpaths.json
2025-04-18 13:22:09 +02:00
mkdir -p $out/stats
for d in ${evalDir}/*; do
2025-04-18 13:22:09 +02:00
cp -r "$d"/stats-by-chunk $out/stats/$(basename "$d")
done
'';
compare = callPackage ./compare { };
full =
{
# Whether to evaluate on a specific set of systems, by default all are evaluated
evalSystems ? if quickTest then [ "x86_64-linux" ] else supportedSystems,
# The number of attributes per chunk, see ./README.md for more info.
chunkSize,
quickTest ? false,
}:
let
evals = symlinkJoin {
name = "evals";
paths = map (
evalSystem:
singleSystem {
inherit quickTest evalSystem chunkSize;
}
) evalSystems;
};
in
combine {
evalDir = evals;
};
in
{
inherit
attrpathsSuperset
singleSystem
combine
compare
# The above three are used by separate VMs in a GitHub workflow,
# while the below is intended for testing on a single local machine
full
;
}