#!/bin/bash
#
#   Usage: build-cpl [letter options] [regrid|data] ver=rev [defs]
#          Quantities in square brackets are optional
#          ver=rev identifies the source revision to be extracted (e.g. branch, tag, hash, ...)
#
# Purpose: Extract coupler source code from a repository into a local directory
#          and then compile the coupler executable and libraries
#
# Options:
# All options begin with a dash (-) and must appear before any other command line arg
# Single letter options may be combined, as in "build-cpl -vv ..."
#   -l   ...list recent revisions and exit (ver=rev is optional in this case)
#   -e   ...extract coupler source code for the specified revision and exit
#   -k   ...keep the cloned dir after compilation (normally this is removed)
#   -t   ...save a tar ball of the build directory to DATAPATH
#   -v   ...increase verbosity (additive)
#   -h   ...show this usage message
#   -x   ...echo shell commands to stdout (used for debugging)
#
# Single word options allowed are:
#   regrid ...build the regrid program rather the coupler library
#   data   ...compile the coupler data model
#
# Definitions:
#   repo=dir      ...a user supplied git repo to use rather than the system default
#   src=dir       ...a user supplied source dir
#                    Files found in this dir will overwrite files cloned from the repo
#   build_dir=dir ...the dir into which the repo will be cloned
#   install=dir   ...the dir into which coupler binary, libraries and modules will be installed
#   cppdefs=file  ...a file containing cpp preprocessor definitions
#   runid=string  ...used in file/dir names
#
# Examples:
#  To list recent revisions in the repository without building anything use
#    build-cpl -l
#
#  To create a local copy of coupler source code for revision 41815cf use
#    build-cpl -e ver=41815cf
#
#  To compile the latest version (ie HEAD) use
#    build-cpl ver=HEAD
#
#  To compile the latest version of the data model use
#    build-cpl ver=HEAD data
#
########################################################################
#
# Larry Solheim ...Apr 2015

FULLPATH=`type $0|awk '{print $3}'` # pathname of this script
Runame=`basename $FULLPATH`
usage() {
  err_exit=0
  while getopts e opt; do
    case $opt in
      e) err_exit=1 ;;
    esac
  done
  shift `expr $OPTIND - 1`

  [ -n "$1" ] && echo >&2 "${Runame}:" "$@"
  echo >&2 " "
  sed >&2 -n '/^###/q; s/^#$/# /; s/^ *$/# /; 3,$s/^# //p;' "$FULLPATH"
  if [ $err_exit -eq 0 ]; then
    exit
  else
    exit 1
  fi
}

# Create time stamp to be used in file names etc
stamp=`date "+%j%H%M%S"$$`

bail(){
  echo "${Runame}: *** ERROR *** $1"
  exit 1
}

# CCRNSRC must be defined
[   -z "$CCRNSRC" ] && bail "CCRNSRC must be defined"
[ ! -d "$CCRNSRC" ] && bail "CCRNSRC=$CCRNSRC is not a directory"

# Set defaults

# The version of code to extract and compile. This must be set by the user.
version=''

# If defined, runid is used in the name of the build dir tarball saved to DATAPATH
runid=''

# The default location of the repository containing coupler source code
source_repo=$CCRNSRC/coupler_dir/coupler.git

# The directory from which this script was invoked
invoke_dir=$PWD

# The directory in which source_repo will be cloned
build_dir="coupler_src_$$"

# A boolean flag to determine whether or not the clone dir will be removed after the build
#   keep_build_dir = 0 ...remove the clone dir
#   keep_build_dir = 1 ...do not remove the clone dir
keep_build_dir=0

# A boolean flag to determine if the build dir should be tarred and saved to DATAPATH
# save_build_dir = 1 means save a tarball of the build dir to DATAPATH
# If save_build_dir has any other value then do not save the build dir
save_build_dir=0

# show_revs = 1 means the program should list recent revisions in the repo and then exit
show_revs=0

# extract_code_only = 1 means extract coupler source code for the
# user supplied revision into build_dir and then exit
extract_code_only=0

# A pathname to a directory containing user supplied source that will overwrite
# any files/dirs of the same name found in build_dir/src
usr_src=''

# For development/debugging only (use -s command line option)
rls_src=/users/tor/acrn/rls/src/coupler/src
use_rls_src=0

# A directory where the coupler code will be installed
install_dir=''

# The name of a file containing cpp preprocessor directives that will be included
# in all fortran source files that require preprocessing. This has historically
# been named CPP_I and this file will be available at compile time when building
# within the gcmjcl framework. For offline builds this file will need to be supplied
# by the user.
cppdefs_file=''

# THREADS is added to the make command line
THREADS=''

# Verbosity flag
verbose=0

# Define build targets
TARGETS=install

DEBUG=''

# Used to control invocation of cccmf to determine CCRNSRC dependencies
# If undef then use the default inside the makefile
ccrnsrc_action=''

# Temporary definition for debugging
# ADD2VPATH=/users/tor/acrn/rls/ocean/CanESM4.2
ADD2VPATH=''

# process command line options
while getopts ehj:klstvx opt
do
  case $opt in
    s) use_rls_src=1 ;;
    l) show_revs=1 ;;
    e) extract_code_only=1 ;;
    t) save_build_dir=1 ;;
    v) verbose=`expr $verbose + 1` ;;
    k) keep_build_dir=1 ;;
    j) THREADS="-j $OPTARG" ;;
    x) set -x ;;
    h) usage ;;
    -) shift; break ;; # end of options
    ?) usage -e $USAGE   ;;
  esac
done
shift `expr $OPTIND - 1`

# Process the remaining command line args
for arg in "$@"; do
  case $arg in
    *=*) var=`echo $arg|awk -F\= '{printf "%s",$1}' -`
         val=`echo "$arg"|awk '{i=index($0,"=")+1;printf "%s",substr($0,i)}' -`
         # add this variable definition to the current environment
         [ -n "$var" ] && eval ${var}=\"\$val\"  # preserve quoted assignments
         val=`echo $val|sed 's/^ *//; s/ *$//'`  # remove leading and trailing space
         [ -z "$val" ] && bail "Invalid command line arg --> $arg <-- Empty value."
         case $var in
           ver) version="$val" ;;                   # git version, tag or branch
           src) usr_src="$val" ;;                   # user supplied source
           repo) source_repo="$val" ;;              # location of git repository
           build_dir) build_dir="$val" ;;           # the dir in which source_repo is cloned
           install) install_dir="$val" ;;           # the dir in which the coupler is installed
           cppdefs) cppdefs_file="$val" ;;          # the file formerly known as CPP_I
           runid) ;;
           ccrnsrc_action) ;;                       # Passed to make
           xlfimp) ;;                               # Passed to make
           FC|FFLAGS|LIBS|INCS|LDFLAGS|CPPFLAGS|ADD2VPATH) ;; # Passed to make
           modver) ;; # Passed to make
           *) bail "Invalid command line arg --> $arg <-- Unknown variable." ;;
         esac
         ;;
      data) TARGETS='install_data'  ;;
    regrid) TARGETS='regrid'
         FC='xlf90_r'
         FFLAGS='-q64 -qdbg -qextname -qnocheck -qnoescape -qxflag=ngenstub -qnoundef -qnosave -qtbtable=full -qxref -qattr'
         ;;
     debug) DEBUG=debug ;;
    *) bail "Invalid command line arg --> $arg <-- Expecting variable assignment or keyword." ;;
  esac
done

