Skip to content

Instantly share code, notes, and snippets.

@AladW
Created February 25, 2020 23:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AladW/c282bca477038177f1eb9d1ff479b2ce to your computer and use it in GitHub Desktop.
Save AladW/c282bca477038177f1eb9d1ff479b2ce to your computer and use it in GitHub Desktop.
WIP
#!/bin/bash
# aur-sync - download and build AUR packages automatically
[[ -v AUR_DEBUG ]] && set -o xtrace
set -o errexit
argv0=sync
XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
AURDEST=${AURDEST:-$XDG_CACHE_HOME/aurutils/$argv0}
PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
# default arguments
build_args=(--clean --syncdeps)
build_extra_args=()
repo_args=()
# default options (enabled)
build=1 chkver_depth=2 download=1 view=1 provides=0 graph=1 log_fmt=diff
# default options (disabled)
rotate=0 update=0
lib32() {
awk -v arch="$(uname -m)" '{
if(arch == "i686") {
gsub(/^lib32-/,"")
gsub(/^gcc-multilib$/,"")
}
print
}'
}
# files: $1 pkgname\tpkgbase $2 pkgname (order by $2)
select_pkgbase() {
awk 'NR == FNR {
map[$1] = $2
next
}
$1 in map {
base = map[$1]
# only print pkgbase on first occurrence
if (base in seen) {
next
} else {
print base
seen[base]
}
}' "$@"
}
# fields: $1 pkgname, $2 depends[<>=]
tr_ver() {
awk -F'[<>=]' '{print $1}'
}
complement() {
# empty set should not return 1
grep -Fxvf "$@" || return $(( $? - 1 ))
}
order() {
cut -f1,2 depends | tr_ver | tsort
}
trap_exit() {
if [[ ! -v AUR_DEBUG ]]; then
rm -rf -- "$tmp" "$tmp_view"
else
printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$tmp"
printf >&2 'AUR_DEBUG: %s: temporary files at %s\n' "$argv0" "$tmp_view"
fi
}
usage() {
plain >&2 'usage: %s [-d repo] [-ALcfnPprRSTuv] pkgname...' "$argv0"
exit 1
}
source /usr/share/makepkg/util/util.sh
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
# mollyguard for makepkg
if (( UID == 0 )) && [[ ! -v AUR_ASROOT ]]; then
warning 'aur-%s is not meant to be run as root.' "$argv0"
warning 'To proceed anyway, set the %s variable.' 'AUR_ASROOT'
exit 1
fi
opt_short='B:d:D:AcfLnpPrRSTuv'
opt_long=('bind:' 'bind-rw:' 'database:' 'repo:' 'directory:' 'ignore:'
'root:' 'makepkg-conf:' 'pacman-conf:' 'chroot' 'continue'
'force' 'ignore-arch' 'log' 'no-confirm' 'no-ver' 'no-graph'
'no-ver-argv' 'no-view' 'print' 'provides' 'rm-deps'
'sign' 'temp' 'upgrades' 'pkgver' 'rebuild' 'rebuild-tree'
'build-command:' 'ignore-file:' 'remove' 'provides-from:'
'new' 'prevent-downgrade' 'verify' 'format:')
opt_hidden=('dump-options' 'allan' 'ignorearch' 'ignorefile:'
'noconfirm' 'nover' 'nograph' 'nover-argv' 'noview'
'rebuildtree' 'rmdeps' 'gpg-sign')
if ! parseopts "$opt_short" "${opt_long[@]}" "${opt_hidden[@]}" -- "$@"; then
usage
fi
set -- "${OPTRET[@]}"
unset pkg pkg_i repo repo_p ignore_file
while true; do
case "$1" in
# sync options
--allan)
rotate=1 ;;
--continue)
download=0 ;;
--format)
shift; log_fmt=$1 ;;
--ignore)
shift; IFS=, read -a pkg -r <<< "$1"
pkg_i+=("${pkg[@]}")
unset pkg ;;
--ignorefile|--ignore-file)
shift; ignore_file=$1 ;;
--nograph|--no-graph)
graph=0 ;;
--nover|--no-ver)
chkver_depth=0 ;;
--nover-argv|--no-ver-argv)
chkver_depth=1 ;;
--noview|--no-view)
view=0 ;;
-P|--provides)
provides=1 ;;
--provides-from)
shift; IFS=, read -a repo -r <<< "$1"
repo_p+=("${repo[@]}")
unset repo ;;
-p|--print)
build=0 ;;
--rebuild)
build_args+=(-f); chkver_depth=1 ;;
--rebuildtree|--rebuild-tree)
build_args+=(-f); chkver_depth=0 ;;
-u|--upgrades)
update=1 ;;
# database options
-d|--database|--repo)
shift; repo_args+=(-d "$1") ;;
--root)
shift; repo_args+=(-r "$1") ;;
# build options
-B|--build-command)
shift; build_args+=(--build-command "$1") ;;
-c|--chroot)
build_args+=(--chroot) ;;
-f|--force)
build_args+=(--force) ;;
--makepkg-conf)
shift; build_args+=(--makepkg-conf "$1") ;;
--pacman-conf)
shift; build_args+=(--pacman-conf "$1")
repo_args+=(--pacman-conf "$1") ;;
--pkgver)
build_args+=(--pkgver) ;;
-S|--sign|--gpg-sign)
build_args+=(--sign) ;;
# build options (devtools)
-D|--directory)
shift; build_args+=(--directory "$1") ;;
--bind)
shift; build_args+=(--bind "$1") ;;
--bind-rw)
shift; build_args+=(--bind-rw "$1") ;;
-T|--temp)
build_args+=(-T) ;;
# build options (makepkg)
-A|--ignorearch|--ignore-arch)
build_args+=(--ignorearch) ;;
-L|--log)
build_args+=(--log) ;;
-n|--noconfirm|--no-confirm)
build_args+=(--noconfirm) ;;
-r|--rmdeps|--rm-deps)
build_args+=(--rmdeps) ;;
# build options (repo-add)
-R|--remove)
build_args+=(--remove) ;;
-v|--verify)
build_args+=(--verify) ;;
--prevent-downgrade)
build_args+=(--prevent-downgrade) ;;
--new)
build_args+=(--new) ;;
# other options
--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
tmp=$(mktemp -d --tmpdir "aurutils-$argv0.XXXXXXXX") || exit
tmp_view=$(mktemp -d --tmpdir "aurutils-$argv0-view.XXXXXXXX") || exit
trap 'trap_exit' EXIT
# Directory for git checksums (revisions viewed by the user)
view_db=$XDG_DATA_HOME/aurutils/view
mkdir -p "$view_db"
# Append contents of ignore file
if [[ -f ${ignore_file=$XDG_CONFIG_HOME/aurutils/sync/ignore} ]]; then
while IFS='#' read -r i _; do
[[ $i ]] && pkg_i+=("$i")
done < "$ignore_file"
fi
if (( rotate )); then
if { hash rot13 && target=$(aur pkglist | shuf -n 1); } 2>/dev/null; then
exec bash -c "{ aur \"$argv0\" -c \"$target\" && repo-elephant | rot13; } 2>&1 | rot13"
else
echo '?'; exit 16 # EBUSY
fi
fi
if ! (( $# + update )); then
error '%s: no targets specified' "$argv0"
exit 1
fi
mkdir -p "$AURDEST"
cd_safe "$tmp"
# Retrieve path to local repo (#448)
aur repo "${repo_args[@]}" --list --status-file=db >db_info
{ IFS= read -r db_name
IFS= read -r db_root
} <db
if [[ -w $db_root/$db_name.db ]]; then
msg >&2 'Using [%s] repository' "$db_name"
else
error '%s: %s: permission denied' "$argv0" "$db_root/$db_name.db"
exit 13
fi
{ if (( $# )); then
printf '%s\n' "$@"
fi
if (( update )); then
aur vercmp --quiet <db_info
fi
} >argv
if [[ -s argv ]]; then
# shellcheck disable=SC2094
# $1 pkgname $2 depends $3 pkgbase $4 pkgver
xargs -a argv -d'\n' aur depends --table >depends
else
plain >&2 "there is nothing to do"
exit
fi
# $1 pkgname $2 pkgbase $3 pkgver
cut -f2 --complement depends | sort -u >pkginfo
{ if (( ${#pkg_i[@]} )); then
warning "$argv0: ignoring %s package" "${pkg_i[@]}"
printf '%s\n' "${pkg_i[@]}"
fi
# Packages with equal or newer versions are taken as complement
# for the queue. If chkver_argv is enabled, packages on the
# command-line are excluded from this complement.
if (( chkver_depth )); then
# note: AUR cannot be queried by pkgbase (FS#57230)
cut -f1,3 pkginfo | aur vercmp -p db_info -c >current
# shellcheck disable=SC2002
case $chkver_depth in
1) cat current | complement argv ;;
2) cat current ;;
esac
fi
# Note: this uses pacman's copy of the repo (as used by makepkg -s)
if (( ${#repo_p[@]} )); then
cut -f1 pkginfo | complement argv | aur-repo-filter "${repo_p[@]/#/--database=}"
elif (( provides )); then
cut -f1 pkginfo | complement argv | aur repo-filter
fi
} >filter
# pkgname queue (AUR + repos)
if order depends >queue_0; then
tac queue_0 | lib32 | complement filter >queue_1
else
# input contains a loop
error '%s: invalid argument' "$argv0"
exit 22
fi
# pkgbase queue (AUR)
cut -f1,2 pkginfo | select_pkgbase - queue_1 >queue
if [[ -s queue ]]; then
cd_safe "$AURDEST"
else
plain >&2 "there is nothing to do"
exit
fi
if (( download )); then
msg >&2 "Retrieving package files"
xargs -a "$tmp"/queue -d'\n' aur fetch -S --results "$tmp"/fetch_results
while IFS=: read -r mode rev_old rev path; do
path=${path#file://} name=${path##*/}
case $mode in
clone)
;;
fetch)
if [[ ! -f $view_db/$name ]]; then
printf '%s\n' "$rev_old" > "$view_db/$name"
fi ;;
merge)
;;
reset)
;;
esac
done < "$tmp"/fetch_results
fi
if (( graph )); then
# Concatenate all SRCINFO files to avoid split aur-graph invocations.
while read -r pkg; do
[[ $pkg ]] && printf '%s/.SRCINFO\n' "$pkg"
done < "$tmp"/queue | xargs -d'\n' cat > "$tmp"/queue_info --
if ! aur graph "$tmp"/queue_info >/dev/null; then
error "%s: failed to verify dependency graph" "$argv0"
exit 1
fi
fi
if (( view )); then
# Avoid directory prefix in printed paths (#452)
viewer() ( cd "$1"; vifm -c 'set vifminfo=' -c 'view!' -c '0' - )
# Link build files in the queue (absolute links)
while read -r pkg; do
[[ $pkg ]] && printf '%s/%s\0' "$PWD" "$pkg"
done < "$tmp"/queue | xargs -0 ln -st "$tmp_view" --
git_log() {
local dir=$1 mode=$2 range=$3
git -C "$dir" --no-pager "$mode" --patch --stat "$range"
}
git_rev() {
local dir=$1 obj=$2
git -C "$dir" rev-parse --verify --quiet "$obj"
}
declare -A heads
while read -r pkg; do
head=$(git_rev "$pkg" HEAD)
heads[$pkg]=$head
if read -r view < "$view_db/$pkg" && rev_view=$(git_rev "$pkg" "$view"); then
if [[ $rev_view != "$head" ]]; then
git_log "$pkg" "$log_fmt" "$rev_view..$head" > "$tmp_view/$pkg.$log_fmt"
fi
elif [[ $view ]]; then
error '%s: %s: invalid revision %s' "$argv0" "$pkg" "$view"
exit 22
else
: aur_view unavailable
fi
done < "$tmp"/queue
if [[ -v AUR_PAGER ]]; then
# shellcheck disable=SC2086
command -- $AUR_PAGER "$tmp_view"
else
{ # Print patch files
find "$tmp_view" -maxdepth 1 -type f
# Print build directories in dependency order
while read -r name; do
[[ $name ]] && find -L "$tmp_view/$name" -maxdepth 1
done < "$tmp"/queue
} | viewer "$tmp_view"
fi
# Update gitsums (viewer exited successfully)
while read -r pkg; do
# XXX check if heads[$pkg] is defined
printf '%s\n' "${heads[$pkg]}" > "$view_db/$pkg"
done < "$tmp"/queue
fi
if (( build )); then
aur build --arg-file "$tmp"/queue --database "$db_name" --root "$db_root" \
"${build_args[@]}" "${build_extra_args[@]}"
else
while read -r name; do
[[ $name ]] && printf '%s/%s\n' "$PWD" "$name"
done < "$tmp"/queue
fi
# vim: set et sw=4 sts=4 ft=sh:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment