-
Notifications
You must be signed in to change notification settings - Fork 3
/
nixpkgs-review-checks-hook
executable file
·443 lines (368 loc) · 17.5 KB
/
nixpkgs-review-checks-hook
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
#!/usr/bin/env bash
# source this in your .bashrc
tb() {
# only upload the last 20000 lines to keep below ~2 MB
tail -20000 | nc termbin.com 9999
}
_nixpkgs-review-checks-hook() {
if [[ (${name:-} == review-shell || -v PR) && -z ${NIXPKGS_REVIEW_CHECKS_RUN:-} ]]; then
lint_check=lint_checks.tmp
report=report.md
# prevent shell from closing with Ctrl+D when changes where made in nixpkgs
set -o ignoreeof
# fix python programs when reviewing python2 packages
export PYTHONPATH=
# archive PR
[[ -n ${PR:-} ]] && nohup savepagenow "https://github.com/NixOS/nixpkgs/pull/$PR" 2>/dev/null &
if [[ -n ${NIXPKGS_REVIEW_CHECKS_DEBUG:-} ]]; then
set -x
fi
# save or restore a backup of original report for debugging
if [[ -f $report.orig ]]; then
command cp "$report.orig" "$report"
else
command cp "$report" "$report.orig"
fi
(
# run commands for each package build
cd logs/ 2>/dev/null || exit
for package_log_file in *.log; do
package="${package_log_file%.*}"
lint_check_package=../lint_checks.$package.tmp
missingMeta="$(nix-instantiate --eval -E "(import ../nixpkgs { }).pkgs.$package.meta" || true)"
if [[ -z $missingMeta ]]; then
cat <<EOF >>"$lint_check_package"
Package is missing a meta section.
If the package is using runCommand please make sure to inherit or create a meta section.
EOF
else
# Get the first one to ensure the list is not empty
missingMaintainers="$(nix-instantiate --eval -E "builtins.head (import ../nixpkgs { }).pkgs.$package.meta.maintainers" || true)"
if [[ -z $missingMaintainers ]]; then
cat <<EOF >>"$lint_check_package"
Package is missing maintainers.
If the package is using runCommand please make sure to inherit or list one or more maintainers.
EOF
fi
fi
package_nix_file="$(cd ../nixpkgs && EDITOR="echo" nix edit -f . "$package" 2>/dev/null || echo skip)"
# skip packages which have no source location like pkgsMusl.stdenv
# runCommand returns a path to trivial-builders.nix which also contains an empty direcotry hash
if [[ ! $package_nix_file =~ pkgs/build-support/trivial-builders.nix && $package_nix_file != skip ]]; then
# check default.nix of the package for common errors and display it
rg --ignore-case --colors "match:bg:yellow" --colors "match:fg:magenta" -e 'pythonImportTests' -e 'pythonImportCheck' -e 'pythonImportTests' -e 'pythonCheckImports' -e '= "\$\{\w*\}";' -e 'propagatedBuildInputs = [ ];' -e 'propogatedBuildInputs' -e 'propagateBuildInputs' -e 'longDecsription' -e 'disabledTestsPaths' "$package_nix_file" &
if rg --ignore-case -e 'pythonImportTests' -e 'pythonImportCheck' -e 'pythonImportTests' -e 'pythonCheckImports' "$package_nix_file"; then
cat <<EOF >>"$lint_check_package"
A typo in pythonImportsCheck got detected.
EOF
fi
if rg --ignore-case -e 'disabledTestsPaths' "$package_nix_file"; then
cat <<EOF >>"$lint_check_package"
A typo in disabledTestPaths got detected.
EOF
fi
breaksCrossCompile="$(rg --ignore-case -e 'CC=cc' -e 'CXX=c++' -e 'AR=ar' -e 'LD=cc' "$package_nix_file" || true)"
if [[ -n $breaksCrossCompile ]]; then
cat <<EOF >>"$lint_check_package"
Using CC=cc, CXX=c++, AR=ar or LD=cc prevents cross compiling.
Please use \`\`CC=\${stdenv.cc.targetPrefix}cc\`\`, \`\`CXX=\${stdenv.cc.targetPrefix}c++\`\`, \`\`AR=\${stdenv.cc.targetPrefix}ar\`\` or \`\`LD=\${stdenv.cc.targetPrefix}cc\`\`.
EOF
fi
avoidableStringConversion="$(rg --ignore-case -e '= "\$\{version\}";' "$package_nix_file" || true)"
if [[ -n $avoidableStringConversion ]]; then
cat <<EOF >>"$lint_check_package"
An avoidable string conversion got detected: \`$avoidableStringConversion\`
Please do not convert variables to a string without modifying them but use them directly instead.
EOF
fi
unsuitableMetaHomepageUse="$(rg --ignore-case -e '\$\{meta.homepage\}' "$package_nix_file" || true)"
if [[ -n $unsuitableMetaHomepageUse ]]; then
cat <<EOF >>"$lint_check_package"
An unsuitable usage of meta.homepage got detected.
Please replace it with the actual URL to not accidentially break things when the homepage gets updated.
EOF
fi
if rg --ignore-case -e '0sjjj9z1dhilhpc8pq4154czrb79z9cm044jvn75kxcjv6v5l2m5' -e '0ip26j2h11n1kgkz36rl4akv694yz65hr72q4kv4b3lxcbi65b3p' "$package_nix_file"; then
cat <<EOF >>"$lint_check_package"
An empty (vendor) directory got detected.
If this is a go package try replacing vendorSha256 = "0sjjj9z1dhilhpc8pq4154czrb79z9cm044jvn75kxcjv6v5l2m5";
with vendorSha256 = null;
EOF
fi
fi # package_nix_file
# nixpkgs-hammer
# TODO: this should use the json output
if [[ $package != ccacheStdenv ]]; then
# if package was not directly edited be less picky with nixpkgs-hammering to avoid noise
#
# changes from the PR reviewing are staged and are compared to the relative path of the current package
# if the package was changed in the PR we display all picky results, if not we hide some noise
#
# disabled checks:
# - attribute-ordering: noisy and almost triggers for all packages
if [[ $(git status --porcelain) =~ ${package_nix_file#"$(realpath nixpkgs)/"} ]]; then
hammer_flags=(--exclude attribute-ordering --exclude no-build-output)
else
# - explicit-phases: noisy and almost triggers for all packages
# - no-flags-array: triggers often and is very commonly used even if half broken
# - missing-patch-comment: has many false positives where the filename is enough
# - missing-phase-hooks: has many false positives
hammer_flags=(--exclude attribute-ordering --exclude explicit-phases --exclude missing-patch-comment --exclude missing-phase-hooks --exclude no-build-output --exclude no-flags-array)
fi
# tail removes "When evaluating attribute ..."
# sed removes color escape codes, adds markdown code fences and replaced home path
hammer_out="$(cd ../nixpkgs/ && nixpkgs-hammer "${hammer_flags[@]}" "$package" 2>&1 | tail -n +2 | sed -E "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g" | sed -e '/^\s*|\s*$/i \\n```' -e '/^\s*|\s*\^$/a ```\n' -e "s/${PWD//\//\\\/}\///g" || echo "No issues found.")"
if [[ -n $hammer_out && ! $hammer_out =~ No\ issues\ found. ]]; then
# for each warning in $hammer_out, also strips them
# replaces the warning line with a null byte
# || [[ -n $warning ]] is required to display the last warning without appending a null byte
echo "$hammer_out" | sed -E -e "/warning: [a-z-]+/s/^/\x0/g" | while read -r -d $'\0' warning || [[ -n $warning ]]; do
if [[ $warning =~ has\ not\ yet\ been\ built ]]; then
continue
fi
echo "$warning" >>"$lint_check_package"
done
fi
fi
if [[ -f $lint_check_package ]]; then
{
cat <<EOF
$package:
<details>
EOF
cat "$lint_check_package"
echo "</details>"
} >>"../$lint_check"
rm "$lint_check_package"
fi
done
# run commands for output in result(s)
if [[ -d ../result ]]; then
result_dir="result*"
elif [[ -d ../results ]]; then
result_dir="results/*"
else
echo "No Results directory found"
fi
is_object() {
local binary=$1
[[ ! -f $binary && ! "$(file --brief --mime-type "$binary")" =~ ^text/.* ]]
}
check_binary() {
local binary=$1
# if symlink or text file
if ! is_object "$binary"; then
return
fi
if [[ $OSTYPE =~ darwin || $(uname) == Darwin ]]; then
otool -L "$binary" | tail -n +2 | awk '{print $1}' | while read -r object; do
if [[ ! -f $object ]]; then
echo "Missing object found in: $binary $object"
fi
done
else
# shellcheck disable=SC2015
ldd "$binary" | rg --ignore-case --colors "match:bg:yellow" --colors "match:fg:magenta" "not found" && echo "Missing object found in: $binary" || true
fi
}
check_output() {
# args mean we want to ignore a directory
if [[ $# == 3 ]]; then
local directory=$1
local ignore=$2
local outputs=$3
ignored="$(find -L "$result" -maxdepth 3 -type d -regextype egrep -regex "$result/$ignore" -printf 'x')"
# return early if the right directory does not exist
if ! [[ -d $result/$directory && -z $ignored ]]; then
return
fi
else
local directory=$1
local outputs=${2:-$1}
# return early if directory does not exist
if [[ ! -d $result/$directory ]]; then
return
fi
fi
local directory_size percentage
# ignore broken symlinks
directory_size=$(du -bsL "$result/$directory" 2>/dev/null | awk '{ print $1 }' || true)
percentage=$(echo "100 / $result_size * $directory_size" | command bc -l)
# only display message if the directory is at least 1 MB and 5% of whole package
if [[ $directory_size -ge 1000000 ]] && echo "$percentage >= 5" | command bc >/dev/null; then
directory_size_iec=$(numfmt --to iec --format "%8.1f" "$directory_size" | tr -d ' ')
echo -e "$(basename "$result"): Ask if output path \u001b[91m$directory\u001b[0m ($directory_size_iec) could be split with \u001b[36moutputs = [ ... \"$outputs\" ];\u001b[0m"
fi
}
if [[ -n ${result_dir:-} ]]; then
for result in ../$result_dir; do
if [[ -d "$result"/bin/ ]]; then
# run bloaty, ldd and otool on binaries and common library locations to check for debug or missing symbols
for binary in "$result"/bin/*; do
check_binary "$binary" &
# shellcheck disable=SC2015
if is_object "$binary"; then
bloaty "$binary" | rg --ignore-case --colors "match:bg:yellow" --colors "match:fg:magenta" debug && echo "Bloaty found an unstripped binary: $binary" || true &
fi
done
fi
if compgen -G "$result"/lib/*.so.* >/dev/null; then
for binary in "$result"/lib/*.so.*; do
check_binary "$binary" &
done
fi
if compgen -G "$result"/libexec/* >/dev/null; then
for binary in "$result"/libexec/*; do
[[ -d $binary ]] && continue
check_binary "$binary" &
done
fi
if compgen -G "$result"/lib/python*/site-packages/*-*.dist-info/ | rg -q "0.0.0"; then
cat <<EOF >>"$lint_check_package"
The python package $result has version number 0.0.0 which means the packaging is most likely broken.
Is the package using setuptool-scm and it is missing in nativeBuildInputs?
EOF
fi
if compgen -G "$result"/share/bash_completion.d/*; then
cat <<EOF >>"$lint_check_package"
bash-completions are installed into \$out/share/bash_completion.d/ which is incompatible with home-manager.
Please use installShellCompletion from the installShellFiles package to place the files under \$out/share/bash-completion/completions/ .
EOF
fi
# check for common missing outputs
result_size=$(du -bsL "$result" 2>/dev/null | awk '{ print $1 }' || true)
# not checking bin because it would apply to to many packages
#check_output bin
check_output include dev
check_output lib "(lib/ghc-.*|lib/python.*/site-packages|lib/ruby/gems)" lib
check_output lib/cmake dev
check_output lib/pkgconfig dev
check_output libexec lib
check_output share/aclocal dev
check_output share/doc doc
check_output share/gtk-doc devdoc
check_output share/devhelp devdoc
check_output share/info info
check_output share/man/man* share/man/man3 man
check_output share/man/man3 devman
done
fi
)
# run hydra-check on build failures and resort to show regressions clearly
(
cat <<EOF >"$report.tmp"
This is a semi-automatic executed nixpkgs-review with [nixpkgs-review-checks extension](https://github.com/SuperSandro2000/nixpkgs-review-checks). It is checked by a human on a best effort basis and does not build all packages (e.g. lumo, tensorflow or pytorch).
EOF
head -1 "$report" >>"$report.tmp"
for state in "marked as broken and skipped" "present in ofBorgs evaluation, but not found in the checkout" "blacklisted" "failed to build" "built"; do
case $state in
"failed to build")
arch=$(nix-instantiate --eval --json --expr builtins.currentSystem | jq -r)
packages_to_check=$(tail -n +2 "$report" | pup -i 0 ':parent-of(:contains("failed to build")) ul li text{}')
packages_fail_on_master=()
packages_fail_new=()
for package in $packages_to_check; do
hydra_state=$(hydra-check --arch="$arch" --channel=master --json "$package" 2>/dev/null)
part_of_eval_set=$(echo "$hydra_state" | jq ."\"$package\""[0].evals || echo skip)
state_on_master=$(echo "$hydra_state" | jq ."\"$package\""[0].success || echo skip)
if [[ $part_of_eval_set == true && $state_on_master == false ]]; then
packages_fail_on_master+=("$package")
else
# state unknown which means probably the package is new and not in master yet
packages_fail_new+=("$package")
fi
done
if [[ -n $NIXPKGS_REVIEW_GITHUB_TOKEN ]]; then
github_username=$(GH_TOKEN=$NIXPKGS_REVIEW_GITHUB_TOKEN gh api user | jq -r .login)
fi
upload_log() {
local package=$1
local url_plain gist_id
# filter home path from the error log and upload it
if [[ -s logs/$package.log ]]; then
url_plain=$(sed -e "s/${HOME//\//\\\/}\///" "logs/$package.log" | tb)
echo 1
if [[ -n $NIXPKGS_REVIEW_GITHUB_TOKEN ]]; then
gist_id=$(basename "$(sed -e "s/${HOME//\//\\\/}\///" "logs/$package.log" | ansi2html | GH_TOKEN=$NIXPKGS_REVIEW_GITHUB_TOKEN gh gist create --public --filename index.html 2>&1 | tail -1)")
fi
fi
echo "<li>" >>"$report.tmp"
if [[ -n ${url_plain:-} ]]; then
if [[ -z $NIXPKGS_REVIEW_GITHUB_TOKEN ]]; then
echo "$package: <a href=\"$url_plain\">plain log</a>" >>"$report.tmp"
else
echo "$package: <a href=\"$url_plain\">plain log</a> | <a href=\"https://htmlpreview.github.io/?https://gist.githubusercontent.com/$github_username/$gist_id/raw/index.html\">pretty log </a>" >>"$report.tmp"
fi
else
echo "$package: log was empty" >>"$report.tmp"
fi
echo "</li>" >>"$report.tmp"
}
if [[ ${#packages_fail_on_master[@]} -gt 0 ]]; then
if [[ ${#packages_fail_on_master[@]} -gt 1 ]]; then
packages_fail_on_master_plural=s
fi
cat <<EOF >>"$report.tmp"
<details>
<summary>${#packages_fail_on_master[@]} package${packages_fail_on_master_plural:-} failed to build and already failed to build on hydra master:</summary>
<ul>
EOF
for package in "${packages_fail_on_master[@]}"; do
upload_log "$package"
done
cat <<EOF >>"$report.tmp"
</ul>
</details>
EOF
fi
if [[ ${#packages_fail_new[@]} -gt 0 ]]; then
if [[ ${#packages_fail_new[@]} -gt 1 ]]; then
packages_fail_new_plural=s
fi
cat <<EOF >>"$report.tmp"
<details>
<summary>${#packages_fail_new[@]} package${packages_fail_new_plural:-} failed to build and are new build failure${packages_fail_new_plural:-}:</summary>
<ul>
EOF
for package in "${packages_fail_new[@]}"; do
upload_log "$package"
done
cat <<EOF >>"$report.tmp"
</ul>
</details>
EOF
fi
;;
*)
# trim first two lines, add a line break to prevent EOF from pup if text is empty and extract summary
tail -n +2 "$report" | {
cat
echo
} | pup -i 0 ":parent-of(:contains(\"$state\"))" >>"$report.tmp"
;;
esac
done
if [[ -f $lint_check ]]; then
{
cat <<EOF
The following issues got detected with the above build packages.
Please fix at least the ones listed with your changed packages:
<details>
EOF
cat "$lint_check"
echo "</details>"
} >>"$report.tmp"
rm "$lint_check"
fi
# remove extra new lines before and afer <li>
sed "$report.tmp" -e '/<li>/N;s/\n//' -e '/<li>/N;s/\n//' >"$report"
rm "$report.tmp"
)
mdcat report.md
if [[ -n ${NIXPKGS_REVIEW_CHECKS_DEBUG:-} ]]; then
set +x
fi
export NIXPKGS_REVIEW_CHECKS_RUN=1
fi
}
export PROMPT_COMMAND="${PROMPT_COMMAND}${PROMPT_COMMAND:+;}_nixpkgs-review-checks-hook"