#### This works
#### xlf90 -qfree=f90 -q64 -O3 -qmaxmem=-1 -qdbg -qfullpath -qwarn64 -qstrict -qfloat=nans:rrm:norsqrt:nofold -qreport -qflttrap=ov:zero:inv:imp:enable -qsigtrap -qsuppress=1501-245 -qalias=intptr -qextname -qintsize=8 -qrealsize=8 -qarch=pwr7 -qtune=pwr7 -qnocheck -qspillsize=3000 -qnoescape -qxflag=ngenstub -bmaxstack:0xF0000000 -qnoundef -qnosave -qtbtable=full -qxref -qattr -bnoquiet -qinitauto=7FF7FFFF -qsource -I. -I/users/tor/acrn/rls/local/aix64/netcdf3.6.3/include regrid.F90 libremap.a libscrip15.a -L/users/tor/acrn/rls/local/aix64/netcdf3.6.3/lib -lnetcdf -L/usr/lib -lessl -lmassvp5 -lmass -lblas -o regrid

#### This does not work
#### xlf90 -qfree=f90 -q64 -O3 -qmaxmem=-1 -qdbg -qfullpath -qwarn64 -qstrict -qfloat=nans:rrm:norsqrt:nofold -qreport -qflttrap=ov:zero:inv:imp:enable -qsigtrap -qsuppress=1501-245 -qalias=intptr -qextname -qintsize=8 -qrealsize=8 -qarch=pwr7 -qtune=pwr7 -qnocheck -qspillsize=3000 -qnoescape -qxflag=ngenstub -bmaxstack:0xF0000000 -qnoundef -qnosave -qtbtable=full -qxref -qattr -bnoquiet -qsource -I. -I/users/tor/acrn/rls/local/aix64/netcdf3.6.3/include regrid.F90 libremap.a libscrip15.a -L/users/tor/acrn/rls/local/aix64/netcdf3.6.3/lib -lnetcdf -L/usr/lib -lessl -lmassvp5 -lmass -lblas -o regrid


[ -z "$source_repo" ]   && usage -e "The source repository name is missing."
[ ! -d "$source_repo" ] && usage -e "Repository is not a directory --> $source_repo <--"

if [ $show_revs -eq 1 ]; then
  # List the 10 most recent revisions in the repository and exit
  # If version is not set then set then use HEAD
  [ -z "$version" ] && version=HEAD
  cd $source_repo
  git log --pretty=format:"%ai ver=%h %s" $version | head -10
  echo " "
  exit
fi

[ -z "$version" ] && usage -e "ver=revision is required on the command line."

# Detemine if this is a bare repo (isbare=true) or not (isbare=false)
isbare=$(cd $source_repo; git rev-parse --is-bare-repository)

# Define variables to contain info about the location of the repo
# and the commit used for this build
COUPLER_COMMIT_ID=$(cd $source_repo; git log -1 --pretty="%H" $version)
COUPLER_REPO_PATH=$source_repo

# Identify a global cpp include file to be used during the coupler build
# If the user has supplied a name on the command line (via cppdefs=file_name) then use it
# otherwise look for a file named CPP_I in the current dir
if [ -z "$cppdefs_file" ]; then
  [ -s CPP_I ] && cppdefs_file="$PWD/CPP_I"
fi

# Save the name of this dir for use below
CWD=$PWD

# Set a default for the install directory when it not suppled by the user
[ -z "$install_dir" ] && install_dir=$PWD

# install_dir must be an absolute pathname
[ -d $install_dir ] || mkdir -p $install_dir || bail "Unable to create install_dir=$install_dir"
install_dir=$(cd $install_dir; pwd)

# Create the dir that will contain the cloned repo
mkdir -p $build_dir || bail "Unable to create $build_dir"

extract_from_git_repo=1
if [ $extract_from_git_repo -eq 1 ]; then
  # Extract the source from a git repository
  # clone_opts="--branch master --single-branch"
  git clone $source_repo $build_dir || bail "Unable to clone $source_repo"

  # Change to the cloned repo and check out the desired revision
  cd $build_dir
  git checkout --quiet $version || bail "Unable to check out revision $version from $source_repo"
  echo "checked out revision $version from $source_repo"

  # Remove any files/dirs that are not required for the build
  rm -fr Makefile bin doc run testing .git .gitignore
else
  # Simply change to build_dir, which will be empty at this point
  cd $build_dir
  # Create an empty src dir here
  mkdir -m755 src
fi

# If all the user wants is the code then stop here
if [ $extract_code_only -eq 1 ]; then
  echo "Coupler source for revision $version is in $build_dir/src"
  exit
fi

# Wait to let the file system catch up
sleep 5

# We should now be in build_dir which must contain a subdir named "src"
[ x`basename $PWD` = x`basename $build_dir` ] || bail "$PWD is not $build_dir"
[ -d src ] || bail "build_dir=$build_dir does not contain a src directory."

# For development/debugging only (use -s command line option)
[ $use_rls_src -eq 1 ] && usr_src=$rls_src

if [ -n "$usr_src" ]; then
  # Overwrite files in the cloned source directory with user supplied files
  [ -d "$usr_src" ] || bail "src=$usr_src is not a directory."
  cp -r $usr_src/* src
  echo "Overwrote files in src with files from $usr_src"

  # Wait to let the file system catch up
  sleep 5
fi

# Verify that we have the right version of make
MAKE=$(which make) || bail "make is missing."
make_version=$($MAKE -version|head -1|awk '{print $NF}')
[[ $make_version < 3.82 ]] &&
     bail "The version of $MAKE is $make_version but version 3.82 or better is required."

# Export certain variables if they have a value
[ -n "$FC" ]        && export FC
[ -n "$FFLAGS" ]    && export FFLAGS
[ -n "$LIBS" ]      && export LIBS
[ -n "$INCS" ]      && export INCS
[ -n "$CPPFLAGS" ]  && export CPPFLAGS
[ -n "$LDFLAGS" ]   && export LDFLAGS
[ -n "$ADD2VPATH" ] && export ADD2VPATH
[ -n "$modver" ]    && export modver
[ -n "$COUPLER_COMMIT_ID" ] && export COUPLER_COMMIT_ID
[ -n "$COUPLER_REPO_PATH" ] && export COUPLER_REPO_PATH

# Define command line options for the following invocation of make
if [ -n "$cppdefs_file" ]; then
  [ -s $cppdefs_file ]   && cppdefs_opt="cppdefs=$cppdefs_file"
fi
[ -n "$ccrnsrc_action" ] && ccsrcact_opt="ccrnsrc_action=$ccrnsrc_action"
[ -n "$xlfimp" ]         && xlfimp_opt="xlfimp=$xlfimp"
make_opts="$cppdefs_opt $ccsrcact_opt $xlfimp_opt"

# Change to the build directory
cd src || bail "The src dir is missing from $build_dir"

if [ x$TARGETS = xregrid ]; then
  echo "Building regrid..."
else
  echo "Building coupler..."
fi
echo "$MAKE $THREADS $TARGETS $DEBUG install_dir=$install_dir $make_opts"

# Build the coupler executables and libraries
fail=0
$MAKE $THREADS $TARGETS $DEBUG install_dir=$install_dir $make_opts >> build-out 2>&1 || fail=1
if [ $fail -eq 1 ]; then
  cat build-out
  echo "Source is in $build_dir"
  bail "Problem building coupler source."
fi

if [ $save_build_dir -eq 1 ]; then
  # Tar up the build dir and save it to DATAPATH
  cd ..
  tar cf BUILD_DIR_BUNDLE . || bail "Problem executing --> tar cf BUILD_DIR_BUNDLE . <--"
  if [ -n "$runid" ]; then
    dir_out=cpl_build_${runid}_`date "+%Y-%m-%d_%H%M%S"`.tar
  else
    dir_out=cpl_build_`date "+%Y-%m-%d_%H%M%S"`.tar
  fi
  save BUILD_DIR_BUNDLE $dir_out || bail "Unable to save $dir_out to DATAPATH"
fi

# Return to the original dir and clean up
cd $CWD
[ $keep_build_dir -eq 1 ] || rm -fr $build_dir
