#!/bin/bash
# aur-build - build packages to a local repository
[[ -v AUR_DEBUG ]] && set -o xtrace
set -o errexit
shopt -s extglob
argv0=build
startdir=$PWD
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'

# default options
chroot=0 no_sync=0 overwrite=0 sign_pkg=0 run_pkgver=0

# default arguments (empty)
chroot_args=() pacconf_args=() repo_add_args=() makepkg_args=() makechrootpkg_makepkg_args=() makepkg_common_args=()

# default arguments
gpg_args=(--detach-sign --no-armor --batch)
makechrootpkg_args=(-c -u)

db_replaces() {
    bsdcat "$1" | awk '
    /%REPLACES%/ {
        while(NF != 0) { getline; print; }
    }'
}

run_msg() {
    printf >&2 'Running %s\n' "$*"
    "$@"
}

trap_exit() {
    if [[ ! -v AUR_DEBUG ]]; then
        rm -rf -- "$tmp"
        # Only remove package directory if all files were moved (#593)
        rm -df -- "$var_tmp"
    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/config.sh
source /usr/share/makepkg/util/message.sh
source /usr/share/makepkg/util/option.sh
source /usr/share/makepkg/util/parseopts.sh
source /usr/share/makepkg/util/pkgbuild.sh
source /usr/share/makepkg/util/util.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

## option parsing
opt_short='a:d:D:U:AcCfnrsvLNRST'
opt_long=('arg-file:' 'chroot' 'database:' 'repo:' 'force' 'root:'
          'sign' 'verify' 'directory:' 'no-sync' 'config:'
          'pacman-conf:' 'results:' 'remove' 'pkgver' 'rmdeps'
          'no-confirm' 'ignore-arch' 'log' 'new' 'makepkg-conf:'
          'bind:' 'bind-rw:' 'prevent-downgrade' 'temp' 'syncdeps'
          'clean' 'namcap' 'checkpkg' 'user:' 'makepkg-args:'
          'buildscript:')
opt_hidden=('dump-options' 'gpg-sign' 'ignorearch' 'noconfirm' 'nosync' 'margs:')

if ! parseopts "$opt_short" "${opt_long[@]}" "${opt_hidden[@]}" -- "$@"; then
    usage
fi
set -- "${OPTRET[@]}"

unset buildscript 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 ;;
        --config)
            shift; pacconf_args+=(--config "$1") ;;
        --buildscript)
            shift; buildscript=$1
            makepkg_common_args+=(-p "$1") ;;
        --nosync|--no-sync)
            no_sync=1 ;;
        --pkgver)
            run_pkgver=1; makepkg_args+=(--noextract) ;;
        --results)
            shift; results_file=$1 ;;
        --root)
            shift; db_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") ;;
        --pacman-conf)
            shift; pacman_conf=$1 ;;
        -N|--namcap)
            makechrootpkg_args+=(-n) ;;
        --checkpkg)
            makechrootpkg_args+=(-C) ;;
        -T|--temp)
            makechrootpkg_args+=(-T) ;;
        -U|--user)
            shift; 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-conf)
            shift; makepkg_conf=$1
            makepkg_common_args+=(--config "$1") ;;
        # makepkg options (build)
        -C|--clean)
            makepkg_args+=(--clean) ;;
        -L|--log)
            makepkg_args+=(--log) ;;
        --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
        --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

# shellcheck disable=SC2174
mkdir -pm 0700 "${TMPDIR:-/tmp}/aurutils-$UID"
tmp=$(mktemp -d --tmpdir "aurutils-$UID/$argv0.XXXXXXXX")
# shellcheck disable=SC2174
mkdir -pm 0700 "${TMPDIR:-/var/tmp}/aurutils-$UID"
var_tmp=$(mktemp -d --tmpdir="${TMPDIR:-/var/tmp/}" "aurutils-$UID/$argv0.XXXXXXXX")

trap 'trap_exit' EXIT
trap 'exit' INT

# Automatically choose repository root and name based on pacman
# configuration and user environment.
case ${db_name=$AUR_REPO} in
    "")
        case ${db_root=$AUR_DBROOT} in
            "") db_path=$(aur repo "${pacconf_args[@]}" --path)
                db_root=$(dirname "$db_path")
                ;;
             *) error '%s: root specified without database name' "$argv0"
                exit 1 ;;
        esac

        # Strip archive file extension (tar.gz, #714)
        db_name=$(basename "$db_path")
        db_name=${db_name%.db*}
        ;;
    *)
        case ${db_root=$AUR_DBROOT} in
            "") db_path=$(aur repo "${pacconf_args[@]}" --path -d "$db_name")
                db_root=$(dirname "$db_path")
                ;;
             *) db_path=$(readlink -f -- "$db_root/$db_name".db)
                ;;
        esac ;;
esac

# Resolve symbolic link to database.
if ! [[ -f $db_path ]]; then
    error '%s: %s: no such file or directory' "$argv0" "$db_path"
    exit 2
