#!/bin/bash
# set -x # uncomment for debug
# Builds packages from ABS recursively. It tries to find dependencies that
# aren't built or need update and then makepkg them in order.

# TODO move __build to chroot

source /etc/makepkg.conf
source /etc/abs.conf
source /etc/libretools.conf

if [ -z $XDG_CONFIG_HOME ]; then # Avoid /libretools dir doesn't exist errors

    error "There's no XDG_CONFIG_HOME var set"; exit 1

elif [ -e $XDG_CONFIG_HOME/libretools/libretools.conf ]; then

    source $XDG_CONFIG_HOME/libretools/libretools.conf

fi

usage() {

    echo "cd to a dir containing a PKGBUILD and run:"
    echo "$0 [options]"
    printf "This script will check dependencies, build them if possible "
    printf "and stage the packages on it's repo."
    echo
    echo "OPTIONS:"
    echo " -h           : this message."
    echo " -a absdir    : set absdir as ABSROOT."
    echo " -b build_dir : use a fullpkg build_dir and only build."
    echo " -c           : check deps only, do not build."
    echo " -d build_dir : use this dir to build. Defaults to mktemp."
    echo " -n           : don't update pacman db."
    echo " -m max_level : check deps until this level"
    echo " -r \"command\" : use this instead of \"$FULLBUILDCMD\""
    echo
    exit 1

}

# Finds a PKGBUILD on toru's path cache
# Look in all caches but pick the first one
# TODO move to a toru flag (-p?)
where_is() {
  local _repo
  local _path
  for _repo in ${REPOS[@]}; do
    _path=$(grep "^${1}:" "${TORUPATH}/${_repo}.paths.cache" 2>/dev/null| \
      cut -d: -f2 2>/dev/null)

    [ -n "${_path}" ] && break
  done

  echo ${_path}

}

# Removes a package from the buildorder
# $1 package name
# $2 buildorder file
remove_buildorder() {
  grep -Evw "${1}" ${2} > ${2}2
  mv -f ${2}2 ${2}

  return $?
}

# Get repo name. Asumes ${ABSROOT}/repo/package/PKGBUILD
guess_repo() {
    basename $(dirname $(pwd))
}

# return : full version spec, including epoch (if necessary), pkgver, pkgrel
#  usage : get_fullver( ${epoch:-0}, $pkgver, $pkgrel )
get_fullver() {
    if [[ $1 -eq 0 ]]; then
# zero epoch case, don't include it in version
        echo $2-$3
    else
        echo $1:$2-$3
    fi

}

# Cleans the build_dir.
cleanup() {
# Do nothing or already cleaned.
    [[ "${do_cleanup}" = false || ! -d ${build_dir} ]] && return 0

# Only do cleanup on level 0.
    msg "Cleaning up..."
    [ $level -eq 0 ] && rm -rf $build_dir/

}

# Checks ABSROOT and look for target pkg deps. Adds them if not built or outdated.
find_deps() {
# Check this level
    source PKGBUILD

# unset PKGBUILD variables
    unset pkgdesc url license groups optdepends provides conflicts replaces \
          backup options install changelog source noextract md5sums build \
          check package
    for _pkg in ${pkgname[@]}; do
      unset package_${_pkg} >/dev/null 2>&1
    done

    local repo=${repo:-$(guess_repo)}
    local pkgbase=${pkgbase:-${pkgname[0]}}
# Provide a default 0 to epoch
    local epoch=${epoch:-0}
    local fullver=$(get_fullver ${epoch} ${pkgver} ${pkgrel})

# Check if the package is already built
    if is_built "${pkgbase}>=${fullver}"; then
# pkg is built and updated
        exit 0
    fi

# greater levels are built first
    echo "${level}:${pkgbase}" >>"${build_dir}/BUILDORDER"
# PKGBUILD is already there
    if [ -d "${build_dir}/${pkgbase}" ]; then
        exit 0
# Copy dir to build_dir
    else
        cp -r ../${pkgbase}/ ${build_dir}/

# to identify repo later
        echo "repo=$repo" > "${build_dir}/${pkgbase}/.INFO"
    fi

# current package plus a space for every level
    msg2 "%${level}s${pkgbase}-${fullver}"

## Check next levels
    declare -i next_level=$level+1

# All deps in separate line, only once, without version.
    deps=($(echo "${depends[@]} ${makedepends[@]}" | \
           sed "s/[=<>]\+[^ ]\+//g" | \
           tr ' ' "\n" | \
           sort -u))

    for _dep in ${deps[@]}; do

        local found=false
        local pkgdir=$(where_is ${_dep})

        if [ -d "${pkgdir}" ]; then
          found=true

          pushd "${pkgdir}" > /dev/null
# runs itself on dep's PKGBUILD dir
          $0 -c -d ${build_dir} -l ${next_level}

# probable circular deps
          [ $? -eq 20 ] && return 20
          popd > /dev/null
        fi

        if ! (( found )); then
          echo "dep_not_found:$_dep" >>$build_dir/log
        fi

    done

## End variable block

    unset next_level dir
}

__build() {
    pushd ${build_dir} >/dev/null

# greater levels must be built first
    build_packages=($(sort -gr $buildorder | cut -d: -f2))

    while [ ${#build_packages[@]} -ge 1 ]; do
        pushd $build_dir/${build_packages[0]} >/dev/null
        source PKGBUILD

        msg2 "${pkgbase:-${pkgname[0]}} $pkgver-$pkgrel"

        msg2 "Checking for non free deps"
        pkgbuild-check-nonfree || {
# this error means nonfree others means fail.
            if [ $? -eq 15 ]; then

              echo "nonfree:$(basename $PWD)" >>$build_dir/log

# take out package from $buildorder
              remove_buildorder "$(basename $PWD)" $buildorder

# build next package
              continue
            fi
        }

        msg2 "Building $(basename $PWD)"

# this buildcmd is on libretools.conf
        $FULLBUILDCMD; r=$?

        case $r in

## Succesfull build
            0)

                plain "The build was succesful."
                if source .INFO && [ -n $repo ]; then

# Calls a local release script if it's used
                    if [ ! -z $HOOKLOCALRELEASE ]; then
                        find -name "*.pkg.tar.?z" -print0 | xargs -0 $HOOKLOCALRELEASE $repo
                    fi

                    librestage $repo || echo "unstaged:$(basename $PWD)" >>$build_dir/log

                    msg "Updating pacman db and packages"
                    sudo pacman -Sy || true

                fi

                echo "built:$(basename $PWD)" >>$build_dir/log
            ;;

# # Build failed
            *)
                error "There were errors while trying to build the package."
                echo "failed:$(basename $PWD)" >>$build_dir/log
            ;;
        esac

        remove_buildorder "${build_packages[0]}" $buildorder || true

# which is next package?
        build_packages=($(sort -gr $buildorder | cut -d: -f2))
        popd > /dev/null
    done

    pkgs=($(grep "nonfree:" $build_dir/log)) && {
        error "Those packages contain nonfree deps:"
        echo ${pkgs[@]} | tr " " "\n" | cut -d: -f2
    }

    pkgs=($(grep "built:" $build_dir/log)) && {
        msg "Those packages were built and staged:"
        echo ${pkgs[@]} | tr " " "\n" | cut -d: -f2
    }

    pkgs=($(grep "failed:" $build_dir/log)) && {
        error "Those packages failed to build:"
        echo ${pkgs[@]} | tr " " "\n" | cut -d: -f2
    }

    pkgs=($(grep "unstaged:" $build_dir/log)) && {
        error "Those packages couldn't be staged because of missing reponame:"
        echo ${pkgs[@]} | tr " " "\n" | cut -d: -f2
    }

    popd >/dev/null
}

# End inmediately but print a useful message
trap_exit() {
  error "$@"
  warning "Leftover files left on $build_dir"

  exit 1
}

# Trap signals from makepkg
set -E
trap 'cleanup' 0
trap 'trap_exit "(prfullpkg:${level}) TERM signal caught. Exiting..."' TERM HUP QUIT
trap 'trap_exit "(prfullpkg:${level}) Aborted by user! Exiting..."' INT
trap 'trap_exit "(prfullpkg:${level}) An unknown error has occurred. Exiting..."' ERR

force_build=""
level=0
noupdate=false
build_only=false
check_deps_only=false
do_cleanup=false
max_level=21

while getopts 'ha:b:cCd:l:nm:r:' arg; do
    case $arg in
        h) usage ;;
        a) ABSROOT="$OPTARG" ;;
        b) build_only=true
           build_dir="$OPTARG"

           if [ -z "${build_dir}" ]; then
               usage
           fi

           if [ ! -r "${build_dir}/BUILDORDER" ] ; then
               error "${build_dir}/BUILDORDER doesn't exist."
               exit 1
           fi

           ;;
        c) check_deps_only=true ;;
        C) do_cleanup=true;;
        d) build_dir="$OPTARG" ;;
# hidden option to know dep level.
        l) level=$OPTARG ;;
        n) noupdate=true;;
        m) max_level=$OPTARG ;;
        r) FULLBUILDCMD="$OPTARG" ;;
    esac
done

if ! (( build_only )); then

# Check if we are actually on a build directory. Do this early.
    if [ ! -r PKGBUILD ]; then
        error "This isn't a build directory"
        usage
    fi

# Run the pre build hook
    if [ ! -z "${HOOKPKGBUILDMOD}" ]; then
        ${HOOKPKGBUILDMOD}
    fi

fi

if [ ${level} -eq 0 ]; then

# use -d option or else mktemp
    build_dir="${build_dir:-$(mktemp -d /tmp/fullpkg.XXXXXX)}"

# in case of custom -d option
    if [ ! -d "${build_dir}" ]; then
        mkdir -p "${build_dir}"
    else
# files already there can screw find_deps
        cleanup
    fi

# make files for log and buildorder
    touch "${build_dir}"/{log,BUILDORDER}
    buildorder="${build_dir}/BUILDORDER"

    if ! (( noupdate )); then

# Always return true
        msg "Updating pacman db and packages"
        sudo pacman -Syu --noconfirm || true

    fi

    if (( build_only )); then

        msg "Building Packages"

        __build

        exit 0

    fi

    msg "Checking dependencies"
fi

# Probable circular deps
[ $level -ge $max_level ] && exit 20

# Find the dependencies on the ABS itself
find_deps || {

# Probable circular deps
    if [ $? -eq 20 ]; then

# Show error only on level 0
       if [ $level -eq 0 ]; then
           error "Check for circular deps on $build_dir/BUILDORDER";
       fi

    fi
# Pass message 20
    exit 20
}

# only build on level 0
if (( check_deps_only )) || [ $level -gt 0 ]; then
   exit 0
fi

# Build the packages
msg "Building packages:"
__build

echo
msg2 "Check if your system works fine and librerelease if it does."

exit 0