Merge pull request #1008 from AladW/fetch-rework-iv
[aurutils.git] / lib / aur-build
blob661401fca8bd1408ca8ce8bc25263ce0eb087f94
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 machine=$(uname -m)
8 startdir=$PWD
9 PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
11 # default options
12 chroot=0 no_sync=0 overwrite=0 sign_pkg=0 run_pkgver=0 dry_run=0 truncate=1
14 # default arguments (empty)
15 chroot_args=() pacconf_args=() repo_args=() repo_add_args=() pkglist_args=()
16 makepkg_args=() makechrootpkg_makepkg_args=() makepkg_common_args=()
18 # default arguments
19 gpg_args=(--detach-sign --no-armor --batch)
20 makechrootpkg_args=(-cu) # -c to clean working copy, -u to sync local repository builds
22 args_csv() {
23 # shellcheck disable=SC2155
24 local str=$(printf '%s,' "$@")
25 printf '%s' "${str%,}"
28 db_replaces() {
29 bsdcat "$1" | awk '/%REPLACES%/ {
30 while(getline && NF != 0) { print; }
34 diag_moved_packages() {
35 # Print diagnostic on non-moved packages (#794)
36 cat <<EOF >&2
37 Note:
38 aur-build encountered an error before moving packages to the local repository.
39 This may happen when signing built packages with gpg (aur build --sign),
40 or with certain makepkg errors.
42 The following files were preserved:
43 EOF
44 #shellcheck disable=SC2030
45 realpath -z -- "$@" | while read -rd ''; do
46 printf '%8s%s\n' ' ' "$REPLY"
47 done
50 diag_pacman_conf() {
51 cat <<EOF >&2
52 Error:
53 aur-build could not find a pacman.conf(5) file for container usage. Before
54 using --chroot, make sure this file is created and valid. See OPTIONS in
55 aur-build(1) for configuration details.
57 The following file path was checked:
58 EOF
59 printf '%8s%s\n' ' ' "$1"
62 # Allow to drop permissions for commands as needed (#907)
63 as_user() {
64 local USER HOME SHELL
66 if [[ $UID == 0 ]] && [[ -v build_user ]]; then
67 # runuser --pty messes up the terminal with AUR_DEBUG set, use setpriv(1)
68 # and replicate the runuser(1) behavior for setting the environment
69 { IFS= read -r USER
70 IFS= read -r HOME
71 IFS= read -r SHELL
72 } < <(getent passwd "$build_user" | awk -F: '{printf("%s\n%s\n%s\n", $1, $6, $7); }')
74 setpriv --reuid "$build_user" --regid "$build_user" --init-groups \
75 env USER="$USER" HOME="$HOME" LOGNAME="$USER" SHELL="$SHELL" -- "$@"
76 else
77 env -- "$@"
81 run_msg() {
82 printf >&2 'Running %s\n' "${*:$1}"
83 "${@:2}"
86 trap_exit() {
87 if [[ ! -v AUR_DEBUG ]]; then
88 rm -rf -- "$tmp"
90 # Only remove package directory if all files were moved (#593)
91 if ! rm -df -- "$var_tmp"; then
92 diag_moved_packages "$var_tmp"/*
94 else
95 printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$tmp"
96 printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$var_tmp"
100 usage() {
101 plain >&2 'usage: %s [-acfNS] [-d repo] [--root path] [--margs makepkg_arg...]' "$argv0"
102 exit 1
105 source /usr/share/makepkg/util/message.sh
106 source /usr/share/makepkg/util/parseopts.sh
108 if [[ ! -v NO_COLOR ]] && [[ ! -v AUR_DEBUG ]]; then
109 [[ -t 2 ]] && colorize
112 ## option parsing
113 opt_short='a:d:D:U:AcCfnrsvLNRST'
114 opt_long=('arg-file:' 'chroot' 'database:' 'force' 'root:' 'sign' 'gpg-sign'
115 'verify' 'directory:' 'no-sync' 'pacman-conf:' 'remove' 'pkgver'
116 'rmdeps' 'no-confirm' 'no-check' 'ignore-arch' 'log' 'new'
117 'makepkg-conf:' 'bind:' 'bind-rw:' 'prevent-downgrade' 'temp'
118 'syncdeps' 'clean' 'namcap' 'checkpkg' 'user:' 'makepkg-args:'
119 'margs:' 'buildscript:' 'dry-run')
120 opt_hidden=('dump-options' 'ignorearch' 'noconfirm' 'nocheck' 'nosync' 'repo:'
121 'results:' 'results-append:')
123 if ! parseopts "$opt_short" "${opt_long[@]}" "${opt_hidden[@]}" -- "$@"; then
124 usage
126 set -- "${OPTRET[@]}"
128 unset build_user db_name db_path db_root makepkg_conf pacman_conf results_file queue
129 while true; do
130 case "$1" in
131 # build options
132 -a|--arg-file)
133 shift; queue=$1 ;;
134 -f|--force)
135 overwrite=1 ;;
136 -c|--chroot)
137 chroot=1 ;;
138 -d|--database|--repo)
139 shift; db_name=$1
140 repo_args+=(--repo "$1") ;;
141 --buildscript)
142 shift; makepkg_common_args+=(-p "$1")
143 pkglist_args+=(-p "$1") ;;
144 --dry-run)
145 dry_run=1 ;;
146 --nosync|--no-sync)
147 no_sync=1 ;;
148 --makepkg-conf)
149 shift; makepkg_conf=$1 ;;
150 --pacman-conf)
151 shift; pacman_conf=$1 ;;
152 --pkgver)
153 run_pkgver=1; makepkg_args+=(--noextract) ;;
154 --root)
155 shift; db_root=$1
156 repo_args+=(--root "$1") ;;
157 -S|--sign|--gpg-sign)
158 sign_pkg=1; repo_add_args+=(-s) ;;
159 # chroot options
160 -D|--directory)
161 shift; chroot_args+=(-D "$1") ;;
162 --bind)
163 shift; makechrootpkg_args+=(-D "$1") ;;
164 --bind-rw)
165 shift; makechrootpkg_args+=(-d"$1") ;;
166 -N|--namcap)
167 makechrootpkg_args+=(-n) ;;
168 --checkpkg)
169 makechrootpkg_args+=(-C) ;;
170 -T|--temp)
171 makechrootpkg_args+=(-T) ;;
172 -U|--user)
173 shift; build_user=$1
174 makechrootpkg_args+=(-U "$1") ;;
175 # makepkg options (common)
176 -A|--ignorearch|--ignore-arch)
177 makepkg_common_args+=(--ignorearch)
178 makechrootpkg_makepkg_args+=(--ignorearch) ;;
179 -n|--noconfirm|--no-confirm)
180 makepkg_common_args+=(--noconfirm) ;;
181 -r|--rmdeps)
182 makepkg_common_args+=(--rmdeps) ;;
183 -s|--syncdeps)
184 makepkg_common_args+=(--syncdeps) ;;
185 # makepkg options (build)
186 -C|--clean)
187 makepkg_args+=(--clean) ;;
188 -L|--log)
189 makepkg_args+=(--log) ;;
190 --nocheck|--no-check)
191 makepkg_args+=(--nocheck)
192 makechrootpkg_makepkg_args+=(--nocheck) ;;
193 --makepkg-args|--margs)
194 shift; IFS=, read -a arg -r <<< "$1"
195 makepkg_args+=("${arg[@]}")
196 makechrootpkg_makepkg_args+=("${arg[@]}") ;;
197 # repo-add options
198 -v|--verify)
199 repo_add_args+=(-v) ;;
200 -R|--remove)
201 repo_add_args+=(-R) ;;
202 --new)
203 repo_add_args+=(-n) ;;
204 --prevent-downgrade)
205 repo_add_args+=(-p) ;;
206 # other options
207 --results)
208 shift; results_file=$1 ;;
209 --results-append)
210 shift; results_file=$1; truncate=0 ;;
211 --dump-options)
212 printf -- '--%s\n' "${opt_long[@]}" ${AUR_DEBUG+"${opt_hidden[@]}"}
213 printf -- '%s' "${opt_short}" | sed 's/.:\?/-&\n/g'
214 exit ;;
215 --) shift; break ;;
216 esac
217 shift
218 done
220 # mollyguard for makepkg
221 if [[ $UID == 0 ]] && ! { [[ -v build_user ]] && [[ -v AUR_ASROOT ]]; }; then
222 warning 'aur-%s is not meant to be run as root.' "$argv0"
223 warning 'To proceed anyway, set the %s variable and specify --user <username>.' 'AUR_ASROOT'
224 exit 1
227 # Assign environment variables
228 : "${db_ext=$AUR_DBEXT}" "${db_root=$AUR_DBROOT}" "${db_repo=$AUR_REPO}"
230 # Custom makepkg command
231 if [[ $MAKEPKG ]]; then
232 # shellcheck disable=SC2086
233 makepkg() { command -- $MAKEPKG "$@"; }
236 # Custom elevation command
237 if [[ $UID == 0 ]]; then
238 sudo() { command -- "$@"; }
240 elif [[ $AUR_PACMAN_AUTH ]]; then
241 # shellcheck disable=SC2086
242 sudo() { command -- $AUR_PACMAN_AUTH "$@"; }
245 # shellcheck disable=SC2174
246 mkdir -pm 0700 "${TMPDIR:-/tmp}/aurutils-$UID"
247 tmp=$(mktemp -d --tmpdir "aurutils-$UID/$argv0.XXXXXXXX")
249 # Only $var_tmp should be writeable by the build user (PKGDEST, signatures)
250 # If UID > 0 and build_user is unset, this is equivalent to $tmp above
251 if [[ -v build_user ]]; then
252 var_tmp_uid=$(id -u "$build_user")
253 else
254 var_tmp_uid=$UID
257 # shellcheck disable=SC2174
258 as_user mkdir -pm 0700 "${TMPDIR:-/var/tmp}/aurutils-$var_tmp_uid"
259 var_tmp=$(as_user mktemp -d --tmpdir="${TMPDIR:-/var/tmp/}" "aurutils-$var_tmp_uid/$argv0.XXXXXXXX")
261 trap 'trap_exit' EXIT
262 trap 'exit' INT
264 if (( chroot )); then
265 # Change the default /usr/share/devtools/pacman-extra.conf in aur-chroot to
266 # /etc/aurutils/pacman-<repo>.conf or /etc/aurutils/pacman-<uname>.conf in
267 # aur-build, and pass it on to aur-chroot (#824, #846)
268 pacman_conf=${pacman_conf-/etc/aurutils/pacman-${db_name:-$machine}.conf}
269 chroot_args+=(--pacman-conf "$pacman_conf")
271 # Early check for availability of pacman.conf (#783)
272 if [[ ! -f $pacman_conf ]]; then
273 diag_pacman_conf "$pacman_conf"
274 exit 2
277 # The default path is /usr/share/devtools/makepkg-<uname.conf>, which is
278 # copied to <container path>/etc/makepkg.conf by arch-nspawn.
279 if [[ -v makepkg_conf ]]; then
280 chroot_args+=(--makepkg-conf "$makepkg_conf")
281 else
282 # When makechrootpkg calls makepkg inside the container, it uses the above
283 # makepkg.conf for most variables including PKGEXT. (makepkg --packagelist)
284 makepkg_conf=$(aur chroot --path "${chroot_args[@]}")/etc/makepkg.conf
285 unset PKGEXT
289 # Propagate makepkg and pacman configuration to other tools. This needs to be
290 # done BEFORE retrieving the local repository name/root.
291 if [[ -v pacman_conf ]]; then
292 pacconf_args+=(--config "$pacman_conf")
294 if [[ ! -f $pacman_conf ]]; then
295 error '%s: %s: not a regular file' "$argv0" "$pacman_conf"
296 exit 2
300 if [[ -v makepkg_conf ]]; then
301 makepkg_common_args+=(--config "$makepkg_conf")
302 pkglist_args+=(--config "$makepkg_conf")
304 if [[ -v makepkg_conf ]] && [[ ! -f $makepkg_conf ]]; then
305 error '%s: %s: not a regular file' "$argv0" "$makepkg_conf"
306 exit 2
310 # Automatically choose the local repository based on the pacman configuration.
311 if [[ $db_name ]] && [[ $db_root ]]; then
312 db_path=$db_root/$db_name.${db_ext:-db}
313 db_path=$(realpath -- "$db_path")
314 else
315 { IFS=: read -r _ db_name
316 IFS=: read -r _ db_root
317 IFS=: read -r _ db_path # canonicalized
318 } < <(as_user aur repo --status "${repo_args[@]}" "${pacconf_args[@]}")
319 wait "$!"
321 db_root=$(realpath -- "$db_root")
323 # Resolve symbolic link to database.
324 if ! [[ -f $db_path ]]; then
325 error '%s: %s: not a regular file' "$argv0" "$db_path"
326 exit 2
328 # Check if build user can write to database
329 elif ! as_user test -w "$db_path"; then
330 error '%s: %s: permission denied' "$argv0" "$db_path"
331 exit 13
334 # Write successfully built packages to file (#437, #980)
335 if [[ -v results_file ]]; then
336 results_file=$(realpath -- "$results_file")
337 (( truncate )) && true | as_user tee "$results_file"
340 if (( chroot )); then
341 # Update pacman and makepkg configuration for the chroot build
342 # queue. A full system upgrade is run on the /root container to
343 # avoid lenghty upgrades for makechrootpkg -u.
344 run_msg 2 aur chroot --create --update "${chroot_args[@]}"
345 else
346 # Generate pacman.conf for isolated upgrade of local repository.
347 { printf '[options]\n'
348 pacconf "${pacconf_args[@]}" --raw --options
350 printf '[%s]\nUsage = All\n' "$db_name"
351 pacconf "${pacconf_args[@]}" --raw --repo="$db_name" SigLevel Server
353 # Allow secondary repositories as targets for -Syu (#956)
354 while IFS= read -r repo; do
355 printf '[%s]\nUsage = Install\n' "$repo"
356 pacconf "${pacconf_args[@]}" --raw --repo="$repo" SigLevel Server
357 done < <(
358 pacconf "${pacconf_args[@]}" --repo-list | grep -Fxv "$db_name"
360 } >"$tmp"/local.conf
363 if [[ -v queue ]]; then
364 exec {fd}< "$queue"
365 else
366 exec {fd}< <(printf '\n')
369 # Early consistency check for signed database
370 if (( ! sign_pkg )); then
371 db_sigs=("$db_root/$db_name".sig "$db_root/$db_name".files.sig)
373 if [[ -f ${db_sigs[0]} ]]; then
374 error '%s: database signature found, but signing is disabled' "$argv0"
376 printf '%q\n' >&2 "${db_sigs[@]}"
377 exit 1
380 elif [[ -v GPGKEY ]]; then
381 as_user gpg --list-keys "$GPGKEY"
382 gpg_args+=(-u "$GPGKEY")
385 while IFS= read -ru "$fd" path; do
386 # Use two cd calls to handle absolute paths in --arg-file
387 cd "$startdir"
388 cd "$path"
390 # Allow running repo-add(8) on existing packages (#839)
391 create_package=1
392 pkglist=()
394 # Run pkgver before --packagelist (#500)
395 if (( run_pkgver )); then
396 as_user makepkg -od "${makepkg_common_args[@]}" >&2
399 # Check if the package is already built, but unlike makepkg, do not exit
400 # with an error when so. A warning avoids a queue of builds aborting because
401 # one member already exists.
402 if (( ! overwrite )) || (( dry_run )); then
403 exists=()
405 while IFS=':' read -r pkgbase pkgpath; do
406 if [[ -f $pkgpath ]]; then
407 (( dry_run )) && printf '%s:%s:%s\n' exist "$pkgbase" "file://$pkgpath"
408 exists+=("$pkgpath")
409 else
410 (( dry_run )) && printf '%s:%s:%s\n' build "$pkgbase" "file://$pkgpath"
412 # pkgbase may differ from pkgname; prefix to package path with --full
413 done < <(as_user PKGDEST="$db_root" aur build--pkglist --full "${pkglist_args[@]}")
415 # Preserve the exit status from aur-build--pkglist (#671)
416 wait "$!"
418 if (( dry_run )); then
419 continue
422 if [[ ${exists[*]} ]]; then
423 warning '%s: skipping existing package (use -f to overwrite)' "$argv0"
424 create_package=0
426 printf '%q\n' >&2 "${exists[@]}"
427 pkglist=("${exists[@]}")
431 if (( create_package )); then
432 if (( chroot )); then
433 if (( ${#makechrootpkg_args[@]} )); then
434 chroot_args+=(--cargs "$(args_csv "${makechrootpkg_args[@]}")")
436 if (( ${#makechrootpkg_makepkg_args[@]} )); then
437 chroot_args+=(--margs "$(args_csv "${makechrootpkg_makepkg_args[@]}")")
439 PKGDEST="$var_tmp" run_msg 2 aur chroot --build "${chroot_args[@]}"
440 else
441 PKGDEST="$var_tmp" LOGDEST=${LOGDEST:-$PWD} \
442 run_msg 3 as_user makepkg "${makepkg_common_args[@]}" "${makepkg_args[@]}"
445 cd "$var_tmp"
446 pkglist=(!(*.sig)) # discard makepkg --sign from package list (#410)
447 else
448 cd "$var_tmp"
449 # pkglist has paths to $db_root/<pkg>
452 # Sign any packages without signatures, even if the packages are existing.
453 # This is done in the temporary directory (write-access for build user).
454 siglist=()
456 for p in "${pkglist[@]}"; do
457 # Package basename (equals $p if create_package=1)
458 p_base=${p##*/}
460 # Signature from makepkg --sign
461 if [[ -f $p_base.sig ]]; then
462 siglist+=("$p_base".sig)
464 # Skipped package build with signature
465 elif [[ -f $db_root/$p_base.sig ]] && [[ ! -f $p_base ]]; then
466 printf >&2 '%s: existing signature file %q\n' "$argv0" "$db_root/$p_base.sig"
468 # No candidate signature, generate one
469 elif (( sign_pkg )); then
470 as_user gpg "${gpg_args[@]}" --output "$p_base".sig "$p"
471 printf >&2 '%s: created signature file %q\n' "$argv0" "$p_base".sig
472 siglist+=("$p_base".sig)
474 done
476 if (( ${#siglist[@]} )); then
477 mv -f "${siglist[@]}" "$db_root"
479 if (( create_package )); then
480 mv -f "${pkglist[@]}" "$db_root"
482 if [[ -v results_file ]]; then
483 printf "build:file://$db_root/%s\n" "${pkglist[@]}" | as_user tee -a "$results_file" >/dev/null
487 # Update database
488 cd "$db_root"
489 as_user LANG=C repo-add "${repo_add_args[@]}" "$db_path" "${pkglist[@]}"
491 if (( chroot )) || (( no_sync )); then
492 continue
493 else
494 replaces=$(grep -Fxf <(db_replaces "$db_path") <(pacman -Qq) | paste -s -d, -)
496 sudo pacman -Fy --config="$tmp"/local.conf
497 sudo pacman -Syu --config="$tmp"/local.conf --ignore="$replaces" --noconfirm
499 done
501 exec {fd}<&-
503 # vim: set et sw=4 sts=4 ft=sh: