#!/bin/bash
#
# Variable and function definitions
#
AUTHOR='J. Pety <pety@iram.fr>'
VERSION=`echo '$Revision: 1.93 $' | cut -d' ' -f2`
PROJECT='GILDAS <http://www.iram.fr/IRAMFR/GILDAS>'
PROGNAME=`basename $0`
#
# Administration
GILDAS_USER=gildas             # ID used to connect to computers
MANAGER=pety                   # Account used for some non-anonymous commands
MODULE=gildas
WORKDIR=$HOME/gildas
LOGFILE=$WORKDIR/logs/rebuild-iram.txt
SYSFILE=rebuild-iram.systems   # Default file where systems are read from
PENDINGFILE=$WORKDIR/to_be_removed.txt # File where versions to be removed are stored
TIMEOUT=90m                    # Time out limit. Units recognized by sleep() command
#
# Mail reports
RECIPIENTS="pety bardeau reynier" # Reports are sent to them
MAILHEADER="[gildas-compilation]"
#
###########################################################################
#
# Ensure a stable $gagadmdir to start the process
#
export gagadmdir=$WORKDIR/gildas-admin
#
###########################################################################
#
# Load global functions
#
source $gagadmdir/rebuild-commons
#
###########################################################################
#
# Define local functions
#
function usage() {
    cat <<EOF 1>&2

Script used:
  1. To rebuild every night the development version of GILDAS on all
     systems available at IRAM. When compilation on all systems is a
     success, cvs-tags the version as "last" (for last fully compilable
     version);
  2. To build every month the monthly version on all IRAM systems.
  3. To build the development (HEAD) version on all IRAM systems. It
     never checks-out HEAD version from CVS. A 'dev' version must
     already be present.
An abbreviated log is sent to the GILDAS maintainer. When restarted by
hand after a bug correction, this scripts tries hard to rebuild only
what is affected by the bug fix. This script is triggered by a cron
job as described in the "rebuild-iram.cron" file.

usage: $PROGNAME version

version:
  dev
  day
  month

options:
  -b         Make month branch starting at the 'last' tag
  -f         Force release even with a failure. Use with extreme care,
             probably only with a timeout error.
  -l         Local build: $PROGNAME will read the builds from $SYSFILE,
             and will build only the ones matching the local system.
             Without this option, builds are performed on remote machines.
             No release in this mode.
  -m         Move 'day' tag to latest GILDAS state of the cvs repository
  -n         Dummy mode, does nothing. List the builds and check which
             one are absent, failed or successful
  -s <file>  Use ./<file> instead of ./$SYSFILE as systems file
  -R         Remove current version before building new ones
  -h         Show this help page
  -v         Show version information

EOF
}
#
function update_version() {
    #
    # Should be protected against commits in HEAD
    #
    # Get VERSION file
    tmpdir=/tmp/$MODULE-$$
    mkdir -p $tmpdir
    cd $tmpdir
    cvs checkout -r $1 $MODULE/VERSION
    #
    # Check $GAG_VERS (set by gagdefver)
    if [ -z "$GAG_VERS" ]; then
	error_exit "\$GAG_VERS is not set"
    fi
    #
    # Update VERSION on CVS branch. Make sure you have cvs-write access.
    rm -f $MODULE/VERSION
    echo $GAG_VERS > $MODULE/VERSION
    cvs commit -m "Automatically set to \"$GAG_VERS\" in branch '$1'" $MODULE/VERSION || error_exit "Committing VERSION file in branch '$1' failed"
    message "Updated VERSION file to \"$GAG_VERS\" in branch '$1'"
    #
    # Cleaning
    cd -
    rm -rf $tmpdir
    unset tmpdir
    #
}
#
function release_version() {
    #
    message "Release sources and documentation for $1"
    $gagadmdir/release-gildas -qa $1
    status=$?
    case $status in
	0) echo "Release: Success"                                  >> $LOGFILE ;;
	1) echo "Release: Wrong input parameters"                   >> $LOGFILE ;;
	2) echo "Release: Something wrong"                          >> $LOGFILE ;;
	*) echo "Release: ${status} is an unrecognized exit status" >> $LOGFILE
    esac
    #
}
#
function build_timeout() {
    #
    # Kill the current build because it reached the time out. Actually, kill
    # the connection to the remote machine, but not the sub-processes
    # themselves. Must check by hand what happened there.
    #
    message "Time is out!"
    # Check that the process is really alive
    ps -p $BUILDPID --no-headers > /dev/null
    if [[ $? == 0 ]] ; then
        message "Sending SIGTERM to process $BUILDPID"
        kill $BUILDPID
    fi
}
#
function exit_timer() {
    #
    # Kill the timer if it still exists. Return an error status if
    # timer does not exist anymore, which means that time reached the
    # limit.
    #
    # Check if the process is alive
    ps -p $TIMERPID --no-headers > /dev/null
    if [[ $? == 0 ]] ; then  # Timer is alive
        message "Killing timer $TIMERPID"
        kill $TIMERPID
        return 0
    else
        return 12  # Error number known by mybuild() function
    fi
}
#
function mybuild() {
    #
    unset BUILDKIND HOST EXEC_SYSTEM SYSTEM CONFIG TARGET
    #
    BUILDKIND=$1                  # exe
    HOST=$2                       # pctcp30.iram.fr
    EXEC_SYSTEM=$3                # x86_64-fedora6
    SYSTEM=$3-$4                  # x86_64-fedora6-ifort
    COMPILER=$4                   # ifort
    if [ -n "$5" ]; then
      # Support syntax like o:myconfig or t:mytarget
      opt=`echo $5 | cut --delimiter=: --fields=1`
      if [ $opt = "o" ]; then
        CONFIG=`echo $5 | cut --delimiter=: --fields=2`
      elif [ $opt = "t" ]; then
        TARGET=`echo $5 | cut --delimiter=: --fields=2`
      fi
    fi
    #
    if [ -z "$CONFIG" ]; then
        SYSCONF=$SYSTEM            # x86_64-fedora6-ifort
    else
        SYSCONF="$SYSTEM-$CONFIG"  # x86_64-fedora6-ifort-staticlink
    fi
    case $BUILDKIND in
	all) if [ "$IN_VERSION" = "dev" ]; then BUILDOPT="$RMOPT -CIPW"; else BUILDOPT="$RMOPT -a"; fi ;;  # No cvs up for dev version
	exe) if [ "$IN_VERSION" = "dev" ]; then BUILDOPT="$RMOPT -CI";   else BUILDOPT="$RMOPT -e"; fi ;;  # No cvs up for dev version
	pdf) BUILDOPT="-P" ;;
	www) BUILDOPT="-W" ;;
	*) echo "$@: $BUILDKIND is an unrecognized kind of build" >> $LOGFILE ; return 255
    esac
    #
    # Define the complete system+config name
    if [ -n "$CONFIG" ]; then
        BUILDOPT="$BUILDOPT -o $CONFIG"
    fi
    if [ -n "$TARGET" ]; then
        BUILDOPT="$BUILDOPT -t $TARGET"
    fi
    #
    if [ "$dummy" ]; then
	# Check if builds were attempted or not, on virtual or local
	# machines.
	if [ -e "$LOGDIR/$SYSCONF.success" ]; then
	    status=0
	elif [ -e "$LOGDIR/$SYSCONF.failure" ]; then
	    # Error code was set by 'rebuild' in the file
	    status=`cat $LOGDIR/$SYSCONF.failure`
	elif [ $HOST = "local" ]; then
	    # Pre-built version not available
	    status=13
	else
	    # Remote version not available
	    status=14
	fi
	#
    elif [ "$localbuild" ]; then
	# Build is done locally for all builds matching the local system
	# (rebuild-iram -l).
	if [ $EXEC_SYSTEM = $LOCAL_EXEC_SYSTEM ]; then
	    message "Matched $BUILDKIND $HOST $SYSTEM $FIFTH."
	    # Execute in a subshell to avoid overwriting environment variables.
	    ( source $HOME/.bash_profile; \
		export GAG_ADDONS=yes; \
		source set-iram-gag-search-path ${COMPILER} && \
		${gagadmdir}/rebuild ${BUILDOPT} -c ${COMPILER} ${IN_VERSION} )
	    status=$?
	    #
	else
	    # Skip. Nothing to do, nothing to check.
	    message "Skip $EXEC_SYSTEM."
	fi
	#
    elif [ $HOST = "local" ]; then
	# Built is done by another call to rebuild-iram -l, in a cron on a
	# virtual machine. The successful build relies on the existence of
	# the .success file in the log directory.
	if [ -e "$LOGDIR/$SYSCONF.success" ]; then
	    status=0
	elif [ -e "$LOGDIR/$SYSCONF.failure" ]; then
	    # Error code was set by 'rebuild' in the file
	    status=`cat $LOGDIR/$SYSCONF.failure`
	else
	    # Error code known there after
	    status=13
	fi
	#
    else
	# Built is done on the remote machine through an ssh connexion
	#
	# Launch the remote compilation. Execute in a subshell for a unique pid
	message "Connecting $HOST as user $GILDAS_USER..."
	( nice ssh $GILDAS_USER@$HOST \
	    "source .bash_profile; export GAG_ADDONS=yes; source set-iram-gag-search-path ${COMPILER} && \
	    ${gagadmdir}/rebuild ${BUILDOPT} -c ${COMPILER} ${IN_VERSION}" ) &
	BUILDPID=$!
	#
	# Start timer in a subshell for a unique pid
	(sleep $TIMEOUT ; build_timeout) &
	TIMERPID=$!
	#
	# Wait for the end of build (might end normally or because of timer)
	wait $BUILDPID
	status=$?
	#
	# Stop the timer if required
	exit_timer
	timeout_status=$?
	if (($timeout_status)); then
	    status=$timeout_status
	fi
	unset timeout_status
    fi
    #
    case $status in
	0) mybuild_message "Success" ;;
	1) mybuild_message "Wrong build options" ;;
	2) mybuild_message "Failure in system definition" ;;
	3) mybuild_message "Failure in directory preparation" ;;
	4) mybuild_message "Failure in removing old version" ;;
	5) mybuild_message "Failure in check-out" ;;
	6) mybuild_message "Failure in environment setting" ;;
	7) mybuild_message "Failure in compilation" ;;
	8) mybuild_message "Failure in installation" ;;
	9) mybuild_message "Failure in PDF doc compilation or installation" ;;
       10) mybuild_message "Failure in HTML doc compilation or installation" ;;
       11) mybuild_message "Failure in symlinking" ;;
       12) mybuild_message "Time out ($TIMEOUT)" ;;
       13) mybuild_message "Pre-built version not available" ;;
       14) mybuild_message "Remote version not available" ;;
	*) mybuild_message "${status} is an unrecognized exit status"
    esac
    return $status
}
###########################################################################
#
# Start work by tracking time.
#
date > $LOGFILE
echo >> $LOGFILE
#
###########################################################################
#
# Option parsing
#
temp=`getopt "bfs:lmnRhv" "$@"`
if [ $? -ne 0 ]; then usage; exit 1; fi
eval set -- "$temp"
unset temp
while [ $1 != -- ]; do
    case "$1" in
    -b) mkbranch=1 ;;
    -f) force=1 ;;
    -s) SYSFILE=$2; shift ;;
    -l) localbuild=1 ;;
    -m) mvtag=1 ;;
    -n) dummy=1 ;;
    -R) remove=1 ;;
    -v) showversion; exit 0 ;;
    -h) usage; exit 0 ;;
    *) usage; exit 1 ;;
    esac
    shift # Next flag
done
shift # Skip double dash
case $1 in
    dev)   IN_VERSION=dev ;;
    day)   IN_VERSION=day;   make_day=1 ;;
    month) IN_VERSION=month; make_month=1 ;;
    *) error_exit "$1 version not supported"
esac
set abc; shift # This line to avoid remanence effect in a portable way
#
# Leave the choice between removing everything to start from clean state
# and just updating to save lot's of time.
#
if [ "$remove" ]; then
    RMOPT="-D "   # Only a 'make distclean' will be performed
else
    RMOPT=
fi
#
###########################################################################
#
# Source .bash_profile to ensure a correct definition of the PATH.
# (In particular the PATH toward the cvsrelease script)
#
source $HOME/.bash_profile
#
# Source GILDAS related definitions at IRAM
#
source $gagadmdir/gildas-iram-def.sh
#
# Following line to enable authentication by pubkey
chmod go=rx $HOME
#
# Define $LOGDIR and $GAG_VERS associated to $IN_VERSION
source $gagadmdir/define-version.sh
gagdefver -d $IN_VERSION || message "Error calling 'gagdefver' in $PROGNAME"
unset gagdefver
#
# Find the local system, needed in local build mode:
if [ "$localbuild" ]; then
    source $gagadmdir/define-system.sh  # Load gagdefsys function
    gagdefsys || message "Error calling 'gagdefsys' in $PROGNAME"
    LOCAL_EXEC_SYSTEM=$GAG_MACHINE-$GAG_TARGET_VERS
    message "Local builds on $LOCAL_EXEC_SYSTEM."
    unset gagdefsys
    unset GAG_COMP_SYSTEM GAG_EXEC_SYSTEM
    unset GAG_MACHINE GAG_CONFIG
    unset GAG_ENV_KIND GAG_ENV_VERS
    unset GAG_TARGET_KIND GAG_TARGET_VERS
    unset GAG_COMPILER_CKIND GAG_COMPILER_CEXE
    unset GAG_COMPILER_FKIND GAG_COMPILER_FEXE
    # Unset LOGFILE used for reports, it will be filled by another
    # call to rebuild-iram.
    unset LOGFILE
fi
#
###########################################################################
#
# Tagging and branching
#
# Need repository write access:
export CVSROOT=:pserver:$MANAGER@netsrv1.iram.fr:/CVS/GILDAS
#
# Tags current version of the main trunk. "day" is the tag used to build
# exactly the same version of the soft on different machines.
# -F is to deplace the "day" tag from its last position to the current
# one.
#
if [ "$make_day" -a "$mvtag" ]; then
    message "Move 'day' tag to 'HEAD' version"
    cvs rtag -r HEAD -F day $MODULE || error_exit "Moving 'day' tag to 'HEAD' version"
    nomore=1
fi
#
# Make branch named mmmyy and starting at "last" tag which stands for last
# fully compiled sources at IRAM
#
if [ "$make_month" -a "$mkbranch" ]; then
    BRANCH=`date '+%b%y' | tr '[:upper:]' '[:lower:]'`
    message "Create monthly branch $BRANCH"
    cvs rtag -r last -b $BRANCH $MODULE || error_exit "Creating $BRANCH branch"
    update_version $BRANCH     # Change VERSION file on branch
    nomore=1
fi
#
# Back to repository read-only access:
export CVSROOT=:pserver:anonymous@netsrv1.iram.fr:/CVS/GILDAS
#
# Rebuild-iram stops after moving/setting the CVS branchs/tags (options
#  -m and -b):
if [ -n "$nomore" ]; then
    message "Success. Call $PROGNAME again to effectively build the version."
    exit 0
fi
#
###########################################################################
#
# First clean pending versions to be removed
#
cd $WORKDIR
#
clean_pending
#
###########################################################################
#
# Does the real job. Build on all kinds of system available at IRAM
#
# At some point we should reset all possible previous builds of the same
# version:
# rm -f $LOGDIR/*.failure $LOGDIR/*.success
#
mybuild_all
#
###########################################################################
#
# Analyze results
#
# Dummy mode. Just exit.
if [ "$dummy" ]; then
    if [ -n "$LOGFILE" ]; then
	cat $LOGFILE
    fi
    exit 0
fi
#
# Local build case (rebuild-iram -l). Check and exit.
if [ -n "$localbuild" ]; then
    if [ -n "$failure" ]; then
	message "Local build FAILURE for $LOCAL_EXEC_SYSTEM."
	exit 99
    else
	message "Local build SUCCESS for $LOCAL_EXEC_SYSTEM."
	exit 0
    fi
fi
#
# Else complete check
if [ -n "$failure" ] && [ -z "$force" ]; then
    # Build was a failure and -f was not invoked => global build failure
    #
    if [ "$make_day" ]; then
	day_failure
	#
    else
	message "Failed to build '$IN_VERSION' version"
    fi
    #
else
    # Build was a success, or a failure but -f was invoked => global build
    # success
    #
    if [ "$make_day" ]; then
	#
	# Move 'last' CVS tag:
	message "Move 'last' cvs tag to 'day' version"
	export CVSROOT=:pserver:$MANAGER@netsrv1.iram.fr:/CVS/GILDAS
	cvs rtag -r day -F last $MODULE || error_exit "Moving 'last' tag"
	export CVSROOT=:pserver:anonymous@netsrv1.iram.fr:/CVS/GILDAS
	#
	day_success 8  # Keep 8 daily versions
	release_version $IN_VERSION
	#
    elif [ "$make_month" ]; then
	#
	month_success
	# Disable the monthly release if $gagadmdir/no-monthly-release is
	# present.
	if [ ! -f $gagadmdir/no-monthly-release ]; then
	    release_version $IN_VERSION
	fi
	#
    else
	message "Successfully built '$IN_VERSION' version"
    fi
    #
fi
#
# Report
#
if [ -n "$failure" ]; then
    report "Failure"
else
    report "Success"
fi
#
###########################################################################
