#!/bin/bash
########################################################################
#
# $Id: buildntest 642294 2008-03-28 17:02:14Z sebor $
#
# script to build and test the Apache C++ Standard Library
#
########################################################################
#
# Licensed to the Apache Software  Foundation (ASF) under one or more
# contributor  license agreements.  See  the NOTICE  file distributed
# with  this  work  for  additional information  regarding  copyright
# ownership.   The ASF  licenses this  file to  you under  the Apache
# License, Version  2.0 (the  "License"); you may  not use  this file
# except in  compliance with the License.   You may obtain  a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the  License is distributed on an  "AS IS" BASIS,
# WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
# implied.   See  the License  for  the  specific language  governing
# permissions and limitations under the License.
#
# Copyright 2007 Rogue Wave Software, Inc.
#
########################################################################
#
# USAGE:
#
#     buildntest [ -e <examples-to-build> ]
#                [ -l <locales-to-build> ]
#                [ -o <output-file> ]
#                [ -t <tests-to-build> ]
#                [ -u <utilities-to-build> ]
#                [ -C <config> ]
#                [ -B <buildtype> ]
#                [ -E <examples-to-run> ]
#                [ -L <locales-to-run> ]
#                [ -M <buildmode> ]
#                [ -P <prefix> ]
#                [ -T <tests-to-run> ]
#
# OPTIONS:
#
#     -e <examples-to-build>
#         Specifies the list of example programs to build. When omitted,
#         defaults to all.
#                            
#     -l <locales-to-build>
#         Specifies the list of locales to build. When omitted, defaults
#         to all.
#                            
#     -o <output-file>
#         Specifies the name of the ouptut file to create. When omitted,
#         defaults to STDOUT and STDERR.
#
#     -t <tests-to-build>
#         Specifies the list of tests to build. When omitted, defaults
#         to all.
#
#     -B <buildtype>
#         Specifies the value of the BUILDTYPE variable to pass to
#         make.
#
#     -C <config>
#         Specifies the value of the CONFIG variable to pass to make.
#
#     -M <buildmode>
#         Specifies the value of the BUILDMODE variable to pass to
#         make.
#
#     -E <examples-to-run>
#         Specifies the list of example programs to run. When omitted,
#         defaults to <examples-to-build>.
#                            
#     -L <locales-to-run>
#         Specifies the list of locales to exhaustively test. When
#         omittedm, defaults to <locales-to-build>.
#                            
#     -P <prefix>
#         Specifies the value for the PREFIX variable to pass to make.
#         When omitted, the test makefile target is not exercised.
#
#     -T <tests-to-run>
#         Specifies the list of tests to run. When omitted, defaults
#         to <tests-to-build>.
#
# EXAMPLES:
#
#     Example 1:
#
#     buildntest -Cgcc.config -B15s \
#                -e"accumulate money_put time_get" \
#                -l"en_US.ISO-8859-1 de_DE.ISO-8859-15" \
#                -t"21.string.append 21.string.replace" \
#                -E"time_get" -L"de_DE.ISO-8859-15" \
#                -Pinstall
#
#     The command above will build the stdcxx library with gcc in the
#     15s build type, then build the accumulate, money_put, and time_get
#     example programs, the en_US.ISO-8859-1 and de_DE.ISO-8859-15
#     locales, and the tests 21.string.append and 21.string.replace.
#     Then it will run the time_get example, followed by running tests
#     for the de_DE.ISO-8859-15 locale, then run the two tests it built
#     and, finally, execute the install target with 'install' as the
#     installation directory.
#
# RESULT FILE FORMAT:
#
# When buildntest is run, it produces an output file named results.xml.
# This name is an historical artifact, as the contents are actually a
# Java-style (key=value pairs) property list. This file is used by the
# nightly automated testing system, so extreme caution should be taken
# when changing either the name of the file or the format of the 
# contents, as changes to the nightly testing system will likely be 
# required.  The majority of this file is currently generated by the 
# parse_runlog.awk script (not in subversion), but generation should be
# moved into the exec utility.
#
# The first property contained in this file must have the name 
# 'result.file.type' and a value of either 'stdcxx-short' or 
# 'stdcxx-extended'. The contents of a file taged as 'stdcxx-extended' 
# are a supserset of the contents of a file taged as 'stdcxx-short', 
# though all properties beyond the header property are optional.
#
# stdcxx-short files contain the following defined properties:
#     state
#         Denotes the state of the run. Valid values are 'C' (denoting a
#         catastrophic failure, such as a configuration filure), 'F'
#         (denoting a failure building the libstd library), 'E' (denoting
#         a failure building the rwtest library), 'L' (denoting a failure
#         building the exec utility), and 'T' (denoting no major 
#         failures).
#     warnings
#         An integer, indicating the number of warnings produced during
#         the build and run process.
#     examples.run
#         An integer, indicating the total number of examples which the
#         infastructure attemted to build.
#     examples.good
#         An integer, indicating the total number of examples which ran
#         to completion and had output matching the expected output (if
#         available).
#     tests.run
#         An integer, indicating the total number of tests which the
#         infastructure attemted to build.
#     tests.good
#         An integer, indicating the number of tests which ran to
#         completion and reported no failing assertions.
#     locales.run
#         An integer, indicating the total number of locale tests which
#         the infastructure attemted to run.
#     locales.good
#         An integer, indicating the number of locale tests which ran to
#         completion and reported no failing assertions.
#     utils.run
#         An integer, indicating the total number of utilities which the
#         infastructure attemted to build.
#     utils.good
#         An integer, indicating the number of utilities which linked
#         successfully.
#
# stdcxx-extended files contain additional properties, with names in the
# form '{test,example,locale}.<name>.{ret,warn,assert}'.  The prefix
# {test,example,locale} denotes the type of executable (test, example, 
# or locale test) that the executable is. The suffix {ret,warn,assert}
# denotes the type of data (return code, warning count, assertion count)
# the property contains. The values are mapped directly from the output
# of the exec utility, with ret coresponding to the STATUS column, warn
# coresponding to the WARN column, and assert coresponding to the 
# FAILED and ASSERTS columns, seperated by the '/' character.
#
########################################################################

##########
# global constants

# date format used to compute real time elapsed between stages
# assumes 366 days/year for simplicity
DATEFMT="((((%Y*366+1%j-100)*24+1%H-100)*60+1%M-100)*60+1%S-100)"
OUTFILE=results.xml

if [ -z "$MAKE" ]; then
    # let callers override the default gmake
    MAKE=gmake
fi

# the name of the top-level source directory (TOPDIR)
TOPDIR=`pwd | sed -e "s/\//\\\\\\\\\\\//g"` #Mangle path for sed usage

# script to replace the name of the top-level source and builr directories
# with the respective symbols for brevity
TRANS="s/${TOPDIR}\/build/\\\$(BUILDDIR)/g;s/${TOPDIR}/\\\$(TOPDIR)/g"


# set the TMPDIR variable if not set
[ -z $TMPDIR ] && TMPDIR=$TMP
[ -z $TMPDIR ] && TMPDIR=/tmp

##########
# global variables

# by default (unless otherwise specified on the command line) build
# and run all examples, locales, tests, and utilities
build_examples="*"
build_locales="*"
build_rwtest="*"
build_tests="*"
build_utils="*"

run_examples="*"
run_locales="*"
run_rwtest="*"
run_tests="*"

# script's own temporary directory
tmpdir=$TMPDIR/stdcxx-tmp.$$


##########
# write_times(): writes the amount of real, user, and system time since
# the last call, or the date and time if this is the first call
write_times()
{
    label=$1

    if [ -z $startsec ]; then
        echo; echo "### date:"
        date
    else
        elapsed="`date +$DATEFMT`-$startsec"

        # compute the minutes and seconds elapsed of real time since
        # the last call to the function
        real_min=$((elapsed))
        real_min=$((real_min / 60))

        real_sec=$((elapsed))
        real_sec=$((real_sec % 60))

        # display the real, user, and system times since the last call
        echo; echo "### real, user, system time ($label):"
        echo "${real_min}m${real_sec}s"
        times
    fi

    # set the new start timestamp
    startsec=`date +$DATEFMT`
}


##########
# make_stage(): executes a stage of the build process, reports real,
# user, and system times, and processes errors
make_stage()
{
    stagedir=$1
    failstate=$2
    stageargs=$3

    # remove the longest prefix matching the pattern "*/"
    stagename="${stagedir##*/}"

    if [ "$failstate" = "" ]; then
        keep_going="-k"
    else
        keep_going=""
    fi

    if [ "$stagename" = "runall" ]; then
        runlog="$TMPDIR/run.$$.log"
        log=$runlog
    else
        buildlog="$TMPDIR/build.$$.log"
        log=$buildlog
    fi

    echo "### $MAKE $keep_going $stagedir $stageargs $MAKEVARS 2>&1 | sed -e \"${TRANS}\" | tee $log:"

    (   # start a subshell to measure user and system times
        # of the subshell commands only
        export TMPDIR=$tmpdir

          $MAKE $keep_going $stagedir $stageargs $MAKEVARS 2>&1 \
        | sed -e "${TRANS}" | tee $log

        # save the status of the first command in the pipeline
        # and pass it to the parent shell after writing the real,
        # user, and system times for the subshell and its children
        status=${PIPESTATUS[0]}

        write_times $stagename

        exit $status
    )

    status=$?

    if [ -r "$runlog" ]; then
        # Parse runall results
        $AWK -f parse_runlog.awk $runlog >>${OUTFILE}

        rm $runlog
    elif [ -r "$buildlog" ]; then
        # count warnings and errors in the stage
        warnings=`grep -i "warning" $buildlog | wc -l`
        errors=`grep -i "error" $buildlog | wc -l`

        diags=": $warnings warnings and $errors errors,"

        rm $buildlog
    fi

    if [ $status -ne 0 ]; then
        if [ "$failstate" = "F" ]; then
          echo "### stage $stagename${diags} exiting with status $status"

          cat <<EOF >> ${OUTFILE}
state=F
warnings=0
examples.run=0
examples.good=0
tests.run=0
tests.good=0
locales.run=0
locales.good=0
utils.run=0
utils.good=0
EOF
          exit $status
      elif [ "$failstate" != "" ]; then
          # if failstate is specified and the exit status is non-zero
          # write out the failstate and exit immediately with an error
          echo "state=$failstate" >> ${OUTFILE}

          echo "### stage $stagename${diags} exiting with status 1"
          exit 1
      fi
    fi

    echo "### stage $stagename${diags} continuing with status $status"
    echo

    return $status
}

##########
# main body of script

# write the name of the script and all arguments
echo "### running $0 $* [$#]"

# script's revision number
myrev='$Revision: 634307 $'
myrev=${myrev#'$Revision: '}   # strip leading text
myrev=${myrev%' $'}            # strip trailing text

# URL to this version of the script in the repository
myurl='$HeadURL: https://svn.apache.org/repos/asf/stdcxx/trunk/bin/buildntest $'
myurl=${myurl#'$HeadURL: '}   # strip leading text
myurl=${myurl%' $'}           # strip trailing text
myurl="$myurl?view=markup&rev=$myrev"

# write the URL to the version of the script that's running
echo; echo "### script source: $myurl"


# process command line options
while getopts ":nv:e:l:o:t:B:C:E:L:M:P:T:" opt_name; do

    echo "$opt_name:$OPTARG"

    case $opt_name in
        # options with no arguments 

        n)  # avoid cleaning up temporary files
            no_clean=1
            ;;

        v)  # output all components (including passing ones)
            verbose=1
            ;;

        # options with arguments 

        e)  # argument is a list of examples to build
            build_examples="$OPTARG"
            ;;

        l)  # argument is a list of locales to build
            build_locales="$OPTARG"
            ;;

        o)  # argument is the name of output file (stdout by default)
            outfile=$OPTARG
            ;;

        t)  # argument is a list of tests to build
            build_tests="$OPTARG"
            ;;

        u)  # argument is a list of utilities to process
            build_utils="$OPTARG"
            ;;

        B)  # argument is the value of the BUILDTYPE variable
            MAKEVARS="$MAKEVARS BUILDTYPE=$OPTARG"
            ;;

        C)  # argument is the value of the CONFIG file name
            MAKEVARS="$MAKEVARS CONFIG=$OPTARG"
            ;;

        E)  # argument is a list of examples to run
            run_examples="$OPTARG"
            ;;

        L)  # argument is a space-separated list of locales
            MAKEVARS="$MAKEVARS LOCALES=\"$OPTARG\""
            run_locales="$OPTARG"
            ;;

        M)  # argument is the value of the BUILDMODE variable
            MAKEVARS="$MAKEVARS BUILDMODE=$OPTARG"
            ;;

        P)  # argument is the value of the PREFIX variable
            MAKEVARS="$MAKEVARS PREFIX=$OPTARG"
            prefix=$OPTARG
            ;;

        T)  # argument is a list of tests to build
            run_tests="$OPTARG"
            ;;

        *) echo "$myname: unknown option : -$opt_name" >&2;
           exit 1;;
    esac;
