#!/bin/sh
#---------------------------------------------------------------------------#
# Copyright (C) 2002-2003 The University of Melbourne.
# This file may only be copied under the terms of the GNU General
# Public License - see the file COPYING in the Mercury distribution.
#---------------------------------------------------------------------------#
#
# submit_patch:
#	This implements a "commit server".
#	It takes as input a log message and a patch.
#	It checks out the Mercury sources, applies the patch,
#	and tests it (by bootchecking in a couple of grades).
#	If the tests pass, and --commit was specified,
#	it also commits the patch.
#	It locks the test directory, so you can submit
#	multiple patches at once and it will only run
#	one of them at a time.

usage="\
Usage: $0 [options] <cvs log message file> <patch file>
Options:
	-d <dirname>, --directory <dirname>
		Run the tests in directory <dirname>.
	-S <megabytes>, --space <megabytes>
		Require this amount of free disk space.
	--gcc
		Test the GCC-based native code back-end.
	-r <CVSROOT>, --repository <CVSROOT>
		Use the specified CVS repository for checking out mercury.
	-g <CVSROOT>, --gcc-repository <CVSROOT>
		Use the specified CVS repository for checking out GCC.
	--branch <branch>
		Use the specified branch tag for checking out the \`mercury'
		and \`tests' directories from the Mercury CVS repository.
	--gcc-branch <branch>
		Use the specified branch tag for checking out GCC.
	--mercury-gcc-branch <branch>
		Use the specified branch tag for checking out the
		\`mercury-gcc' directory from the Mercury CVS repository.
	--configure <configure-options>
		Use the specified options when configuring mercury.
	--gcc-configure <configure-options>
		Use the specified options when configuring GCC.
	-b <bootcheck-options>, --bootcheck <bootcheck-options>
		Bootcheck with the specified options, in addition
		the standard bootchecks (asm_fast.gc & hlc.gc).
	-u <email-address>, --user <email-address>
		Mail the output to the specified user.
	-t <title>, --title <title>
		Specify a name for this patch.
		The patch title is used in the test sub-directory name,
		and is also included in the email subject line.
	-c, --commit
		Commit the patch, if the tests pass.
	-h, --help
		Display this usage message.
"

#---------------------------------------------------------------------------#

# Default option settings

# Name of this patch
title=$$

# User to mail results to
user=`whoami`

# Place to run the tests
test_root=/tmp/$user/mercury

# Disk space needed (in megabytes)
disk_space_required=300

# "true" if we should build with the GCC back-end,
# so that `--target asm' works.
do_gcc=false

# CVS Repositories
gcc_cvsroot=:pserver:anoncvs@gcc.gnu.org:/cvs/gcc
mercury_cvsroot=/home/mercury1/repository
if [ -d "$mercury_cvsroot/." ]; then
	:
else
	mercury_cvsroot=:pserver:guest@cvs.mercury.cs.mu.oz.au:$mercury_cvsroot
fi

# CVS branches
branch=
gcc_branch=
mercury_gcc_branch=

# Configure options
configure_opts=
gcc_configure_opts="--enable-checking --enable-languages=c,mercury"

# A "#"-separated list of bootchecks to run.
bootchecks="--grade asm_fast.gc # --grade hlc.gc".

# "true" if we should commit the patch if/when the tests pass.
commit=false

#-----------------------------------------------------------------------------#
#
# Parse the options
#

parse_options() {
    orig_command="$0 $*"
    while [ $# -gt 0 ]; do
	case "$1" in
	-t|--title)
		title="$2"; shift ;;
	-t*)
		title="` expr $1 : '-t\(.*\)' `"; ;;

	--gcc)
		do_gcc=true ;;

	-d|--directory)
		test_root="$2"; shift ;;
	-d*)
		test_root="` expr $1 : '-d\(.*\)' `"; ;;

	-r|--repository)
		mercury_cvsroot="$2"; shift ;;
	-r*)
		mercury_cvsroot="` expr $1 : '-r\(.*\)' `"; ;;

	-g|--gcc-repository)
		gcc_cvsroot="$2"; shift ;;
	-g*)
		gcc_cvsroot="` expr $1 : '-g\(.*\)' `"; ;;

	--branch)
		branch="-r$2"; shift ;;

	--gcc-branch)
		gcc_branch="-r$2"; shift ;;

	--mercury-gcc-branch)
		mercury_gcc_branch="-r$2"; shift ;;

	--configure)
		configure_opts="$2"; shift ;;

	--gcc-configure)
		gcc_configure_opts="$2"; shift ;;
	

	-s|--space)
		disk_space_required="$2"; shift ;;
	-s*)
		disk_space_required="` expr $1 : '-s\(.*\)' `"; ;;

	-b|--bootcheck)
		bootchecks="$bootchecks#$2"; shift ;;
	-b*)
		bootchecks="$bootchecks#` expr $1 : '-b\(.*\)' `"; ;;

	-u|--user)
		user="$2"; shift ;;
	-u*)
		user="` expr $1 : '-u\(.*\)' `"; ;;

	-c-|--no-commit)
		commit=false ;;
	-c|--commit)
		commit=true ;;

	-h|--help)
		echo "$usage";
		exit 0 ;;

	--)	
		shift; break ;;
	-*)
		echo "$0: unknown option \`$1'" 1>&2
		echo "$usage" 1>&2
		exit 1 ;;
	*)
		break ;;
	esac
	shift
    done

    if [ $# -ne 2 ]; then
	echo "$0: wrong number of arguments" 1>&2
	echo "usage: $0 [options] <log message file> <patch file>" 1>&2
	echo "Use \`--help' for help." 1>&2
	exit 1
    fi
    logmessage=$1
    patchfile=$2
    test_dir=$test_root/test_$title
}

#-----------------------------------------------------------------------------#

obtain_lock() {
	until mkdir $test_root/lock; do
		sleep 60
	done
	echo "Process $$ on host `hostname -f`" > $test_root/lock/info
}

release_lock() {
	rm -f $test_root/lock/info
	rmdir $test_root/lock
}

#-----------------------------------------------------------------------------#

die() {
	host="`hostname -f 2>&1`"
	msg="
*** submit_patch failed:
*** $@

Log file in $test_dir/OUTPUT on host $host.

Leaving the build directory $test_dir intact in case
you need to use it to debug the problem.
You must to remove this directory when you have finished with it.
"
	echo "$msg" 1>&2
	echo "$msg" | mail -s "auto-test ($title) failed" $user
	release_lock
	exit 1
}

succeed() {
	msg="
*** check_patch succeeded.

Log file in $test_dir/OUTPUT.

Leaving the build directory $test_dir intact in case you want to
browse $test_dir/OUTPUT.
You must to remove this directory when you have finished with it.
"
	echo "$msg" 1>&2
	echo "$msg" | mail -s "auto-test ($title) succeeded" $user
}

#-----------------------------------------------------------------------------#

# check we've got a reasonable amount of free disk space -- no point
# starting if we'll only run out of disk space.

check_disk_space() {

	free=`df -m $test_root/. | awk '
		NR == 2 && NF > 4 { print $4; exit; }
		NR == 3 { print $3; }
	'`
	echo "Free disk space: $free megabytes"
	[ "$free" -gt $disk_space_required ] ||
		die "Insufficient disk space on $test_root"
}

#-----------------------------------------------------------------------------#

apply_patch() {
	# We want the patch to apply properly regardless of whether it
	# includes "mercury/" prefixes on the file names for the files in
	# the "mercury" and "tests" directories.
	# So (1) we cd to the mercury directory before applying the patch,
	# so that it works if the "mercury/" prefix is omitted
	# but (2) we add a symlink "mercury" -> "."
	# so that it works if the prefix is included
	# and (3) we also add symlinks "gcc" -> "../gcc", 
	cd mercury				|| die "cd mercury failed"
	ln -s . mercury				|| die "ln -s failed"
	ln -s ../tests tests			|| die "ln -s failed"
	ln -s ../gcc gcc			|| die "ln -s failed"
	ln -s ../mercury-gcc mercury-gcc	|| die "ln -s failed"
	patch -p0 < ../PATCH			|| die "applying patch failed"
	rm mercury tests gcc mercury-gcc	|| die "can't remove symlinks"
	cd ..					|| die "cd .. failed"
}

