#!/bin/bash
set -e
#  em_autobuild : emdebian target package autobuilder.
#
#  Copyright (C) 2008 Neil Williams <codehelp@debian.org>
#
#  This package 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/>.
#

PROG=em_autobuild

function usagehelp () {
    # print out help message
    cat <<EOF
$PROG - emdebian target package autobuilder.
version $OURVERSION

Syntax: $PROG [OPTIONS] [COMMAND]

Commands:
-o|--omit-current:       Exclude packages that appear to be up to date.
   --package PACKAGE:    Auto-build a single package only.
-?|-h|--help|--version:  Print this help message and exit

Options:
-a|--arch:               Override the default cross-build architecture 
                          (from dpkg-cross).
-p|--pbuilder:           Run the autobuild in a clean cross-building chroot.
-e|--errexit:            Force the build to halt at the first build failure.
-l|--log:                Log emsource output to a timestamped log file
                          (probably best used with -p).
-u|--handler:            An executable script to do the upload of the build log.
-s|--script FILENAME:    Override the default package set and
                          second-stage install.
-m|--machine NAME:       Specify a machine name for customisation hooks
-v|--variant NAME:       Specify a machine variant name for customisation hooks
   --machine-path PATH:  Specify a path to replace the $WORK/machine default

Parses the /usr/lib/emdebian-tools/emdebian.crossd suite script (default) or
a user-specified suite script, collates a list of base and required binary
packages, converts each to the respective source package using the apt-cross
cache and attempts to build each unique source package with emsource.

Use -e|--exit to force the build sequence to halt when the first package
fails to build cleanly - this includes lintian errors generated by the
Emdebian lintian check set. (Errors from the standard Debian checks are
ignored.)

When omitting packages that appear up to date, em_autobuild uses the
embug data created by emtargetcmp and autobuilds all affected packages,
independent of and suite script. em_autobuild does not call emtargetcmp
directly so a repeat run of em_autobuild can be used when the first build
fails (the failed build is flagged in embug and will be skipped next time).
To get a fresh selection of packages that are older in Emdebian than in 
Debian, run emtargetcmp before em_autobuild.

em_autobuild looks for the packages flagged in embug --l as "Newer in 
Debian". If a package has failed to build or has out of date patches,
this flag is replaced and em_autobuild will omit that package too. Such
packages need to be fixed separately so that the patches can be updated.

The build process will update the embug data if any requested package
fails to build. Removing embug flags is, currently, a manual process.

At the end of a successful autobuild, use emrecent to check and upload
the packages.

When using --log, the emsource build log is written out to the package
directory beneath \$WORK/buildd:
/opt/emdebian/buildd/a/apt/trunk/apt-arm-1213105765.log
Log file support is intended for autobuilders where the build log output
is not useful when emailed to the admin by at or cron. Therefore, in --log
mode, $PROG tries to be silent. The 'buildd' directory could then be
made available over HTTP etc.

When using $PROG, note that enabling logging can cause you to be unable
to enter passwords for SSH keys etc. (This is expected as --log is
intended for use on unattended buildd machines that do not have SVN commit
access and where the autobuild is likely to happen inside a chroot so that
sudo is not an issue). When testing, consider using -p as well as -l or
extending the length of time that your SSH and/or sudo passwords are
cached.

To upload build logs after unattended autobuilds, use the --handler option
to specify a script which will be called once for each package with the
full path to the build log file as the only argument. The script can then
use whatever methods are supported in your environment to upload the build
log. --handler is ignored if --log is not used.

If the handler script also uploads the packages  (e.g. using emrecent in
noninteractive mode), the handler script needs to check that the build was
successful then upload the packages before appending the .upload file
to the end of the build log for that package. This allows the buildd.php
report script to list the package as "Installed" instead of "pending".

EOF
}

. /usr/lib/emdebian-tools/empbuilderlib

SUITE=unstable
MACHINE=
VARIANT=
CLEAN=
HANDLER=
SCRIPT=/usr/lib/emdebian-tools/emdebian.crossd
WORKDIR=`perl -e 'use Cwd; use Emdebian::Tools; use Config::Auto; use Debian::DpkgCross; \
&read_config; \
my $w = &get_workdir; \
$w = cwd if (! -d $w); \
print $w;';`
COMPLETE_ARGS="$0"