done

# default to running the same set of components (examples, locales,
# and tests) as those specified to be built
if [ "$run_examples" = "*" ]; then
    run_examples="$build_examples"
fi

if [ "$run_locales" = "*" ]; then
    run_locales="$build_locales"
fi

if [ "$run_tests" = "*" ]; then
    run_tests="$build_tests"
fi


# remove command line options and their arguments from the command line
shift $(($OPTIND - 1))


# Try to make certain we clean up the temp directory
# This is an ugly workaround for an HPUX 11.23 glitch we haven't managed
# to reproduce in manual testing.
trap 'rm -rf $tmpdir' EXIT INT QUIT TERM

mkdir $tmpdir


# start by writing out information about the host to stdout
echo; echo "### uname -a:"
uname -a

osname=`uname -s`

AWK=awk
case "$osname" in
    (AIX)
    # output the amount of real memory installed on the host
    echo; echo "### /usr/sbin/lsattr -El sys0 -a realmem:"
    /usr/sbin/lsattr -El sys0 -a realmem
    ;;

    (HP-UX)
    # grep the system log for the amount of physical memory
    echo; echo "### grep Physical /var/adm/syslog/syslog.log:"
    grep Physical /var/adm/syslog/syslog.log
    ;;

    (Linux)
    # output the contents of the /etc/<distro>-release file
    # to identify the Linux distribution
    echo; echo "### cat " /etc/*-release ":"
    cat /etc/*-release

    # output the amount of installed physical memory and swap space
    echo; echo "### free -o:"
    free -o

    # output information about installed processors
    echo; echo "### cat /proc/cpuinfo:"
    cat /proc/cpuinfo
    ;;

    (SunOS)
    # use POSIX awk (nawk) rather than Solaris awk
    # using nawk rather than /usr/xpg4/bin/awk as the later has a bad bug
    AWK=nawk

    # output the amount of installed physical memory
    echo; echo "### prtconf | grep Memory:"
    /usr/sbin/prtconf | grep Memory

    # output information about installed processors
    echo; echo "### /usr/sbin/psrinfo -v:"
    /usr/sbin/psrinfo -v
    ;;

esac

# output the amount of installed, available, and used disk space
# in all mounted filesystems
echo; echo "### df -k:"
df -k

# echo today's date
write_times


# echo environment to stdout for refernce
echo; echo "### env:"
env

echo

# Start our output file
echo "result.file.type=stdcxx-extended" > ${OUTFILE}

##########
# Create build directory, set state to 'C' on failure
make_stage "builddir" "C"

# Configure build directory, set state to 'C' on failure
make_stage "config" "C"

# Build library, set state to 'F' on failure
make_stage "-Cbuild/lib" "F"

# show the library and its size
echo; echo "### ls -l build/lib/lib*"
ls -l build/lib/lib*

echo


# State is now 'L' or better


# Build examples, ignore errors
if [ "$build_examples" != "" ]; then
    if [ "$build_examples" = "*" ]; then
        unset list
    else
        list="$build_examples"
    fi
    make_stage "-Cbuild/examples" "" "$list"
fi


# Build utilities, save return code for later
if [ "$build_utils" != "" ]; then
    if [ "$build_utils" = "*" ]; then
        unset list
    else
        list="$build_utils"
    fi
   
    make_stage "-Cbuild/bin" "" "$list"
    utils_status=$?
else
    utils_status=1
fi


# Build rwtest library, save return code for later
if [ "$build_rwtest" != "" ]; then
    if [ "$build_rwtest" = "*" ]; then
        unset list
    else
        list="$build_rwtest"
    fi
    make_stage "-Cbuild/rwtest" "" "$list"
    rwtest_status=$?
else
    rwtest_status=1
fi


##########
# Create a counter so we can keep track of the number of 'good' utilities.
# While not the most accurate, we're calling a utility good if it exists and
# is executable.  The utilities we check are the exec, locale and localedef, 
# all of which reside in the build/bin subdirectory and were built above.
# The reason this definition isn't accurate is because it doesn't tell us if
# the utility is behaving correctly.  However, to make such checks would be
# more difficult.
UTILS=0

# We want to check that the exec utility is good prior to using it via
# the $MAKE runall target
if [ -x "build/bin/exec" ]; then
    if [ 0 == ${rwtest_status} ]; then
        echo "state=T" >> ${OUTFILE}

        # Build tests only if rwtest built, ignore errors

        if [ "$build_tests" = "*" ]; then
            unset list
        else
            list="$build_tests"
        fi

        make_stage "-Cbuild/tests" "" "$list"
    else
        echo "state=E" >> ${OUTFILE}

        if [ 0 == ${utils_status} ]; then
            utils_status=${rwtest_status}
        fi
    fi
    #Record it as good
    UTILS=`expr ${UTILS} + 1`

    # Run successfully built executables, ignore errors
    if [    "$run_examples" = "*" \
         -a "$run_locales" = "*" \
         -a "$run_tests" = "*" ]; then
        make_stage "runall"
    else
        if [ "$run_examples" != "" ]; then
            if [ "$run_examples" = "*" ]; then
                unset list
            else
                list="$run_examples"
            fi

            make_stage "-Cbuild/examples" "" "runall $list"
        fi

        if [ "$run_locales" != "" ]; then
            if [ "$run_locales" = "*" ]; then
                unset list
            else
                list="$run_locales"
            fi

            make_stage "-Cbuild/bin" "" "runall $list"
        fi

        if [ "$run_tests" != "" ]; then
            if [ "$run_tests" = "*" ]; then
                unset list
            else
                list="$run_tests"
            fi

            make_stage "-Cbuild/tests" "" "runall $list"
        fi
    fi    
else
    echo "state=L" >> ${OUTFILE}
fi

if [ -x "build/bin/locale" ]; then
    UTILS=`expr ${UTILS} + 1`
fi

if [ -x "build/bin/localedef" ]; then
    UTILS=`expr ${UTILS} + 1`
fi

echo "utils.run=3" >> ${OUTFILE}
echo "utils.good="${UTILS} >> ${OUTFILE}


if [ "$prefix" != "" ]; then
    make_stage "-Cbuild" "" "install"
fi


# write out the size of the buildspace and (when specified)
# the installation directory BEFORE cleaning it up for reference
echo; echo "### du -sk build build/* $prefix"
du -sk build build/* $prefix


# Clean up most of what we built.  We don't want to clean/realclean include, 
# as clean deletes the config.log, and realclean deletes the config.h.
# The top level realclean target affects the lib, rwtest, bin, test, 
# plumbhall, and example directories (via the .DEFAULT rule).
echo; echo "### $MAKE realclean"
$MAKE realclean

# write out the size of the buildspace AFTER cleaning it up for reference
echo; echo "### du -sk ./build/ ./build/*"
du -sk ./build/ ./build/*

# write the amount of real, user, and system time
write_times "total"

# Pass the captured return code from make util/make rwtest back out
exit ${utils_status}