#-----------------------------------------------------------------------------#

do_cvs_add_remove() {
	comm -13 FILES.old FILES.new > FILES.added   || die "comm"
	comm -23 FILES.old FILES.new > FILES.removed || die "comm"
	if [ -s FILES.added ]; then
		cvs add `cat FILES.added`	|| die "cvs add failed"
	fi
	if [ -s FILES.removed ]; then
		cvs remove `cat FILES.removed`	|| die "cvs remove failed"
	fi
	return 0
}

#-----------------------------------------------------------------------------#

do_bootchecks() {
	old_IFS=$IFS
	IFS=#
	for bootcheck in $bootchecks; do
		IFS=$old_IFS
		eval tools/bootcheck $bootcheck ||
			die "tools/bootcheck $bootcheck failed"
		IFS=#
	done
	IFS=$old_IFS
}

#-----------------------------------------------------------------------------#

main() {
    parse_options "$@"			|| die "parse_options failed"
    [ -d $test_root ] || mkdir -p $test_root ||
					   die "creating dir $test_root failed"
    obtain_lock				|| die "can't obtain lock"
    trap 'die "interrupted"' 1 2 3 13 15
    check_disk_space			|| die "insufficient disk space"
    mkdir $test_dir			|| die "mkdir $test_dir failed"
    echo "Testing in directory $test_dir"
    {
        set -x
	cp $patchfile $test_dir/PATCH	|| die "error copying $patchfile"
	cp $logmessage $test_dir/CVSLOG	|| die "error copying $logmessage"
	cd $test_dir			|| die "cd $test_dir failed"
	case $do_gcc in true)
		CVSROOT=$gcc_cvsroot
		export CVSROOT
		cvs checkout $gcc_branch gcc || die "GCC cvs checkout failed"
		(cd gcc && ./contrib/gcc_update --touch)
		CVSROOT=$mercury_cvsroot
		export CVSROOT
		cvs checkout $mercury_gcc_branch mercury-gcc \
					|| die "mercury-gcc checkout"
		(cd gcc/gcc && ln -s ../../mercury-gcc mercury)
		(cd gcc/gcc/mercury && ln -s ../mercury mercury)
		;;
	esac
        CVSROOT=$mercury_cvsroot
	export CVSROOT
	cvs checkout $branch mercury	|| die "cvs checkout mercury failed"
	cvs checkout $branch tests	|| die "cvs checkout tests failed"
	touch FILES.old FILES.new
	find . | sort > FILES.old
	apply_patch			|| die "apply_patch failed"
	find . | sort > FILES.new
	do_cvs_add_remove		|| die "do_cvs_add_remove failed"
	cd mercury			|| die "cd mercury failed"
	autoconf			|| die "autoconf failed"
	sh configure --prefix=$test_dir/install $configure_opts \
					|| die "configure failed"
	case $do_gcc in true)
		cd ../gcc		|| die "cd ../gcc failed"
		sh configure --prefix=$test_dir/install $gcc_configure_opts \
					|| die "gcc configure failed"
		make bootstrap		|| die "gcc make bootstrap failed"
		cd ../mercury		|| die "cd ../mercury failed"
		;;
	esac
	make				|| die "make failed"
	do_bootchecks			|| die "do_bootchecks failed"
	case $do_gcc in true)
		cd ../gcc		|| die "cd ../gcc failed"
		make -k check		|| die "gcc make -k check failed"
		cd ../mercury		|| die "cd ../mercury failed"
		;;
	esac
	case $commit in true)
		cvs commit -m"`cat ../CVSLOG`" || die "cvs commit failed"
		;;
	esac
	cd ..				|| die "cd .."
	rm -rf mercury tests mercury-gcc gcc || die "rm -rf failed"
    } </dev/null > $test_dir/OUTPUT 2>&1
    release_lock
    set +x
    succeed
    exit 0
}
#-----------------------------------------------------------------------------#
main "$@"
#-----------------------------------------------------------------------------#
