1#!/usr/bin/env bash
2set -e
3
4# --print: avoid dependency on environment
5optPrint=
6if [ "$1" == "--print" ]; then
7 optPrint=true
8 shift
9fi
10
11if [ "$#" != 1 ] && [ "$#" != 2 ]; then
12 cat <<-EOF
13 Usage: $0 [--print] commit-spec [commit-spec]
14 You need to be in a git-controlled nixpkgs tree.
15 The current state of the tree will be used if the second commit is missing.
16 EOF
17 exit 1
18fi
19
20# A slightly hacky way to get the config.
21parallel="$(echo 'config.rebuild-amount.parallel or false' | nix-repl . 2>/dev/null \
22 | grep -v '^\(nix-repl.*\)\?$' | tail -n 1 || true)"
23
24echo "Estimating rebuild amount by counting changed Hydra jobs."
25
26toRemove=()
27
28cleanup() {
29 rm -rf "${toRemove[@]}"
30}
31trap cleanup EXIT SIGINT SIGQUIT ERR
32
33MKTEMP='mktemp --tmpdir nix-rebuild-amount-XXXXXXXX'
34
35nixexpr() {
36 cat <<-EONIX
37 let
38 lib = import $1/lib;
39 hydraJobs = import $1/pkgs/top-level/release.nix
40 # Compromise: accuracy vs. resources needed for evaluation.
41 { supportedSystems = cfg.systems or [ "x86_64-linux" "x86_64-darwin" ]; };
42 cfg = (import $1 {}).config.rebuild-amount or {};
43
44 recurseIntoAttrs = attrs: attrs // { recurseForDerivations = true; };
45
46 # hydraJobs leaves recurseForDerivations as empty attrmaps;
47 # that would break nix-env and we also need to recurse everywhere.
48 tweak = lib.mapAttrs
49 (name: val:
50 if name == "recurseForDerivations" then true
51 else if lib.isAttrs val && val.type or null != "derivation"
52 then recurseIntoAttrs (tweak val)
53 else val
54 );
55
56 # Some of these contain explicit references to platform(s) we want to avoid;
57 # some even (transitively) depend on ~/.nixpkgs/config.nix (!)
58 blacklist = [
59 "tarball" "metrics" "manual"
60 "darwin-tested" "unstable" "stdenvBootstrapTools"
61 "moduleSystem" "lib-tests" # these just confuse the output
62 ];
63
64 in
65 tweak (builtins.removeAttrs hydraJobs blacklist)
66 EONIX
67}
68
69# Output packages in tree $2 that weren't in $1.
70# Changing the output hash or name is taken as a change.
71# Extra nix-env parameters can be in $3
72newPkgs() {
73 # We use files instead of pipes, as running multiple nix-env processes
74 # could eat too much memory for a standard 4GiB machine.
75 local -a list
76 for i in 1 2; do
77 local l="$($MKTEMP)"
78 list[$i]="$l"
79 toRemove+=("$l")
80
81 local expr="$($MKTEMP)"
82 toRemove+=("$expr")
83 nixexpr "${!i}" > "$expr"
84
85 nix-env -f "$expr" -qaP --no-name --out-path --show-trace $3 \
86 | sort > "${list[$i]}" &
87
88 if [ "$parallel" != "true" ]; then
89 wait
90 fi
91 done
92
93 wait
94 comm -13 "${list[@]}"
95}
96
97# Prepare nixpkgs trees.
98declare -a tree
99for i in 1 2; do
100 if [ -n "${!i}" ]; then # use the given commit
101 dir="$($MKTEMP -d)"
102 tree[$i]="$dir"
103 toRemove+=("$dir")
104
105 git clone --shared --no-checkout --quiet . "${tree[$i]}"
106 (cd "${tree[$i]}" && git checkout --quiet "${!i}")
107 else #use the current tree
108 tree[$i]="$(pwd)"
109 fi
110done
111
112newlist="$($MKTEMP)"
113toRemove+=("$newlist")
114# Notes:
115# - the evaluation is done on x86_64-linux, like on Hydra.
116# - using $newlist file so that newPkgs() isn't in a sub-shell (because of toRemove)
117newPkgs "${tree[1]}" "${tree[2]}" '--argstr system "x86_64-linux"' > "$newlist"
118
119# Hacky: keep only the last word of each attribute path and sort.
120sed -n 's/\([^. ]*\.\)*\([^. ]*\) .*$/\2/p' < "$newlist" \
121 | sort | uniq -c
122
123if [ -n "$optPrint" ]; then
124 echo
125 cat "$newlist"
126fi
127