#!/usr/bin/env bash
# 
# dagpkg - create a directed graph of package dependencies and build
#          them in topological order

# Copyright (C) 2014 Nicolás Reynolds <fauno@parabola.nu>
# Copyright (C) 2014 Michał Masłowski <mtjm@mtjm.eu>
#
# License: GNU GPLv3+
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
set -e

source libremessages
source "$(librelib conf)"

# Source variables from libretools
load_files libretools
check_vars libretools FULLBUILDCMD || exit 1
#check_vars libretools HOOKPREBUILD HOOKLOCALRELEASE || exit 1 # optional

# Source variables from makepkg
load_files makepkg
check_vars makepkg CARCH || exit 1

# Globals:
# - temp_dir
# - log
# - name (sort of, it's also local to visit_pkgbuild() )
# - prev
# - I
# - marks
# - various PKGBUILD variables:
#   - pkgbase/pkgname
#   - epoch/pkgver/pkgrel
#   - arch
#   - {,make,check}depends

# End inmediately but print an useful message
trap_exit() {
	local signal=$1; shift
	local msg=("$@")
	term_title "error!"
	echo
	error "(%s) %s (leftovers on %s)" \
		"${0##*/}" "$(print "${msg[@]}")" "${temp_dir}"
	trap -- "$signal"
	kill "-$signal" "$$"
}

setup_traps trap_exit

source_pkgbuild() {
	# Source this PKGBUILD, if it doesn't exist, exit
	if ! load_PKGBUILD &>/dev/null; then
		error "No PKGBUILD in %s" "$PWD"
		exit 1
	fi

	# Save resources
	# This is intentionally less exhaustive than unset_PKGBUILD()
	# XXX: document which things we actually *want* to not be unset.
	unset pkgdesc license groups backup install md5sums sha1sums \
		sha256sums source options &>/dev/null

	unset build package &>/dev/null

	local _pkg
	for _pkg in "${pkgname[@]}"; do
		unset "package_${_pkg}" &>/dev/null || true
	done

	# This is the name of the package
	name="${pkgbase:-${pkgname[0]}}"
}

source_pkgbuild

# A temporary work dir and log file
temp_dir="${1:-$(mktemp -dt ${name}-testpkg-XXXX)}"
log="${temp_dir}/buildorder"

# Mark array for DFS-based topological sort.  See
# https://en.wikipedia.org/wiki/Topological_sort for an explanation of
# the algorithm.  Key: package name, value: 0 for unvisited package, 1
# during visit, 2 after visit.
declare -A marks

# Visit a PKGBUILD for graph building.
visit_pkgbuild() {
	# The name of the previous package
	prev="${1}"

	local name
	source_pkgbuild

	# If it's already built we don't bother
	if is_built "${pkgname[0]}" "$(get_full_version "${pkgname[0]}")"; then
		return
	fi

	# Detect cycle or already visited package
	case "${marks[$name]:-0}" in
		1) msg2 "cycle found with %s depending on %s" $prev $name
			exit 1;;
		2) return;;
	esac

	msg "%s (%s)" ${name} ${prev}

	if ! in_array "${CARCH}" "${arch[@]}"; then
		warning "%s isn't ported to %s yet" ${name} ${CARCH}
	fi

	# If the envvar I contains this package, ignore it and exit
	if in_array "$name" $I; then
		msg2 "%s ignored" ${name}
		return
	fi

	# Mark the package as being visited
	marks[$name]=1

	# Recurse into dependencies
	local d
	for d in "${depends[@]}" "${makedepends[@]}" "${checkdepends[@]}"; do
		# Cleanup dependency versions
		d=$(echo $d | sed "s/[<>=].*//")

		# Where's the pkgbuild?
		local w=$(toru-where $d)

		# Skip if not available
		test -z "$w" && continue

		# Go to this dir
		pushd $w &>/dev/null

		visit_pkgbuild "$name"

		popd &>/dev/null
	done

	# Mark the package as finished
	marks[$name]=2
	# Append it to the reversed list of packages to build.
	echo "$name" >> "${log}"
}

# If we specified a work dir on the cli it means we want to skip
# dependency graph creation and jump to build whatever is there
if [ -z "${1}" ]; then
	# Visit the root PKGBUILD to make the graph.
	visit_pkgbuild ""
else
	msg "Resuming build..."
fi

# enter work dir
pushd "${temp_dir}" &>/dev/null
nl ${log} | while read order pkg; do
	# skip if already built
	if test -f "${pkg}/built_ok"; then
		warning "tried to build %s twice" "%{pkg}"
		continue
	fi

	# where's this package?
	local w="$(toru-where "$pkg")"
	test -z "$w" && continue

	# copy to work dir if not already
	# this means you can make modifications to the pkgbuild during the
	# graph build or remove the dir after a build failure and let dagpkg
	# copy a new version
	test -d "$pkg" || cp -r "$w" "$pkg"
	pushd "$pkg" &>/dev/null

	term_title "%s(%s)" "$pkg" "$order"

	msg "Building %s" ${pkg}

	# upgrade the system
	# this would probably have to go on HOOKPREBUILD if you're working
	# outside chroots
	sudo -E pacman -Syu --noconfirm

	# run the pre build command from libretools.conf
	if [[ -n "$HOOKPREBUILD" ]]; then
		${HOOKPREBUILD}
	fi

	# run the build command
	${FULLBUILDCMD}

	# Run local release hook with $1 = $repo
	if [[ -n "$HOOKLOCALRELEASE" ]]; then
		${HOOKLOCALRELEASE} "$(basename "$(dirname "$w")")"
	fi

	# it's built!
	touch built_ok

	popd &>/dev/null
done

popd &>/dev/null
# cleanup
rm -rf ${log} "${temp_dir}"

term_title "done"