mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-08-27 02:34:53 +00:00
The performance comparison summary previously stated it compared against "its pull request base branch (e.g., 'master')" regardless of the actual base branch. Plumb the base branch classification computed in prepare.js through the eval workflow to the `compare` derivation so the summary names the real base branch (e.g. "staging-25.11"). The local `eval.full` helper defaults to "master", matching its touched-files convention. Assisted-by: claude-code with claude-opus-4-8[1m]-high
310 lines
9.4 KiB
Nix
310 lines
9.4 KiB
Nix
{
|
|
callPackage,
|
|
lib,
|
|
jq,
|
|
runCommand,
|
|
writeText,
|
|
python3,
|
|
stdenvNoCC,
|
|
makeWrapper,
|
|
codeowners,
|
|
}:
|
|
let
|
|
python = python3.withPackages (ps: [
|
|
ps.numpy
|
|
ps.pandas
|
|
ps.scipy
|
|
ps.tabulate
|
|
]);
|
|
|
|
cmp-stats = stdenvNoCC.mkDerivation {
|
|
pname = "cmp-stats";
|
|
version = lib.trivial.release;
|
|
|
|
dontUnpack = true;
|
|
|
|
nativeBuildInputs = [ makeWrapper ];
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
mkdir -p $out/share/cmp-stats
|
|
|
|
cp ${./cmp-stats.py} "$out/share/cmp-stats/cmp-stats.py"
|
|
|
|
makeWrapper ${python.interpreter} "$out/bin/cmp-stats" \
|
|
--add-flags "$out/share/cmp-stats/cmp-stats.py"
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
meta = {
|
|
description = "Performance comparison of Nix evaluation statistics";
|
|
license = lib.licenses.mit;
|
|
mainProgram = "cmp-stats";
|
|
maintainers = with lib.maintainers; [ philiptaron ];
|
|
};
|
|
};
|
|
in
|
|
{
|
|
combinedDir,
|
|
touchedFilesJson,
|
|
baseBranch,
|
|
ownersFile ? ../../OWNERS,
|
|
}:
|
|
let
|
|
# Usually we expect a derivation, but when evaluating in multiple separate steps, we pass
|
|
# nix store paths around. These need to be turned into (fake) derivations again to track
|
|
# dependencies properly.
|
|
# We use two steps for evaluation, because we compare results from two different checkouts.
|
|
# CI additionalls spreads evaluation across multiple workers.
|
|
combined = if lib.isDerivation combinedDir then combinedDir else lib.toDerivation combinedDir;
|
|
|
|
/*
|
|
Derivation that computes which packages are affected (added, changed or removed) between two revisions of nixpkgs.
|
|
Note: "platforms" are "x86_64-linux", "aarch64-darwin", ...
|
|
|
|
---
|
|
Inputs:
|
|
- beforeDir, afterDir: The evaluation result from before and after the change.
|
|
They can be obtained by running `nix-build -A ci.eval.full` on both revisions.
|
|
|
|
---
|
|
Outputs:
|
|
- changed-paths.json: Various information about the changes:
|
|
{
|
|
attrdiff: {
|
|
added: ["package1"],
|
|
changed: ["package2", "package3", "package4"],
|
|
removed: ["package4"],
|
|
},
|
|
attrdiffByKernel: {
|
|
darwin: {
|
|
added: [],
|
|
changed: ["package2", "package4"],
|
|
removed: ["package4"],
|
|
},
|
|
linux: {
|
|
added: ["package1"],
|
|
changed: ["package3", "package4"],
|
|
removed: [],
|
|
},
|
|
},
|
|
attrdiffByPlatform: {
|
|
aarch64-darwin: {
|
|
added: [],
|
|
changed: ["package2"],
|
|
removed: ["package4"],
|
|
},
|
|
aarch64-linux: {
|
|
added: ["package1"],
|
|
changed: ["package3"],
|
|
removed: [],
|
|
},
|
|
x86_64-linux: {
|
|
added: [],
|
|
changed: ["package4"],
|
|
removed: [],
|
|
},
|
|
},
|
|
labels: {
|
|
"10.rebuild-darwin: 1-10": true,
|
|
"10.rebuild-linux: 1-10": true
|
|
},
|
|
rebuildsByKernel: {
|
|
darwin: ["package1", "package2"],
|
|
linux: ["package1", "package2", "package3"]
|
|
},
|
|
rebuildCountByKernel: {
|
|
darwin: 2,
|
|
linux: 3,
|
|
},
|
|
rebuildsByPlatform: {
|
|
aarch64-darwin: ["package1", "package2"],
|
|
aarch64-linux: ["package1", "package2"],
|
|
x86_64-linux: ["package1", "package2", "package3"],
|
|
x86_64-darwin: ["package1"],
|
|
},
|
|
}
|
|
- step-summary.md: A markdown render of the changes
|
|
|
|
---
|
|
Implementation details:
|
|
|
|
Helper functions can be found in ./utils.nix.
|
|
Two main "types" are important:
|
|
|
|
- `packagePlatformPath`: A string of the form "<PACKAGE_PATH>.<PLATFORM>"
|
|
Example: "python312Packages.numpy.x86_64-linux"
|
|
|
|
- `packagePlatformAttr`: An attrs representation of a packagePlatformPath:
|
|
Example: { name = "python312Packages.numpy"; platform = "x86_64-linux"; }
|
|
*/
|
|
inherit (import ./utils.nix { inherit lib; })
|
|
groupByKernel
|
|
convertToPackagePlatformAttrs
|
|
groupAttrdiffByKernel
|
|
groupAttrdiffByPlatform
|
|
groupByPlatform
|
|
extractPackageNames
|
|
getLabels
|
|
;
|
|
|
|
# Attrs
|
|
# - keys: "added", "changed", "removed" and "rebuilds"
|
|
# - values: lists of `packagePlatformPath`s
|
|
diffAttrs = builtins.fromJSON (builtins.readFile "${combined}/combined-diff.json");
|
|
|
|
rebuildsPackagePlatformAttrs = convertToPackagePlatformAttrs diffAttrs.rebuilds;
|
|
|
|
changed-paths =
|
|
let
|
|
attrdiff = lib.mapAttrs (_: extractPackageNames) {
|
|
inherit (diffAttrs) added changed removed;
|
|
};
|
|
attrdiffByPlatform = groupAttrdiffByPlatform {
|
|
inherit (diffAttrs) added changed removed;
|
|
};
|
|
attrdiffByKernel = groupAttrdiffByKernel {
|
|
inherit (diffAttrs) added changed removed;
|
|
};
|
|
rebuildsByPlatform = groupByPlatform rebuildsPackagePlatformAttrs;
|
|
rebuildsByKernel = groupByKernel rebuildsPackagePlatformAttrs;
|
|
rebuildCountByKernel = lib.mapAttrs (
|
|
kernel: kernelRebuilds: lib.length kernelRebuilds
|
|
) rebuildsByKernel;
|
|
rebuildNames = extractPackageNames diffAttrs.rebuilds;
|
|
in
|
|
writeText "changed-paths.json" (
|
|
builtins.toJSON {
|
|
inherit attrdiff attrdiffByKernel attrdiffByPlatform;
|
|
inherit
|
|
rebuildsByPlatform
|
|
rebuildsByKernel
|
|
rebuildCountByKernel
|
|
;
|
|
labels =
|
|
getLabels rebuildCountByKernel
|
|
# Sets "10.rebuild-*-stdenv" label to whether the "stdenv" attribute was changed.
|
|
// lib.mapAttrs' (
|
|
kernel: rebuilds: lib.nameValuePair "10.rebuild-${kernel}-stdenv" (lib.elem "stdenv" rebuilds)
|
|
) rebuildsByKernel
|
|
// {
|
|
"10.rebuild-nixos-tests" =
|
|
lib.elem "nixosTests.simple-container" rebuildNames || lib.elem "nixosTests.simple-vm" rebuildNames;
|
|
};
|
|
}
|
|
);
|
|
|
|
getMaintainers = callPackage ./maintainers.nix { };
|
|
|
|
inherit
|
|
(getMaintainers {
|
|
affectedAttrPaths = map (a: a.packagePath) (
|
|
convertToPackagePlatformAttrs (diffAttrs.changed ++ diffAttrs.removed)
|
|
);
|
|
changedFiles = lib.importJSON touchedFilesJson;
|
|
})
|
|
users
|
|
teams
|
|
packages
|
|
;
|
|
in
|
|
runCommand "compare"
|
|
{
|
|
# Don't depend on -dev outputs to reduce closure size for CI.
|
|
nativeBuildInputs = map lib.getBin [
|
|
jq
|
|
cmp-stats
|
|
codeowners
|
|
];
|
|
users = builtins.toJSON users;
|
|
teams = builtins.toJSON teams;
|
|
packages = builtins.toJSON (lib.map (lib.concatStringsSep ".") packages);
|
|
passAsFile = [
|
|
"users"
|
|
"teams"
|
|
"packages"
|
|
];
|
|
}
|
|
''
|
|
mkdir $out
|
|
|
|
cp ${changed-paths} $out/changed-paths.json
|
|
|
|
{
|
|
echo
|
|
echo "# Packages"
|
|
echo
|
|
jq -r -f ${./generate-step-summary.jq} < ${changed-paths}
|
|
} >> $out/step-summary.md
|
|
|
|
{
|
|
echo
|
|
echo "# Performance comparison"
|
|
echo
|
|
echo "This compares the performance of this branch against the \`${baseBranch}\` branch."
|
|
echo
|
|
} >> $out/step-summary.md
|
|
|
|
# cmp-stats only compares the stats chunks present in both revisions, so the
|
|
# comparison is still produced when packages were added/removed. The paired
|
|
# chunks may cover different attrs in that case, so caveat the figures.
|
|
if ! jq -e '(.attrdiff.added | length == 0) and (.attrdiff.removed | length == 0)' "${changed-paths}" > /dev/null; then
|
|
{
|
|
echo "> [!NOTE]"
|
|
echo "> The package sets differ between the two revisions. This comparison only"
|
|
echo "> covers packages evaluated in both, so treat the figures as approximate."
|
|
echo
|
|
} >> $out/step-summary.md
|
|
fi
|
|
|
|
{
|
|
echo "For further help please refer to: [ci/README.md](https://github.com/NixOS/nixpkgs/blob/master/ci/README.md)"
|
|
echo
|
|
} >> $out/step-summary.md
|
|
|
|
cmp-stats --explain ${combined}/before/stats ${combined}/after/stats >> $out/step-summary.md
|
|
|
|
jq -r '.[]' "${touchedFilesJson}" > ./touched-files
|
|
readarray -t touchedFiles < ./touched-files
|
|
echo "This PR touches ''${#touchedFiles[@]} files"
|
|
|
|
# TODO: Move ci/OWNERS to Nix and produce owners.json instead of owners.txt.
|
|
touch "$out/owners.txt"
|
|
for file in "''${touchedFiles[@]}"; do
|
|
result=$(codeowners --file "${ownersFile}" "$file")
|
|
|
|
# Remove the file prefix and trim the surrounding spaces
|
|
read -r owners <<< "''${result#"$file"}"
|
|
if [[ "$owners" == "(unowned)" ]]; then
|
|
echo "File $file is unowned"
|
|
continue
|
|
fi
|
|
echo "File $file is owned by $owners"
|
|
|
|
# Split up multiple owners, separated by arbitrary amounts of spaces
|
|
IFS=" " read -r -a entries <<< "$owners"
|
|
|
|
for entry in "''${entries[@]}"; do
|
|
# GitHub technically also supports Emails as code owners,
|
|
# but we can't easily support that, so let's not
|
|
if [[ ! "$entry" =~ @(.*) ]]; then
|
|
echo -e "\e[33mCodeowner \"$entry\" for file $file is not valid: Must start with \"@\"\e[0m"
|
|
# Don't fail, because the PR for which this script runs can't fix it,
|
|
# it has to be fixed in the base branch
|
|
continue
|
|
fi
|
|
# The first regex match is everything after the @
|
|
entry=''${BASH_REMATCH[1]}
|
|
|
|
echo "$entry" >> "$out/owners.txt"
|
|
done
|
|
|
|
done
|
|
|
|
cp "$usersPath" "$out/maintainers.json"
|
|
cp "$teamsPath" "$out/teams.json"
|
|
cp "$packagesPath" "$out/packages.json"
|
|
''
|