elif ! [[ -w $db_path ]]; then
    error '%s: %s: permission denied' "$argv0" "$db_path"
    exit 13
fi

# Custom makepkg command
if [[ $MAKEPKG ]]; then
    makepkg() { command -- "$MAKEPKG" "$@"; }
fi

# Write successfully built packages to file (#437)
if [[ -v results_file ]]; then
    : >"$results_file" # truncate file
fi

if (( chroot )); then
    if [[ -v makepkg_conf ]]; then
        chroot_args+=(--makepkg-conf "$makepkg_conf")
    fi
    if [[ -v pacman_conf ]]; then
        chroot_args+=(--pacman-conf "$pacman_conf")
    else
        chroot_args+=(--pacman-conf "/etc/aurutils/pacman-$db_name.conf")
    fi

    # makepkg --packagelist includes the package extension, but
    # makechrootpkg ignores PKGEXT set on the host. Retrieve it
    # seperately.
    packagelist() {
        run_msg aur chroot --packagelist "${chroot_args[@]}"
    }

    # 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 aur chroot --create "${chroot_args[@]}"
    run_msg aur chroot --update "${chroot_args[@]}"
else
    # Use libmakepkg to avoid a performance hit from linting the
    # PKGBUILD twice (in both makepkg and makepkg --packagelist)
    packagelist() (
        load_makepkg_config "${makepkg_conf-}"
        source_safe "${buildscript-PKGBUILD}"

        PKGDEST="${PKGDEST:-$startdir}" print_all_package_names
    )

    # Configuration for host builds.
    { printf '[options]\n'
      pacconf "${pacconf_args[@]}" --raw --options

      printf '[%s]\n' "$db_name"
      pacconf "${pacconf_args[@]}" --raw --repo="$db_name"
    } > "$tmp"/local.conf
fi

if [[ -v queue ]]; then
    exec {fd}< "$queue"
else
    exec {fd}< <(printf '\n')
fi

if (( sign_pkg )); then
    if [[ -v GPGKEY ]]; then
        gpg --list-keys "$GPGKEY"
        gpg_args+=(-u "$GPGKEY")
    fi
else
    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
fi

while IFS= read -ru "$fd" path; do
    cd_safe "$startdir/$path"

    # Run pkgver before --packagelist (#500)
    if (( run_pkgver )); then
        run_msg makepkg -od "${makepkg_common_args[@]}"
    fi

    if (( ! overwrite )); then
        unset exists

        while IFS= read -r candidate_pkg; do
            [[ -f $candidate_pkg ]] && exists+=("$candidate_pkg")
        done < <(PKGDEST="$db_root" packagelist)

        # Preserve the exit status from makepkg --packagelist (#671)
        wait $!

        if [[ ${exists[*]} ]]; then
            # XXX: use lint_pkgbuild in this case as no lint from a
            # makepkg invocation may take place
            warning '%s: skipping existing package (use -f to overwrite)' "$argv0"

            printf '%q\n' >&2 "${exists[@]}"
            continue
        fi
    fi

    if (( chroot )); then
        PKGDEST="$var_tmp" run_msg aur chroot --build "${chroot_args[@]}" \
               -- "${makechrootpkg_args[@]}" \
               -- "${makechrootpkg_makepkg_args[@]}"
    else
        PKGDEST="$var_tmp" run_msg makepkg \
               "${makepkg_common_args[@]}" "${makepkg_args[@]}"
    fi

    cd_safe "$var_tmp"
    pkglist=(!(*.sig)) # discard makepkg --sign from package list (#410)
    siglist=()

    # pkglist should not be empty (#513)
    if [[ ${pkglist[*]} == '!(*.sig)' ]]; then
        error '%s: invalid argument (no packages found)' "$argv0"
        exit 22
    fi

    for p in "${pkglist[@]}"; do
        if [[ -f $p.sig ]]; then
            warning '%s: existing package signature found' "$argv0"
            siglist+=("$p".sig)

        elif (( sign_pkg )); then
            gpg "${gpg_args[@]}" --output "$p".sig "$p"
            siglist+=("$p".sig)
        fi
    done

    # Move build products (relative paths) if previous steps were successful
    mv -f "${pkglist[@]}" "${siglist[@]}" "$db_root"

    # Update database
    cd_safe "$db_root"
    LANG=C repo-add "${repo_add_args[@]}" "$db_path" "${pkglist[@]}"

    if [[ -v results_file ]]; then
        printf >> "$results_file" "build:file://${db_path%/*}/%s\n" "${pkglist[@]}"
    fi

    if (( chroot )) || (( no_sync )); then
        continue
    else
        replaces=$(grep -Fxf <(db_replaces "$db_path") <(pacman -Qq) | paste -s -d, -)

        sudo pacman -Fy  --config="$tmp"/local.conf
        sudo pacman -Syu --config="$tmp"/local.conf --ignore="$replaces" --noconfirm
    fi
done

exec {fd}<&-

# vim: set et sw=4 sts=4 ft=sh:
