diff options
author | bill-auger <mr.j.spam.me@gmail.com> | 2020-10-26 00:59:34 -0400 |
---|---|---|
committer | bill-auger <mr.j.spam.me@gmail.com> | 2020-10-26 02:08:06 -0400 |
commit | e5d02b363472083090c6e40df46629028407cc3d (patch) | |
tree | 3b72de94e3b7ce67b145259645432ace6f2d14f7 /pacpur | |
parent | 63d5ee70ab5d0daeff0e5455a462bcab16c9d95f (diff) |
rename script pacaur->pacpur
Diffstat (limited to 'pacpur')
-rwxr-xr-x | pacpur | 1888 |
1 files changed, 1888 insertions, 0 deletions
@@ -0,0 +1,1888 @@ +#!/bin/bash +# shellcheck source=/dev/null + +# +# pacpur: a PUR helper that minimizes user interaction +# + +shopt -s extglob nullglob + +export LC_COLLATE='C' +# gettext initialization +export TEXTDOMAIN='pacpur' TEXTDOMAINDIR='/usr/share/locale' + +declare -r version='4.8.6' + +# +# Config +# + +# Options +aur=0 asdeps=0 asexplicit=0 checkdeps=0 clean=0 color='' devel=0 downloadonly=0 help=0 +info=0 installpkg=0 needed=0 noconfirm=0 noedit=0 operation='' pace=0 pacQ=0 pacS=0 +rebuild=0 refresh=0 repo=0 search=0 upgrade=0 ver=0 +declare -i ccount=0 dcount=0 pac=0 +pacmanarg=() pacopts=() auropts=() makeopts+=() pkgs=() +export QUIET="${QUIET:-0}" + +# determine whether we have gettext and replace with noop if not +command -v gettext >/dev/null || gettext() { printf '%s\n' "$@";} + +# check if we have sudo, if not use su instead, but this isn't a good time +command -v sudo >/dev/null || + sudo() { + local params + printf -v params '%q ' "$@" + su root -c "${params}" + } + +# import libmakepkg +for lib in config option parseopts; do + source "${LIBRARY:=/usr/share/makepkg}/util/${lib}.sh" +done +load_makepkg_config +: "${PKGDEST:=}" "${CARCH:=}" +check_buildenv check n || checkdeps=1 + +## determine config location +XDG_CONFIG_DIRS="${XDG_CONFIG_DIRS:-/etc/xdg}" XDG_CONFIG_DIRS="${XDG_CONFIG_DIRS//://pacpur:}/pacpur:" +while read -rd: i; do [[ ! -d "${i}" ]] || { configdir="${i}"; break;} done <<<"${XDG_CONFIG_DIRS}" +declare -r tmpdir="${XDG_RUNTIME_DIR:-/tmp}" configdir="${configdir:-/etc/xdg/pacpur}" \ + userconfigdir="${XDG_CONFIG_HOME:-${HOME}/.config}/pacpur" \ + usercachedir="${XDG_CACHE_HOME:-${HOME}/.cache}/pacpur" + +# set default config variables +editor="${VISUAL:-${EDITOR:-vi}}" # build files editor +displaybuildfiles='diff' # display build files (none|diff|full) +silent=0 # silence output +sortby='name' # sort method (name|votes|popularity) +sortorder='ascending' # sort order (ascending|descending) +sudoloop=1 # prevent sudo timeout + +# set variables +declare -r pacman="${PACMAN:-pacman}" # pacman binary +declare -r clonedir="${PURDEST:-${usercachedir}}" # clone directory +pacman_conf=('pacman-conf') + +# source xdg config +[[ ! -r "${configdir}/config" ]] || source "${configdir}/config" +[[ ! -r "${userconfigdir}/config" ]] || source "${userconfigdir}/config" + +case "${silent}" in true) silent=1 ;; false) silent=0 ;; esac +case "${sudoloop}" in true) sudoloop=1;; false) sudoloop=0;; esac + +declare -r vcs='-@(cvs|svn|git|hg|bzr|darcs|daily*|nightly*)' # vcs package glob + +[[ -d "${clonedir}" && -w "${clonedir}" ]] || mkdir -p "${clonedir}" # setup clonedir + +# +# Functions +# + +ClassifyPkgs() { + local i noaurpkgs=() + # global aurpkgs repopkgs + ((! repo)) || repopkgs=("${pkgs[@]}") aurpkgs=() + ((! aur)) || aurpkgs=("${pkgs[@]#aur/}") repopkgs=() # search aur/pkgs in PUR + if ((! repo && ! aur)); then + for i in "${pkgs[@]}"; do + [[ "${i::4}" = 'aur/' ]] && aurpkgs+=("${i:4}") || noaurpkgs+=("${i}") # search aur/pkgs in PUR + done + if [[ "${noaurpkgs[*]}" ]]; then + while read -r i; do + [[ "${i}" != 'error: database not found: '* ]] || continue + i="${i#error: target not found: }" + [[ "${i}" != 'error: '* ]] || continue + [[ " ${noaurpkgs[*]} " = *' '+([a-zA-Z0-9.+-])"/${i} "* ]] || aurpkgs+=("${i}") + done < <(LC_ALL='C' "${pacman}" -Sp "${noaurpkgs[@]}" 2>&1 >/dev/null) + fi + mapfile -t repopkgs < <(CommArr -13 aurpkgs noaurpkgs) + fi +} + +Core() { + GetIgnoredPkgs + GetIgnoredGrps + ((! upgrade)) || UpgradeAur + IgnoreChecks + DepsSolver + IgnoreDepsCheck + ProviderChecks + ConflictChecks + ReinstallChecks + OutOfDateChecks + OrphanChecks + Prompt + MakePkgs +} + +UpgradeAur() { + local foreignpkgs allaurpkgs i + # global aurpkgs + info $"%sStarting PUR upgrade...%s" "${colorW}" "${reset}" + mapfile -t aurpkgs < <(auracle sync -q) # use auracle to find out of date PUR packages + + mapfile -t foreignpkgs < <("${pacman}" -Qmq) + SetInfo "${foreignpkgs[@]}" + mapfile -t allaurpkgs < <(GetInfo Name) + + # foreign packages check + while read -r i; do + warn $"%s is %snot present%s in PUR -- skipping" "${colorW}${i}${reset}" "${colorY}" "${reset}" + done < <(CommArr -13 allaurpkgs foreignpkgs) + + # add devel packages + if ((devel)); then + needed=1 + for i in "${allaurpkgs[@]}"; do + [[ "${i}" != *${vcs} || " ${aurpkgs[*]} " = *" ${i} "* ]] || aurpkgs+=("${i}") + done + fi + + aurpkgs+=("${pkgs[@]}") + + SortArrAssign aurpkgs # avoid possible duplicate + + NothingToDo "${aurpkgs[@]}" +} + +IgnoreChecks() { + local checkaurpkgs checkaurpkgsver checkaurgrps i j + # global aurpkgs rmaurpkgs + [[ "${ignoredpkgs[*]:-}${ignoredgrps[*]:-}" ]] || return + + # remove PUR pkgs versioning + aurpkgsnover=("${aurpkgs[@]%%[><=]*}") + + # check targets + SetInfo "${aurpkgsnover[@]}" + mapfile -t checkaurpkgs < <(GetInfo Name) + mapfile -t errdeps < <(CommArr -3 aurpkgsnover checkaurpkgs) + aurpkgsnover=() + + mapfile -t checkaurpkgsver < <(GetInfo Version) + for i in "${!checkaurpkgs[@]}"; do + [[ "${checkaurpkgs[i]}" != *${vcs} ]] || checkaurpkgsver[i]=$"latest" + isignored=0 + if [[ "${ignoredpkgs[*]}" && " ${ignoredpkgs[*]} " = *" ${checkaurpkgs[i]} "* ]]; then + isignored=1 + elif [[ "${ignoredgrps[*]}" ]]; then + mapfile -t checkaurgrps < <(GetInfo Groups "${checkaurpkgs[i]}") + MapfileAdd checkaurgrps < <(expac -Ql'\n' '%G' "${checkaurpkgs[i]}") + for j in "${checkaurgrps[@]}"; do + [[ " ${ignoredgrps[*]} " != *" ${j} "* ]] || isignored=1 + done + fi + + if ((isignored)); then + if ((! upgrade)); then + if ((! noconfirm)); then + if ! Proceed y $"%s is in IgnorePkg/IgnoreGroup. Install anyway?" "${checkaurpkgs[i]}"; then + warn $"skipping target: %s" "${colorW}${checkaurpkgs[i]}${reset}" + rmaurpkgs+=("${checkaurpkgs[i]}") + continue + fi + else + warn $"skipping target: %s" "${colorW}${checkaurpkgs[i]}${reset}" + rmaurpkgs+=("${checkaurpkgs[i]}") + continue + fi + else + warn $"%s: ignoring package upgrade (%s => %s)" "${colorW}${checkaurpkgs[i]}${reset}" \ + "${colorR}$(expac -Q '%v' "${checkaurpkgs[i]}")${reset}" \ + "${colorG}${checkaurpkgsver[i]}${reset}" + rmaurpkgs+=("${checkaurpkgs[i]}") + continue + fi + fi + aurpkgsnover+=("${checkaurpkgs[i]}") + done + + aurpkgs=("${aurpkgsnover[@]}") + NothingToDo "${aurpkgs[@]}" +} + +DepsSolver() { + local i j aurpkgsconflicts repodeps=() + # global aurpkgs aurpkgsnover aurpkgsproviders aurdeps deps errdeps + # global errdepsnover foreignpkgs depsAname depsAver depsAood depsQver + printf '%s\n' $"resolving dependencies..." + + # remove PUR pkgs versioning + aurpkgsnover=("${aurpkgs[@]%%[><=]*}") + + # set unversioned info + SetInfo "${aurpkgsnover[@]}" + + # set targets providers + aurpkgsproviders=("${aurpkgsnover[@]}") + MapfileAdd aurpkgsproviders < <(GetInfo Provides) + aurpkgsproviders=("${aurpkgsproviders[@]%%[><=]*}") + + # check targets conflicts + mapfile -t aurpkgsconflicts < <(GetInfo Conflicts) + if [[ "${aurpkgsconflicts[*]}" ]]; then + aurpkgsconflicts=("${aurpkgsconflicts[@]%%[><=]*}") + mapfile -t aurpkgsconflicts < <(CommArr -12 aurpkgsproviders aurpkgsconflicts) + for i in "${aurpkgsconflicts[@]}"; do + [[ " ${aurpkgsnover[*]} " = *" ${i} "* ]] || continue + [[ " $(GetInfo Conflicts "${i}") " != *" ${i} "* ]] || continue + fail $"unresolvable package conflicts detected" + error $"failed to prepare transaction (conflicting dependencies: %s)" "${i}" \ + "${E_INSTALL_DEPS_FAILED}" + done + fi + + deps=("${aurpkgsnover[@]}") + + [[ "${foreignpkgs[*]}" ]] || mapfile -t foreignpkgs < <("${pacman}" -Qmq) + FindDepsAur "${aurpkgsnover[@]}" + + # avoid possible duplicate + mapfile -t deps < <(CommArr -13 aurdepspkgs deps) + deps+=("${aurdepspkgs[@]}") + + # ensure correct dependency order + SetInfo "${deps[@]}" + SortDepsAur "${aurpkgs[@]}" + mapfile -t deps < <(tsort <<< "${tsortdeps[@]}" 2>/dev/null) + wait "$!" || error $"dependency cycle detected" "${E_INSTALL_DEPS_FAILED}" + + # get PUR packages info + mapfile -t depsAname < <(GetInfo Name) + mapfile -t depsAver < <(GetInfo Version) + mapfile -t depsAood < <(GetInfo OutOfDate) + mapfile -t depsAmain < <(GetInfo Maintainer) + for i in "${!depsAname[@]}"; do + read -r depsQver[i] < <(expac -Qs '%v' "^${depsAname[i]}$") + : "${depsQver[i]:=%}" # avoid empty elements shift + [[ "${depsAname[i]}" != *${vcs} ]] || depsAver[i]=$"latest" + done + + # no results check + if [[ "${errdeps[*]}" ]]; then + for i in "${!errdepsnover[@]}"; do + if [[ " ${aurpkgsnover[*]} " = *" ${errdepsnover[i]} "* ]]; then + fail $"no results found for %s" "${errdeps[i]}" + else + unset -v currenterrdep + # find relevant tsorted deps chain + for j in "${deps[@]}"; do + tsorterrdeps+=("${j}") + [[ "${j}" != "${errdepsnover[i]}" ]] || break + done + for j in "${!tsorterrdeps[@]}"; do tsorterrdeps+=("${deps[j]}") + [[ "${deps[j]}" != "${errdepsnover[i]}" ]] || break; done + # reverse deps order + mapfile -t tsorterrdeps < \ + <(for ((j=0; j<${#tsorterrdeps[@]}; j++)) { printf '%s\n' "${tsorterrdeps[-j-1]}";}) + errdepslist+=("${tsorterrdeps[0]}") + FindDepsAurError "${tsorterrdeps[@]}" + mapfile -t errdepslist < \ + <(for ((j=0; j<${#errdepslist[@]}; j++)) { printf '%s\n' "${errdepslist[-j-1]}";}) + fail $"no results found for %s (dependency tree: %s)" "${errdeps[i]}" "${errdepslist[*]}" + fi + done + exit "${E_INSTALL_DEPS_FAILED}" + fi + + # return all binary deps + FindDepsRepo "${repodeps[@]}" + + # avoid possible duplicate + SortArrAssign repodepspkgs +} + +FindDepsAur() { + local depAname depAver depspkgs depspkgsaurtmp builtpkg assumedepspkgs vcsdepspkgs=() aurversionpkgs=() + local aurversionpkgsname aurversionpkgsver aurversionpkgsaurver aurversionpkgsverdiff i j + # global aurpkgsnover depspkgsaur errdeps repodeps aurdepspkgs prevdepspkgsaur foreignpkgs + ((dcount < 2)) || return + + # set info + if [[ "${depspkgsaur[*]}" ]]; then + SetInfo "${depspkgsaur[@]}" + aurversionpkgs=("${prevdepspkgsaur[@]}") + else + SetInfo "${aurpkgsnover[@]}" + aurversionpkgs=("${aurpkgs[@]}") + fi + + # versioning check + if [[ "${aurversionpkgs[*]}" ]]; then + for i in "${!aurversionpkgs[@]}"; do + aurversionpkgsname="${aurversionpkgs[i]%%[><=]*}" aurversionpkgsver="${aurversionpkgs[i]##*[><=]}" + aurversionpkgsaurver="$(GetInfo Version "${aurversionpkgsname}")" + aurversionpkgsverdiff="$(RunVercmp "${aurversionpkgsaurver}" "${aurversionpkgsver}")" + + # not found in PUR nor repo + [[ "${aurversionpkgsaurver}" || " ${errdeps[*]} " = *" ${aurversionpkgs[i]} "* ]] || + { errdeps+=("${aurversionpkgs[i]}"); continue;} + + case "${aurversionpkgs[i]}" in + *">"*|*"<"*|*"="*) # found in PUR but version not correct + case "${aurversionpkgs[i]}" in + *">="*) ((aurversionpkgsverdiff < 0)) || continue;; + *"<="*) ((aurversionpkgsverdiff > 0)) || continue;; + *">"*) ((aurversionpkgsverdiff <= 0)) || continue;; + *"<"*) ((aurversionpkgsverdiff >= 0)) || continue;; + *"="*) ((aurversionpkgsverdiff)) || continue;; + esac + [[ " ${errdeps[*]} " = *" ${aurversionpkgs[i]} "* ]] || errdeps+=("${aurversionpkgs[i]}");; + *) continue;; + esac + done + fi + + mapfile -t depspkgs < <(GetInfo Depends) + + # cached packages makedeps check + if [[ ! "${PKGDEST}" ]] || ((rebuild)); then + MapfileAdd depspkgs < <(GetInfo MakeDepends) + ((! checkdeps)) || MapfileAdd depspkgs < <(GetInfo CheckDepends) + else + depspkgsaurtmp=("${depspkgsaur[@]:-${aurpkgs[@]}}") + for i in "${!depspkgsaurtmp[@]}"; do + depAname="$(GetInfo Name "${depspkgsaurtmp[i]}")" + depAver="$(GetInfo Version "${depspkgsaurtmp[i]}")" + GetBuiltPkg "${depAname}-${depAver}" "${PKGDEST}" + if [[ ! "${builtpkg}" ]]; then + MapfileAdd depspkgs < <(GetInfo MakeDepends "${depspkgsaurtmp[i]}") + ((! checkdeps)) || MapfileAdd depspkgs < <(GetInfo CheckDepends "${depspkgsaurtmp[i]}") + fi + builtpkg='' + done + fi + + # remove deps provided by targets + [[ ! "${aurpkgsproviders[*]}" ]] || mapfile -t depspkgs < <(CommArr -13 aurpkgsproviders depspkgs) + + # workaround for limited RPC support of architecture dependent fields + if [[ "${CARCH}" = 'i686' ]]; then + for i in "${!depspkgs[@]}"; do + case "${depspkgs[i]}" in lib32-*|gcc-multilib*) unset -v 'depspkgs[i]';; esac + done + fi + + if ((devel)); then + # remove versioning and check providers + depspkgs=("${depspkgs[@]%%[><=]*}") + for i in "${!depspkgs[@]}"; do + read -r j < <(expac -Qs '%n' "^${depspkgs[i]}$") + if [[ "${j}" ]]; then + depspkgs[i]="${j}" + [[ " ${ignoredpkgs[*]} " = *" ${j} "* || "${j}" != *${vcs} ]] || vcsdepspkgs+=("${j}") + else + foreignpkgs+=("${depspkgs[i]}") + fi + done + # reorder devel + mapfile -t depspkgs < <("${pacman}" -T "${depspkgs[@]}") + mapfile -t depspkgs < <(CommArr -3 depspkgs vcsdepspkgs) + else + mapfile -t depspkgs < <("${pacman}" -T "${depspkgs[@]}") + fi + + # split repo and PUR depends pkgs + unset -v depspkgsaur + if [[ "${depspkgs[*]}" ]]; then + # remove all pkgs versioning + if ((dcount == 1)); then + depspkgs=("${depspkgs[@]%%[><=]*}") + # assume installed deps + elif [[ "${assumeinstalled[*]}" ]]; then + # remove versioning + assumeinstalled=("${assumeinstalled[@]%%[><=]*}") + for i in "${!assumeinstalled[@]}"; do + unset -v assumedepspkgs + for j in "${!depspkgs[@]}"; do + assumedepspkgs[j]="${depspkgs[j]%%[><=]*}" + [[ " ${assumedepspkgs[*]} " = *" ${assumeinstalled[i]} "* ]] && + depspkgs[j]="${assumeinstalled[i]}" + done + done + mapfile -t depspkgs < <(CommArr -13 assumeinstalled depspkgs) + fi + if [[ "${depspkgs[*]}" ]]; then + mapfile -t depspkgsaur < <(LC_ALL='C' "${pacman}" -Sp "${depspkgs[@]}" 2>&1 >/dev/null) + depspkgsaur=("${depspkgsaur[@]#error: target not found: }") + MapfileAdd repodeps < <(CommArr -13 depspkgsaur depspkgs) + fi + fi + unset -v depspkgs + + # remove duplicates + [[ ! "${depspkgsaur[*]}" ]] || mapfile -t depspkgsaur < <(CommArr -13 aurdepspkgs depspkgsaur) + + # dependency cycle check + [[ ! "${prevdepspkgsaur[*]}" || "${prevdepspkgsaur[*]}" != "${depspkgsaur[*]}" ]] || + error $"dependency cycle detected (%s)" "${depspkgsaur[*]}" "${E_INSTALL_DEPS_FAILED}" + + if [[ "${depspkgsaur[*]}" ]]; then + ((dcount)) || prevdepspkgsaur=("${depspkgsaur[@]}") # store for PUR version check + depspkgsaur=("${depspkgsaur[@]%%[><=]*}") # remove versioning + fi + + [[ ! "${depspkgsaur[*]}" ]] || { aurdepspkgs+=("${depspkgsaur[@]}"); FindDepsAur "${depspkgsaur[@]}";} +} + +SortDepsAur() { + local i j sortdepspkgs sortdepspkgsaur sortaurpkgs=("${checkedsortdepspkgsaur[@]:-${aurpkgs[@]}}") + # global checkedsortdepspkgsaur allcheckedsortdepspkgsaur errdepsnover + + checkedsortdepspkgsaur=() + for i in "${!sortaurpkgs[@]}"; do + sortdepspkgsaur=() + + mapfile -t sortdepspkgs < <(GetInfo Depends "${sortaurpkgs[i]}") + MapfileAdd sortdepspkgs < <(GetInfo MakeDepends "${sortaurpkgs[i]}") + ((! checkdeps)) || MapfileAdd sortdepspkgs < <(GetInfo CheckDepends "${sortaurpkgs[i]}") + + # remove versioning + errdepsnover=("${errdeps[@]%%[><=]*}") + + # check PUR deps only + for j in "${!sortdepspkgs[@]}"; do + sortdepspkgs[j]="${sortdepspkgs[j]%%[><=]*}" + MapfileAdd sortdepspkgsaur < <(GetInfo Name "${sortdepspkgs[j]}") + # add erroneous PUR deps + [[ " ${errdepsnover[*]} " != *" ${sortdepspkgs[j]} "* ]] || sortdepspkgsaur+=("${sortdepspkgs[j]}") + done + + # prepare tsort list + if [[ "${sortdepspkgsaur[*]}" ]]; then + for j in "${!sortdepspkgsaur[@]}"; do + tsortdeps+=("${sortaurpkgs[i]} ${sortdepspkgsaur[j]}") + done + else + tsortdeps+=("${sortaurpkgs[i]} ${sortaurpkgs[i]}") + fi + + # filter non checked deps + mapfile -t sortdepspkgsaur < <(CommArr -13 allcheckedsortdepspkgsaur sortdepspkgsaur) + if [[ "${sortdepspkgsaur[*]}" ]]; then + checkedsortdepspkgsaur+=("${sortdepspkgsaur[@]}") + allcheckedsortdepspkgsaur+=("${sortdepspkgsaur[@]}") + SortArrAssign allcheckedsortdepspkgsaur + fi + done + if [[ "${checkedsortdepspkgsaur[*]}" ]]; then + SortArrAssign checkedsortdepspkgsaur + SortDepsAur "${checkedsortdepspkgsaur[@]}" + fi +} + +FindDepsAurError() { + local i nexterrdep="${nexterrdep-}" nextallerrdeps + # global errdepsnover errdepslist tsorterrdeps currenterrdep + + for i in "${tsorterrdeps[@]}"; do + [[ " ${errdepsnover[*]} ${errdepslist[*]} " = *" ${i} "* ]] && break || nexterrdep="${i}" + done + + currenterrdep="${currenterrdep:-${tsorterrdeps[0]}}" + if [[ " ${aurpkgs[*]} " != *" ${nexterrdep} "* ]]; then + mapfile -t nextallerrdeps < <(GetInfo Depends "${nexterrdep}") + MapfileAdd nextallerrdeps < <(GetInfo MakeDepends "${nexterrdep}") + ((! checkdeps)) || MapfileAdd nextallerrdeps < <(GetInfo CheckDepends "${nexterrdep}") + + # remove versioning + nextallerrdeps=("${nextallerrdeps[@]%%[><=]*}") + + [[ " ${nextallerrdeps[*]} " = *" ${currenterrdep} "* ]] && + errdepslist+=("${nexterrdep}") currenterrdep="${tsorterrdeps[0]}" + tsorterrdeps=("${tsorterrdeps[@]:1}") + FindDepsAurError "${tsorterrdeps[@]}" + else + for i in "${!aurpkgs[@]}"; do + mapfile -t nextallerrdeps < <(GetInfo Depends "${aurpkgs[i]}") + MapfileAdd nextallerrdeps < <(GetInfo MakeDepends "${aurpkgs[i]}") + ((! checkdeps)) || MapfileAdd nextallerrdeps < <(GetInfo CheckDepends "${aurpkgs[i]}") + + # remove versioning + nextallerrdeps=("${nextallerrdeps[@]%%[><=]*}") + + [[ " ${nextallerrdeps[*]} " != *" ${currenterrdep} "* ]] || errdepslist+=("${aurpkgs[i]}") + done + fi +} + +FindDepsRepo() { + local allrepodepspkgs=() repodepspkgstmp=() + # global repodeps repodepspkgs + [[ "${repodeps[*]}" ]] || return + + # reduce root binary deps + SortArrAssign repodeps + + # add initial repodeps + [[ "${repodepspkgs[*]}" ]] || repodepspkgs=("${repodeps[@]}") + repodepspkgs=("${repodepspkgs[@]:-${repodeps[@]}}") + + # get non installed binary deps, no version check needed as all deps are repo deps + [[ ! "${repodeps[*]}" ]] || mapfile -t allrepodepspkgs < <(expac -S1l'\n' '%E' "${repodeps[@]}") + [[ ! "${allrepodepspkgs[*]}" ]] || + mapfile -t repodepspkgstmp < <("${pacman}" -T "${allrepodepspkgs[@]}" | sort -u) + + # remove duplicate + [[ ! "${repodepspkgstmp[*]}" ]] || mapfile -t repodepspkgstmp < <(CommArr -13 repodepspkgs{,tmp}) + [[ ! "${repodepspkgstmp[*]}" ]] || { repodepspkgs+=("${repodepspkgstmp[@]}") + repodeps=("${repodepspkgstmp[@]}"); FindDepsRepo "${repodeps[@]}";} +} + +FindDepsRepoProvider() { + local -a allproviderrepodepspkgs=() providerrepodepspkgstmp=() + # global repodepspkgs + [[ "${providerspkgs[*]}" ]] || return + + # reduce root binary deps + SortArrAssign providerspkgs + + # get non installed repo deps + [[ ! "${providerspkgs[*]}" ]] || + mapfile -t allproviderrepodepspkgs < <(expac -S1l'\n' '%E' "${providerspkgs[@]}") + # no version check needed as all deps are binary + [[ ! "${allproviderrepodepspkgs[*]}" ]] || + mapfile -t providerrepodepspkgstmp < <("${pacman}" -T "${allproviderrepodepspkgs[@]}" | sort -u) + + # remove duplicate + [[ ! "${providerrepodepspkgstmp[*]}" ]] || + mapfile -t providerrepodepspkgstmp < <(CommArr -13 repodepspkgs providerrepodepspkgstmp) + + [[ ! "${providerrepodepspkgstmp[*]}" ]] || { repodepspkgs+=("${providerrepodepspkgstmp[@]}") + providerspkgs=("${providerrepodepspkgstmp[@]}"); FindDepsRepoProvider "${providerspkgs[@]}";} +} + +IgnoreDepsCheck() { + local i repodepspkgsgrp=() + # global ignoredpkgs ignoredgrps aurpkgs aurdepspkgs aurdepspkgsgrp rmaurpkgs deps repodepspkgs + [[ "${ignoredpkgs[*]}" || "${ignoredgrps[*]}" ]] || return + + # add checked targets and preserve tsorted order + deps=("${deps[@]::${#aurpkgs[@]}}") + + # check dependencies + for i in "${repodepspkgs[@]}"; do + isignored=0 + if [[ " ${ignoredpkgs[*]} " = *" ${i} "* ]]; then + isignored=1 + elif [[ "${ignoredgrps[*]}" ]]; then + mapfile -t repodepspkgsgrp < <(expac -S1l'\n' '%G' "${i}") + MapfileAdd repodepspkgsgrp < <(expac -Ql'\n' '%G' "${i}") + for j in "${repodepspkgsgrp[@]}"; do + [[ " ${ignoredgrps[*]} " != *" ${j} "* ]] || isignored=1 + done + fi + + if ((isignored)); then + { ((! upgrade)) && warn $"skipping target: %s" "${colorW}${i}${reset}";} || + warn $"%s: ignoring package upgrade" "${colorW}${i}${reset}" + error $"Unresolved dependency '%s'" "${colorW}${i}${reset}" "${E_INSTALL_DEPS_FAILED}" + fi + done + for i in "${aurdepspkgs[@]}"; do + # skip already checked dependencies + [[ " ${aurpkgs[*]} " != *" ${i} "* ]] || continue + [[ " ${rmaurpkgs[*]} " != *" ${i} "* ]] || + error $"Unresolved dependency '%s'" "${colorW}${i}${reset}" "${E_INSTALL_DEPS_FAILED}" + + isignored=0 + if [[ " ${ignoredpkgs[*]} " = *" ${i} "* ]]; then + isignored=1 + elif [[ "${ignoredgrps[*]}" ]]; then + mapfile -t aurdepspkgsgrp < <(GetInfo Groups "${i}") + MapfileAdd aurdepspkgsgrp < <(expac -Ql'\n' '%G' "${i}") + for j in "${aurdepspkgsgrp[@]}"; do + [[ " ${ignoredgrps[*]} " != *" ${j} "* ]] || isignored=1 + done + fi + + if ((isignored)); then + if ((! noconfirm)); then + if ! Proceed y $"%s dependency is in IgnorePkg/IgnoreGroup. Install anyway?" "${i}"; then + warn $"skipping target: %s" "${colorW}${i}${reset}" + error $"Unresolved dependency '%s'" "${colorW}${i}${reset}" "${E_INSTALL_DEPS_FAILED}" + fi + else + { ((upgrade)) && warn $"%s: ignoring package upgrade" "${colorW}${i}${reset}";} || + warn $"skipping target: %s" "${colorW}${i}${reset}" + error $"Unresolved dependency '%s'" "${colorW}${i}${reset}" "${E_INSTALL_DEPS_FAILED}" + fi + fi + deps+=("${i}") + done +} + +ProviderChecks() { + local providersdeps{,nover} repodepspkgsprovided providerspkgs {,providers}{,nb} provided=() + # global repodepspkgs repoprovidersconflictingpkgs repodepsSver repodepsSrepo repodepsQver + [[ "${repodepspkgs[*]}" ]] || return + + # filter directly provided deps + # shellcheck disable=SC2034 + mapfile -t noprovidersdeps < <(expac -S1 '%n' "${repodepspkgs[@]}") + mapfile -t providersdeps < <(CommArr -13 noprovidersdeps repodepspkgs) + + # remove installed providers + mapfile -t providersdeps < <("${pacman}" -T "${providersdeps[@]}" | sort -u) + + for i in "${!providersdeps[@]}"; do + # check versioning + providersdepsname="${providersdeps[i]%%[><=]*}" providersdepsver="${providersdeps[i]##*[><=]}" + mapfile -t providersdepsSname < <(expac -Ss '%n' "^${providersdepsname}$") + mapfile -t providersdepsSver < <(expac -Ss '%v' "^${providersdepsname}$") + + case "${providersdeps[i]}" in + *">"*|*"<"*|*"="*) + for j in "${!providersdepsSname[@]}"; do + providersdepsverdiff="$(RunVercmp "${providersdepsver}" "${providersdepsSver[j]}")" + # found in repo but version not correct + case "${providersdeps[i]}" in + *">="*) ((providersdepsverdiff < 0)) || continue;; + *"<="*) ((providersdepsverdiff > 0)) || continue;; + *">"*) ((providersdepsverdiff <= 0)) || continue;; + *"<"*) ((providersdepsverdiff >= 0)) || continue;; + *"="*) ((providersdepsverdiff)) || continue;; + esac + providersdepsnover+=("${providersdepsSname[j]}") + done + ;; + esac + + # remove versioning + providersdeps[i]="${providersdeps[i]%%[><=]*}" + + # list providers + mapfile -t providers < <(expac -Ss '%n' "^${providersdeps[i]}$" | sort -u) + + # filter out non matching versioned providers + [[ ! "${providersdepsnover[*]}" ]] || mapfile -t providers < <(CommArr -12 providers{depsnover,}) + + # skip if provided in dependency chain + repodepspkgsprovided=0 + for j in "${providers[@]}"; do + [[ " ${repodepspkgs[*]} " != *" ${j} "* ]] || repodepspkgsprovided=1 + done + ((! repodepspkgsprovided)) || continue + + # skip if already provided + if [[ "${providerspkgs[*]}" ]]; then + MapfileAdd provided < <(IFS='|'; expac -Ssl'\n' '%S' "^(${providerspkgs[*]})$") + [[ " ${provided[*]} " != *" ${providersdeps[i]} "* ]] || continue + fi + + if ((! noconfirm && ${#providers[*]} > 1)); then + info $"%sThere are %s providers available for %s:%s" "${colorW}" "${#providers[@]}" \ + "${providersdeps[i]}" "${reset}" + expac -S1 ' %!) %n (%r) ' "${providers[@]}" + nb='-1' + providersnb="$((${#providers[@]} - 1))" # count from 0 + while ((nb < 0 || nb >= ${#providers})); do + printf '\n%s ' $"Enter a number (default=0):" + case "${TERM:-}" in + dumb) read -r nb;; + *) read -rn "${#providersnb}" nb; printf '\n';; + esac + + case "${nb}" in + +([0-9])) if ((nb < 0 || nb >= ${#providers[@]})); then + printf '\n' + fail $"invalid value: %s is not between 0 and %s" "${nb}" "${providersnb}"; ((--i)) + else + break + fi;; + '') nb=0;; + *) fail $"invalid number: %s" "${nb}";; + esac + done + else + nb=0 + fi + [[ ! "${providers[*]}" ]] || providerspkgs+=("${providers[nb]}") + done + + # add selected providers to repo deps and store for installation + repodepspkgs+=("${providerspkgs[@]}") repoprovidersconflictingpkgs+=("${providerspkgs[@]}") + + FindDepsRepoProvider "${providerspkgs[@]}" + + # get binary packages info + if [[ "${repodepspkgs[*]}" ]]; then + mapfile -t repodepspkgs < <(expac -S1 '%n' "${repodepspkgs[@]}" | sort -u) + mapfile -t repodepsSver < <(expac -S1 '%v' "${repodepspkgs[@]}") + if [[ "$("${pacman_conf[@]}" VerbosePkgLists)" ]]; then + for i in "${!repodepspkgs[@]}"; do + repodepsQver[i]="$(expac -Q '%v' "${repodepspkgs[i]}")" + done + fi + mapfile -t repodepsSrepo < <(expac -S1 '%r/%n' "${repodepspkgs[@]}") + fi +} + +ConflictChecks() { + local allQprovides allQconflicts Aprovides Aconflicts aurconflicts aurAconflicts Qrequires i j + local k l repodepsprovides repodepsconflicts repodepsconflictsname checkedrepodepsconflicts=() + local repodepsconflictsver localver repoconflictingpkgs repoconflicts=() + # global deps depsAname aurdepspkgs aurconflictingpkgs aurconflictingpkgskeep aurconflictingpkgsrm + # global depsQver repodepspkgs repoconflictingpkgskeep repoconflictingpkgsrm repoprovidersconflictingpkgs + printf '%s\n' $"looking for inter-conflicts..." + + # shellcheck disable=SC2034 + mapfile -t allQprovides < <(expac -Q '%n') + MapfileAdd allQprovides < <(expac -Ql'\n' '%S') # no versioning + # shellcheck disable=SC2034 + mapfile -t allQconflicts < <(expac -Ql'\n' '%C') + + # PUR conflicts + Aprovides=("${depsAname[@]}") + MapfileAdd Aprovides < <(GetInfo Provides) + mapfile -t Aconflicts < <(GetInfo Conflicts) + # remove PUR versioning + Aprovides=("${Aprovides[@]%%[><=]*}") Aconflicts=("${Aconflicts[@]%%[><=]*}") + mapfile -t aurconflicts < <(CommArr -12 Aprovides allQconflicts; CommArr -12 Aconflicts allQprovides) + SortArrAssign aurconflicts + + for i in "${aurconflicts[@]}"; do + aurAconflicts=() + [[ " ${depsAname[*]} " != *" ${i} "* ]] || aurAconflicts=("${i}") + for j in "${depsAname[@]}"; do + [[ " $(GetInfo Conflicts "${j}") " != *" ${i} "* ]] || aurAconflicts+=("${j}") + done + + for j in "${aurAconflicts[@]}"; do + Aprovides=() + read -r k < <(expac -Qs '%n' "^${i}$") + ((installpkg)) || [[ " ${aurdepspkgs[*]} " = *" ${j} "* ]] || continue # download only + [[ "${j}" != "${k}" && "${k}" ]] || continue # skip if reinstalling or if no conflict exists + + Aprovides=("${j}") + if ((! noconfirm)) && [[ " ${aurconflictingpkgs[*]} " != *" ${k} "* ]]; then + if ! Proceed n $"%s and %s are in conflict (%s). Remove %s?" "${j}" "${k}" "${i}" "${k}"; then + aurconflictingpkgs+=("${j}" "${k}") + aurconflictingpkgskeep+=("${j}") + aurconflictingpkgsrm+=("${k}") + for l in "${!depsAname[@]}"; do + [[ " ${depsAname[l]} " != *"${k}"* ]] || read -r depsQver[l] < <(expac -Qs '%v' "^${k}$") + done + MapfileAdd Aprovides < <(GetInfo Provides "${j}") + Aprovides=("${Aprovides[@]%%[><=]*}") # remove PUR versioning + [[ " ${Aprovides[*]} ${aurconflictingpkgsrm[*]} " = *" ${k} "* ]] || CheckRequires "${k}" + break + else + fail $"unresolvable package conflicts detected" + fail $"failed to prepare transaction (conflicting dependencies)" + if ((upgrade)); then + mapfile -t Qrequires < <(expac -Ql'\n' '%N' "${i}") + error $"%s and %s are in conflict (required by %s)" "${j}" "${k}" "${Qrequires[*]}" \ + "${E_INSTALL_DEPS_FAILED}" + else + error $"%s and %s are in conflict" "${j}" "${k}" "${E_INSTALL_DEPS_FAILED}" + fi + fi + fi + MapfileAdd Aprovides < <(GetInfo Provides "${j}") + Aprovides=("${Aprovides[@]%%[><=]*}") # remove PUR versioning + [[ " ${Aprovides[*]} ${aurconflictingpkgsrm[*]} " = *" ${k} "* ]] || CheckRequires "${k}" + done + done + + NothingToDo "${deps[@]}" + + # repo conflicts + if [[ "${repodepspkgs[*]}" ]]; then + # shellcheck disable=SC2034 + repodepsprovides=("${repodepspkgs[@]}") + # no versioning + MapfileAdd repodepsprovides < <(expac -S1l'\n' '%S' "${repodepspkgs[@]}") + mapfile -t repodepsconflicts < <(expac -S1l'\n' '%H' "${repodepspkgs[@]}") + + # versioning check + for i in "${!repodepsconflicts[@]}"; do + repodepsconflictsname="${repodepsconflicts[i]%%[><=]*}" + repodepsconflictsver="${repodepsconflicts[i]##*[><=]}" + localver="$(expac -Q '%v' "${repodepsconflictsname}")" + repodepsconflictsverdiff="$(RunVercmp "${repodepsconflictsver}" "${localver}")" + + if [[ "${localver}" ]]; then + case "${repodepsconflicts[i]}" in + *">="*) ((repodepsconflictsverdiff < 0)) || continue;; + *"<="*) ((repodepsconflictsverdiff > 0)) || continue;; + *">"*) ((repodepsconflictsverdiff <= 0)) || continue;; + *"<"*) ((repodepsconflictsverdiff >= 0)) || continue;; + *"="*) ((repodepsconflictsverdiff)) || continue;; + esac + checkedrepodepsconflicts+=("${repodepsconflictsname}") + fi + done + + MapfileAdd repoconflicts < <(CommArr -12 repodepsprovides allQconflicts) + MapfileAdd repoconflicts < <(CommArr -12 checkedrepodepsconflicts allQprovides) + SortArrAssign repoconflicts + fi + + for i in "${repoconflicts[@]}"; do + read -r j < <(expac -Ss '%n' "^${i}$") + read -r k < <(expac -Qs '%n' "^${i}$") + [[ "${j}" != "${k}" && "${k}" ]] || continue # skip when no conflict with repopkgs + if ((! noconfirm)) && [[ " ${repoconflictingpkgs[*]} " != *" ${k} "* ]]; then + if ! Proceed n $"%s and %s are in conflict (%s). Remove %s?" "${j}" "${k}" "${i}" "${k}"; then + repoconflictingpkgs+=("${j}" "${k}") repoconflictingpkgskeep+=("${j}") + repoconflictingpkgsrm+=("${k}") repoprovidersconflictingpkgs+=("${j}") + mapfile -t Qprovides < <(expac -Ssl'\n' '%S' "^${k}$") + [[ " ${Qprovides[*]} ${repoconflictingpkgsrm[*]} " = *" ${k} "* ]] || CheckRequires "${k}" + break + else + fail $"unresolvable package conflicts detected" + fail $"failed to prepare transaction (conflicting dependencies)" + if ((upgrade)); then + mapfile -t Qrequires < <(expac -Ql'\n' '%N' "${i}") + error $"%s and %s are in conflict (required by %s)" "${j}" "${k}" "${Qrequires[*]}" \ + "${E_INSTALL_DEPS_FAILED}" + else + error $"%s and %s are in conflict" "${j}" "${k}" "${E_INSTALL_DEPS_FAILED}" + fi + fi + fi + mapfile -t Qprovides < <(expac -Ssl'\n' '%S' "^${k}$") + [[ " ${Qprovides[*]} " = *" ${k} "* ]] || CheckRequires "${k}" + done +} + +ReinstallChecks() { + local i j depsAtmp=("${depsAname[@]}") + # global aurpkgs aurdepspkgs deps aurconflictingpkgs depsAname depsQver depsAver depsAood depsAmain + for i in "${!depsAtmp[@]}"; do + [[ " ${aurpkgs[*]} " = *" ${depsAname[i]} "* && + " ${aurconflictingpkgs[*]} " != *" ${depsAname[i]} "* ]] || continue + [[ "${depsQver[i]}" != ?('%') && "$(RunVercmp "${depsAver[i]}" "${depsQver[i]}")" -le 0 ]] || continue + ((installpkg)) || [[ " ${aurdepspkgs[*]} " = *" ${depsAname[i]} "* ]] || continue + if [[ "${depsAname[i]}" = *${vcs} ]]; then + warn $"%s latest revision -- fetching" "${colorW}${depsAname[i]}${reset}" + elif ((needed)); then + warn $"%s-%s is up to date -- skipping" "${colorW}${depsAname[i]}" "${depsQver[i]}${reset}" + for ((j=0; j < ${#deps[@]}; j++)) { [[ "${deps[j]}" != "${depsAname[i]}" ]] || unset -v 'deps[j]';} + deps=("${deps[@]}") + unset -v 'depsAname[i]' 'depsQver[i]' 'depsAver[i]' 'depsAood[i]' 'depsAmain[i]' + else + warn $"%s-%s is up to date -- reinstalling" "${colorW}${depsAname[i]}" "${depsQver[i]}${reset}" + fi + done + depsAname=("${depsAname[@]}") depsQver=("${depsQver[@]}") depsAver=("${depsAver[@]}") + depsAood=("${depsAood[@]}") depsAmain=("${depsAmain[@]}") + NothingToDo "${deps[@]}" +} + +OutOfDateChecks() { + local i + # global depsAname depsAver depsAood + for i in "${!depsAname[@]}"; do + ((depsAood[i] <= 0)) || + warn $"%s-%s has been flagged %sout of date%s on %s" "${colorW}${depsAname[i]}" "${depsAver[i]}${reset}" "${colorR}" "${reset}" "${colorY}$(printf '%(%c)T' "${depsAood[i]}")${reset}" + done +} + +OrphanChecks() { + local i + # global depsAname depsAver depsAmain + for i in "${!depsAname[@]}"; do + [[ "${depsAmain[i]}" != '%' ]] || warn $"%s-%s is %sorphaned%s in PUR" \ + "${colorW}${depsAname[i]}" "${depsAver[i]}${reset}" "${colorR}" "${reset}" + done +} + +Prompt() { + local i binaryksize sumk summ builtpkg cachedpkgs stroldver strnewver strsize depsver + local repodepspkgsver strrepodlsize strrepoinsize strsumk strsumm lreposizelabel lreposize + # global repodepspkgs repodepsSver depsAname depsAver depsArepo depsAcached lname lver lsize + # global deps depsQver repodepspkgs repodepsSrepo repodepsQver repodepsSver + # compute binary size + if [[ "${repodepspkgs[*]}" ]]; then + mapfile -t binaryksize < <(expac -S1 '%k' "${repodepspkgs[@]}") + mapfile -t binarymsize < <(expac -S1 '%m' "${repodepspkgs[@]}") + sumk=0 summ=0 + for i in "${!repodepspkgs[@]}"; do + GetBuiltPkg "${repodepspkgs[i]}-${repodepsSver[i]}" "$("${pacman_conf[@]}" CacheDir)" + [[ ! "${builtpkg}" ]] || binaryksize[i]=0 + ((sumk += binaryksize[i])) + ((summ += binarymsize[i])) + done + sumk="$((sumk / 1048576)).$((sumk / 1024 % 1024 * 100 / 1024))" + summ="$((summ / 1048576)).$((summ / 1024 % 1024 * 100 / 1024))" + fi + + # cached packages check + for i in "${!depsAname[@]}"; do + [[ "${PKGDEST}" && rebuild -eq 0 ]] || break + GetBuiltPkg "${depsAname[i]}-${depsAver[i]}" "${PKGDEST}" + [[ "${builtpkg}" ]] && + cachedpkgs+=("${depsAname[i]}") depsAcached[i]='('$"cached"')' || depsAcached[i]='' + builtpkg='' + done + + if [[ "$("${pacman_conf[@]}" VerbosePkgLists)" ]]; then + straurname=$"PUR Packages ""(${#deps[@]})" strreponame=$"Repo Packages ""(${#repodepspkgs[@]})" + stroldver=$"Old Version" strnewver=$"New Version" strsize=$"Download Size" + depsArepo=("${depsAname[@]/#/aur/}") + lname="$(GetLength "${depsArepo[@]}" "${repodepsSrepo[@]}" "${straurname}" "${strreponame}")" + lver="$(GetLength "${depsQver[@]}" "${depsAver[@]}" "${repodepsQver[@]}" "${repodepsSver[@]}" \ + "${stroldver}" "${strnewver}")" + lsize="$(GetLength "${strsize}")" + + # local version column cleanup + for ((i=0; i<${#deps[@]}; i++)) { [[ "${depsQver[i]}" != '%' ]] || depsQver[i]='';} + # show detailed output + printf "\n${colorW}%-${lname}s %-${lver}s %-${lver}s${reset}\n\n" \ + "${straurname}" "${stroldver}" "${strnewver}" + for i in "${!deps[@]}"; do + printf "%-${lname}s ${colorR}%-${lver}s${reset} ${colorG}%-${lver}s${reset} %${lsize}s\n" \ + "${depsArepo[i]}" "${depsQver[i]}" "${depsAver[i]}" "${depsAcached[i]-}" + done + + if [[ "${repodepspkgs[*]}" ]]; then + for i in "${!repodepspkgs[@]}"; do + binarysize[i]="$((binaryksize[i] / 1048576)).$((binaryksize[i] / 1024 % 1024 * 100 / 1024))" + done + printf "\n${colorW}%-${lname}s %-${lver}s %-${lver}s %s${reset}\n\n" \ + "${strreponame}" "${stroldver}" "${strnewver}" "${strsize}" + for i in "${!repodepspkgs[@]}"; do + printf "%-${lname}s ${colorR}%-${lver}s${reset} ${colorG}%-${lver}s${reset} %${lsize}s\n" \ + "${repodepsSrepo[i]}" "${repodepsQver[i]}" "${repodepsSver[i]}" "${binarysize[i]}"$" MiB" + done + fi + else + # show version + for i in "${!deps[@]}"; do + depsver+="${depsAname[i]}-${depsAver[i]} " + done + for i in "${!repodepspkgs[@]}"; do + repodepspkgsver+="${repodepspkgs[i]}-${repodepsSver[i]} " + done + printf "\n${colorW}%-16s${reset} %s\n" $"PUR Packages ""(${#deps[@]})" "${depsver}" + [[ ! "${repodepspkgs[*]}" ]] || + printf "${colorW}%-16s${reset} %s\n" $"Repo Packages ""(${#repodepspkgs[@]})" "${repodepspkgsver}" + fi + + if [[ "${repodepspkgs[*]}" ]]; then + strrepodlsize=$"Repo Download Size:" strrepoinsize=$"Repo Installed Size:" strsumk="${sumk}"$"MiB" + strsumm="${summ}"$"MiB" lreposizelabel="$(GetLength "${strrepodlsize}" "${strrepoinsize}")" + lreposize="$(GetLength "${strsumk}" "${strsumm}")" + printf "\n${colorW}%-${lreposizelabel}s${reset} %${lreposize}s\n" "${strrepodlsize}" "${strsumk}" + printf "${colorW}%-${lreposizelabel}s${reset} %${lreposize}s\n" "${strrepoinsize}" "${strsumm}" + fi + + printf '\n' + if ((installpkg)); then + Proceed y $"Proceed with installation?" || exit "${E_FAIL}" + else + Proceed y $"Proceed with download?" || exit "${E_FAIL}" + fi +} + +DownloadPkgs() { + local i + # global basepkgs + info $"%sRetrieving package(s)...%s" "${colorW}" "${reset}" + GetPkgbase "$@" + + # no results check + [[ "${basepkgs[*]}" ]] || error $"no results found" "${E_INSTALL_DEPS_FAILED}" + + # record previous HEAD for diff viewing + if [[ "${displaybuildfiles}" = 'diff' ]]; then + for i in "${basepkgs[@]/#/${clonedir}/}"; do + [[ ! -d "${i}" ]] || git -C "${i}" update-ref AUR_SEEN HEAD + done + fi + + auracle -C "${clonedir}" clone "$@" >/dev/null || error $"failed to retrieve packages" 1 # clone +} + +EditPkgs() { + local viewed=0 i j erreditpkg prev + # global cachedpkgs installscripts editor + ((! noedit)) || return + for i in "$@"; do + [[ " ${cachedpkgs[*]} " != *" ${i} "* ]] || continue + GetInstallScripts "${i}" + if ((! pace)); then + prev="$(git -C "${clonedir}/${i}" rev-parse AUR_SEEN 2>/dev/null)" + if [[ "${displaybuildfiles}" = 'diff' && "${prev}" != 'AUR_SEEN' ]]; then + # show diff + if git -C "${clonedir}/${i}" diff --quiet --no-ext-diff "${prev}" -- . ':!\.SRCINFO'; then + warn $"%s build files are up-to-date -- skipping" "${colorW}${i}${reset}" + else + if Proceed y $"View %s build files diff?" "${i}"; then + git -C "${clonedir}/${i}" diff --no-ext-diff "${prev}" -- . ':!\.SRCINFO' || + erreditpkg+=("${i}") + info $"%s build files diff viewed" "${colorW}${i}${reset}"; viewed=1 + fi + fi + elif [[ "${displaybuildfiles}" != 'none' ]]; then + # show pkgbuild + if Proceed y $"View %s PKGBUILD?" "${i}"; then + if [[ -e "${clonedir}/${i}/PKGBUILD" ]]; then + "${editor}" "${clonedir}/${i}/PKGBUILD" && + info $"%s PKGBUILD viewed" "${colorW}${i}${reset}" || erreditpkg+=("${i}") + else + error $"Could not open %s PKGBUILD" "${colorW}${i}${reset}" "${E_MISSING_FILE}" + fi + fi + # show install script + if [[ "${installscripts[*]}" ]]; then + for j in "${installscripts[@]}"; do + if Proceed y $"View %s script?" "${j}"; then + if [[ -e "${clonedir}/${i}/${j}" ]]; then + "${editor}" "${clonedir}/${i}/${j}" && + info $"%s script viewed" "${colorW}${j}${reset}" || + erreditpkg+=("${i}") + else + error $"Could not open %s script" "${colorW}${j}${reset}" "${E_MISSING_FILE}" + fi + fi + done + fi + fi + else + # show pkgbuild and install script + if [[ -e "${clonedir}/${i}/PKGBUILD" ]]; then + "${editor}" "${clonedir}/${i}/PKGBUILD" && + info $"%s PKGBUILD viewed" "${colorW}${i}${reset}" || erreditpkg+=("${i}") + else + error $"Could not open %s PKGBUILD" "${colorW}${i}${reset}" "${E_MISSING_FILE}" + fi + if [[ "${installscripts[*]}" ]]; then + for j in "${installscripts[@]}"; do + if [[ -e "${clonedir}/${i}/${j}" ]]; then + "${editor}" "${clonedir}/${i}/${j}" && + info $"%s script viewed" "${colorW}${j}${reset}" || erreditpkg+=("${i}") + else + error $"Could not open %s script" "${colorW}${j}${reset}" "${E_MISSING_FILE}" + fi + done + fi + fi + done + + if [[ "${erreditpkg[*]}" ]]; then + erreditpkg=("${erreditpkg[@]/#/${colorW}}") + fail $"%s errored on exit" "${erreditpkg[@]/%/${reset}}" + exit "${E_FAIL}" + fi + + if [[ "${displaybuildfiles}" = 'diff' ]] && ((viewed)); then + if ((installpkg)); then + Proceed y $"Proceed with installation?" || exit + else + Proceed y $"Proceed with download?" || exit + fi + fi +} + +MakePkgs() { + local oldorphanpkgs orphanpkgs oldoptionalpkgs optionalpkgs pkgsdepslist + local vcsclients vcschecked aurpkgsAver aurpkgsQver built{,deps}pkgs + local checkpkgsdepslist isaurdeps makedeps errmakepkgs i j k l + # global deps basepkgs sudoloop pkgsbase pkgsdeps aurpkgs aurdepspkgs builtpkg + # global repoprovidersconflictingpkgs + + # download + DownloadPkgs "${deps[@]}" + EditPkgs "${basepkgs[@]}" + + # current orphan and optional packages + # shellcheck disable=SC2034 + mapfile -t oldorphanpkgs < <("${pacman}" -Qtdq) + mapfile -t oldoptionalpkgs < <("${pacman}" -Qttdq) + # shellcheck disable=SC2034 + mapfile -t oldoptionalpkgs < <(CommArr -13 oldorphanpkgs oldoptionalpkgs) + + # initialize sudo + if [[ "$(sudo -V 2>/dev/null)" = 'Sudo'* ]]; then + if sudo -n "${pacman}" -V &>/dev/null || sudo -v; then + ((! sudoloop)) || SudoV & + fi + fi + + # split packages support + for i in "${!pkgsbase[@]}"; do + for j in "${!deps[@]}"; do + [[ "${pkgsbase[i]}" != "${pkgsbase[j]}" || "${pkgsdeps[*]}" = *",${deps[j]}"?(,*) ]] || + pkgsdeps[i]+=",${deps[j]}" + done + done + pkgsdeps=("${pkgsdeps[@]#,}") # remove empty array indices and leading ',' + + # reverse deps order + mapfile -t basepkgs < <(for ((i=0; i<${#basepkgs[@]}; i++)) { printf '%s\n' "${basepkgs[-i-1]}";}) + mapfile -t basepkgs < <(for ((i=0; i<${#pkgsdeps[@]}; i++)) { printf '%s\n' "${pkgsdeps[-i-1]}";}) + + # integrity check + for i in "${!basepkgs[@]}"; do + mapfile -td',' pkgsdepslist < <(printf '%s' "${pkgsdeps[i]}") # get split packages list + + # cache check + builtpkg='' + if [[ "${basepkgs[i]}" != *${vcs} ]]; then + for j in "${pkgsdepslist[@]}"; do + [[ ! "${PKGDEST}" ]] || ((rebuild)) || + GetBuiltPkg "${j}-$(GetInfo Version "${j}")" "${PKGDEST}" + done + fi + + # install vcs clients (checking pkgbase extension only does not take fetching specific + # commit into account) + vcsclients=() + mapfile -t makedeps < <(GetInfo MakeDepends "${basepkgs[i]}") + + for j in git subversion mercurial bzr cvs darcsl; do + [[ " ${makedeps[*]} " != *" ${j} "* ]] || vcsclients+=("${j}") + done + for j in "${vcsclients[@]}"; do + if [[ " ${vcschecked[*]} " != *" ${j} "* ]]; then + expac -Qs '' "^${j}$" || sudo "${pacman}" -S --asdeps --noconfirm "${j}" + vcschecked+=("${j}") + fi + done + + if [[ ! "${builtpkg-}" ]] || ((rebuild)); then + cd_safe "${clonedir}/${basepkgs[i]}" + info $"Checking %s integrity..." "${colorW}${pkgsdeps[i]}${reset}" + MakePkg -f --verifysource >/dev/null || errmakepkgs+=("${pkgsdeps[i]}") + # extraction, prepare and pkgver update + info $"Preparing %s..." "${colorW}${pkgsdeps[i]}${reset}" + MakePkg -Cod --skipinteg || errmakepkgs+=("${pkgsdeps[i]}") + fi + done + if [[ "${errmakepkgs[*]}" ]]; then + errmakepkgs=("${errmakepkgs[@]/#/${colorW}}") + fail $"failed to verify integrity or prepare %s package" "${errmakepkgs[@]/%/${reset}}" + # remove sudo lock + rm -f "${tmpdir}/pacpur.sudov.lck" + exit "${E_FAIL}" + fi + + # check database lock + [[ ! -e "/var/lib/pacman/db.lck" ]] || error $"db.lck exists in /var/lib/pacman" "${E_FAIL}" + + # set build lock + [[ ! -e "${tmpdir}/pacpur.build.lck" ]] || error $"pacpur.build.lck exists in %s" "${tmpdir}" "${E_FAIL}" + :>"${tmpdir}/pacpur.build.lck" + + # install provider packages and repo conflicting packages that makepkg --noconfirm cannot handle + if [[ "${repoprovidersconflictingpkgs[*]}" ]]; then + info $"Installing %s dependencies..." "${colorW}${repoprovidersconflictingpkgs[*]}${reset}" + sudo "${pacman}" -S "${repoprovidersconflictingpkgs[@]}" --ask 36 --asdeps --noconfirm + fi + + # main + for i in "${!basepkgs[@]}"; do + mapfile -td',' pkgsdepslist < <(printf '%s' "${pkgsdeps[i]}") # get split packages list + + cd_safe "${clonedir}/${basepkgs[i]}" + # retrieve updated version + mapfile -td'-' k < <(makepkg --packagelist); aurpkgsAver="${k[-3]}-${k[-2]}"; unset -v k + # build devel if necessary only (supported protocols only) + if [[ "${basepkgs[i]}" = *${vcs} ]]; then + # check split packages update + checkpkgsdepslist=() + for j in "${pkgsdepslist[@]}"; do + aurpkgsQver="$(expac -Qs '%v' "^${j}$")" + if ((needed && ! rebuild)) && + [[ "${aurpkgsQver}" && "$(RunVercmp "${aurpkgsQver}" "${aurpkgsAver}")" -ge 0 ]]; then + warn $"%s is up-to-date -- skipping" "${colorW}${j}${reset}"; continue + else + checkpkgsdepslist+=("${j}") + fi + done + if [[ "${checkpkgsdepslist[*]}" ]]; then + pkgsdepslist=("${checkpkgsdepslist[@]}") + else + continue + fi + fi + + # check package cache + for j in "${pkgsdepslist[@]}"; do + builtpkg='' + [[ ! "${PKGDEST}" ]] || ((rebuild)) || GetBuiltPkg "${j}-${aurpkgsAver}" "${PKGDEST}" + if [[ "${builtpkg}" ]]; then + if [[ " ${aurdepspkgs[*]} " = *" ${j} "* ]] || ((installpkg)); then + info $"Installing %s cached package..." "${colorW}${j}${reset}" + sudo "${pacman}" -U --ask 36 --noconfirm "${pacopts[@]}" "${builtpkg}" + [[ " ${aurpkgs[*]} " = *" ${j} "* ]] || + sudo "${pacman}" -D "${j}" --asdeps "${pacopts[@]}" &>/dev/null + else + warn $"Package %s already available in cache" "${colorW}${j}${reset}" + fi + pkgsdeps=("${pkgsdeps[@]/#${j},}"); pkgsdeps=("${pkgsdeps[@]/%,${j}}") + pkgsdeps=("${pkgsdeps[@]//,${j},/,}") + for k in "${!pkgsdeps[@]}"; do + [[ "${pkgsdeps[k]}" != "${j}" ]] || pkgsdeps[k]='%' + done + continue + fi + done + [[ "${pkgsdeps[i]}" != '%' ]] || continue + + # build + info $"Building %s package(s)..." "${colorW}${pkgsdeps[i]}${reset}" + + # install then remove binary deps + makeopts=("${makeopts[@]/-r/}") + + if ((! installpkg)); then + isaurdeps=0 + for j in "${pkgsdepslist[@]}"; do + [[ " ${aurdepspkgs[*]} " != *" ${j} "* ]] || isaurdeps=1 + done + ((! isaurdeps)) || makeopts+=('-r') + fi + + # skip install for packages that fail to build + MakePkg -sefc --noconfirm || { errmakepkgs+=("${pkgsdeps[i]}"); continue;} + + # retrieve filename + builtpkgs=() builtdepspkgs=() + for j in "${pkgsdepslist[@]}"; do + builtpkg='' + GetBuiltPkg "${j}-${aurpkgsAver}" "${PKGDEST:-${clonedir}/${basepkgs[i]}}" + [[ " ${aurdepspkgs[*]} " = *" ${j} "* ]] && builtdepspkgs+=("${builtpkg}") || + builtpkgs+=("${builtpkg}") + done + + # install + if ((installpkg)) || [[ ! "${builtpkgs[*]}" ]]; then + info $"Installing %s package(s)..." "${colorW}${pkgsdeps[i]}${reset}" + sudo "${pacman}" -U "${builtdepspkgs[@]}" "${builtpkgs[@]}" \ + --ask 36 "${pacopts[@]}" --noconfirm + fi + + # set dep status + if ((installpkg)); then + for j in "${pkgsdepslist[@]}"; do + [[ " ${aurpkgs[*]} " = *" ${j} "* ]] || sudo "${pacman}" -D "${j}" --asdeps &>/dev/null + ((! asdeps)) || sudo "${pacman}" -D "${j}" --asdeps &>/dev/null + ((! asexplicit)) || sudo "${pacman}" -D "${j}" --asexplicit &>/dev/null + done + fi + done + + # remove PUR deps + if ((! installpkg)); then + [[ ! "${aurdepspkgs[*]}" ]] || mapfile -t aurdepspkgs < <(expac -Q '%n' "${aurdepspkgs[@]}") + [[ ! "${aurdepspkgs[*]}" ]] || { info $"Removing installed PUR dependencies..." + sudo "${pacman}" -Rsn "${aurdepspkgs[@]}" --noconfirm;} + # re-add removed conflicting packages + [[ ! "${aurconflictingpkgsrm[*]}${repoconflictingpkgsrm[*]}" ]] || sudo "${pacman}" -S \ + "${aurconflictingpkgsrm[@]}" "${repoconflictingpkgsrm[@]}" --ask 36 --asdeps --needed --noconfirm + fi + + rm -f "${tmpdir}/pacpur."{'build','sudov'}'.lck' # remove locks + + # new orphan and optional packages check + # shellcheck disable=SC2034 + mapfile -t orphanpkgs < <("${pacman}" -Qdtq) + while read -r i; do + warn $"%s is now an %sorphan%s package" "${colorW}${i}${reset}" "${colorY}" "${reset}" + done < <(CommArr -13 oldorphanpkgs orphanpkg) + mapfile -t optionalpkgs < <("${pacman}" -Qdttq) + # shellcheck disable=SC2034 + mapfile -t optionalpkgs < <(CommArr -13 orphanpkgs optionalpkgs) + while read -r i; do + warn $"%s is now an %soptional%s package" "${colorW}${i}${reset}" "${colorY}" "${reset}" + done < <(CommArr -13 oldoptionalpkgs optionalpkgs) + + # makepkg and install failure check + if [[ "${errmakepkgs[*]}" ]]; then + errmakepkgs=("${errmakepkgs[@]/#/${colorW}}") + fail $"failed to build %s package(s)" "${errmakepkgs[@]/%/${reset}}" + exit "${E_PACKAGE_FAILED}" + fi +} + +CleanCache() { + local cachepkgs foreignpkgs foreignpkgsbase + mapfile -t cachedir < <("${pacman_conf[@]}" CacheDir) + [[ ! "${cachedir[*]}" ]] || cachedir=("${cachedir[@]%/}") PKGDEST="${PKGDEST%/}" + if [[ "${PKGDEST}" && " ${cachedir[*]} " != *" ${PKGDEST} "* ]]; then + ((ccount != 1)) || printf '\n%s\n %s\n' $"Packages to keep:" $"All locally installed packages" + printf '\n%s %s\n' $"PUR cache directory:" "${PKGDEST}" + if ((ccount == 1)); then + if Proceed y $"Do you want to remove all other packages from PUR cache?"; then + printf '%s\n' $"removing old packages from cache..." + cachepkgs=("${PKGDEST}/"*) cachepkgs=("${cachepkgs[@]##*/}") + for i in "${cachepkgs[@]%-*}"; do + [[ "${i}" = "$(expac -Q '%n-%v' "${i%-*-*}")" ]] || rm "${PKGDEST}/${i}-"* + done + fi + else + Proceed n $"Do you want to remove ALL files from PUR cache?" || + { printf '%s\n' $"removing all files from PUR cache..." + rm "${PKGDEST}/"* &>/dev/null;} + fi + fi + + if [[ -d "${SRCDEST}" ]]; then + ((ccount != 1)) || printf '\n%s\n %s\n' $"Sources to keep:" $"All development packages sources" + printf '\n%s %s\n' $"PUR source cache directory:" "${SRCDEST}" + if ((ccount == 1)) && + Proceed y $"Do you want to remove all non development files from PUR source cache?"; then + printf '%s\n' $"removing non development files from source cache..." + rm -f "${SRCDEST}/"* &>/dev/null + elif ! Proceed n $"Do you want to remove ALL files from PUR source cache?"; then + printf '%s\n' $"removing all files from PUR source cache..." + rm -rf "${SRCDEST:?}/"* + fi + fi + if [[ -d "${clonedir}" ]]; then + if ((ccount == 1)); then + if [[ ! "${pkgs[*]}" ]]; then + printf '\n%s\n %s\n' $"Clones to keep:" $"All locally installed clones" + else + printf '\n%s\n %s\n' $"Clones to keep:" $"All other locally installed clones" + fi + fi + printf '\n%s %s\n' $"PUR clone directory:" "${clonedir}" + if ((ccount == 1)); then + mapfile -t foreignpkgs < <("${pacman}" -Qmq) + mapfile -t foreignpkgsbase < <(expac -Q '%e' "${foreignpkgs[@]}") + # get target + if [[ "${pkgs[*]}" ]]; then + mapfile -t pkgsbase < <(expac -Q '%e' "${pkgs[@]}") + mapfile -t aurpkgsbase < <(CommArr -13 pkgsbase foreignpkgsbase) + if Proceed y $"Do you want to remove %s clones from PUR clone directory?" \ + "${aurpkgsbase[*]}"; then + printf '%s\n\n' $"removing uninstalled clones from PUR clone cache..." + for i in "${aurpkgsbase[@]}"; do + [[ ! -d "${clonedir}/${i}" ]] || rm -rf "${clonedir:?}/${i}" + done + fi + else + if Proceed y $"Do you want to remove all uninstalled clones from PUR clone directory?"; then + printf '%s\n\n' $"removing uninstalled clones from PUR clone cache..." + for i in "${clonedir}/"*; do + [[ ! -d "${i}" || " ${foreignpkgsbase[*]} " = *" ${i} "* ]] || + rm -rf "${clonedir:?}/${i}" + done + fi + if [[ ! "${PKGDEST}" || ! "${SRCDEST}" ]]; then + if Proceed y $"Do you want to remove all untracked files from PUR clone directory?"; then + printf '%s\n' $"removing untracked files from PUR clone cache..." + for i in "${clonedir}/"*; do + [[ ! -d "${i}" ]] || + git --git-dir="${i}/.git" --work-tree="${i}" clean -ffdx &>/dev/null + done + fi + fi + fi + else + if ! Proceed n $"Do you want to remove ALL clones from PUR clone directory?"; then + printf '%s\n' $"removing all clones from PUR clone cache..." + for i in "${clonedir}/"*; do [[ ! -d "${i}" ]] || rm -rf "${i}"; done + fi + fi + fi + exit "${E_OK}" +} + +GetIgnoredPkgs() { + # global ignoredpkgs + MapfileAdd ignoredpkgs < <("${pacman_conf[@]}" IgnorePkg) +} + +GetIgnoredGrps() { + # global ignoredgrps + MapfileAdd ignoredgrps < <("${pacman_conf[@]}" IgnoreGroup) +} + +GetInstallScripts() { + # global installscripts + [[ ! -d "${clonedir}/$1" ]] || + installscripts=("${clonedir}/$1/"*'.install') installscripts=("${installscripts[@]##*/}") +} + +GetBuiltPkg() { + local ext + # global builtpkg + # check PKGEXT suffix first, then default .xz suffix for repository packages in pacman cache + # and lastly all remaining suffixes in case PKGEXT is locally overridden + for ext in "${PKGEXT}" .pkg.tar{.xz,,.gz,.bz2,.lzo,.lrz,.Z}; do + builtpkg="$2/$1-${CARCH}${ext}" + [[ -f "${builtpkg}" ]] || builtpkg="$2/$1-any${ext}" + [[ ! -f "${builtpkg}" ]] || break + done + [[ -f "${builtpkg}" ]] || { builtpkg=''; return 1;} +} + +GetPkgbase() { + local i + # global pkgsbase basepkgs + [[ "${pkgsbase[*]:-}" ]] || pkgsbase=() + SetInfo "$@" + for i in "$@"; do + MapfileAdd pkgsbase < <(GetInfo PackageBase "${i}") + done + for i in "${pkgsbase[@]}"; do + [[ " ${basepkgs[*]} " = *" ${i} "* ]] || basepkgs+=("${i}") + done +} + +# shellcheck disable=SC2034,SC2154 +SetInfo() { + # Use auracle formatted info output for all aur packages passed to SetInfo, + # and sort it into associated arrays + local i apkgs=() ainfo=() + local -n field + + unset -v Name PackageBase Version Maintainer OutOfDate Groups {,Make,Check}Depends Provides Conflicts + [[ "$*" ]] || return + declare -Ag \ + {,c}{Name,PackageBase,Version,Maintainer,OutOfDate,Groups,{,Make,Check}Depends,Provides,Conflicts} + + for i in "$@"; do + if [[ " ${cName[*]} " = *" ${i} "* ]]; then + Name[${i}]="${cName[${i}]}" PackageBase[${i}]="${cPackageBase[${i}]}" + Version[${i}]="${cVersion[${i}]}" Maintainer[${i}]="${cMaintainer[${i}]}" + OutOfDate[${i}]="${cOutOfDate[${i}]}" Groups[${i}]="${cGroups[${i}]}" + Depends[${i}]="${cDepends[${i}]}" MakeDepends[${i}]="${cMakeDepends[${i}]}" + CheckDepends[${i}]="${cCheckDepends[${i}]}" Provides[${i}]="${cProvides[${i}]}" + Conflicts[${i}]="${cConflicts[${i}]}" + else + apkgs+=("${i}") + fi + done + + if [[ "${apkgs[*]}" ]]; then + mapfile -t ainfo < <(auracle info "${apkgs[@]}" -F \ + $'Name[{name}]={name}\nPackageBase[{name}]={pkgbase} + Version[{name}]={version}\nMaintainer[{name}]={maintainer} + OutOfDate[{name}]={outofdate:%s}\nGroups[{name}]={groups} + Depends[{name}]={depends}\nMakeDepends[{name}]={makedepends} + CheckDepends[{name}]={checkdepends}\nProvides[{name}]={provides} + Conflicts[{name}]={conflicts}'); ainfo=("${ainfo[@]##* }") + [[ ! "${ainfo[*]}" ]] || declare -g "${ainfo[@]}" "${ainfo[@]/#/c}" + fi + for i in "${!Maintainer[@]}"; do : "${Maintainer[${i}]:=%}"; done + for field in Groups Depends MakeDepends CheckDepends Provides Conflicts; do + for i in "${!field[@]}"; do + [[ "${field[${i}]}" ]] || unset -v "field[${i}]" + done + done +} + +GetInfo() { + local i field + field="$(declare -p "$1")" + local -A "${field/#declare -A $1/field}"; shift + + if (($#)); then + for i in "$@"; do + if [[ "${i}" && "${field[${i}]:-}" ]]; then + printf '%s\n' "${field[${i}]// /$'\n'}" + fi + done + elif [[ "${field[*]}" ]]; then + printf '%s\n' "${field[@]// /$'\n'}" + fi +} + +CheckRequires() { + local -a Qrequires + if mapfile -t Qrequires < <(expac -Ql'\n' '%N' "$@"); wait "$!"; then + fail $"failed to prepare transaction (could not satisfy dependencies)" + error $"%s: requires %s" "${Qrequires[*]}" "$*" "${E_INSTALL_DEPS_FAILED}" + fi +} + +Proceed() { + local answer ret readfullline=0 + + if [[ "${TERM:-}" = 'dumb' ]] || ((clean)); then + readfullline=1 + fi + case "$1" in + y) printf "%s::%s %s$2 [Y/n] %s" "${colorB}" "${reset}" "${colorW}" "${@:3}" "${reset}" + ((! noconfirm)) || { printf '\n'; return 0;} + for ((;;)); do + if ((readfullline)); then + read -r answer + else + read -rsn 1 answer + fi + case "${answer}" in + [Yy]|'') ret=0; break;; + [Nn]) ret=1; break;; + *) ((! readfullline)) || { ret=1; break;};; + esac + done;; + n) printf "%s::%s %s$2 [y/N] %s" "${colorB}" "${reset}" "${colorW}" "${@:3}" "${reset}" + ((! noconfirm)) || { printf '\n'; return 0;} + for ((;;)); do + if ((readfullline)); then + read -r answer + else + read -rsn1 answer + fi + case "${answer}" in + [Nn]|'') ret=0; break;; + [Yy]) ret=1; break;; + *) ((! readfullline)) || { ret=0; break;};; + esac + done;; + esac + ((readfullline)) || printf '%s\n' "${answer}" + return "${ret}" +} + +msg() { # shellcheck disable=SC2059 + printf "$1 $2\n" "${@:3}" +} + +info() { + msg "${colorB}::${reset}" "$@" +} + +warn() { + msg "${colorY}warning:${reset}" "$@" >&2 +} + +fail() { + msg "${colorR}error:${reset}" "$@" >&2 +} + +error() { + fail "${@:1:$#-1}"; exit "${!#}" +} + +GetLength() { + local length=0 i + for i in "$@"; do + ((length=${#i} > length ? ${#i} : length)) + done + printf '%s\n' "${length}" +} + +NothingToDo() { + (($#)) || { printf '%s\n' $" there is nothing to do"; exit "${E_OK}";} +} + +SudoV() { + local sleep + :>"${tmpdir}/pacpur.sudov.lck" + exec {sleep}<> <(:) + while [[ -e "${tmpdir}/pacpur.sudov.lck" ]]; do + sudo "${pacman}" -V >/dev/null + read -rt 298 -u "${sleep}" || : + done +} + +CommArr() { + # args are names of 2 array names and arguments to comm + local arr='' arr1="$2[@]" arr2="$3[@]" + [[ ! "${arr:=$(comm "$1" <(SortArr "${!arr1-}") <(SortArr "${!arr2-}"))}" ]] || + printf '%s\n' "${arr}" +} + +SortArr() { + local IFS=$'\n' # \n as only IFS character so $* expands newline-delimited + ((! $#)) || sort -u <<<"$*" # pass all arguments to sort -u +} + +SortArrAssign() { + local -n arr="$1" + mapfile -t "$1" < <(SortArr "${arr[@]}") +} + +RunVercmp() { + case "$1" in + "$2") printf '%d' 0;; + *) vercmp "$1" "$2";; + esac +} + +MapfileAdd() { + [[ -v "$1" ]] || declare -ga "$1=()" + local -n arr="$1" + mapfile -tO "${#arr[@]}" "$1" +} + +MakePkg() { + if ((silent)); then + makepkg "$@" "${makeopts[@]}" &>/dev/null + else + makepkg "$@" "${makeopts[@]}" + fi +} + +Cancel() { + printf '\n' + rm -f "${tmpdir}"/pacpur.{build,sudov}.lck + exit 130 +} + +Usage() { + printf '%s\n' \ + $"usage: pacpur <operation> [options] [target(s)] -- See also pacpur(8)" \ + $"operations:" \ + $" pacman extension" \ + $" -S, -Ss, -Si, -Sw, -Su, -Qu, -Sc, -Scc" \ + $" extend pacman operations to the PUR" \ + $" general" \ + $" -v, --version display version information" \ + $" -h, --help display help information" \ + '' \ + $"options:" \ + $" pacman extension - can be used with the -S, -Ss, -Si, -Sw, -Su, -Sc, -Scc operations" \ + $" -p, --pur only search, build, install or clean target(s) from the PUR" \ + $" -r, --repo only search, build, install or clean target(s) from the repositories" \ + $" general" \ + $" -e, --edit edit target(s) PKGBUILD and view install script" \ + $" -q, --quiet show less information for query and search" \ + $" --devel consider PUR development packages upgrade" \ + $" --foreign consider already installed foreign dependencies" \ + $" --ignore ignore a package upgrade (can be used more than once)" \ + $" --needed do not reinstall already up-to-date target(s)" \ + $" --noconfirm do not prompt for any confirmation" \ + $" --noedit do not prompt to edit files" \ + $" --rebuild always rebuild package(s)" \ + $" --silent silence output" + exit "${E_OK}" +} + +# +# Main +# + +trap Cancel INT + +# options +shortopts='DFGQRSTUVacdeghiklmnopqrstuvwxy' +longopts=('database' 'files' 'query' 'remove' 'sync' 'deptest' 'upgrade') +printf -v case_ops '%s|' "${longopts[@]}" +longopts+=('asdeps' 'asexplicit' 'cascade' 'changelog' 'check' 'clean' 'confirm' 'dbonly' 'debug' + 'deps' 'disable-download-timeout' 'downloadonly' 'explicit' 'file' 'foreign' 'groups' + 'help' 'info' 'list' 'machinereadable' 'native' 'needed' 'noconfirm' 'nodeps' + 'noprogressbar' 'nosave' 'noscriptlet' 'owns' 'print' 'recursive' 'refresh' 'regex' + 'quiet' 'search' 'sysupgrade' 'unneeded' 'unrequired' 'upgrades' 'verbose') +printf -v case_lo '%s|' "${longopts[@]}" +longopts_arg=('arch:' 'assume-installed:' 'cachedir:' 'color:' 'config:' 'dbpath:' 'gpgdir:' 'hookdir:' + 'ignore:' 'ignoregroup:' 'logfile:' 'overwrite:' 'print-format:' 'root:' 'sysroot:') +printf -v case_lo_arg '%s|' "${longopts_arg[@]%:}" +longopts_aur=('aur' 'by:' 'devel' {,'no'}'edit' 'literal' 'mflags:' 'rebuild' 'repo' 'rsort:' + 'searchby:' 'silent' 'sort:' {,'no'}'sudoloop' 'version') +printf -v case_lo_aur_arg '%s' "${longopts_aur[@]%%*[^:]}"; case_lo_aur_arg="${case_lo_aur_arg//:/|}" +parseopts "${shortopts}" "${longopts[@]}" "${longopts_arg[@]}" "${longopts_aur[@]}" -- "$@" || + exit "${E_INVALID_OPTION}" +set -- "${OPTRET[@]}" + +for ((;;)) { + case "$1" in + -S|--sync) pacS=1 installpkg=1 operation='sync' ;;& + -Q|--query) pacQ=1 ;;& + -c|--clean) clean=1 ccount+=1 ;;& + -d|--nodeps) makeopts+=("$1") dcount+=1 ;;& + -h|--help) help=1 ;;& + -i|--info) info=1 ;;& + -n|--native) repo=1 ;;& + -q|--quiet) QUIET=1 pacmanarg+=('-q') auropts+=("$1") ;;& + -s|--search) search=1 ;;& + -u|--upgrades|--sysupgrade) upgrade=1 installpkg=1 ;;& + -v) ver=1 ;;& + -w|--downloadonly) downloadonly=1 ;;& + -y|--refresh) refresh=1 ;;& + --as@(deps|explicit)) declare "${1#--}"=1 ;;& + --@(needed|noconfirm)) declare "${1#--}"=1 ;;& + --assume-installed) assumeinstalled+=("$2") ;;& + --color) color="$2" ;;& + --config) pacman_conf+=("$1" "$2") ;;& + --ignore) MapfileAdd ignoredpkgs <<<"${2//,/$'\n'}" ;;& + --ignoregroup) MapfileAdd ignoredgrps <<<"${2//,/$'\n'}" ;;& + --version) operation='version' ;;& + -e|--edit) pace=1 ;;& + -p|--pur) aur=1 ;;& + -r|--repo) repo=1 ;;& + --@(devel|noedit|rebuild)) declare "${1#--}"=1 ;;& + --literal) auropts+=("$1") ;;& + --mflags) MapfileAdd makeopts <<<"${2//[[:space:]]/$'\n'}" ;;& + --silent) silent=1 pacmanarg+=('-q') auropts+=('-q') makeopts+=('-L') ;;& + --@(search|sort)by) auropts+=("--searchby=$2") ;;& + --sort) sortorder='ascending' sortby="$2" ;;& + --rsort) sortorder='descending' sortby="$2" ;;& + --sudoloop) sudoloop=1 ;;& + --nosudoloop) sudoloop=0 ;;& + -[DFQRSTUV]) pacmanarg+=("$1") pac+=1 ;;& + -[cg-ik-qs-y]) pacmanarg+=("$1") ;;& + --@(${case_ops%|})) pacmanarg+=("${1:1:2}") pacmanarg[-1]="${pacmanarg[-1]^^}" pac+=1 ;;& + --@(${case_lo%|})|-d) pacopts+=("$1") ;;& + --@(${case_lo_arg%|})) pacopts+=("$1=$2") ;;& + --@(${case_lo_aur_arg%|})) shift ;;& + *) shift ;;& + --) break + esac +} + +# help or version +{ [[ "${operation}" != 'version' ]] && ((pac || ! ver));} || + { printf 'pacpur %s\n' "${version}"; exit "${E_OK}";} +((pac || ! help)) || Usage + +# sorting +case "${sortorder}" in + ascending) auropts+=("--sort=${sortby}");; + descending) auropts+=("--rsort=${sortby}");; +esac + +# packages +for arg in "$@"; do + case "${arg}" in + -) MapfileAdd pkgs;; + *) pkgs+=("${arg}");; + esac +done + +# color +[[ ! "${color}" && (! "$("${pacman_conf[@]}" Color)" || "${operation}" = 'upgrades' || + QUIET -ne 0 && search -ne 0) ]] && color='never' || color='auto' + +case "${color}" in + never) declare -r reset='' colorR='' colorG='' colorY='' colorB='' colorM='' colorW='' + pacopts+=('--color=never') auropts+=('--color=never') makeopts+=('-m');; + *) declare -r reset=$'\e[0m' colorR=$'\e[1;31m' colorG=$'\e[1;32m' colorY=$'\e[1;33m' \ + colorB=$'\e[1;34m' colorM=$'\e[1;35m' colorW=$'\e[1;39m' + pacopts+=('--color=auto') auropts+=('--color=auto');; +esac + +# sanity check +((! aur)) || refresh=0 +((! pace || pac)) || operation='edit' +((! pacQ || ! pace)) || pacmanarg+=('-e') +((! pacQ || ! upgrade)) || operation='upgrades' +((! pacS || ! downloadonly)) || installpkg=0 +((! pacS )) || [[ " ${pacmanarg[*]} ${pacopts[*]} " != *' -'@([glp]|-group|-list|-print)' '* ]] || operation='' +((! pacS || ! clean)) || search=0 info=0 upgrade=0 +((pac <= 1)) || error $"only one operation may be used at a time" "${E_FAIL}" +[[ "${operation}" != 'sync' ]] || ((search || info || clean || EUID)) || + error $"you cannot perform this operation as root" "${E_ROOT}" +((! pacS || ! search || ! info)) || + error $"invalid option: '--info' and '--search' may not be used together" "${E_INVALID_OPTION}" +command -v "${editor%% *}" >/dev/null || + error $"%s\$VISUAL%s and %s\$EDITOR%s environment variables not set or defined %seditor%s not found" \ + "${colorW}" "${reset}" "${colorW}" "${reset}" "${colorW}" "${reset}" "${E_MISSING_FILE}" +[[ "${PACMAN-}" != "${0##*/}" ]] || + error $"you cannot use %spacpur%s as PACMAN environment variable" "${colorW}" "${reset}" "${E_FAIL}" +[[ -w "${clonedir}" ]] || + error $"%s does not have write permission" "${colorW}${clonedir}${reset}" "${E_FS_PERMISSIONS}" +[[ "${pkgs[*]}" || ("${operation}" != @(sync|edit) && " ${pacmanarg[*]} " != *' -'[RU]' '*) ]] || + ((help + refresh + upgrade + clean + info)) || error $"no targets specified (use -h for help)" "${E_FAIL}" +((! repo || ! aur)) || + error $"invalid option: '--repo' and '--pur' may not be used together" "${E_INVALID_OPTION}" + +# operations +case "${operation}" in + edit) # edit (-e) handling + GetPkgbase "${pkgs[@]}" + EditPkgs "${pkgsbase[@]}" + exit;; + sync) + # search (-Ss, -s) handling + if ((search)); then + if ((! aur)); then + if ((refresh)); then + sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" -- "${pkgs[@]}" + else + "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" -- "${pkgs[@]}" + fi + fi + exitrepo="$?" + if ((! repo)) && [[ "${pkgs[*]}" ]]; then + auracle search "${auropts[@]}" -- "${pkgs[@]#aur/}" + fi + exitaur="$?" + ((! exitrepo || ! exitaur)) && exit "${E_OK}" || exit "${E_FAIL}" # exit code + # info (-Si, -i) handling + elif ((info)); then + if [[ ! "${pkgs[*]}" ]] && ((refresh)); then + sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" + elif [[ ! "${pkgs[*]}" ]]; then + "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" + else + ClassifyPkgs "${pkgs[@]}" + fi + if [[ "${repopkgs[*]}" ]]; then + if ((refresh)); then + sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" "${repopkgs[@]}" + else + "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" "${repopkgs[@]}" + fi + fi + if [[ "${aurpkgs[*]}" ]]; then + if ((refresh)) && [[ ! "${repopkgs[*]}" ]]; then + sudo "${pacman}" -Sy "${pacopts[@]}" + fi + ((aur)) || info $"Package(s) %s not found in repositories, trying %sPUR%s..." \ + "${colorW}${aurpkgs[*]}${reset}" "${colorM}" "${reset}" + auracle info "${auropts[@]}" -- "${aurpkgs[@]}" + fi + # clean (-Sc) handling + elif ((clean)); then + ((aur)) || sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" "${repopkgs[@]}" + ((repo)) || CleanCache "${pkgs[@]}" + # sysupgrade (-Su, -u) handling + elif ((upgrade)); then + [[ ! "${pkgs[*]}" ]] || ClassifyPkgs "${pkgs[@]}" + if ((! aur)); then + if ! sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" "${repopkgs[@]}" && ((repo)); then + exit "${E_FAIL}" + fi + fi + if [[ "${aurpkgs[*]}" ]] && ((! aur)); then + info $"Package(s) %s not found in repositories, trying %sPUR%s..." \ + "${colorW}${aurpkgs[*]}${reset}" "${colorM}" "${reset}" + fi + ((repo)) || Core + elif ((help)); then + "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" -- "${pkgs[@]}" + # sync (-S, -y), downloadonly (-Sw, -m), refresh (-Sy) + else + if [[ ! "${pkgs[*]}" ]]; then + sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" + else + ClassifyPkgs "${pkgs[@]}" + fi + [[ ! "${repopkgs[*]}" ]] || sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" "${repopkgs[@]}" + if [[ "${aurpkgs[*]}" ]]; then + if ((refresh)) && [[ ! "${repopkgs[*]}" ]]; then + sudo "${pacman}" -Sy "${pacopts[@]}" + fi + ((aur)) || info $"Package(s) %s not found in repositories, trying %sPUR%s..." \ + "${colorW}${aurpkgs[*]}${reset}" "${colorM}" "${reset}" + Core + fi + fi + exit;; + upgrades) # upgrades (-Qu) handling + ((aur)) || "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" -- "${pkgs[@]}" + exitrepo="$?" + if ((! repo)); then + [[ "${pkgs[*]}" ]] || + mapfile -t pkgs < <("${pacman}" -q "${pacmanarg[@]/%-u/-m}" \ + "${pacopts[@]/%--upgrades/--foreign}") + auracle sync "${auropts[@]}" -- "${pkgs[@]}" + fi + exitaur="$?" + if ((! exitrepo || ! exitaur)); then + exit "${E_OK}" + else + exit "${E_FAIL}" + fi;; + *) # other operations handling + if [[ " ${pacmanarg[*]} " = *' -F '* ]] && ((refresh)); then + sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" -- "${pkgs[@]}" + elif [[ ! "${pkgs[*]}" || " ${pacmanarg[*]} " = *' -'[DFQTglp]' '* ]] && + ((! asdeps && ! asexplicit && ! refresh)); then + "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" -- "${pkgs[@]}" + else + sudo "${pacman}" "${pacmanarg[@]}" "${pacopts[@]}" -- "${pkgs[@]}" + fi + exit;; +esac |