while [ -n "$1" ]; do
case "$1" in
	--help|-h|-\?|--version)
		usagehelp
		exit;
	;;
	-a|--arch)
		shift
		ARCH=$1
		# chomp the argument to --arch
		shift
	;;
	-o|--omit-current)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		OMIT=1
	;;
	--package)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		PACKAGE=$1
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
	;;
	-p|--pbuilder)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		PBUILDER=1
		CLEAN="sudo empdebuild clean"
	;;
	-l|--log)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		COLOUR="ANSI_COLORS_DISABLED=true"
		LOG=1
	;;
	-e|--errexit)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		USE_E=1
	;;
	-u|--handler)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		HANDLER=$1
		if [ ! -x "$HANDLER" ]; then
			echo "$HANDLER needs to be executable. Halt."
			exit
		fi
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
	;;
	-s|--script)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		SCRIPT=$1
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
	;;
	-m|--machine)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		MACHINE=$1
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
	;;
	-v|--variant)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		VARIANT=$1
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
	;;
	--machine-path)
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
		MACHINEPATH=$1
		COMPLETE_ARGS="$COMPLETE_ARGS $1"
		shift
	;;
	*)
		echo "Unrecognised option: $1"
		echo
		usagehelp
		exit;
	;;
esac
done

if [ ! -z "$LOG" ]; then
	# must be able to create this directory and trees beneath it.
	mkdir -p "$WORKDIR/buildd"
fi

if [ $MACHINEPATH ]; then
	CUSTOM="--machine-path $MACHINEPATH"
else
MACHINEPATH="${WORKDIR}/machine/"
fi

if [ $MACHINE ]; then
	CUSTOM+=" --machine $MACHINE"
fi

if [ $VARIANT ]; then
	CUSTOM+=" --variant $VARIANT"
fi

# include packages.conf if --machine used.
if [ "$MACHINE" != "x" -a "$VARIANT" != "x" ]; then
	# sets INCLUDE, SCRIPT, MIRROR etc.
	if [ -f ${MACHINEPATH}/$MACHINE/$VARIANT/packages.conf ]; then
		. ${MACHINEPATH}/$MACHINE/$VARIANT/packages.conf
	fi
	if [ -z "$OMIT" -a ! -z "$LOG" -a -z "$PACKAGE" ]; then
		echo "Using $MACHINE:$VARIANT $SCRIPT"
	fi
fi

SRC=""
checkarch
COMPLETE_ARGS="$COMPLETE_ARGS -a $ARCH"

parse_binary()
{
	arch=$1
	suite=$2
	package=$3
	if [ -z $package ]; then
		return;
	fi
	cmd="use Emdebian::Tools; \$h=(&lookup_sourcepkg($arch, \"$package\"));print \$h->{\"$package\"};";
	SOURCE=`perl -e "$cmd"`
	# bug: dependency list is not recursive.
	# need to use the debootstrap code to get the full list first.
	depcmd="use Emdebian::Tools; print (&lookup_dependencies(arm, \"$package\"));";
	DEPS=`perl -e "$depcmd"`
	for pkg in $DEPS; do
		cmd="use Emdebian::Tools; \$h=(&lookup_sourcepkg($arch, \"$pkg\"));print \$h->{\"$pkg\"};";
		DEPSRC=`perl -e "$cmd"`
		SRC="$SRC $DEPSRC"
	done
	SRC="$SRC $SOURCE"
	SRC=$(echo $(echo $SRC | tr ' ' '\n' | sort | uniq))
}

try_autobuild()
{
	. /usr/share/debootstrap/functions
	if [ -z "$LOG" ]; then
		echo "Obtaining package lists from $SCRIPT"
	fi
	. $SCRIPT
	work_out_debs
	requiredX=$(echo $(echo $required | tr ' ' '\n' | sort | uniq))
	baseX=$(echo $(echo $base | tr ' ' '\n' | sort | uniq))
	if [ -z "$LOG" ]; then
		echo "Trying to automate rebuilds of rootfs packages:"
		echo $requiredX $baseX | fold -s
	fi
	for pkg in $baseX; do
		parse_binary $arch $suite $pkg
		if [ -z "$LOG" ]; then
			echo -n .
		fi
	done
	if [ $INCLUDE ]; then
		LIST=`echo $INCLUDE | tr ',' ' '`
		for pkg in $LIST; do
			parse_binary $arch $suite $pkg
			if [ -z "$LOG" ]; then
				echo -n .
			fi
		done
	fi
	if [ -z "$LOG" ]; then
		echo
	fi
	# emsource -b will fail (and set the flag) if lintian fails at the end
	if [ -z $USE_E ]; then
		set +e
	fi
	for pkg in $SRC; do
		run_emsource $pkg
	done
}

update_target ()
{
	if [ $LOG ]; then
		export $COLOUR
		emtargetcmp -a $ARCH -q &> $WORKDIR/buildd/targetcompare
	fi
}

