summary refs log tree commit diff
path: root/maintainers/scripts/rebuild-amount.sh
blob: bedd352db5f3889d550d9bde16ccd8f9cc0cde39 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env bash
set -e

# --print: avoid dependency on environment
optPrint=
if [ "$1" == "--print" ]; then
    optPrint=true
    shift
fi

if [ "$#" != 1 ] && [ "$#" != 2 ]; then
    cat <<EOF
    Usage: $0 [--print] from-commit-spec [to-commit-spec]
        You need to be in a git-controlled nixpkgs tree.
        The current state of the tree will be used if the second commit is missing.

        Examples:
          effect of latest commit:
              $ $0 HEAD^
              $ $0 --print HEAD^
          effect of the whole patch series for 'staging' branch:
              $ $0 origin/staging staging
EOF
    exit 1
fi

# A slightly hacky way to get the config.
parallel="$(echo 'config.rebuild-amount.parallel or false' | nix-repl . 2>/dev/null \
            | grep -v '^\(nix-repl.*\)\?$' | tail -n 1 || true)"

echo "Estimating rebuild amount by counting changed Hydra jobs (parallel=${parallel:-unset})."

toRemove=()

cleanup() {
    rm -rf "${toRemove[@]}"
}
trap cleanup EXIT SIGINT SIGQUIT ERR

MKTEMP='mktemp --tmpdir nix-rebuild-amount-XXXXXXXX'

nixexpr() {
    cat <<EONIX
        let
          lib = import $1/lib;
          hydraJobs = import $1/pkgs/top-level/release.nix
            # Compromise: accuracy vs. resources needed for evaluation.
            { supportedSystems = cfg.systems or [ "x86_64-linux" "x86_64-darwin" ]; };
          cfg = (import $1 {}).config.rebuild-amount or {};

          recurseIntoAttrs = attrs: attrs // { recurseForDerivations = true; };

          # hydraJobs leaves recurseForDerivations as empty attrmaps;
          # that would break nix-env and we also need to recurse everywhere.
          tweak = lib.mapAttrs
            (name: val:
              if name == "recurseForDerivations" then true
              else if lib.isAttrs val && val.type or null != "derivation"
                      then recurseIntoAttrs (tweak val)
              else val
            );

          # Some of these contain explicit references to platform(s) we want to avoid;
          # some even (transitively) depend on ~/.nixpkgs/config.nix (!)
          blacklist = [
            "tarball" "metrics" "manual"
            "darwin-tested" "unstable" "stdenvBootstrapTools"
            "moduleSystem" "lib-tests" # these just confuse the output
          ];

        in
          tweak (builtins.removeAttrs hydraJobs blacklist)
EONIX
}

# Output packages in tree $2 that weren't in $1.
# Changing the output hash or name is taken as a change.
# Extra nix-env parameters can be in $3
newPkgs() {
    # We use files instead of pipes, as running multiple nix-env processes
    # could eat too much memory for a standard 4GiB machine.
    local -a list
    for i in 1 2; do
        local l="$($MKTEMP)"
        list[$i]="$l"
        toRemove+=("$l")

        local expr="$($MKTEMP)"
        toRemove+=("$expr")
        nixexpr "${!i}" > "$expr"

        nix-env -f "$expr" -qaP --no-name --out-path --show-trace $3 \
            | sort > "${list[$i]}" &

        if [ "$parallel" != "true" ]; then
            wait
        fi
    done

    wait
    comm -13 "${list[@]}"
}

# Prepare nixpkgs trees.
declare -a tree
for i in 1 2; do
    if [ -n "${!i}" ]; then # use the given commit
        dir="$($MKTEMP -d)"
        tree[$i]="$dir"
        toRemove+=("$dir")

        git clone --shared --no-checkout --quiet . "${tree[$i]}"
        (cd "${tree[$i]}" && git checkout --quiet "${!i}")
    else #use the current tree
        tree[$i]="$(pwd)"
    fi
done

newlist="$($MKTEMP)"
toRemove+=("$newlist")
# Notes:
#    - the evaluation is done on x86_64-linux, like on Hydra.
#    - using $newlist file so that newPkgs() isn't in a sub-shell (because of toRemove)
newPkgs "${tree[1]}" "${tree[2]}" '--argstr system "x86_64-linux"' > "$newlist"

# Hacky: keep only the last word of each attribute path and sort.
sed -n 's/\([^. ]*\.\)*\([^. ]*\) .*$/\2/p' < "$newlist" \
    | sort | uniq -c

if [ -n "$optPrint" ]; then
    echo
    cat "$newlist"
fi