1{
2 callPackage,
3 lib,
4 jq,
5 runCommand,
6 writeText,
7 python3,
8 stdenvNoCC,
9 makeWrapper,
10}:
11let
12 python = python3.withPackages (ps: [
13 ps.numpy
14 ps.pandas
15 ps.scipy
16 ps.tabulate
17 ]);
18
19 cmp-stats = stdenvNoCC.mkDerivation {
20 pname = "cmp-stats";
21 version = lib.trivial.release;
22
23 dontUnpack = true;
24
25 nativeBuildInputs = [ makeWrapper ];
26
27 installPhase = ''
28 runHook preInstall
29
30 mkdir -p $out/share/cmp-stats
31
32 cp ${./cmp-stats.py} "$out/share/cmp-stats/cmp-stats.py"
33
34 makeWrapper ${python.interpreter} "$out/bin/cmp-stats" \
35 --add-flags "$out/share/cmp-stats/cmp-stats.py"
36
37 runHook postInstall
38 '';
39
40 meta = {
41 description = "Performance comparison of Nix evaluation statistics";
42 license = lib.licenses.mit;
43 mainProgram = "cmp-stats";
44 maintainers = with lib.maintainers; [ philiptaron ];
45 };
46 };
47in
48{
49 combinedDir,
50 touchedFilesJson,
51 githubAuthorId,
52 byName ? false,
53}:
54let
55 # Usually we expect a derivation, but when evaluating in multiple separate steps, we pass
56 # nix store paths around. These need to be turned into (fake) derivations again to track
57 # dependencies properly.
58 # We use two steps for evaluation, because we compare results from two different checkouts.
59 # CI additionalls spreads evaluation across multiple workers.
60 combined = if lib.isDerivation combinedDir then combinedDir else lib.toDerivation combinedDir;
61
62 /*
63 Derivation that computes which packages are affected (added, changed or removed) between two revisions of nixpkgs.
64 Note: "platforms" are "x86_64-linux", "aarch64-darwin", ...
65
66 ---
67 Inputs:
68 - beforeDir, afterDir: The evaluation result from before and after the change.
69 They can be obtained by running `nix-build -A ci.eval.full` on both revisions.
70
71 ---
72 Outputs:
73 - changed-paths.json: Various information about the changes:
74 {
75 attrdiff: {
76 added: ["package1"],
77 changed: ["package2", "package3"],
78 removed: ["package4"],
79 },
80 labels: {
81 "10.rebuild-darwin: 1-10": true,
82 "10.rebuild-linux: 1-10": true
83 },
84 rebuildsByKernel: {
85 darwin: ["package1", "package2"],
86 linux: ["package1", "package2", "package3"]
87 },
88 rebuildCountByKernel: {
89 darwin: 2,
90 linux: 3,
91 },
92 rebuildsByPlatform: {
93 aarch64-darwin: ["package1", "package2"],
94 aarch64-linux: ["package1", "package2"],
95 x86_64-linux: ["package1", "package2", "package3"],
96 x86_64-darwin: ["package1"],
97 },
98 }
99 - step-summary.md: A markdown render of the changes
100
101 ---
102 Implementation details:
103
104 Helper functions can be found in ./utils.nix.
105 Two main "types" are important:
106
107 - `packagePlatformPath`: A string of the form "<PACKAGE_PATH>.<PLATFORM>"
108 Example: "python312Packages.numpy.x86_64-linux"
109
110 - `packagePlatformAttr`: An attrs representation of a packagePlatformPath:
111 Example: { name = "python312Packages.numpy"; platform = "x86_64-linux"; }
112 */
113 inherit (import ./utils.nix { inherit lib; })
114 groupByKernel
115 convertToPackagePlatformAttrs
116 groupByPlatform
117 extractPackageNames
118 getLabels
119 ;
120
121 # Attrs
122 # - keys: "added", "changed", "removed" and "rebuilds"
123 # - values: lists of `packagePlatformPath`s
124 diffAttrs = builtins.fromJSON (builtins.readFile "${combined}/combined-diff.json");
125
126 changedPackagePlatformAttrs = convertToPackagePlatformAttrs diffAttrs.changed;
127 rebuildsPackagePlatformAttrs = convertToPackagePlatformAttrs diffAttrs.rebuilds;
128 removedPackagePlatformAttrs = convertToPackagePlatformAttrs diffAttrs.removed;
129
130 changed-paths =
131 let
132 rebuildsByPlatform = groupByPlatform rebuildsPackagePlatformAttrs;
133 rebuildsByKernel = groupByKernel rebuildsPackagePlatformAttrs;
134 rebuildCountByKernel = lib.mapAttrs (
135 kernel: kernelRebuilds: lib.length kernelRebuilds
136 ) rebuildsByKernel;
137 in
138 writeText "changed-paths.json" (
139 builtins.toJSON {
140 attrdiff = lib.mapAttrs (_: extractPackageNames) { inherit (diffAttrs) added changed removed; };
141 inherit
142 rebuildsByPlatform
143 rebuildsByKernel
144 rebuildCountByKernel
145 ;
146 labels =
147 getLabels rebuildCountByKernel
148 # Sets "10.rebuild-*-stdenv" label to whether the "stdenv" attribute was changed.
149 // lib.mapAttrs' (
150 kernel: rebuilds: lib.nameValuePair "10.rebuild-${kernel}-stdenv" (lib.elem "stdenv" rebuilds)
151 ) rebuildsByKernel
152 // {
153 "10.rebuild-nixos-tests" =
154 lib.elem "nixosTests.simple" (extractPackageNames diffAttrs.rebuilds)
155 &&
156 # Only set this label when no other label with indication for staging has been set.
157 # This avoids confusion whether to target staging or batch this with kernel updates.
158 lib.last (lib.sort lib.lessThan (lib.attrValues rebuildCountByKernel)) <= 500;
159 # Set the "11.by: package-maintainer" label to whether all packages directly
160 # changed are maintained by the PR's author.
161 "11.by: package-maintainer" =
162 maintainers ? ${githubAuthorId}
163 && lib.all (lib.flip lib.elem maintainers.${githubAuthorId}) (
164 lib.flatten (lib.attrValues maintainers)
165 );
166 };
167 }
168 );
169
170 maintainers = callPackage ./maintainers.nix { } {
171 changedattrs = lib.attrNames (lib.groupBy (a: a.name) changedPackagePlatformAttrs);
172 changedpathsjson = touchedFilesJson;
173 removedattrs = lib.attrNames (lib.groupBy (a: a.name) removedPackagePlatformAttrs);
174 inherit byName;
175 };
176in
177runCommand "compare"
178 {
179 # Don't depend on -dev outputs to reduce closure size for CI.
180 nativeBuildInputs = map lib.getBin [
181 jq
182 cmp-stats
183 ];
184 maintainers = builtins.toJSON maintainers;
185 passAsFile = [ "maintainers" ];
186 }
187 ''
188 mkdir $out
189
190 cp ${changed-paths} $out/changed-paths.json
191
192 {
193 echo
194 echo "# Packages"
195 echo
196 jq -r -f ${./generate-step-summary.jq} < ${changed-paths}
197 } >> $out/step-summary.md
198
199 if jq -e '(.attrdiff.added | length == 0) and (.attrdiff.removed | length == 0)' "${changed-paths}" > /dev/null; then
200 # Chunks have changed between revisions
201 # We cannot generate a performance comparison
202 {
203 echo
204 echo "# Performance comparison"
205 echo
206 echo "This compares the performance of this branch against its pull request base branch (e.g., 'master')"
207 echo
208 echo "For further help please refer to: [ci/README.md](https://github.com/NixOS/nixpkgs/blob/master/ci/README.md)"
209 echo
210 } >> $out/step-summary.md
211
212 cmp-stats --explain ${combined}/before/stats ${combined}/after/stats >> $out/step-summary.md
213
214 else
215 # Package chunks are the same in both revisions
216 # We can use the to generate a performance comparison
217 {
218 echo
219 echo "# Performance Comparison"
220 echo
221 echo "Performance stats were skipped because the package sets differ between the two revisions."
222 echo
223 echo "For further help please refer to: [ci/README.md](https://github.com/NixOS/nixpkgs/blob/master/ci/README.md)"
224 } >> $out/step-summary.md
225 fi
226
227 cp "$maintainersPath" "$out/maintainers.json"
228 ''