#!/bin/bash
#
#   srcpac - A tool to rebuild official Arch Linux packages from source
#
#   Copyright (C) 2004-2009 Jason Chu <jason@archlinux.org>
#   Copyright (C) 2009-2011 Andrea Scarpino <andrea@archlinux.org>
#
# 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 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/>.
#

ver=0.10.4

declare -a args

[ -f /etc/abs.conf ] && source /etc/abs.conf
[ -f /etc/makepkg.conf ] && source /etc/makepkg.conf
[ -f ~/.makepkg.conf ] && source ~/.makepkg.conf

usage()
{
	echo "usage: $(basename $0) <operation> [...]"
	echo "operations:"
	echo "        $(basename $0) {-h --help}"
	echo "        $(basename $0) {-V --version}"
	echo "        $(basename $0) {-Q --query}   [options] [<package(s)>]"
	echo "        $(basename $0) {-R --remove}  [options] <package(s)>"
	echo "        $(basename $0) {-S --sync}    [options] [package(s)]"
    echo "$(basename $0) options are based on pacman, so check the pacman man page"
	echo "$(basename $0) adds some option to -S:"
	echo "options:"
	echo "  -b, --build    builds the targets from source"
	echo "  -m, --makedeps remove the makedepends after the build"
	echo "  -o, --onlyconf applies the changes and displays the PKGBUILD without building"
	
	exit 0
}

version()
{
	echo
	echo "                       $(basename $0) v${ver}"
	echo "                       Copyright (C) 2009-2011 Andrea Scarpino"
	echo "                       Copyright (C) 2004-2009 Jason Chu"
	echo
	echo "                       This program may be freely redistributed under"
	echo "                       the terms of the GNU General Public License."
	echo

	exit 0
}

##
# Applies changes of source build packages to PKGUILDs,
# if -o option is set we only display the PKGBUILD in a pager
##
apply_config()
{
	conf=/etc/srcpac.d/${1}
	if [ -f ${conf} ]; then
		if [ "${3}" = "noreplace" ]; then
			[ -z ${PAGER} ] && PAGER=less
			sed -f ${conf} ${2}/PKGBUILD | ${PAGER}
		else
			sed -i -f ${conf} ${2}/PKGBUILD
		fi
	fi
}

##
# Searches the ABS tree for a possible package candidates
##
get_candidates()
{
	local found=0

	for pkgbuild in ${ABSROOT}*/*/PKGBUILD; do
		unset pkgbase
		unset pkgname
		source $pkgbuild &> /dev/null
		if [ "${1}" = "${pkgbase}" ]; then
			found=1
			break
		else
			for _package in "${pkgname[@]}"; do
				if [ "${1}" = "${_package}" ]; then					
					found=1
					break
				fi
			done
		fi

		if [ ${found} -eq 1 ]; then
			break
		fi
	done

	if [ ${found} -eq 1 ]; then
		local already=0
		local tmp=${pkgbuild#${ABSROOT}}
		for c in ${candidates}; do
			if [ "${tmp/\/PKGBUILD}" = "$c" ]; then
				already=1
				break
			fi
		done
		if [ ${already} -eq 0 ]; then
			candidates="${candidates} ${tmp/\/PKGBUILD}"
		fi
	else
		echo "Error: Could not find \"${1}\" under ${ABSROOT}"
	fi
}

##
# Updates the ABS tree and the pacman repositories
##
refresh()
{
	echo -n "Starting ABS sync..."
	abs &> /dev/null
	echo "done"
	pacman -Sy
	if [ ${#args[@]} -eq 0 -a $SYSUPGRADE -eq 0 ]; then
		exit 0
	fi
}

##
# Build and/or install a package
##
build_packages()
{
	action="build"
	if [ "$1" = "install" ]; then
		action="install"
	fi

	shift

	pkg=${1#*/}
	pkgdir="${ABSROOT}/${1}"
	builddir=/var/srcpac/${pkg}

	MAKEPKGOPTS="-c -s"
	[ $NODEPS -eq 1 ] && MAKEPKGOPTS="${MAKEPKGOPTS} -d"
	[ $MAKEDEPS -eq 1 ] && MAKEPKGOPTS="${MAKEPKGOPTS} -r"
	[ $NOCONFIRM -eq 1 ] && MAKEPKGOPTS="${MAKEPKGOPTS} --noconfirm"

	# create the build dir and apply configuration
	[[ -d ${builddir} ]] && rm -rf ${builddir}
	mkdir -p ${builddir}
	cp ${pkgdir}/* ${builddir}
	apply_config ${pkg} ${builddir}

	cd ${builddir}

	if [ -z "${SUDO_USER}" ]; then
		chown -R nobody ${builddir}
		makepkg ${MAKEPKGOPTS} --asroot
	else
		chown -R ${SUDO_USER} ${builddir}
		sudo -u ${SUDO_USER} makepkg ${MAKEPKGOPTS}
	fi

	if [ $? -eq 0 ]; then
		CACHEDIR=$(grep '^#CacheDir' /etc/pacman.conf | cut -d= -f2 | sed s/#//)
		cp ${builddir}/*.pkg.tar.?z ${CACHEDIR}

		if [ "${action}" = "install" ]; then
			PACARGS="-U"
			[ $NODEPS -eq 1 ] && PACARGS="${PACARGS}dd"
			[ $FORCE -eq 1 ] && PACARGS="${PACARGS}f"
			[ $ROOT -eq 1 ] && PACARGS="${PACARGS} -r $NEWROOT"
			[ $ASDEPS -eq 1 ] && PACARGS="${PACARGS} --asdeps"
			[ $ASEXPLICIT -eq 1 ] && PACARGS="${PACARGS} --asexplicit"
			[ $NOCONFIRM -eq 1 ] && PACARGS="${PACARGS} --noconfirm"

			if [ "${pkg}" = "${pkgbase}" ]; then
				declare -a builtpkgs
				local count=1
				echo ":: More packages have been built:"
				for builtpkg in ${builddir}/*.pkg.tar.?z; do
					bname="${builtpkg%*-*-*-*.pkg.tar.?z}"
					builtpkgs[count]="${bname#${builddir}/}"
					echo "$count) ${builtpkgs[count]}"
					let count=count+1
				done

				if [ ${NOCONFIRM} -eq 0 ]; then
					local pkglist
					echo "Enter a selection (default=all):"
					read
					if [ -n "${REPLY}" ]; then
						for i in ${REPLY}; do
							pkglist="${pkglist} ${builddir}/${builtpkgs[$i]}-${pkgver}-*.pkg.tar.?z"
						done
						pacman $PACARGS ${pkglist}
					fi
				else
					pacman $PACARGS ${builddir}/*.pkg.tar.?z
				fi
			else
				pacman $PACARGS ${builddir}/${pkg}*.pkg.tar.?z
			fi

			touch /var/lib/srcpac/${pkg}
		fi
	else
		broken="${broken} ${pkg}"
	fi

	rm -rf ${builddir}
}

check_args()
{
	# Options
	MAJOR=""
	ASDEPS=0
	ASEXPLICIT=0
	NODEPS=0
	FORCE=0
	INFO=0
	SYSUPGRADE=0
	DOWNLOAD=0
	REFRESH=0
	IGNORE=0
	IGNOREPKG=""
	NOCONFIRM=0
	ROOT=0
	NEWROOT=""
	BUILD=0
	ONLYCONF=0
	MAKEDEPS=1

	ARGLIST=$@
	ARGSANS=""

	while [ "$#" -ne "0" ]; do
		case $1 in
			--help) usage ;;
			--version) version ;;
			--query)
				MAJOR="query"
				ARGSANS="$ARGSANS $1"
				;;
			--remove)
				MAJOR="remove"
				ARGSANS="$ARGSANS $1"
				;;
			--sync)
				MAJOR="sync"
				ARGSANS="$ARGSANS $1"
				;;
			--asdeps)
				ASDEPS=1
				ARGSANS="$ARGSANS $1"
				;;
			--asexplicit)
				ASEXPLICIT=1
				ARGSANS="$ARGSANS $1"
				;;
			--nodeps)
				NODEPS=1
				ARGSANS="$ARGSANS $1"
				;;
			--force)
				FORCE=1
				ARGSANS="$ARGSANS $1"
				;;
			--info)
				INFO=1
				ARGSANS="$ARGSANS $1"
				;;
			--sysupgrade)
				SYSUPGRADE=1
				ARGSANS="$ARGSANS $1"
				;;
			--downloadonly)
				DOWNLOAD=1
				ARGSANS="$ARGSANS $1"
				;;
			--refresh)
				REFRESH=1
				ARGSANS="$ARGSANS $1"
				;;
			--ignore)
				IGNORE=1
				IGNOREPKG="$IGNOREPKG $2"
				ARGSANS="$ARGSANS $1 $2"
				;;
			--noconfirm)
				NOCONFIRM=1
				ARGSANS="$ARGSANS $1"
				;;
			--root)
				ROOT=1
				NEWROOT="$2"
				ARGSANS="$ARGSANS $1 $2"
				shift
				;;
			--build)
				BUILD=1
				ARGSANS="$ARGSANS $1"
				;;
			--onlyconf)
				ONLYCONF=1
				ARGSANS="$ARGSANS $1"
				;;
			--makedeps)
				MAKEDEPS=0
				ARGSANS="$ARGSANS $1"
				;;
			--*)
				ARGSANS="$ARGSANS $1"
				;;
			-*)
				ARGSANS="$ARGSANS $1"
				if [ $(echo $1 | grep r) ]; then
					OPTIONAL=$2
				fi
				OPTIND=1
				while getopts ":VQRSbdfimuyr:ow" opt $1 $OPTIONAL; do
					case $opt in
						V) version ;;
						Q) MAJOR="query" ;;
						R) MAJOR="remove" ;;
						S) MAJOR="sync" ;;
						b) BUILD=1 ;;
						d) NODEPS=1 ;;
						f) FORCE=1 ;;
						i) INFO=1 ;;
						m) MAKEDEPS=0 ;;
						o) ONLYCONF=1 ;;
						r) ROOT=1
						   NEWROOT="${OPTARG}"
						   ;;
						u) SYSUPGRADE=1 ;;
						w) DOWNLOAD=1 ;;
						y) REFRESH=1 ;;
					esac
				done
				;;
			*)
				args[${#args[@]}]=$1
				;;
		esac
		shift
	done
}

##
# Remove the srcpac package file
##
remove()
{
	if [ -f /var/lib/srcpac/$1 ]; then
		echo -n "removing source reference $1... "
		rm /var/lib/srcpac/$1
		echo "done"
	fi
}

##
# Check if a package has been built using srcpac
##
is_built()
{
	built=0
	if [ -f /var/lib/srcpac/$1 ]; then
		built=1
	else
		built=0
	fi
}

main()
{
	check_args $@

	if [ -z "${MAJOR}" ]; then
		usage
		exit 1
	fi
	
	if [ ${UID} -ne 0 -a -z "${SUDO_USER}" -a ${MAJOR} != "query" ]; then
		echo "Error: You need to use sudo or to be root"
		exit 1
	fi

	if [ -z "${ABSROOT}" ]; then
		echo "Error: The ABSROOT environment variable is not defined"
		exit 1
	fi

	if [ ! -d /var/lib/srcpac ]; then
		mkdir /var/lib/srcpac
	fi

	if [ "${MAJOR}" = "remove" ]; then
		pacman $ARGLIST
		
		for pkg in ${args[@]}; do
			remove $pkg
		done
	fi

	if [ "${MAJOR}" = "query" ]; then
		for pkg in ${args[@]}; do
			if [ $INFO -eq 1 ]; then
				pacman -Qi $pkg | head -n -1
			else
				echo -n $(pacman -Q $pkg)
			fi

			if [ $? -eq 0 ]; then
				is_built $pkg
				if [ $INFO -eq 1 ]; then
					echo -n "Source         : "	
					if [ $built -eq 1 ]; then
						echo "Yes"
					else
						echo "No"
					fi
					echo
				else
					if [ $built -eq 1 ]; then
						echo " [source]"
					else
						echo
					fi
				fi
			fi
		done
	fi

	if [ "${MAJOR}" = "sync" ]; then
		if [ $BUILD -eq 0 -a $ONLYCONF -ne 1 ]; then
			if [ $SYSUPGRADE -eq 1 ]; then
				[ $REFRESH -eq 1 ] && refresh

				if [ $IGNORE -eq 1 ]; then
					local ignorestr=$(echo $IGNOREPKG | tr "," "|")
					local output=$(pacman -Qqu --noconfirm | grep -Ev ${ignorestr})
				else
					local output=$(pacman -Qqu --noconfirm)
				fi

				declare -a packages
				declare -a regpac
				for pkg in $output; do
					is_built $pkg
					if [ $built -eq 1 ]; then
						packages[${#packages[@]}]=$pkg
					else
						regpac[${#regpac[@]}]=$pkg
					fi
				done

				PACARGS="-S"
				[ $NODEPS -eq 1 ] && PACARGS="${PACARGS}dd"
				[ $FORCE -eq 1 ] && PACARGS="${PACARGS}f"
				[ $ROOT -eq 1 ] && PACARGS="${PACARGS} -r $NEWROOT"
				[ $IGNORE -eq 1 ] && PACARGS="${PACARGS} --ignore $IGNOREPKG"
				[ $NOCONFIRM -eq 1 ] && PACARGS="${PACARGS} --noconfirm"

				if [ -n "${regpac[*]}" ]; then
					pacman $PACARGS ${regpac[*]}
				fi
			else
				[ $REFRESH -eq 1 ] && refresh
				pacman $ARGLIST
				exit 0
			fi
		else
			[ $REFRESH -eq 1 ] && refresh

			if [ $DOWNLOAD -eq 1 ]; then
				build_packages "build" ${args[@]}
				exit 0
			fi

			if [ $SYSUPGRADE -eq 1 ]; then
				local output=$(pacman -Qqu --noconfirm)
			else
				local output="${args[*]}"
			fi

			declare -a packages
			for pkg in $output; do
				local pkgver=$(LC_ALL=C pacman -Qi $pkg 2>/dev/null | grep Version | awk '{print $3}')
				[ -z ${pkgver} ] && pkgver=0
				local repopkgver=$(LC_ALL=C pacman -Si $pkg 2>/dev/null | grep Version | awk '{print $3}' | head -n 1)
				[ -z ${repopkgver} ] && repopkgver=0
				if [ $(vercmp $repopkgver $pkgver) -eq 0 ]; then
					echo "warning: $pkg-$pkgver is up to date -- reinstalling"
				fi
				packages[${#packages[@]}]=$pkg
			done

			if [ $ONLYCONF -eq 1 ]; then
				for pkg in ${packages[@]}; do
					get_candidates $pkg
				done

				for pkg in ${candidates}; do
					apply_config "${pkg#*/}" "${ABSROOT}/$pkg" noreplace
				done
				exit 0
			fi
		fi

		for pkg in ${packages[@]}; do
			get_candidates $pkg
		done

		if [ -n "${candidates}" ]; then
			echo
			echo -n "Source Targets: "
			for pkg in ${candidates}; do
				echo -n "${pkg#*/} "
			done
			echo
			if [ ${NOCONFIRM} -eq 0 ]; then
				echo -n "Proceed? [Y/n]"
				read
				if [ "${REPLY}" != "y" -a "${REPLY}" != "Y" -a "${REPLY}" != "" ]; then
					exit 0
				fi
			fi

			for pkg in ${candidates}; do
				build_packages "install" ${pkg}
			done
		fi

		if [ -n "${broken}" ]; then
			echo "Build failed for:${broken}"
		fi
	fi
}

main $@

exit 0