#!/usr/bin/env bash
set -euE
# libremakepkg

# Copyright (C) 2010-2012 Nicolás Reynolds <fauno@parabola.nu>
# Copyright (C) 2010-2012 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
# Copyright (C) 2012 Michał Masłowski <mtjm@mtjm.eu>
# Copyright (C) 2012-2014 Luke Shumaker <lukeshu@sbcglobal.net>
#
# License: GNU GPLv2+
#
# This file is part of Parabola.
#
# Parabola 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 2 of the License, or
# (at your option) any later version.
#
# Parabola 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Parabola. If not, see <http://www.gnu.org/licenses/>.

. $(librelib conf)
. $(librelib messages)
. $(librelib chroot/makechrootpkg.sh)

set -o pipefail
shopt -s nullglob
umask 0022

# Global variables:
readonly _indent="$(librelib chroot/indent)"
readonly INCHROOT=$([[ -f /.arch-chroot ]] && echo true || echo false)
NONET=true # can be changed with the -N flag
# {PKG,SRC,SRCPKG,LOG}DEST set at runtime by makepkg.conf
# MAKEFLAGS, PACKAGER set at runtime by makepkg.conf
# LIBREUSER, LIBREHOME are set by conf.sh
librechroot_flags=()

# Hooks ########################################################################

hook_pre_build=(:)
hook_post_build=(:)
hook_check_pkgbuild=(:)
hook_check_pkg=(:)
. $(librelib chroot/hooks-chcleanup.sh)
. $(librelib chroot/hooks-check.sh)
. $(librelib chroot/hooks-distcc.sh)

# Boring/mundane functions #####################################################

indent() {
	"$_indent" ' |  '
}

# Usage: _check_perms_dir $directory
# Make sure that $directory is readable and executable (searchable) by 'nobody'
check_directory_permissions() (
	local dir=$1
	# `cd` to the directory, then test `.`; that way if parent
	# directories aren't readable, we aren't testing for that.  We
	# only need the last element in `$dir`.
	cd "$dir"
	if ! sudo -u nobody test -r . -a -x .; then
		error "Directory '%s' must be readable by user 'nobody'" "$dir"
		return 1
	fi
	return 0
)

# Usage: exit_copy $copydir $src_owner
# End immediately, but copy log files out
exit_copy() {
	local copydir=$1
	local src_owner=$2
	if ! $INCHROOT; then
		msg "Copying log and package files out of the chroot..."
		move_products "$copydir" "$src_owner"
	fi
}

