build: add error if `chroot --status` fails (#1151)
[aurutils.git] / lib / aur-build
blob1c4de944be9a4ad838b4076f93597bf508807345
1 #!/bin/bash
2 # aur-build - build packages to a local repository
3 [[ -v AUR_DEBUG ]] && set -o xtrace
4 set -o errexit
5 shopt -s extglob
6 argv0=build
7 startdir=$PWD
8 PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
10 # Avoid CDPATH screwing with cd (#1047)
11 unset -v CDPATH
13 # default options
14 chroot=0 no_sync=0 overwrite=0 sign_pkg=0 run_pkgver=0 truncate=1 status=0
16 # default arguments (empty)
17 chroot_args=() chroot_build_args=() sync_args=() repo_args=() repo_add_args=()
18 makepkg_pkglist_args=() makepkg_args=() makepkg_common_args=() read_args=()
20 # default arguments
21 gpg_args=(--detach-sign --no-armor --batch)
23 args_csv() {
24 # shellcheck disable=SC2155
25 local str=$(printf '%s,' "$@")
26 printf '%s' "${str%,}"
29 # Print diagnostic on non-moved packages (#794)
30 diag_moved_packages() {
31 echo >&2 'Note:'
33 cat <<EOF | pr -to 4 >&2
34 aur-$argv0 encountered an error before moving packages to the local repository.
35 This may happen when signing built packages with gpg (aur $argv0 --sign),
36 or with certain makepkg errors.
38 The following files were preserved:
39 EOF
40 printf '%s\n' "${@@Q}" | pr -to 8 >&2
43 get_local_upgrades() {
44 local repo=$1
46 # pacman prints diagnostics (::) to standard output, but does not accept
47 # repositories starting in `:: `. Redirect output accordingly.
48 LANG=C pacman -Sup "${@:2}" --print-format '%r/%n' | awk -F/ -v repo="$repo" '
49 $1 ~ repo {print $1 "/" $2}
50 $1 ~ /^:: / {print $0 >"/dev/stderr"}
52 return "${PIPESTATUS[0]}"
55 trap_exit() {
56 if [[ ! -v AUR_DEBUG ]]; then
57 rm -rf -- "$tmp"
59 # Only remove package directory if all files were moved (#593)
60 if ! rm -df -- "$var_tmp"; then
61 diag_moved_packages "$var_tmp"/*
63 else
64 printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$tmp"
65 printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$var_tmp"
69 usage() {
70 printf >&2 'usage: %s [-acfNS] [-d repo] [--root path] [--margs makepkg_arg...]\n' "$argv0"
71 exit 1
74 # mollyguard for makepkg
75 if (( UID == 0 )) && [[ ! -v AUR_ASROOT ]]; then
76 printf >&2 'warning: aur-%s is not meant to be run as root.\n' "$argv0"
77 printf >&2 'warning: To proceed anyway, set the %s variable.\n' 'AUR_ASROOT'
78 exit 1
81 ## option parsing
82 opt_short='a:d:D:U:AcCfnrsvzLNRST'
83 opt_long=('arg-file:' 'chroot' 'database:' 'force' 'root:' 'sign' 'gpg-sign'
84 'verify' 'directory:' 'no-sync' 'pacman-conf:' 'remove' 'pkgver'
85 'rmdeps' 'no-confirm' 'no-check' 'ignore-arch' 'log' 'new'
86 'makepkg-conf:' 'bind:' 'bind-rw:' 'prevent-downgrade' 'temp'
87 'syncdeps' 'clean' 'namcap' 'checkpkg' 'makepkg-args:' 'user:'
88 'margs:' 'buildscript:' 'null' 'dbext:' 'cleanbuild')
89 opt_hidden=('dump-options' 'ignorearch' 'noconfirm' 'nocheck' 'nosync' 'repo:'
90 'results:' 'results-append:' 'status')
92 if opts=$(getopt -o "$opt_short" -l "$(args_csv "${opt_long[@]}" "${opt_hidden[@]}")" -n "$argv0" -- "$@"); then
93 eval set -- "$opts"
94 else
95 usage
98 unset buildscript db_ext db_name db_path db_root makepkg_conf pacman_conf queue results_file
99 while true; do
100 case "$1" in
101 # build options
102 -a|--arg-file)
103 shift; queue=$1 ;;
104 -f|--force)
105 overwrite=1 ;;
106 -c|--chroot)
107 chroot=1 ;;
108 -d|--database|--repo)
109 shift; db_name=$1
110 repo_args+=(--repo "$1") ;;
111 --dbext)
112 shift; db_ext=$1 ;;
113 --buildscript)
114 shift; buildscript=$1; makepkg_common_args+=(-p "$1")
115 makepkg_pkglist_args+=(-p "$1") ;;
116 --nosync|--no-sync)
117 no_sync=1 ;;
118 --makepkg-conf)
119 shift; makepkg_conf=$1
120 chroot_args+=(--makepkg-conf "$1") ;;
121 --pacman-conf)
122 shift; pacman_conf=$1
123 chroot_args+=(--pacman-conf "$1") ;;
124 --pkgver)
125 run_pkgver=1; makepkg_args+=(--noextract)
126 chroot_build_args+=(--margs '--holdver') ;;
127 --root)
128 shift; db_root=$1
129 repo_args+=(--root "$1") ;;
130 -S|--sign|--gpg-sign)
131 sign_pkg=1; repo_add_args+=(-s) ;;
132 # chroot options
133 -D|--directory)
134 shift; chroot_args+=(--directory "$1") ;;
135 --bind)
136 shift; chroot_args+=(--bind "$1") ;;
137 --bind-rw)
138 shift; chroot_args+=(--bind-rw "$1") ;;
139 # XXX: different meaning between aur-build -U (--user) and aur-chroot -U (--update)
140 -U|--user)
141 shift; chroot_args+=(--user "$1") ;;
142 -N|--namcap)
143 chroot_args+=(--namcap) ;;
144 --checkpkg)
145 chroot_args+=(--checkpkg) ;;
146 -T|--temp)
147 chroot_args+=(--temp) ;;
148 # makepkg options (common)
149 -A|--ignorearch|--ignore-arch)
150 chroot_build_args+=(--ignorearch)
151 makepkg_common_args+=(--ignorearch) ;;
152 -n|--noconfirm|--no-confirm)
153 makepkg_common_args+=(--noconfirm) ;;
154 -r|--rmdeps)
155 makepkg_common_args+=(--rmdeps) ;;
156 -s|--syncdeps)
157 makepkg_common_args+=(--syncdeps) ;;
158 # makepkg options (build)
159 -C|--clean)
160 makepkg_args+=(--clean) ;;
161 --cleanbuild)
162 makepkg_args+=(--cleanbuild) ;;
163 -L|--log)
164 makepkg_args+=(--log) ;;
165 --nocheck|--no-check)
166 chroot_build_args+=(--nocheck)
167 makepkg_args+=(--nocheck) ;;
168 # XXX: cannot take arguments that contain commas (e.g. for file paths)
169 --makepkg-args|--margs)
170 shift; chroot_build_args+=(--margs "$1")
171 IFS=, read -a arg -r <<< "$1"
172 makepkg_args+=("${arg[@]}") ;;
173 # repo-add options
174 -v|--verify)
175 repo_add_args+=(-v) ;;
176 -R|--remove)
177 repo_add_args+=(-R) ;;
178 --new)
179 repo_add_args+=(-n) ;;
180 --prevent-downgrade)
181 repo_add_args+=(-p) ;;
182 # other options
183 --results)
184 shift; results_file=$1 ;;
185 --results-append)
186 shift; results_file=$1; truncate=0 ;;
187 -z|--null)
188 read_args+=(-d $'\0') ;;
189 --status)
190 status=1 ;;
191 --dump-options)
192 printf -- '--%s\n' "${opt_long[@]}" ${AUR_DEBUG+"${opt_hidden[@]}"}
193 printf -- '%s' "${opt_short}" | sed 's/.:\?/-&\n/g'
194 exit ;;
195 --) shift; break ;;
196 esac
197 shift
198 done
200 # Assign environment variables
201 db_ext=${db_ext:-$AUR_DBEXT} db_name=${db_name:-$AUR_REPO} db_root=${db_root:-$AUR_DBROOT}
203 # shellcheck disable=SC2174
204 mkdir -pm 0700 -- "${TMPDIR:-/tmp}/aurutils-$UID"
205 tmp=$(mktemp -d --tmpdir "aurutils-$UID/$argv0.XXXXXXXX")
207 # shellcheck disable=SC2174
208 mkdir -pm 0700 -- "${TMPDIR:-/var/tmp}/aurutils-$UID"
209 var_tmp=$(mktemp -d --tmpdir="${TMPDIR:-/var/tmp/}" "aurutils-$UID/$argv0.XXXXXXXX")
211 trap 'trap_exit' EXIT
212 trap 'exit' INT
214 if (( chroot )); then
215 # If the database name is specified (no auto-detection), use it for
216 # retrieving pacman and makepkg configuration in /etc/aurutils.
217 [[ -n $db_name ]] && chroot_args+=(--suffix "$db_name")
219 # Retrieve the makepkg and pacman configuration used in the container.
220 # The container is created and updated in a later step, after information on
221 # the local repository was retrieved.
222 { IFS=: read -r _ _
223 IFS=: read -r _ pacman_conf
224 IFS=: read -r _ makepkg_conf
225 } < <(aur chroot "${chroot_args[@]}" --status)
227 # Print an error if `chroot --status` fails (#1152)
228 if ! wait "$!"; then
229 printf '%s: error: failed to read chroot configuration\n' "$argv0"
230 exit 1
233 # Ensure PKGEXT defined in the container makepkg.conf is used for
234 # makepkg calls on the host (`makepkg --packagelist`).
235 unset PKGEXT
238 # Propagate `makepkg` configuration (`aur chroot`, `--makepkg-conf`)
239 if [[ -v makepkg_conf ]]; then
240 makepkg_common_args+=(--config "$makepkg_conf") # `makepkg`, `makepkg -od` (`--pkgver`)
241 makepkg_pkglist_args+=(--config "$makepkg_conf") # `makepkg --packagelist`
243 if [[ ! -r $makepkg_conf ]]; then
244 printf >&2 '%s: %s: permission denied\n' "$argv0" "$makepkg_conf"
245 exit 13
249 # Propagate `pacman` configuration (`aur chroot`, `--pacman-conf`)
250 if [[ -v pacman_conf ]]; then
251 repo_args+=(--config "$pacman_conf") # `aur repo`
252 sync_args+=(--config "$pacman_conf") # `pacman --sync`
254 if [[ ! -r $pacman_conf ]]; then
255 printf >&2 '%s: %s: permission denied\n' "$argv0" "$pacman_conf"
256 exit 13
260 # Automatically choose the local repository based on `pacman`
261 # configuration. With container builds, this repository is not
262 # necessarily configured on the host.
263 { IFS=: read -r _ db_name
264 IFS=: read -r _ db_root
265 IFS=: read -r _ db_path # canonicalized
266 } < <(aur repo "${repo_args[@]}" --status)
267 wait "$!"
269 # Check that a valid database extension was retrieved (#700, #1038)
270 if [[ -z $db_ext ]] && [[ $db_path =~ \.db$ ]]; then
271 printf >&2 '%s: %s does not have a valid database archive extension\n' "$argv0" "$db_path"
272 # TODO: this usually happens on file systems not supporting symbolic links
273 # (SMB/CIFS). Add a diagnostic to point towards AUR_DBEXT in this case
274 exit 2
277 if [[ ! -f $db_path ]]; then
278 printf >&2 '%s: %s: not a regular file\n' "$argv0" "$db_path"
279 exit 2
282 # Propagate pacman/makepkg configuration to other tools (#1135)
283 if (( status )); then
284 printf 'repo:%s\nroot:%s\npath:%s\n' "$db_name" "$db_root" "$db_path"
285 printf 'makepkg:%s\n' "${makepkg_conf:--}"
286 printf 'pacman:%s\n' "${pacman_conf:--}"
287 exit 0
290 # File permission checks
291 if [[ ! -w $db_path ]]; then
292 printf >&2 '%s: %s: permission denied\n' "$argv0" "$db_path"
293 exit 13
296 # Write successfully built packages to file (#437, #980)
297 if [[ -v results_file ]]; then
298 results_file=$(realpath -- "$results_file")
299 (( truncate )) && true | tee "$results_file"
302 if [[ -v queue ]]; then
303 exec {fd}< "$queue"
304 else
305 exec {fd}< <(echo "$startdir")
308 # Early consistency check for signed database
309 if (( ! sign_pkg )); then
310 db_sigs=("$db_root/$db_name".sig "$db_root/$db_name".files.sig)
312 if [[ -f ${db_sigs[0]} ]]; then
313 printf >&2 '%s: database signature found, but signing is disabled\n' "$argv0"
315 printf '%q\n' >&2 "${db_sigs[@]}"
316 exit 1
319 elif [[ -v GPGKEY ]]; then
320 #shellcheck disable=SC2086
321 ${AUR_GPG:-gpg} --list-keys "$GPGKEY"
322 gpg_args+=(-u "$GPGKEY")
325 if (( chroot )); then
326 # Update pacman and makepkg configuration for the chroot build
327 # queue. A full system upgrade is run on the /root container to
328 # avoid lenghty upgrades for makechrootpkg -u.
329 aur chroot "${chroot_args[@]}" --create --update
332 # Early check for `makepkg` buildscript
333 buildscript=${buildscript:-PKGBUILD}
335 while IFS= read "${read_args[@]}" -ru "$fd" path; do
336 cd -- "$startdir"
337 [[ $path ]] && cd -- "$path"
339 if [[ ! -f $buildscript ]]; then
340 printf >&2 '%s: %q does not exist\n' "$argv0" "$buildscript"
341 exit 2
344 # Allow running repo-add(8) on existing packages (#839)
345 create_package=1
346 pkglist=()
348 # Run pkgver function before --packagelist (#500)
349 if (( run_pkgver )); then
350 #shellcheck disable=SC2086
351 ${AUR_MAKEPKG:-makepkg} -od "${makepkg_common_args[@]}" >&2
354 # Retrieve list of potential package paths. This is used to (optionally)
355 # check if package paths are already available in the local repository
356 # before builds. If so, the build is skipped and the path is passed to
357 # repo-add (create_package=0). If no paths are available, the package is
358 # assumed to not exist and the build proceeds as usual (create_package=1).
359 if (( ! overwrite )); then
360 exists=()
361 #shellcheck disable=SC2086
362 while read -r pkgpath; do
363 [[ -f $pkgpath ]] && exists+=("$pkgpath")
364 done < <(PKGDEST="$db_root" ${AUR_BUILD_PKGLIST:-aur build--pkglist} "${makepkg_pkglist_args[@]}")
366 wait "$!" # Check `build--pkglist` exit status
368 if (( ${#exists[@]} )); then
369 printf >&2 '%s: warning: skipping existing package (use -f to overwrite)\n' "$argv0"
370 create_package=0
372 printf '%q\n' >&2 "${exists[@]}"
373 pkglist=("${exists[@]}")
376 if (( ${#exists[@]} )) && [[ -v results_file ]]; then
377 printf "exist:file://%s\n" "${exists[@]}" | tee -a "$results_file" >/dev/null
381 if (( create_package )); then
382 if (( chroot )); then
383 PKGDEST="$var_tmp" aur chroot "${chroot_args[@]}" --build "${chroot_build_args[@]}"
384 else
385 #shellcheck disable=SC2086
386 PKGDEST="$var_tmp" LOGDEST="${LOGDEST:-$PWD}" \
387 ${AUR_MAKEPKG:-makepkg} "${makepkg_common_args[@]}" "${makepkg_args[@]}"
390 cd -- "$var_tmp"
391 pkglist=(!(*.sig)) # discard makepkg --sign from package list (#410)
392 else
393 cd -- "$var_tmp"
394 # pkglist has paths to $db_root/<pkg>
397 # Sign any packages without signatures, even if the packages are existing.
398 siglist=()
400 for p in "${pkglist[@]}"; do
401 # Package basename (equals $p if create_package=1)
402 p_base=${p##*/}
404 # Signature from makepkg --sign
405 if [[ -f $p_base.sig ]]; then
406 siglist+=("$p_base".sig)
408 # Skipped package build with signature
409 elif [[ -f $db_root/$p_base.sig ]] && [[ ! -f $p_base ]]; then
410 printf >&2 '%s: existing signature file %q\n' "$argv0" "$db_root/$p_base.sig"
412 # No candidate signature, generate one
413 elif (( sign_pkg )); then
414 #shellcheck disable=SC2086
415 ${AUR_GPG:-gpg} "${gpg_args[@]}" --output "$p_base".sig "$p"
417 printf >&2 '%s: created signature file %q\n' "$argv0" "$p_base".sig
418 siglist+=("$p_base".sig)
420 done
422 if (( create_package )); then
423 mv -f "${siglist[@]}" "${pkglist[@]}" "$db_root"
425 if [[ -v results_file ]]; then
426 printf "build:file://$db_root/%s\n" "${pkglist[@]}" | tee -a "$results_file" >/dev/null
429 elif (( ${#siglist[@]} )); then
430 mv -f "${siglist[@]}" "$db_root"
433 # Update database
434 #shellcheck disable=SC2086
435 env -C "$db_root" LANG=C ${AUR_REPO_ADD:-repo-add} "${repo_add_args[@]}" "$db_path" "${pkglist[@]}"
437 if (( chroot )) || (( no_sync )); then
438 continue
439 else
440 #shellcheck disable=SC2086
441 ${AUR_PACMAN_AUTH:-sudo} pacsync "${sync_args[@]}" "$db_name"
442 #shellcheck disable=SC2086
443 ${AUR_PACMAN_AUTH:-sudo} pacsync "${sync_args[@]}" "$db_name" --dbext=.files
445 # Retrieve upgrade targets in local repository. May error in case of
446 # conflicts or dependency errors.
447 mapfile -t targets < <(get_local_upgrades "$db_name" "${sync_args[@]}")
448 wait "$!"
450 if (( ${#targets[@]} )); then
451 printf >&2 "%s: upgrading packages in repository '%s'\n" "$argv0" "$db_name"
452 #shellcheck disable=SC2086
453 printf '%s\n' "${targets[@]}" | ${AUR_PACMAN_AUTH:-sudo} pacman "${sync_args[@]}" -S --noconfirm -
456 done
458 exec {fd}<&-
460 # vim: set et sw=4 sts=4 ft=sh: