#!/bin/bash
# TO TEST: (on find_deps)
# * Detect pkgnames by provides, replaces, etc. instead of dir tree

source /etc/makepkg.conf
source /etc/abs.conf
source /etc/libretools.conf
source /usr/bin/libremessages

# Avoid /libretools dir doesn't exist errors
if [ -z $XDG_CONFIG_HOME ]; then
  error "There's no XDG_CONFIG_HOME var set"
  exit 1
fi
# set queue_file and ban_file
[ -e $XDG_CONFIG_HOME/libretools/libretools.conf ] && \
  source $XDG_CONFIG_HOME/libretools/libretools.conf
queue_file=$XDG_CONFIG_HOME/libretools/queue
ban_file=$XDG_CONFIG_HOME/libretools/ban


##### START FUNCTIONS #####

function 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           : do not check deps but build. Needs -d"
    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 " -o           : work offline (avoid queuing)"
    # printf " -f pkgname  : build even when a package has been built. "
    # printf " Use it as many times as needed\n"
    echo
}

# Queue Management
# * Always get the queue list from the server
# * Add/Remove from queue
# * Check if a package is listed

# Get the queue list from the server
get_queue() {
    [[ "$OFFLINE" -eq true ]] && return 0

    rsync -e ssh -aq $PARABOLAHOST:mips64el/queue $queue_file >/dev/null 2>&1 || {
        error "Failed to retrieve queue list"
        return 1
    }
}

# Put the queue list on the server
put_queue() {
    [[ "$OFFLINE" = true ]] && return 0

    rsync -e ssh -aq $queue_file $PARABOLAHOST:mips64el/queue >/dev/null 2>&1 || {
        error "Failed to put queue list"
        return 1
    }
}

# Add packages to the queue
update_queue() {
    get_queue
    echo "$(basename $PWD):$PACKAGER" >> $queue_file
    put_queue || return $?
}

# Remove a package from the queue
remove_queue() {
    [[ "$OFFLINE" -eq true ]] && return 0

    get_queue

    grep -Evw "^$(basename $PWD)" ${queue_file} > ${queue_file}2
    mv -f ${queue_file}2 ${queue_file}

    put_queue && return 0 || return $?
}

# Checks if a package is listed
check_queue() {
    [[ "$OFFLINE" = "true" ]] && return 0

    get_queue

    local packager=$(grep -w "$(basename $PWD)" ${queue_file} | cut -d ':' -f2)

    [ -n "$packager" -a "$packager" != "$PACKAGER" ] && {
        warning "$(basename $PWD) is being packaged by $packager. Please wait."
        return 1
    }

    return 0
}

# END Queue Management #

## Build order management ##

# 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}
}

# Guesses the repo name according to the parent dir of the current package
# assuming the form repo/package/PKGBUILD
guess_repo() {
    basename $(dirname $(pwd))
}

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

# Usage: cleanup [ $(basename $PWD) ] from PKGBUILD dir
# cleans the build_dir
function cleanup {
# Do nothing OR
# Already cleaned
    [[ "${do_cleanup}" = "n" || ! -d ${build_dir} ]] && return 0

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

# Check PKGBUILD and find non built or outdated deps
# on ABSROOT which should be abslibre-misp64el
function find_deps {
    ## Check this level.
    source PKGBUILD
    local repo=${repo:-$(guess_repo)}
    local pkgbase=${pkgbase:-${pkgname[0]}}
    local epoch=${epoch:-0}
    local fullver=$(get_full_version ${epoch} ${pkgver} ${pkgrel})

# If package and correct ${fullver} is built exit
# TODO?: If this package is in force_build: skip this step
    if is_built "${pkgbase}=${fullver}"; then
        exit 0
    fi

# Tell which packages are deeper in deps (even if they are on build_dir)
# so we can build them first.
    echo "${level}:${pkgbase}" >> "${build_dir}/BUILDORDER"

# if pkgbuild directory is on build_dir, do not copy and exit
    if [ -d "${build_dir}/${pkgbase}" ]; then
        exit 0
    else
# Copy dir to build_dir
        cp -r ../${pkgbase}/ ${build_dir}/
# Info to eval later
        echo "repo=$repo" > "${build_dir}/${pkgbase}/.INFO"
    fi
    
# Inform the current package plus a space for every level for indent
    msg2 "%${level}s${pkgbase}-${fullver}"

## Check next levels
# Clean version checking
    deps=$(echo "${depends[@]} ${makedepends[@]}" | \
           sed "s/[=<>]\+[^ ]\+//g" | \
           tr ' ' "\n" | \
           sort -u)

# Increase build level
    declare -i next_level=$level+1
    
    for _dep in ${deps[@]}; do
        for _repo in ${REPOS[@]}; do
            # try to find $_dep on each repo from dirname
            if [ -e "${ABSROOT}/${_repo}/${_dep}/PKGBUILD" ]; then
                pushd "${ABSROOT}/${_repo}/${_dep}" > /dev/null
                $0 -c -d ${build_dir} -l ${next_level}

                # Circular deps must fail
                [ $? -eq 20 ] && return 20
                popd > /dev/null
                break 1 # found, go to next dep

# if search pkgname in repo doesn't work
# this should find pkgsplits
#            elif _dir=($(find "$ABSROOT/${_repo}" -type f -name PKGBUILD -print0 2>/dev/null | \
#            "xargs" -0 -e grep -HEw "pkgname=|pkgbase=|provides=" | grep -w "$_dep" 2>&1));
#            then
#                _dir=$(dirname $(echo $_dir | cut -d: -f1))
#                plain "guess for $_dep -> $_dir"
#                pushd $_dir > /dev/null
#                $0 -c -d ${build_dir} -l ${next_level}
## Circular deps must fail
#                [ $? -eq 20 ] && return 20
#                popd > /dev/null
#                break 1 # found, go to next dep

            else
                echo "dep_not_found:$_dep:$_repo" >> $build_dir/log
            fi
        done
    done

    unset next_level dir
    # unset PKGBUILD variables
    unset pkgname pkgver pkgrel epoch pkgdesc arch url license groups depends \
          makedepens checkdepends optdepends provides conflicts replaces backup \
          options install changelog source noextract md5sums build check package
}

function _pkg_build () {
    pushd ${build_dir}  > /dev/null
    # packages to build are on $buildorder
    # 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"

# Check if pkg is being built
        msg2 "Checking build queue"
        check_queue || {
            echo "someone_is_building:$(basename $PWD)" >> $build_dir/log

            remove_buildorder "$(basename $PWD)" $buildorder
            continue
        }

# Let everybody know we're building this.
        msg2 "Updating build queue"
        update_queue || {
            warning "Couldn't update the queue, let your partners know about this."
        }

        msg2 "Checking for non free deps"
        pkgbuild-check-nonfree || {
# pkgbuild-check-nonfree fails with 15 if there are nonfree deps,
# fails with something else if blacklist can't be retrieved
            if [ $? -eq 15 ]; then
# log they have nonfree deps and so didn't build
              echo "nonfree:$(basename $PWD)" >> $build_dir/log
# take out package from $buildorder
              remove_buildorder "$(basename $PWD)" $buildorder
# continue building 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."
                source .INFO && [ -n $repo ] && {

# Calls a local release script if it's used
                    [ -z $HOOKLOCALRELEASE ] || \
                      $HOOKLOCALRELEASE $repo *.pkg.tar.?z

# Stage for releasing
                    librestage $repo || {
                        echo "unstaged:$(basename $PWD)" >> $build_dir/log
                    }

                    msg "Updating pacman db and packages"
                    sudo pacman -Syu --noconfirm
                }

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

# Package was built or failed: take it out of $buildorder
        remove_buildorder "${build_packages[0]}" $buildorder

# Take package out from queue
        remove_queue

# Set build_packages before next cycle run
        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() {
  remove_queue

  error "$@"
  warning "Leftover files left on $build_dir"

  exit 1
}

## END FUNCTIONS ##

## Trap signals
# From makepkg
set -E
trap 'cleanup' 0
trap 'trap_exit "TERM signal caught. Exiting..."' TERM HUP QUIT
trap 'trap_exit "Aborted by user! Exiting..."' INT
trap 'trap_exit "An unknown error has occurred. Exiting..."' ERR

force_build=""
level=0
noupdate='n'
build_only='n'
check_deps_only='n'
do_cleanup='n'
max_level=21
OFFLINE=false
while getopts 'ha:bcCd:l:nm:r:o' arg; do
    case $arg in
        h) usage; exit 0 ;;
        a) ABSROOT="$OPTARG" ;;
        b) build_only='y' ;;
        c) check_deps_only='y' ;;
        C) do_cleanup='y';;
        # f) force_build+="-f pkgname " ;;
        d) build_dir="$OPTARG" ;;
# hidden option to know what to build first.
# if $level > 0 it will not build
        l) level=$OPTARG ;;
        n) noupdate='y';;
        m) max_level=$OPTARG ;;
        r) FULLBUILDCMD="$OPTARG" ;;
        o) OFFLINE=true ;;
    esac
done

# Check if we are actually on a build directory
# Do this early
[ ! -r PKGBUILD ] && {
    error "This isn't a build directory"
    usage && exit 1
}

# Add mips64el if missing from arch=() and it isn't an 'any' package
if ! grep mips64el PKGBUILD >/dev/null; then
  plain "Adding mips64el arch"
  sed -i "s/^\(arch=([^)anym]\+\))/\1 'mips64el')/" "PKGBUILD"
fi

# Only on level 0
if [ $level -eq 0 ]; then
# if build_dir exist use it, else make a build_dir
    build_dir=${build_dir:-$(mktemp -d /tmp/fullpkg.XXXXXX)}

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

    [ $noupdate = 'n' ] && {
        msg "Updating pacman db and packages"
        sudo pacman -Syu --noconfirm
    }

# Build only
    [ $build_only == 'y' ] && {
        _pkg_build
        exit 0
    }

    msg "Checking dependencies"
fi

## if $level = 20 it's highly likely there are circular deps
[ $level -ge $max_level ] && exit 20

# Tries to find deps and build order
find_deps || { 
    # if find_deps finds circular deps
    # it should exit with status 20
    [ $? -eq 20 ] && {
        # only show message on level 0
        [ $level -eq 0 ] && error "Check for circular deps on $build_dir/BUILDORDER";
    }
    exit 20
}

# levels greater than 0 must only check deps
[ $check_deps_only = 'y' -o $level -gt 0 ] && exit 0

# check BUILDORDER to not include banned deps and
[ $level -eq 0 -a -d $build_dir ] && {
# Check for banned deps
    if [ -w $ban_file -a -r $ban_file ]; then
        chmod o+rw $ban_file || error "Ban file is not readable/writable ($ban_file)"

    else
        rsync -e ssh -aq $PARABOLAHOST:mips64el/ban >/dev/null 2>&1 || {
            warning "Failed to get ban list" && [ -r $ban_file ] && {

# Use local copy of ban file if it is avaliable and continue.
              search=$(cat ${ban_file} | tr "\n" "|")
# Keep track of banned files
              egrep -w "$search" ${buildorder} >> ${build_dir}/banned
# Take banned packages out from buildorder
              egrep -vw "$search" ${buildorder} > ${buildorder}2
              mv -f ${buildorder}2 ${buildorder}
              unset search
            }
        }
    fi
}

## START Building
msg "Building packages:"

# If the queue file isn't writable go into offline mode
[ ! -w $queue_file ] && {
  error "Can't write queue file"
  OFFLINE=true
}

# Build the package
_pkg_build

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

exit 0