Merge pull request #329823 from ExpidusOS/fix/pkgsllvm/elfutils
[NixPkgs.git] / maintainers / scripts / nix-diff.sh
blob0c65e29cf4351210fc07e35e147a503ee223d053
1 #!/usr/bin/env nix-shell
2 #! nix-shell -i bash -p coreutils gnugrep gnused
4 ################################################################################
5 # nix-diff.sh #
6 ################################################################################
7 # This script "diffs" Nix profile generations. #
8 # #
9 # Example: #
10 ################################################################################
11 # > nix-diff.sh 90 92 #
12 # + gnumake-4.2.1 #
13 # + gnumake-4.2.1-doc #
14 # - htmldoc-1.8.29 #
15 ################################################################################
16 # The example shows that as of generation 92 and since generation 90, #
17 # gnumake-4.2.1 and gnumake-4.2.1-doc have been installed, while #
18 # htmldoc-1.8.29 has been removed. #
19 # #
20 # The example above shows the default, minimal output mode of this script. #
21 # For more features, run `nix-diff.sh -h` for usage instructions. #
22 ################################################################################
24 usage() {
25 cat <<EOF
26 usage: nix-diff.sh [-h | [-p profile | -s] [-q] [-l] [range]]
27 -h: print this message before exiting
28 -q: list the derivations installed in the parent generation
29 -l: diff every available intermediate generation between parent and
30 child
31 -p profile: specify the Nix profile to use
32 * defaults to ~/.nix-profile
33 -s: use the system profile
34 * equivalent to: -p /nix/var/nix/profiles/system
35 profile: * should be something like /nix/var/nix/profiles/default, not a
36 generation link like /nix/var/nix/profiles/default-2-link
37 range: the range of generations to diff
38 * the following patterns are allowed, where A, B, and N are positive
39 integers, and G is the currently active generation:
40 A..B => diffs from generation A to generation B
41 ~N => diffs from the Nth newest generation (older than G) to G
42 A => diffs from generation A to G
43 * defaults to ~1
44 EOF
47 usage_tip() {
48 echo 'run `nix-diff.sh -h` for usage instructions' >&2
49 exit 1
52 while getopts :hqlp:s opt; do
53 case $opt in
55 usage
56 exit
59 opt_query=1
62 opt_log=1
65 opt_profile=$OPTARG
68 opt_profile=/nix/var/nix/profiles/system
70 \?)
71 echo "error: invalid option -$OPTARG" >&2
72 usage_tip
74 esac
75 done
76 shift $((OPTIND-1))
78 if [ -n "$opt_profile" ]; then
79 if ! [ -L "$opt_profile" ]; then
80 echo "error: expecting \`$opt_profile\` to be a symbolic link" >&2
81 usage_tip
83 else
84 opt_profile=$(readlink ~/.nix-profile)
85 if (( $? != 0 )); then
86 echo 'error: unable to dereference `~/.nix-profile`' >&2
87 echo 'specify the profile manually with the `-p` flag' >&2
88 usage_tip
92 list_gens() {
93 nix-env -p "$opt_profile" --list-generations \
94 | sed -r 's:^\s*::' \
95 | cut -d' ' -f1
98 current_gen() {
99 nix-env -p "$opt_profile" --list-generations \
100 | grep -E '\(current\)\s*$' \
101 | sed -r 's:^\s*::' \
102 | cut -d' ' -f1
105 neg_gen() {
106 local i=0 from=$1 n=$2 tmp
107 for gen in $(list_gens | sort -rn); do
108 if ((gen < from)); then
109 tmp=$gen
110 ((i++))
111 ((i == n)) && break
113 done
114 if ((i < n)); then
115 echo -n "error: there aren't $n generation(s) older than" >&2
116 echo " generation $from" >&2
117 return 1
119 echo $tmp
122 match() {
123 argv=("$@")
124 for i in $(seq $(($#-1))); do
125 if grep -E "^${argv[$i]}\$" <(echo "$1") >/dev/null; then
126 echo $i
127 return
129 done
130 echo 0
133 case $(match "$1" '' '[0-9]+' '[0-9]+\.\.[0-9]+' '~[0-9]+') in
135 diffTo=$(current_gen)
136 diffFrom=$(neg_gen $diffTo 1)
137 (($? == 1)) && usage_tip
140 diffFrom=$1
141 diffTo=$(current_gen)
144 diffFrom=${1%%.*}
145 diffTo=${1##*.}
148 diffTo=$(current_gen)
149 diffFrom=$(neg_gen $diffTo ${1#*~})
150 (($? == 1)) && usage_tip
153 echo 'error: invalid invocation' >&2
154 usage_tip
156 esac
158 dirA="${opt_profile}-${diffFrom}-link"
159 dirB="${opt_profile}-${diffTo}-link"
161 declare -a temp_files
162 temp_length() {
163 echo -n ${#temp_files[@]}
165 temp_make() {
166 temp_files[$(temp_length)]=$(mktemp)
168 temp_clean() {
169 rm -f ${temp_files[@]}
171 temp_name() {
172 echo -n "${temp_files[$(($(temp_length)-1))]}"
174 trap 'temp_clean' EXIT
176 temp_make
177 versA=$(temp_name)
178 refs=$(nix-store -q --references "$dirA")
179 (( $? != 0 )) && exit 1
180 echo "$refs" \
181 | grep -v env-manifest.nix \
182 | sort \
183 > "$versA"
185 print_tag() {
186 local gen=$1
187 nix-env -p "$opt_profile" --list-generations \
188 | grep -E "^\s*${gen}" \
189 | sed -r 's:^\s*::' \
190 | sed -r 's:\s*$::'
193 if [ -n "$opt_query" ]; then
194 print_tag $diffFrom
195 cat "$versA" \
196 | sed -r 's:^[^-]+-(.*)$: \1:'
198 print_line=1
201 if [ -n "$opt_log" ]; then
202 gens=$(for gen in $(list_gens); do
203 ((diffFrom < gen && gen < diffTo)) && echo $gen
204 done)
205 # Force the $diffTo generation to be included in this list, instead of using
206 # `gen <= diffTo` in the preceding loop, so we encounter an error upon the
207 # event of its nonexistence.
208 gens=$(echo "$gens"
209 echo $diffTo)
210 else
211 gens=$diffTo
214 temp_make
215 add=$(temp_name)
216 temp_make
217 rem=$(temp_name)
218 temp_make
219 out=$(temp_name)
221 for gen in $gens; do
223 [ -n "$print_line" ] && echo
225 temp_make
226 versB=$(temp_name)
228 dirB="${opt_profile}-${gen}-link"
229 refs=$(nix-store -q --references "$dirB")
230 (( $? != 0 )) && exit 1
231 echo "$refs" \
232 | grep -v env-manifest.nix \
233 | sort \
234 > "$versB"
236 in=$(comm -3 -1 "$versA" "$versB")
237 sed -r 's:^[^-]*-(.*)$:\1+:' <(echo "$in") \
238 | sort -f \
239 > "$add"
241 un=$(comm -3 -2 "$versA" "$versB")
242 sed -r 's:^[^-]*-(.*)$:\1-:' <(echo "$un") \
243 | sort -f \
244 > "$rem"
246 cat "$rem" "$add" \
247 | sort -f \
248 | sed -r 's:(.*)-$:- \1:' \
249 | sed -r 's:(.*)\+$:\+ \1:' \
250 | grep -v '^$' \
251 > "$out"
253 if [ -n "$opt_query" -o -n "$opt_log" ]; then
255 lines=$(wc -l "$out" | cut -d' ' -f1)
256 tag=$(print_tag "$gen")
257 (( $? != 0 )) && exit 1
258 if [ $lines -eq 0 ]; then
259 echo "$tag (no change)"
260 else
261 echo "$tag"
263 cat "$out" \
264 | sed 's:^: :'
266 print_line=1
268 else
269 echo "diffing from generation $diffFrom to $diffTo"
270 cat "$out"
273 versA=$versB
275 done
277 exit 0