# Usage; run_hook $hookname $args...
run_hook() {
	local hookname=$1; shift
	local hookvar="hook_${hookname}[@]"

	local fails=()
	for hook in "${!hookvar}"; do
		"$hook" "$@" || fails+=("$hook")
	done |& indent

	if [[ ${#fails[@]} -gt 0 ]]; then
		error "Failure(s) in %s: %s" "$hookname" "${fails[*]}"
		return 1
	else
		return 0
	fi
}

# Usage: add_to_local_repo $copydir $pkgfiles...
add_to_local_repo() {
	local copydir=$1; shift
	mkdir -p "$copydir/repo"
	local pkgfile
	for pkgfile in "$@"; do
		cp "$pkgfile" "$copydir/repo"
		pushd "$copydir/repo" >/dev/null
		repo-add repo.db.tar.gz "${pkgfile##*/}"
		popd >/dev/null
	done
}

hook_post_build+=('cleanup')
cleanup() {
	local copydir=$1
	rm -f -- "$copydir"/chroot{prepare,build}
}

build() (
	local copydir=$1; shift
	local repack=$1; shift

	local run_ynet=()
	local run_nnet=()
	if $INCHROOT; then
		run_ynet=(unshare)
		run_nnet=(unshare -n)
	else
		run_ynet=(librechroot "${librechroot_flags[@]}"    run)
		run_nnet=(librechroot "${librechroot_flags[@]}" -N run)
	fi
	$NONET || run_nnet=("${run_ynet[@]}")

	prepare_chroot "$copydir" "$LIBREHOME" "$repack" false
	"${run_ynet[@]}" /chrootprepare false "$@" |& indent
	run_hook pre_build "$copydir"
	trap "run_hook post_build '$copydir'" EXIT
	"${run_nnet[@]}" /chrootbuild   false "$@" |& indent
)

# The main program #############################################################

usage() {
	print "Usage: %s [options] [-- makepkg args]" "${0##*/}"
	print 'This program will build your package.'
	echo
	prose 'If run from outside of a chroot, command will make the following
	       configuration changes in the chroot:'
	bullet 'whatever changes `librechroot` makes.'
	bullet 'set `{PKG,SRC,SRCPKG,LOG}DEST` in `/etc/makepkg.conf`'
	bullet 'set `PACKAGER` in `/etc/makepkg.conf` to reflect the value
	        outside of the chroot.'
	bullet '(maybe) delete `/build/.makepkg.conf`'
	bullet '(maybe) delete `/build/.ssh/config`'
	prose 'If run from inside of a chroot, this command will:'
	bullet '(maybe) delete `~/.makepkg.conf`'
	bullet '(maybe) delete `~/.ssh/config`'
	prose 'The above "maybe"s happen as part of the workarounds to make
	       distcc work in a network-less environment.  They will happen if
	       both `socat` and `distcc` are installed in the chroot.'
	echo
	prose 'The `-n` and `-l` options behave identically to librechroot, see
	       the documentation there.'
	echo
	print 'Options:'
	print ' %s options:' librechroot
	flag "-n <$(_ CHROOT)>" 'Name of the chroot to use'
	flag "-l <$(_ COPY)>"   'Name of, or absolute path to, the chroot copy to use'
	flag "-w <$(_ 'PATH[:PATH]')>" 'Bind mount a file or directory, read/write'
	flag "-r <$(_ 'PATH[:PATH]')>" 'Bind mount a file or directory, read-only'
	print ' %s options:' libremakepkg
	flag '-N' 		"Don't disable networking during build() and
	                         package(). PLEASE don't use this unless you
                                 have a special reason, its use is a violation
                                 of Parabola policy."
	flag '-R'               'Repackage contents of the package without rebuilding'
	flag '-h'               'Show this message'
}

# Convenience method for use in option parsing
err_chflag() {
	local flag=$1
	error 'The -%s flag does not make sense inside of a chroot' "$flag"
	return 1
}

main() {
	# Initial variable values ##############################################
	local copy=$([[ $LIBREUSER == root ]] && echo copy || echo "$LIBREUSER")
	local makepkg_args=(-s --noconfirm -L)
	local repack=false
	local chroot=''

	# Parse command line options ###########################################
	while getopts 'n:l:w:r:NRh' flag ; do
		case "${flag}" in
			n) if $INCHROOT; then err_chflag "$flag"; else
				chroot=$OPTARG; fi;;
			l) if $INCHROOT; then err_chflag "$flag"; else
				copy=$OPTARG; fi;;
			w|r) if $INCHROOT; then err_chflag "$flag"; else
				librechroot_flags+=(-$flag "$OPTARG"); fi;;
			N) NONET=false;;
			R) repack=true; makepkg_args+=(-R);;
			h) usage; return 0;;
			*) usage >&2; return 1;;
		esac
	done
	shift $(($OPTIND - 1))
	# Pass all arguments after -- right to makepkg
	makepkg_args+=("$@")

	# Resolve the chroot path ##############################################
	local copydir
	if $INCHROOT; then
		copydir='/'
	else
		load_files chroot
		check_vars chroot CHROOTDIR CHROOT
		[[ -z ${chroot} ]] || CHROOT=$chroot
		if [[ ${copy:0:1} = / ]]; then
			copydir=$copy
		else
			copydir="${CHROOTDIR}/${CHROOT}/${copy}"
		fi
		unset CHROOTDIR CHROOTEXTRAPKG
	fi
	unset chroot

	# Load makepkg configuration ###########################################
	# Note that all of these are globals
	PKGDEST="$(get_var makepkg PKGDEST "$PWD")"
	SRCDEST="$(get_var makepkg SRCDEST "$PWD")"
	SRCPKGDEST="$(get_var makepkg SRCPKGDEST "$PWD")"
	LOGDEST="$(get_var makepkg LOGDEST "$PWD")"
	MAKEFLAGS="$(get_var makepkg MAKEFLAGS '')"
	PACKAGER="$(get_var makepkg PACKAGER '')"

	# Quick sanity check ###################################################

	if (( EUID )); then
		error "This program must be run as root"
		exit 1
	fi

	if [[ ! -f PKGBUILD ]]; then
		# This is the message used by makepkg
		error "PKGBUILD does not exist."
		exit 1
	fi

	# Make sure that the various *DEST directories exist
	mkdir -p -- "$PKGDEST" "$SRCDEST" "$SRCPKGDEST" "$LOGDEST"
	# Check the permissions for $startdir and $SRCDEST
	(
		declare -i ret=0
		check_directory_permissions "$PWD" || ret=1
		if ! [[ "$PWD" -ef "$SRCDEST" ]]; then
			check_directory_permissions "$SRCDEST" || ret=1
		fi
		exit $ret
	)

	# OK, we are starting now ##############################################

	if $INCHROOT; then
		lock 9 "/build/.lock" \
			"Waiting for existing lock on build directory to be released"
	else
		librechroot_flags+=(
			-r "$PWD:/startdir_host"
			-r "$SRCDEST:/srcdest_host"
			-n "$CHROOT"
			-l "$copy"
		)

		# Obtain a lock on the chroot
		lock 9 "$copydir.lock" \
			"Waiting for existing lock on chroot copy to be released: [%s]" "$copy"
		# Create the chroot if it does not exist
		msg 'Initializing the chroot...'
		librechroot "${librechroot_flags[@]}" make |& indent
	fi

	# Set target CARCH
	# note that we waited until after locking/creating the chroot to do this
	export CARCH="$(MAKEPKG_CONF=$copydir/etc/makepkg.conf get_var makepkg CARCH)"

	# Pre-build
	msg 'Starting pre-build activities...'
	run_hook check_pkgbuild
	msg 'Downloading sources...'
	download_sources "$copydir" "$LIBREUSER" |& indent

	# Build
	msg 'Starting to build the package...'
	trap "exit_copy '$copydir' '$LIBREUSER'" EXIT
	build "$copydir" "$repack" "${makepkg_args[@]}"

	# Post-build
	msg 'Starting post-build activities...'
	run_hook check_pkg
	add_to_local_repo "$copydir" "$copydir"/pkgdest/*.pkg.tar* |& indent
}

main "$@"