run_emsource ()
{
	STAMP=`date +%s | tr -d '\n'`
	PKGDIR=`echo $pkg | cut -b1`
	PKGDIR="buildd/$PKGDIR/$pkg/trunk"
	if [ ! -d "$WORKDIR/$PKGDIR" ]; then
		mkdir -p "$WORKDIR/$PKGDIR"
	fi
	if [ $LOG ]; then
		ISSUDOSET=`perl -e '$e=\`printenv\`; ($e =~ /SUDO_USER/) ? print "yes" : print "no";'`
		if [ $ISSUDOSET = "yes" ] ; then
			echo "Running $PROG --log under sudo may cause problems"
			echo "if your sudo privileges time out during an autobuild."
			echo "Try using 'at' instead."
			echo "'sudo echo \"$COMPLETE_ARGS\" | at now'"
			exit
		else
			CLEAN="empdebuild clean"
		fi
		# not possible to redirect STDOUT and STDERR *and* append
		# http://tldp.org/LDP/abs/html/io-redirection.html
		TMPFILE=`mktemp /tmp/$PROG-XXXXXX`
		export $COLOUR
		if [ -z "$PBUILDER" ]; then
			nice -n 7 emsource -q -a $ARCH $CUSTOM $pkg &> $TMPFILE
		else
			# empdebuild --autobuild runs emsource inside the chroot.
			# (autobuild enables internal quiet handling)
			nice -n 7 empdebuild --arch $ARCH --autobuild $pkg &> $TMPFILE
		fi
		if [ -d $WORKDIR/pbuilder/build/ -a ! -z "$CLEAN" ]; then
			$CLEAN &> /dev/null
		fi
		LOGFILE="$WORKDIR/$PKGDIR/$pkg-$ARCH-$STAMP.log"
		echo "Package: $pkg" > $LOGFILE
		# if the build was able to begin, dpkg-buildpackage will output
		# the calculated version string for us to parse. If the patches
		# fail to apply, emsource (>= 1.1.6) outputs some version data
		# for us to parse here.
		PATTERN="dpkg-buildpackage: source version "
		VERSION=`grep "$PATTERN" $TMPFILE | sed -e 's/$PATTERN//'`
		if [ ! -z "$VERSION" ]; then
			echo "Version: $VERSION" >> $LOGFILE
		else
			PATTERN="emsource: Attempted version: ";
			VERSION=`grep "$PATTERN" $TMPFILE | sed -e 's/$PATTERN//'`
			if [ ! -z "$VERSION" ]; then
				echo "Version: $VERSION" >> $LOGFILE
			fi
		fi
		# Host == Handheld 
		# builD == Desktop
		echo "Host architecture: $ARCH" >> $LOGFILE
		BUILD=`hostname -f`
		echo "Buildd machine: $BUILD" >> $LOGFILE
		BUILDCPU=`dpkg-architecture -qDEB_BUILD_ARCH_CPU`
		echo "Build architecture: $BUILDCPU" >> $LOGFILE
		echo "$PROG: emdebian-tools ($OURVERSION)" >> $LOGFILE
		echo -n `apt-cross --version` >> $LOGFILE
		if [ "$MACHINE" -a "$VARIANT" ]; then
			echo "Customisations: $MACHINE:$VARIANT {$SCRIPT}" >> $LOGFILE
		fi
		echo '*****************************************************' >> $LOGFILE
		echo >> $LOGFILE
		cat $TMPFILE >> $LOGFILE
		rm $TMPFILE
		TMPFILE=
		# need to upload the logfile and packages
		if [ ! -z "$HANDLER" ]; then
			$HANDLER $LOGFILE
		fi
	else
		# not logging - colour not changed.
		echo "Starting build of $pkg for $ARCH"
		if [ -z "$PBUILDER" ]; then
			nice -n 7 emsource -q -a $ARCH $CUSTOM --build-dep -b $pkg
		else
			nice -n 7 emsource -q -a $ARCH $CUSTOM -p $pkg
		fi
	fi
}

if [ "$PACKAGE" ]; then
	apt-cross -a $ARCH -q -c &> /dev/null
	pkg=$PACKAGE
	if [ -z "$LOG" ]; then
		echo "Running a single build for $pkg"
	fi
	if [ -z $USE_E ]; then
		set +e
	fi
	run_emsource $pkg
	update_target
	exit
fi

if [ $OMIT ]; then
	apt-cross -a $ARCH -q -c > /dev/null
	emtargetcmp -a $ARCH -q &> /dev/null
	BUILD=`perl -MEmdebian::Tools -MConfig::Auto -e '$config = &target_config(); foreach my $pkg (sort keys %$config) { if ($config->{$pkg} =~ /debian/) { print "$pkg\n"; } }'`
	if [ -z $USE_E ]; then
		set +e
	fi
	for pkg in $BUILD; do
		run_emsource $pkg
	done
	update_target
	exit
fi

try_autobuild
update_target

if [ ! -z "$CLEAN" ]; then
	$CLEAN &> /dev/null
fi
