485 lines
15 KiB
Bash
Executable File
485 lines
15 KiB
Bash
Executable File
#!/bin/bash
|
|
# aur-build - build packages to a local repository
|
|
[[ -v AUR_DEBUG ]] && set -o xtrace
|
|
set -o errexit
|
|
shopt -s extglob
|
|
argv0=build
|
|
# Reset path when running elevated (#979)
|
|
[[ $UID == 0 ]] && PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
machine=$(uname -m)
|
|
startdir=$PWD
|
|
PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
|
|
|
# default options
|
|
chroot=0 no_sync=0 overwrite=0 sign_pkg=0 run_pkgver=0 dry_run=0 truncate=1
|
|
|
|
# default arguments (empty)
|
|
chroot_args=() pacconf_args=() repo_args=() repo_add_args=() pkglist_args=()
|
|
makepkg_args=() makechrootpkg_makepkg_args=() makepkg_common_args=()
|
|
|
|
# default arguments
|
|
gpg_args=(--detach-sign --no-armor --batch)
|
|
makechrootpkg_args=(-cu) # -c to clean working copy, -u to sync local repository builds
|
|
|
|
args_csv() {
|
|
# shellcheck disable=SC2155
|
|
local str=$(printf '%s,' "$@")
|
|
printf '%s' "${str%,}"
|
|
}
|
|
|
|
diag_moved_packages() {
|
|
# Print diagnostic on non-moved packages (#794)
|
|
cat <<EOF >&2
|
|
Note:
|
|
aur-build encountered an error before moving packages to the local repository.
|
|
This may happen when signing built packages with gpg (aur build --sign),
|
|
or with certain makepkg errors.
|
|
|
|
The following files were preserved:
|
|
EOF
|
|
#shellcheck disable=SC2030
|
|
realpath -z -- "$@" | while read -rd ''; do
|
|
printf '%8s%s\n' ' ' "$REPLY"
|
|
done
|
|
}
|
|
|
|
diag_pacman_conf() {
|
|
cat <<EOF >&2
|
|
Error:
|
|
aur-build could not find a pacman.conf(5) file for container usage. Before
|
|
using --chroot, make sure this file is created and valid. See OPTIONS in
|
|
aur-build(1) for configuration details.
|
|
|
|
The following file path was checked:
|
|
EOF
|
|
printf '%8s%s\n' ' ' "$1"
|
|
}
|
|
|
|
# Allow to drop permissions for commands as needed (#907)
|
|
as_user() {
|
|
local USER HOME SHELL
|
|
|
|
if [[ $UID == 0 ]] && [[ -v build_user ]]; then
|
|
# runuser --pty messes up the terminal with AUR_DEBUG set, use setpriv(1)
|
|
# and replicate the runuser(1) behavior for setting the environment
|
|
{ IFS= read -r USER
|
|
IFS= read -r HOME
|
|
IFS= read -r SHELL
|
|
} < <(getent passwd "$build_user" | awk -F: '{printf("%s\n%s\n%s\n", $1, $6, $7); }')
|
|
|
|
setpriv --reuid "$build_user" --regid "$build_user" --init-groups \
|
|
env USER="$USER" HOME="$HOME" LOGNAME="$USER" SHELL="$SHELL" -- "$@"
|
|
else
|
|
env -- "$@"
|
|
fi
|
|
}
|
|
|
|
run_msg() {
|
|
printf >&2 'Running %s\n' "${*:$1}"
|
|
"${@:2}"
|
|
}
|
|
|
|
trap_exit() {
|
|
if [[ ! -v AUR_DEBUG ]]; then
|
|
rm -rf -- "$tmp"
|
|
|
|
# Only remove package directory if all files were moved (#593)
|
|
if ! rm -df -- "$var_tmp"; then
|
|
diag_moved_packages "$var_tmp"/*
|
|
fi
|
|
else
|
|
printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$tmp"
|
|
printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$var_tmp"
|
|
fi
|
|
}
|
|
|
|
usage() {
|
|
plain >&2 'usage: %s [-acfNS] [-d repo] [--root path] [--margs makepkg_arg...]' "$argv0"
|
|
exit 1
|
|
}
|
|
|
|
source /usr/share/makepkg/util/message.sh
|
|
source /usr/share/makepkg/util/parseopts.sh
|
|
|
|
if [[ ! -v NO_COLOR ]] && [[ ! -v AUR_DEBUG ]]; then
|
|
[[ -t 2 ]] && colorize
|
|
fi
|
|
|
|
## option parsing
|
|
opt_short='a:d:D:U:AcCfnrsvLNRST'
|
|
opt_long=('arg-file:' 'chroot' 'database:' 'force' 'root:' 'sign' 'gpg-sign'
|
|
'verify' 'directory:' 'no-sync' 'pacman-conf:' 'remove' 'pkgver'
|
|
'rmdeps' 'no-confirm' 'no-check' 'ignore-arch' 'log' 'new'
|
|
'makepkg-conf:' 'bind:' 'bind-rw:' 'prevent-downgrade' 'temp'
|
|
'syncdeps' 'clean' 'namcap' 'checkpkg' 'user:' 'makepkg-args:'
|
|
'margs:' 'buildscript:' 'dry-run')
|
|
opt_hidden=('dump-options' 'ignorearch' 'noconfirm' 'nocheck' 'nosync' 'repo:'
|
|
'results:' 'results-append:')
|
|
|
|
if ! parseopts "$opt_short" "${opt_long[@]}" "${opt_hidden[@]}" -- "$@"; then
|
|
usage
|
|
fi
|
|
set -- "${OPTRET[@]}"
|
|
|
|
unset build_user db_name db_path db_root makepkg_conf pacman_conf results_file queue
|
|
while true; do
|
|
case "$1" in
|
|
# build options
|
|
-a|--arg-file)
|
|
shift; queue=$1 ;;
|
|
-f|--force)
|
|
overwrite=1 ;;
|
|
-c|--chroot)
|
|
chroot=1 ;;
|
|
-d|--database|--repo)
|
|
shift; db_name=$1
|
|
repo_args+=(--repo "$1") ;;
|
|
--buildscript)
|
|
shift; makepkg_common_args+=(-p "$1")
|
|
pkglist_args+=(-p "$1") ;;
|
|
--dry-run)
|
|
dry_run=1 ;;
|
|
--nosync|--no-sync)
|
|
no_sync=1 ;;
|
|
--makepkg-conf)
|
|
shift; makepkg_conf=$1 ;;
|
|
--pacman-conf)
|
|
shift; pacman_conf=$1 ;;
|
|
--pkgver)
|
|
run_pkgver=1; makepkg_args+=(--noextract) ;;
|
|
--root)
|
|
shift; db_root=$1
|
|
repo_args+=(--root "$1") ;;
|
|
-S|--sign|--gpg-sign)
|
|
sign_pkg=1; repo_add_args+=(-s) ;;
|
|
# chroot options
|
|
-D|--directory)
|
|
shift; chroot_args+=(-D "$1") ;;
|
|
--bind)
|
|
shift; makechrootpkg_args+=(-D "$1") ;;
|
|
--bind-rw)
|
|
shift; makechrootpkg_args+=(-d"$1") ;;
|
|
-N|--namcap)
|
|
makechrootpkg_args+=(-n) ;;
|
|
--checkpkg)
|
|
makechrootpkg_args+=(-C) ;;
|
|
-T|--temp)
|
|
makechrootpkg_args+=(-T) ;;
|
|
-U|--user)
|
|
shift; build_user=$1
|
|
makechrootpkg_args+=(-U "$1") ;;
|
|
# makepkg options (common)
|
|
-A|--ignorearch|--ignore-arch)
|
|
makepkg_common_args+=(--ignorearch)
|
|
makechrootpkg_makepkg_args+=(--ignorearch) ;;
|
|
-n|--noconfirm|--no-confirm)
|
|
makepkg_common_args+=(--noconfirm) ;;
|
|
-r|--rmdeps)
|
|
makepkg_common_args+=(--rmdeps) ;;
|
|
-s|--syncdeps)
|
|
makepkg_common_args+=(--syncdeps) ;;
|
|
# makepkg options (build)
|
|
-C|--clean)
|
|
makepkg_args+=(--clean) ;;
|
|
-L|--log)
|
|
makepkg_args+=(--log) ;;
|
|
--nocheck|--no-check)
|
|
makepkg_args+=(--nocheck)
|
|
makechrootpkg_makepkg_args+=(--nocheck) ;;
|
|
--makepkg-args|--margs)
|
|
shift; IFS=, read -a arg -r <<< "$1"
|
|
makepkg_args+=("${arg[@]}")
|
|
makechrootpkg_makepkg_args+=("${arg[@]}") ;;
|
|
# repo-add options
|
|
-v|--verify)
|
|
repo_add_args+=(-v) ;;
|
|
-R|--remove)
|
|
repo_add_args+=(-R) ;;
|
|
--new)
|
|
repo_add_args+=(-n) ;;
|
|
--prevent-downgrade)
|
|
repo_add_args+=(-p) ;;
|
|
# other options
|
|
--results)
|
|
shift; results_file=$1 ;;
|
|
--results-append)
|
|
shift; results_file=$1; truncate=0 ;;
|
|
--dump-options)
|
|
printf -- '--%s\n' "${opt_long[@]}" ${AUR_DEBUG+"${opt_hidden[@]}"}
|
|
printf -- '%s' "${opt_short}" | sed 's/.:\?/-&\n/g'
|
|
exit ;;
|
|
--) shift; break ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# mollyguard for makepkg
|
|
if [[ $UID == 0 ]] && ! { [[ -v build_user ]] && [[ -v AUR_ASROOT ]]; }; then
|
|
warning 'aur-%s is not meant to be run as root.' "$argv0"
|
|
warning 'To proceed anyway, set the %s variable and specify --user <username>.' 'AUR_ASROOT'
|
|
exit 1
|
|
fi
|
|
|
|
# Assign environment variables
|
|
: "${db_ext=$AUR_DBEXT}" "${db_root=$AUR_DBROOT}" "${db_repo=$AUR_REPO}"
|
|
|
|
# Custom makepkg command
|
|
if [[ $MAKEPKG ]]; then
|
|
# shellcheck disable=SC2086
|
|
makepkg() { command -- $MAKEPKG "$@"; }
|
|
fi
|
|
|
|
# Custom elevation command
|
|
if [[ $UID == 0 ]]; then
|
|
sudo() { command -- "$@"; }
|
|
|
|
elif [[ $AUR_PACMAN_AUTH ]]; then
|
|
# shellcheck disable=SC2086
|
|
sudo() { command -- $AUR_PACMAN_AUTH "$@"; }
|
|
fi
|
|
|
|
# shellcheck disable=SC2174
|
|
mkdir -pm 0700 "${TMPDIR:-/tmp}/aurutils-$UID"
|
|
tmp=$(mktemp -d --tmpdir "aurutils-$UID/$argv0.XXXXXXXX")
|
|
|
|
# Only $var_tmp should be writeable by the build user (PKGDEST, signatures)
|
|
# If UID > 0 and build_user is unset, this is equivalent to $tmp above
|
|
if [[ -v build_user ]]; then
|
|
var_tmp_uid=$(id -u "$build_user")
|
|
else
|
|
var_tmp_uid=$UID
|
|
fi
|
|
|
|
# shellcheck disable=SC2174
|
|
as_user mkdir -pm 0700 "${TMPDIR:-/var/tmp}/aurutils-$var_tmp_uid"
|
|
var_tmp=$(as_user mktemp -d --tmpdir="${TMPDIR:-/var/tmp/}" "aurutils-$var_tmp_uid/$argv0.XXXXXXXX")
|
|
|
|
trap 'trap_exit' EXIT
|
|
trap 'exit' INT
|
|
|
|
if (( chroot )); then
|
|
# Change the default /usr/share/devtools/pacman-extra.conf in aur-chroot to
|
|
# /etc/aurutils/pacman-<repo>.conf or /etc/aurutils/pacman-<uname>.conf in
|
|
# aur-build, and pass it on to aur-chroot (#824, #846)
|
|
pacman_conf=${pacman_conf-/etc/aurutils/pacman-${db_name:-$machine}.conf}
|
|
chroot_args+=(--pacman-conf "$pacman_conf")
|
|
|
|
# Early check for availability of pacman.conf (#783)
|
|
if [[ ! -f $pacman_conf ]]; then
|
|
diag_pacman_conf "$pacman_conf"
|
|
exit 2
|
|
fi
|
|
|
|
# The default path is /usr/share/devtools/makepkg-<uname.conf>, which is
|
|
# copied to <container path>/etc/makepkg.conf by arch-nspawn.
|
|
if [[ -v makepkg_conf ]]; then
|
|
chroot_args+=(--makepkg-conf "$makepkg_conf")
|
|
else
|
|
# When makechrootpkg calls makepkg inside the container, it uses the above
|
|
# makepkg.conf for most variables including PKGEXT. (makepkg --packagelist)
|
|
makepkg_conf=$(aur chroot --path "${chroot_args[@]}")/etc/makepkg.conf
|
|
unset PKGEXT
|
|
fi
|
|
fi
|
|
|
|
# Propagate makepkg and pacman configuration to other tools. This needs to be
|
|
# done BEFORE retrieving the local repository name/root.
|
|
if [[ -v pacman_conf ]]; then
|
|
pacconf_args+=(--config "$pacman_conf")
|
|
|
|
if [[ ! -f $pacman_conf ]]; then
|
|
error '%s: %s: not a regular file' "$argv0" "$pacman_conf"
|
|
exit 2
|
|
fi
|
|
fi
|
|
|
|
if [[ -v makepkg_conf ]]; then
|
|
makepkg_common_args+=(--config "$makepkg_conf")
|
|
pkglist_args+=(--config "$makepkg_conf")
|
|
|
|
if [[ -v makepkg_conf ]] && [[ ! -f $makepkg_conf ]]; then
|
|
error '%s: %s: not a regular file' "$argv0" "$makepkg_conf"
|
|
exit 2
|
|
fi
|
|
fi
|
|
|
|
# Automatically choose the local repository based on the pacman configuration.
|
|
if [[ $db_name ]] && [[ $db_root ]]; then
|
|
db_path=$db_root/$db_name.${db_ext:-db}
|
|
db_path=$(realpath -- "$db_path")
|
|
else
|
|
{ IFS=: read -r _ db_name
|
|
IFS=: read -r _ db_root
|
|
IFS=: read -r _ db_path # canonicalized
|
|
} < <(as_user aur repo --status "${repo_args[@]}" "${pacconf_args[@]}")
|
|
wait "$!"
|
|
fi
|
|
db_root=$(realpath -- "$db_root")
|
|
|
|
# Resolve symbolic link to database.
|
|
if ! [[ -f $db_path ]]; then
|
|
error '%s: %s: not a regular file' "$argv0" "$db_path"
|
|
exit 2
|
|
|
|
# Check if build user can write to database
|
|
elif ! as_user test -w "$db_path"; then
|
|
error '%s: %s: permission denied' "$argv0" "$db_path"
|
|
exit 13
|
|
fi
|
|
|
|
# Write successfully built packages to file (#437, #980)
|
|
if [[ -v results_file ]]; then
|
|
results_file=$(realpath -- "$results_file")
|
|
(( truncate )) && true | as_user tee "$results_file"
|
|
fi
|
|
|
|
if (( chroot )); then
|
|
# Update pacman and makepkg configuration for the chroot build
|
|
# queue. A full system upgrade is run on the /root container to
|
|
# avoid lenghty upgrades for makechrootpkg -u.
|
|
run_msg 2 aur chroot --create --update "${chroot_args[@]}"
|
|
fi
|
|
|
|
if [[ -v queue ]]; then
|
|
exec {fd}< "$queue"
|
|
else
|
|
exec {fd}< <(printf '\n')
|
|
fi
|
|
|
|
# Early consistency check for signed database
|
|
if (( ! sign_pkg )); then
|
|
db_sigs=("$db_root/$db_name".sig "$db_root/$db_name".files.sig)
|
|
|
|
if [[ -f ${db_sigs[0]} ]]; then
|
|
error '%s: database signature found, but signing is disabled' "$argv0"
|
|
|
|
printf '%q\n' >&2 "${db_sigs[@]}"
|
|
exit 1
|
|
fi
|
|
|
|
elif [[ -v GPGKEY ]]; then
|
|
as_user gpg --list-keys "$GPGKEY"
|
|
gpg_args+=(-u "$GPGKEY")
|
|
fi
|
|
|
|
while IFS= read -ru "$fd" path; do
|
|
# Use two cd calls to handle absolute paths in --arg-file
|
|
cd "$startdir"
|
|
cd "$path"
|
|
|
|
# Allow running repo-add(8) on existing packages (#839)
|
|
create_package=1
|
|
pkglist=()
|
|
|
|
# Run pkgver before --packagelist (#500)
|
|
if (( run_pkgver )); then
|
|
as_user makepkg -od "${makepkg_common_args[@]}" >&2
|
|
fi
|
|
|
|
# Check if the package is already built, but unlike makepkg, do not exit
|
|
# with an error when so. A warning avoids a queue of builds aborting because
|
|
# one member already exists.
|
|
if (( ! overwrite )) || (( dry_run )); then
|
|
exists=()
|
|
|
|
while IFS=':' read -r pkgbase pkgpath; do
|
|
if [[ -f $pkgpath ]]; then
|
|
(( dry_run )) && printf '%s:%s:%s\n' exist "$pkgbase" "file://$pkgpath"
|
|
exists+=("$pkgpath")
|
|
else
|
|
(( dry_run )) && printf '%s:%s:%s\n' build "$pkgbase" "file://$pkgpath"
|
|
fi
|
|
# pkgbase may differ from pkgname; prefix to package path with --full
|
|
done < <(as_user PKGDEST="$db_root" aur build--pkglist --full "${pkglist_args[@]}")
|
|
|
|
# Preserve the exit status from aur-build--pkglist (#671)
|
|
wait "$!"
|
|
|
|
if (( dry_run )); then
|
|
continue
|
|
fi
|
|
|
|
if [[ ${exists[*]} ]]; then
|
|
warning '%s: skipping existing package (use -f to overwrite)' "$argv0"
|
|
create_package=0
|
|
|
|
printf '%q\n' >&2 "${exists[@]}"
|
|
pkglist=("${exists[@]}")
|
|
fi
|
|
fi
|
|
|
|
if (( create_package )); then
|
|
if (( chroot )); then
|
|
if (( ${#makechrootpkg_args[@]} )); then
|
|
chroot_args+=(--cargs "$(args_csv "${makechrootpkg_args[@]}")")
|
|
fi
|
|
if (( ${#makechrootpkg_makepkg_args[@]} )); then
|
|
chroot_args+=(--margs "$(args_csv "${makechrootpkg_makepkg_args[@]}")")
|
|
fi
|
|
PKGDEST="$var_tmp" run_msg 2 aur chroot --build "${chroot_args[@]}"
|
|
else
|
|
PKGDEST="$var_tmp" LOGDEST=${LOGDEST:-$PWD} \
|
|
run_msg 3 as_user makepkg "${makepkg_common_args[@]}" "${makepkg_args[@]}"
|
|
fi
|
|
|
|
cd "$var_tmp"
|
|
pkglist=(!(*.sig)) # discard makepkg --sign from package list (#410)
|
|
else
|
|
cd "$var_tmp"
|
|
# pkglist has paths to $db_root/<pkg>
|
|
fi
|
|
|
|
# Sign any packages without signatures, even if the packages are existing.
|
|
# This is done in the temporary directory (write-access for build user).
|
|
siglist=()
|
|
|
|
for p in "${pkglist[@]}"; do
|
|
# Package basename (equals $p if create_package=1)
|
|
p_base=${p##*/}
|
|
|
|
# Signature from makepkg --sign
|
|
if [[ -f $p_base.sig ]]; then
|
|
siglist+=("$p_base".sig)
|
|
|
|
# Skipped package build with signature
|
|
elif [[ -f $db_root/$p_base.sig ]] && [[ ! -f $p_base ]]; then
|
|
printf >&2 '%s: existing signature file %q\n' "$argv0" "$db_root/$p_base.sig"
|
|
|
|
# No candidate signature, generate one
|
|
elif (( sign_pkg )); then
|
|
as_user gpg "${gpg_args[@]}" --output "$p_base".sig "$p"
|
|
printf >&2 '%s: created signature file %q\n' "$argv0" "$p_base".sig
|
|
siglist+=("$p_base".sig)
|
|
fi
|
|
done
|
|
|
|
if (( ${#siglist[@]} )); then
|
|
mv -f "${siglist[@]}" "$db_root"
|
|
fi
|
|
if (( create_package )); then
|
|
mv -f "${pkglist[@]}" "$db_root"
|
|
|
|
if [[ -v results_file ]]; then
|
|
printf "build:file://$db_root/%s\n" "${pkglist[@]}" | as_user tee -a "$results_file" >/dev/null
|
|
fi
|
|
fi
|
|
|
|
# Update database
|
|
cd "$db_root"
|
|
as_user LANG=C repo-add "${repo_add_args[@]}" "$db_path" "${pkglist[@]}"
|
|
|
|
if (( chroot )) || (( no_sync )); then
|
|
continue
|
|
else
|
|
# Helper which can be used for e.g. sudoers rules (#1012)
|
|
# Like `makepkg --syncdeps`, this affects the host and so uses the host
|
|
# pacman configuration. --pacman-conf (which may also point to
|
|
# a world-writeable file) is not applied.
|
|
sudo aur build--sync --repo "$db_name"
|
|
fi
|
|
done
|
|
|
|
exec {fd}<&-
|
|
|
|
# vim: set et sw=4 sts=4 ft=sh:
|