#!/bin/bash
set -x
#
#   Usage: build-exe [letter options] [defs] model_job
#          Quantities in square brackets are optional
#          model_job is a CCCma job string
#
# Purpose: compile the CanESM AGCM, coupler, or diagnostic programs
#
# 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-exe -vv ..."
#   -a     ...Compile the AGCM
#   -c     ...Compile the coupler
#   -j INT ...Use INT tasks when compiling (INT is 8 by default)
#   -m     ...Populate the build directory and create the make file but do not compile
#             This option implies -k
#   -k     ...Keep the build dir after compilation (normally this is removed)
#   -s     ...Save the model executables after compilation (not saved by default)
#   -v     ...Increase verbosity (additive)
#   -h     ...Show this usage message
#   -x     ...Echo shell commands to stdout (used for debugging)
#
# Definitions:
#   canesm_root=DIR          ...the root directory of an existing CanESM source tree
#                               Currently this means that it must contain at least the following set of subdirs
#                                   $canesm_root/CanAM
#                                   $canesm_root/CanCPL
#                                   $canesm_root/CanDIAG
#                                   $canesm_root/CanNEMO
#                                   $canesm_root/CCCma_tools
#                               with each subdir containing source code for the corresponding model component
#   build_dir=DIR            ...the directory in which the build will take place
#   progs=LIST               ...A white space separated list of files that contain "main" programs
#                               This is in addition to those implied by the -a, -c, -d or -n options
#   cancpl_exec=STRING       ...the name of the coupler executable (this name will be augmented with commit info)
#   canam_exec=STRING        ...the name of the AGCM executable (this name will be augmented with commit info)
#
# Examples:
#  To compile the coupler with version and repo location defined by the variables
#  cancpl_repo and cancpl_ver that are found in the model string use
#    build-exe -c my_model_job
#
#  To compile the AGCM with versions and repo locations defined by the variables
#  canam_repo, canam_ver, candiag_repo and candiag_ver found in the model string use
#    build-exe -a my_model_job
#
#
########################################################################
#
# Larry Solheim ...Mar 2017

FULLPATH=$(type $0|awk '{print $3}') # pathname of this script
Runame=`basename $FULLPATH`
usage() {
  err_exit=0
  if [ "$1" = "-e" ]; then
    err_exit=1
    shift
  fi

  [ -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 a time stamp to be used in file names etc
stamp=$(date "+%j_%H%M%S"_$$)

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

warn(){
  echo "${Runame}: **WW** $1"
}

setTargetPfx() {
# Verify that runid, year and mon have values when target_pfx is not set
# and then define a default value for target_pfx
if [ -z "$target_pfx" ]; then
  [ -z "$runid" ] && { runid=ineedaname; warn "setTargetPfx: runid is not defined. using runid=$runid"; }
  [ -z "$year" ]  && { year=0000;        warn "setTargetPfx: year is not defined. using year=$year"; }
  [ -z "$mon" ]   && { mon=01;           warn "setTargetPfx: mon is not defined. using mon=$mon"; }
  # Do not use the first branch of this conditional until a discussion about these names has been had
  if [ 1 -eq 2 -a -n "$exetag" ]; then
    # information about the CanESM commit that was used to build is available in exetag
    target_pfx=mc_${runid}+${exetag}_${year}_m${mon}_
  else
    target_pfx=mc_${runid}_${year}_m${mon}_
  fi
  warn "setTargetPfx: runid=$runid  year=$year mon=$mon  (re)set target_pfx=$target_pfx"
fi
}

GIT=git
this_mach=$(uname -n|awk -F. '{print $1}')
case $this_mach in
  xc1*) this_mach=hare;   GIT="ssh hare git" ;;
  xc2*) this_mach=brooks; GIT="ssh brooks git" ;;
  xc3*) this_mach=banting; GIT="ssh banting git" ;;
  xc4*) this_mach=daley; GIT="ssh daley git" ;;
  *-ppp1) this_mach=ppp1 ;;
  *-ppp2) this_mach=ppp2 ;;
  hpcr3*|cs3*|*-ppp3) this_mach=ppp3 ;;
  hpcr4*|cs4*|*-ppp4) this_mach=ppp4 ;;
esac

# Set defaults

model_job=''

# If defined canesm_root must be the root dir of a CanESM source tree
# Currently this means that it must contain at least the following set of subdirs
#   $canesm_root/CanAM
#   $canesm_root/CanCPL
#   $canesm_root/CanDIAG
#   $canesm_root/CanNEMO
# with each subdir containing source code for the corresponding model component
canesm_root=''

# The names of the "main" programs that are to be compiled
declare -a MAINS

# The names of the executables that are saved to disk
declare -a TARGETS

# The directory in which the executable will be created
build_dir=''

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

# create_makefile_only = 1 means clone all source and create the makefile but do not build
# This implies -k (aka keep_build_dir=1)
create_makefile_only=0

build_agcm=0
build_agcm_32bit=0
build_coupler=0

# save_exec = 1 means save model executables after they are created
# otherwise model executables are never saved
save_exec=0

# Options passed to make
make_opts=''

# The number of tasks used by make
make_tasks=8

# Verbosity flag
verbose=0

# The name of a file containing info (commits, executable names, ...) about the build
build_info=''

DEBUG=''

# Temporary definition for debugging
ADD2VPATH=''

# The name of the compiler used on the xc40
xc40_compiler=''

# XC40 specific options passed to cccmf
xc40_opts=''

# Run or do not run up2patch from cccmf
up2patch_opt=" --up2patch"

# process command line options
while getopts abcdfhj:kmsvx opt
do
  case $opt in
    a) build_agcm=1 ;;
    b) build_agcm_32bit=1 ;;
    c) build_coupler=1 ;;
    m) create_makefile_only=1
       keep_build_dir=1
       ;;
    v) verbose=$((verbose + 1)) ;;
    k) keep_build_dir=1 ;;
    j) make_tasks=$OPTARG ;;
    s) save_exec=1 ;;
    x) set -x ;;
    h) usage ;;
    -) shift; break ;; # end of options
    ?) usage -e $USAGE ;;
  esac
done
shift $((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" ] && usage -e "Invalid command line arg --> $arg <-- Empty value."
         # set a parameter that may be used below to determine if a particular variable
         # was defined on the comand line by the user
         eval sbu_${var}=1
         case $var in
           canesm_root)       ;;
           canam_exec)        ;;
           cancpl_exec)       ;;
           build_dir)         ;;
           target_pfx)        ;;
           build_info)        ;;
           runid)             ;;
           cppdefs_file)      ;;
           cppdefs_diag)      ;;
           compiler)     xc40_compiler="$val" ;;
           progs) for file in ${progs}; do
                    MAINS[${#MAINS[*]}]=$(basename $file)
                    TARGETS[${#TARGETS[*]}]=$(basename $file .f)
                  done
                  up2patch_opt=" --noup2patch"
                  ;;
           FC|FFLAGS|LIBS|INCS|LDFLAGS|CPPFLAGS|ADD2VPATH)
                         export $var ; eval sbu_${var}=1 ;; # Passed to cccmf
           *) usage -e "Invalid command line arg --> $arg <-- Unknown variable." ;;
         esac
         ;;
    debug) DEBUG=debug ;;
    *) if [ -z "$model_job" ]; then
         model_job=$arg
       else
         usage -e "Invalid command line arg --> $arg <--"
       fi
       ;;
  esac
done

[ -z "$model_job" ] &&
    usage -e "A file containing the model job string must be supplied on the command line"

[ -z "$canesm_root" ] &&
    usage -e  "build-exe: the CanESM source directory must be given at the command line! (i.e. 'canesm_root=SOURCE_DIR')"

# get full path to code source directory
canesm_root=$(readlink -f $canesm_root 2>/dev/null)

# Define certain make command line arguments
[ -n "$make_tasks" ] && make_opts+=" -j $make_tasks"

# Ensure a full pathname for the model job so that it is visible from the build dir
model_job_tmp=$(readlink -f $model_job 2>/dev/null)
[ -z "$model_job_tmp" ] &&
    bail "Cannot find model job file $model_job"
model_job=$model_job_tmp

# if we are compiling the AGCM or Coupler, cccmf needs cppdefs_file and cppdefs_diag
if [ $build_agcm -eq 1 -o $build_coupler -eq 1 ]; then
  if [ -z "$cppdefs_file" ] ; then 
    # cppdefs_file wasn't provided at CL.. extract from model job
    eval `grep -m 1 "^ *cppdefs_file=" $model_job`
    echo cppdefs_file=$cppdefs_file
    [ -z "$cppdefs_file" ] && 
        bail "build-exe: cppdefs_file must be defined in the model job or provided at the CL when compiling the coupler or AGCM"
  fi
  if [ -z "$cppdefs_diag" ] ; then
    # cppdefs_diag wasn't provided at CL.. extract from model job
    eval `grep -m 1 "^ *cppdefs_diag=" $model_job`
    echo cppdefs_diag=$cppdefs_diag
    [ -z "$cppdefs_diag" ] &&
        bail "build-exe: cppdefs_diag must be defined in the model job or provided at the CL when compiling the coupler or AGCM"
  fi
fi

# Keep track of the invocation directory
invoke_dir=$PWD

#=========================
#=== Setup build directory
#=========================
eval set_by_user=\$sbu_build_dir
if [ -z "$set_by_user" ]; then
  # The user has not supplied a build dir name on the command line
  case ${build_agcm}_${build_coupler} in
    1_0) # build AGCM only
             build_dir=agcm ;;
    0_1) # build coupler only
             build_dir=coupler ;;
    *) build_dir=build_$stamp ;;
  esac
fi

# If build_dir is not an absolute path name then make it relative to the current working directory
# build_dir may or may not exist at this point
[[ "${build_dir:0:1}" == "/" ]] || build_dir=$(readlink -f $PWD)/$build_dir

echo "build-exe: build_dir = $build_dir"

# remove build directory if it already exists
if [ -e $build_dir ]; then
  warn "build-exe: The build directory already exists:  $build_dir"
  warn "It will be removed and then recreated."
  rm -rf $build_dir
  sleep 1
fi

# create build directory and navigate into it
mkdir -m 755 -p $build_dir || bail "Unable to create $build_dir"
cd $build_dir || bail "Unable to change to the build dir $build_dir"
echo "build-exe: Building this project in $build_dir"

# Define the build_info file name unless it has been supplied by the user
[ -z "$build_info" ] && build_info=$build_dir/build-info-$stamp

# Create a subdirectory named build for the "out of source" build and change to it
[ -d build ] || mkdir -m 755 build
cd build

# The parameter build_dir names the parent of the "true" build directory
true_build_dir=$PWD
echo "build-exe: Build targets will be created in $true_build_dir"

# Copy the model job string here
cp $model_job .
model_job=$(basename $model_job)
[ -s $model_job ] || bail "Problem copying $model_job to the build_directory $build_dir"

# Create (actually append to) the build info file
echo "Created: $(date)" >> $build_info
echo "   User: $(whoami)" >> $build_info

# Create a file containing all variables that are defined in the user supplied model job
# and define some of these variables in the current environment
# The variables in this file will be defined 1 per line with each line of the form
#    var = value
# The value may be empty if the variable is defined but has a NULL value
export build_agcm
export build_coupler
echo build_agcm=$build_agcm
echo build_coupler=$build_coupler
cccmf model_job=$model_job parmsub_file=parmsub_parameters
[ -s parmsub_parameters ] || bail "Unable to create parmsub parameters file"
# Define a list of variable names to be extracted from the model job string, if present therein
vlist="runid year mon year_restart mon_restart model start modver"
echo "build-exe: These variables enter the environment from the user supplied file $model_job"
for var in $vlist; do
  # Do not reset a variable that was set on the command line
  eval set_by_user=\$sbu_$var
  [ -n "$set_by_user" ] && continue
  val=$(sed -n 's/^ *'$var' *= *//p' parmsub_parameters)
  if [ -n "$val" ]; then
    # Only define variables that have a value
    eval ${var}=\"$val\"
    echo "build-exe: Added ${var}=$val"
    echo "found in ${model_job}: ${var}=$val" >> $build_info
  fi
done

#==================================
#=== Set up compilation environment
#==================================
if [ x$this_mach = "xhare" -o x$this_mach = "xbrooks" ]; then
  # Define fortran compiler and options used on the xc40
  xc40_compiler=${xc40_compiler:=intel}
  case $xc40_compiler in
    intel)
      # load required modules
      # curr_prgenv will typically be one of cray, gnu or intel
      curr_prgenv=$(module list 2>&1|grep PrgEnv|sed 's/^.*PrgEnv-//; s/\/.*//')
      [[ -z "$curr_prgenv" ]] && { echo "Unable to identify the current programming environment."; exit 1; }
      if [[ "$curr_prgenv" != "intel" ]]; then
        module swap PrgEnv-$curr_prgenv PrgEnv-intel
      fi
      if [ $build_coupler -eq 1 ] ; then
        module load cray-hdf5
        module load cray-netcdf
        module load cray-mpich
        module load cray-shmem
        module load atp
        export ATP_ENABLED=1
        export FOR_IGNORE_EXCEPTIONS=true
      fi

        # xc40_opts="$xc40_opts prgenv=intel"
        WSZ='-r8 -i8 -align array64byte ' # 64-bit
        # WSZ='-align array64byte ' # 32-bit
        CPPDEFS='-fpp -D_IMPI_=4 -D_Cray_XC30'
        # OPT='-openmp -list -traceback -O1 -init=arrays -init=zero -fp-model precise -assume buffered_stdout,protect_parens -convert big_endian -threads'
        # OPT='-openmp -list -traceback -O3                         -fp-model precise -assume buffered_stdout,protect_parens -convert big_endian -threads -xCORE-AVX2'
      if [ $build_agcm -eq 1 -a $build_agcm_32bit -eq 1 ]; then
        OPT='-openmp -list -traceback -O2 -mp1                                      -assume buffered_stdout,protect_parens -convert big_endian -threads -xCORE-AVX2'
      else
        OPT='-openmp -list -traceback -O3                                           -assume buffered_stdout,protect_parens -convert big_endian -threads -xCORE-AVX2'
      fi
      #DBG='-g'
      DBG='  '
      [ -z "$sbu_FC" ]     && export FC=ftn
      [ -z "$sbu_FFLAGS" ] && export FFLAGS="$CPPDEFS $DBG -Qoption,link,--noinhibit-exec $OPT $WSZ"
      # CCCma default F77_openmp
      # FFLAGS="-r8 -i8 -nbs -WB -g -O2 -traceback -m64 -axCORE-AVX-I -fixed -mp1 -fp-model strict -Bstatic -fpe0 -ftrapuv -openmp"
      ;;

    cce|cray)
      # load required modules
      # curr_prgenv will typically be one of cray, gnu or intel
      curr_prgenv=$(module list 2>&1|grep PrgEnv|sed 's/^.*PrgEnv-//; s/\/.*//')
      [[ -z "$curr_prgenv" ]] && { echo "Unable to identify the current programming environment."; exit 1; }
      if [[ "$curr_prgenv" != "cray" ]]; then
        module swap PrgEnv-$curr_prgenv PrgEnv-cray
      fi
      module load cray-hdf5
      module load cray-netcdf
      module load cray-mpich
      module load cray-shmem
      module load atp
      export ATP_ENABLED=1

      # xc40_opts="$xc40_opts prgenv=cray"
      WSZ='-s real64 -s integer64 -s word_pointer'
      CPPDEFS='-D_IMPI_=4 '
      # OPT=' -O2 -Othread1,fp2,ipa1,vector2,cache2,scalar2 -h flex_mp=conservative -h byteswapio'
      if [ $build_coupler -eq 1 ]; then
        OPT=' -G0 -Othread0 -h flex_mp=conservative -h byteswapio'
      else
        OPT=' -G0 -h flex_mp=conservative -h byteswapio'
      fi
      [ -z "$sbu_FC" ]     && export FC=ftn
      [ -z "$sbu_FFLAGS" ] && export FFLAGS="-eZ -ra $OPT $WSZ $CPPDEFS -O nointerchange"
      ;;
  esac
fi

if [ x$this_mach = "xbanting" -o x$this_mach = "xdaley" ]; then
  # Define fortran compiler and options used on the xc40
  xc40_compiler=${xc40_compiler:=intel}
  case $xc40_compiler in
    intel)
      # load required modules
      # curr_prgenv will typically be one of cray, gnu or intel
      curr_prgenv=$(module list 2>&1|grep PrgEnv|sed 's/^.*PrgEnv-//; s/\/.*//')
      [[ -z "$curr_prgenv" ]] && { echo "Unable to identify the current programming environment."; exit 1; }
      if [[ "$curr_prgenv" != "intel" ]]; then
        module swap PrgEnv-$curr_prgenv PrgEnv-intel
      fi
      if [ $build_coupler -eq 1 ] ; then
        module load craype-x86-skylake
	module load cray-hdf5
	module load cray-netcdf
	module load cray-mpich
	module load cray-shmem
	module load atp
	export ATP_ENABLED=1
	export FOR_IGNORE_EXCEPTIONS=true
      fi
      if [ $build_agcm -eq 1 ] ; then
        # module load cray-netcdf
	module unload cray-libsci # unload libsci which is in conflict with -mkl
      fi

      # These are used for the NEMO build
      # In time these deinitions will be set elsewhere but simply hard code them here for now
      nemo_arch=intel_xc40-cancpl
      [ -z "$nemo_exec" ] && nemo_exec=testing_intel_ccc_cancpl_orca1_lim.exe

      # xc40_opts="$xc40_opts prgenv=intel"
      WSZ='-r8 -i8 -align array64byte ' # 64-bit
#     WSZ='-align array64byte ' # 32-bit
      CPPDEFS='-fpp -D_IMPI_=4 -D_Cray_XC30'
#     OPT='-openmp -list -traceback -O1 -init=arrays -init=zero -fp-model precise -assume buffered_stdout,protect_parens -convert big_endian -threads'
#     OPT='-openmp -list -traceback -O3                         -fp-model precise -assume buffered_stdout,protect_parens -convert big_endian -threads -xCORE-AVX2'
      if [ $build_agcm -eq 1 ]; then
	if [ $build_agcm_32bit -eq 1 ]; then
	  OPT='-openmp -list -traceback -O2 -mp1  -static-intel -assume buffered_stdout,protect_parens -convert big_endian -threads -mkl -axSKYLAKE-AVX512'
	else
	  OPT='-openmp -list -traceback -O3       -static-intel -assume buffered_stdout,protect_parens -convert big_endian -threads -mkl -axSKYLAKE-AVX512'
	fi
      else
	OPT='-openmp -list -traceback -O3       -static-intel -assume buffered_stdout,protect_parens -convert big_endian -threads      -axSKYLAKE-AVX512'
      fi
#     DBG='-g'
      DBG='  '
      [ -z "$sbu_FC" ]     && export FC=ftn
      [ -z "$sbu_FFLAGS" ] && export FFLAGS="$CPPDEFS $DBG -Qoption,link,--noinhibit-exec $OPT $WSZ"
      # CCCma default F77_openmp
      # FFLAGS="-r8 -i8 -nbs -WB -g -O2 -traceback -m64 -axCORE-AVX-I -fixed -mp1 -fp-model strict -Bstatic -fpe0 -ftrapuv -openmp"
      ;;

    cce|cray)
      # load required modules
      # curr_prgenv will typically be one of cray, gnu or intel
      curr_prgenv=$(module list 2>&1|grep PrgEnv|sed 's/^.*PrgEnv-//; s/\/.*//')
      [[ -z "$curr_prgenv" ]] && { echo "Unable to identify the current programming environment."; exit 1; }
      if [[ "$curr_prgenv" != "cray" ]]; then
        module swap PrgEnv-$curr_prgenv PrgEnv-cray
      fi
      module load craype-x86-skylake
      module load cray-hdf5
      module load cray-netcdf
      module load cray-mpich
      module load cray-shmem
      module load atp
      export ATP_ENABLED=1

      # These are used for the NEMO build
      # In time these deinitions will be set elsewhere but simply hard code them here for now
      nemo_arch=cray_xc40-cancpl
      [ -z "$nemo_exec" ] && nemo_exec=testing_cray_ccc_cancpl_orca1_lim.exe

      # xc40_opts="$xc40_opts prgenv=cray"
      WSZ='-s real64 -s integer64 -s word_pointer'
      CPPDEFS='-D_IMPI_=4 '
      # OPT=' -O2 -Othread1,fp2,ipa1,vector2,cache2,scalar2 -h flex_mp=conservative -h byteswapio'
      if [ $build_coupler -eq 1 ]; then
        OPT=' -G0 -Othread0 -h flex_mp=conservative -h byteswapio'
      else
        OPT=' -G0 -h flex_mp=conservative -h byteswapio'
      fi
      [ -z "$sbu_FC" ]     && export FC=ftn
      [ -z "$sbu_FFLAGS" ] && export FFLAGS="-eZ -ra $OPT $WSZ $CPPDEFS -O nointerchange"
      ;;
  esac
fi
#===============================================================================
#=== Check main source directory and store repos and commits for logging
#===============================================================================

# Change back to the root build directory and store repo hashes in build_info file
cd $build_dir

echo "build-exe: Looking for source code in $canesm_root"

# Log the CanESM commit
CANESM_COMMIT=$(cd $canesm_root; git rev-parse HEAD) || bail "$canesm_root is not a repository!" 

# Define a string containing CanESM commit info to incorporate into executable names below
exetag=${CANESM_COMMIT:0:8}

# Define the full pathname of directories containing relevant source repos
AGCMSRC=$canesm_root/CanAM
DIAGSRC=$canesm_root/CanDIAG
CPLRSRC=$canesm_root/CanCPL
NEMOSRC=$canesm_root/CanNEMO
TOOLSSRC=$canesm_root/CCCma_tools

[ -d $AGCMSRC ]  || bail "$AGCMSRC is not a directory"
[ -d $DIAGSRC ]  || bail "$DIAGSRC is not a directory"
[ -d $CPLRSRC ]  || bail "$CPLRSRC is not a directory"
[ -d $NEMOSRC ]  || bail "$NEMOSRC is not a directory"
[ -d $TOOLSSRC ] || bail "$TOOLSSRC is not a directory"

# get commits from sub repos
AGCM_COMMIT=$(cd $AGCMSRC; git rev-parse HEAD) || bail "$AGCMSRC is not a repository!"
DIAG_COMMIT=$(cd $DIAGSRC; git rev-parse HEAD) || bail "$DIAGSRC is not a repository!"
CPLR_COMMIT=$(cd $CPLRSRC; git rev-parse HEAD) || bail "$CPLRSRC is not a repository!"
NEMO_COMMIT=$(cd $NEMOSRC; git rev-parse HEAD) || bail "$NEMOSRC is not a repository!"
TOOLS_COMMIT=$(cd $TOOLSSRC; git rev-parse HEAD) || bail "$TOOLSSRC is not a repository!"

# Log Commit Information
echo "build-exe: ==========================================================="   | tee -a $build_info
echo "build-exe: CANESM_SRC=$canesm_root"                                       | tee -a $build_info
echo "build-exe: CANESM_COMMIT=$CANESM_COMMIT"                                  | tee -a $build_info
echo "build-exe: COUPLER_COMMIT=$CPLR_COMMIT"                                   | tee -a $build_info
echo "build-exe: AGCM_COMMIT=$AGCM_COMMIT"                                      | tee -a $build_info
echo "build-exe: NEMO_COMMIT=$NEMO_COMMIT"                                      | tee -a $build_info
echo "build-exe: TOOLS_COMMIT=$TOOLS_COMMIT"                                    | tee -a $build_info

# Define a prefix to be used as part of the name for the targets
# but only if no value for target_pfx was provided by the user
[ -z "$sbu_target_pf" ] && setTargetPfx
echo "build-exe: Using target_pfx=$target_pfx"                                  | tee -a $build_info
echo "build-exe: ==========================================================="   | tee -a $build_info

#=======================================================================
#=== Build list of source files and targets for the makefile generation
#=======================================================================
# Change to the dir in which the build will actually take place
cd $true_build_dir

# Create a link to the coupler preprocessor include file
[ -s $CPLRSRC/src/drivers/cpl_cppdefs ] && ln -s $CPLRSRC/src/drivers/cpl_cppdefs

# Define/append MAIN and TARGET lists
if [ $build_coupler -eq 1 ]; then
  # Push the name of the coupler driver onto MAINS
  MAINS[${#MAINS[*]}]=cpl_main.F90
  # Set a default value if cancpl_exec was not supplied on the command line
  # Verify that runid, year and mon have values if the user has not supplied a value for cancpl_exec
  [ -z "$cancpl_exec" ] && setTargetPfx
  #cancpl_exec=${cancpl_exec:=${target_pfx:-mc_${runid}_${year}_m${mon}_}}cpl.exe
  cancpl_exec=${cancpl_exec:-mc_${runid}_${year_restart}_m${mon_restart}_cpl.exe}

  TARGETS[${#TARGETS[*]}]=$cancpl_exec
fi

if [ $build_agcm -eq 1 ]; then
  MAINS[${#MAINS[*]}]=${modver:-gcm18}.dk
  # Define the name of the AGCM executable that will be saved below after compilation
  # Set a default value if canam_exec was not supplied on the command line
  # Verify that runid, year and mon have values if the user has not supplied a value for canam_exec
  [ -z "$canam_exec" ] && setTargetPfx
  #canam_exec=${canam_exec:=${target_pfx:-mc_${runid}_${year_restart}_m${mon_restart}_}}ab
  canam_exec=${canam_exec:-mc_${runid}_${year_restart}_m${mon_restart}_ab}

  #echo "canam_exec", $canam_exec
  TARGETS[${#TARGETS[*]}]=$canam_exec
fi
[[ ${#MAINS[*]} == 0 ]] && bail "No main programs were specified"

echo "build-exe: Programs to be compiled: ${MAINS[@]}"
echo "build-exe:                 targets: ${TARGETS[@]}"
echo " "  >> $build_info
echo "Programs to be compiled: ${MAINS[@]}"   >> $build_info
echo "                targets: ${TARGETS[@]}" >> $build_info

# Create a file containing a list of source directories, one directory per line
# This list is passed to cccmf and used to find dependencies for each build target
path_list_file=curr_path_list
rm -f $path_list_file
touch $path_list_file

if [ $build_agcm -eq 1 ]; then
  # Include lsmod dirs when compiling the AGCM
  echo "$AGCMSRC/lsmod/agcm/gcm18" >> $path_list_file
  echo "$AGCMSRC/lsmod/agcm/gcm18/MODULES" >> $path_list_file
  echo "$AGCMSRC/lsmod/agcm/gcm18/MODULES/pla" >> $path_list_file
fi

# Include selected lssub dirs
# comm_dirs=find
declare -a lssub_comm_path_list
declare -a lssub_model_path_list
declare -a lssub_diag_path_list
lssub_comm_path_list=($DIAGSRC/lssub/comm/io
                      $DIAGSRC/lssub/comm/gen
                      $DIAGSRC/lssub/comm/init
                      $DIAGSRC/lssub/comm/nrecipes
                      $DIAGSRC/lssub/comm/imsl
                      $DIAGSRC/lssub/comm/trans
                      $DIAGSRC/lssub/comm/ocean)

lssub_model_path_list=($AGCMSRC/lssub/model/agcm/gcm18/phys
                 $AGCMSRC/lssub/model/agcm/gcm18/ocean
                 $AGCMSRC/lssub/model/agcm/gcm18/io
                 $AGCMSRC/lssub/model/agcm/gcm18/conv
                 $AGCMSRC/lssub/model/agcm/gcm18/ctem
                 $AGCMSRC/lssub/model/agcm/gcm18/mixed_layer_model
                 $AGCMSRC/lssub/model/agcm/gcm18/modl
                 $AGCMSRC/lssub/model/agcm/gcm18/isccp_simulator
                 $AGCMSRC/lssub/model/agcm/gcm18/rad
                 $AGCMSRC/lssub/model/agcm/gcm18/radforce
                 $AGCMSRC/lssub/model/agcm/gcm18/relax
                 $AGCMSRC/lssub/model/agcm/gcm18/river_routing
                 $AGCMSRC/lssub/model/agcm/gcm18/species
                 $AGCMSRC/lssub/model/agcm/gcm18/trans
                 $AGCMSRC/lssub/model/agcm/gcm18/pam
                 $AGCMSRC/lssub/model/agcm/gcm18/mam_rad
                 $AGCMSRC/lssub/model/agcm/gcm18/mam_nonoro_gwd
                 $AGCMSRC/lssub/model/agcm/gcm18/class_v36
                 $AGCMSRC/lssub/model/agcm/gcm18/lakes)

lssub_diag_path_list=($DIAGSRC/lssub/diag/gen
                      $DIAGSRC/lssub/comm/gen/7u
                      $DIAGSRC/lssub/diag/trans)

if [ $build_agcm -eq 1 ]; then
  for dir in ${lssub_comm_path_list[*]}; do
    echo $dir >> $path_list_file
  done
  for dir in ${lssub_model_path_list[*]}; do
    echo $dir >> $path_list_file
  done
  for dir in ${lssub_diag_path_list[*]}; do
    echo $dir >> $path_list_file
  done
fi

# Identify the path to the coupler source code
if [ $build_coupler -eq 1 ]; then
  # If the coupler executable is to be create then include the drivers subdir
  echo "$(readlink -f $CPLRSRC/src/comm 2>/dev/null)"    >> $path_list_file
  echo "$(readlink -f $CPLRSRC/src/drivers 2>/dev/null)" >> $path_list_file
  if [ $build_agcm -eq 0 ]; then
    # Add the lsmod dir only if it was not already added above
    echo "$AGCMSRC/lsmod/agcm/gcm18" >> $path_list_file
    # Add common lssub dirs only if they were not included above
    for dir in ${lssub_comm_path_list[*]}; do
      echo $dir >> $path_list_file
    done
    for dir in ${lssub_model_path_list[*]}; do
      echo $dir >> $path_list_file
    done
  fi
else
  if [ $build_agcm -eq 1 ]; then
    # Include only the common code subdir from the coupler
    echo "$(readlink -f $CPLRSRC/src/comm 2>/dev/null)" >> $path_list_file
  fi
fi

if [[ $verbose > 0 ]]; then
  echo "build-exe: Path list"
  cat $path_list_file
fi

echo " "  >> $build_info
echo "Path list:"   >> $build_info
cat $path_list_file >> $build_info

# Create a list of directories in which *.dk and *.cdk files are found
declare -a dk_path_list
dk_path_list=($AGCMSRC/lsmod/agcm/gcm18
              $AGCMSRC/lsmod/agcm/gcm18/MODULES
              $AGCMSRC/lsmod/agcm/gcm18/MODULES/pla)

pathdk_list_file=curr_pathdk_list
rm -f $pathdk_list_file
touch $pathdk_list_file
for dir in ${dk_path_list[*]}; do
  echo $dir >> $pathdk_list_file
done

#=======================
#=== Create the makefile
#=======================
echo "build-exe: Creating make file in $build_dir"

### TODO ### For now gcmparm is compiled by the intel compiler and is dynamically linked
### TODO ### so we need the intel programming environment to run cccmf, which uses gcmparm
# time ( module swap PrgEnv-cray PrgEnv-intel; env CCRNSRC='' cccmf $up2patch_opt ${MAINS[@]} \
time ( env CCRNSRC='' cccmf $up2patch_opt ${MAINS[@]} \
                          target="${TARGETS[*]}" \
                          model_job=$model_job \
                          make_file=makefile \
                          path_list=$path_list_file \
                          pathdk_list=$pathdk_list_file \
                          cppdefs=$cppdefs_file \
                          cppdefs_diag=$cppdefs_diag \
                          $xc40_opts ) || bail "Problem in cccmf"


if [ $build_agcm -eq 1 -a $build_agcm_32bit -eq 1 ]; then
  # Modify makefile to compile AGCM in 32-bit mode
  sed -i 's/FFLAGS := \(.*\) -r8\(.*\)/FFLAGS := \1\2/g' makefile # remove -r8 flag
  sed -i 's/FFLAGS := \(.*\) -i8\(.*\)/FFLAGS := \1\2/g' makefile # remove -i8 flag
  sed -i '/^FFLAGS := /a FFLAGS_CPL := -r8 -i8' makefile # add FFLAFS_CPL line for -r8 -i8 flags
  sed -i 's/$(FC) -c $(FREE) $(FFLAGS)\(.*\)-o com_cpl.o/$(FC) -c $(FREE) $(FFLAGS) $(FFLAGS_CPL)\1-o com_cpl.o/' makefile # add FFLAGS_CPL to com_cpl.o
  sed -i 's/$(FC) -c $(FREE) $(FFLAGS)\(.*\)-o cpl_agcm.o/$(FC) -c $(FREE) $(FFLAGS) $(FFLAGS_CPL)\1-o cpl_agcm.o/' makefile # add FFLAGS_CPL to cpl_agcm.o
  sed -i 's/$(FC) -c $(FIXED) $(FFLAGS)\(.*\)-o mpi_getcpl2.o/$(FC) -c $(FIXED) $(FFLAGS) $(FFLAGS_CPL)\1-o mpi_getcpl2.o/' makefile # add FFLAGS_CPL to mpi_getcpl2.o
  sed -i 's/$(FC) -c $(FIXED) $(FFLAGS)\(.*\)-o mpi_putggb2.o/$(FC) -c $(FIXED) $(FFLAGS) $(FFLAGS_CPL)\1-o mpi_putggb2.o/' makefile # add FFLAGS_CPL to mpi_putggb2.o
#   sed -i '/^FFLAGS_CPL := /a FFLAGS_PRC := -fp-model precise' makefile # add FFLAFS_PRC line for -fp-model precise flag
#   sed -i 's/$(FC) -c $(FIXED) $(FFLAGS)\(.*\)cond11.f/$(FC) -c $(FIXED) $(FFLAGS) $(FFLAGS_PRC)\1cond11.f/' makefile # add FFLAGS_PRC to cond11.f
#   sed -i 's/$(FC) -c $(FIXED) $(FFLAGS)\(.*\)vrtdf22.f/$(FC) -c $(FIXED) $(FFLAGS) $(FFLAGS_PRC)\1vrtdf22.f/' makefile # add FFLAGS_PRC to vrtdf22.f
fi

#===============================================================================
#=== Run make to build the requested targets
#===============================================================================
if [ $create_makefile_only -ne 1 ]; then
  # Actually build the target
  echo "build-exe: Programs to be built in ${build_dir}:  ${MAINS[@]}"
  make_opts+=" COUPLER_COMMIT_ID=$CPLR_COMMIT"
  make_opts+=" COUPLER_REPO_PATH=$CPLRSRC"
  [[ $verbose > 0 ]] && echo "build-exe: make_opts: $make_opts"
  echo " "  >> $build_info
  echo "make command: make $make_opts "  >> $build_info
  time env modver='' make $make_opts 2>make-err >make-out
  if [ $? -ne 0 ]; then
    # Always keep the build directory when make fails
    keep_build_dir=1
    echo "-------------------------- make stderr -------------------------------"
    cat make-err
    echo "----------------------------------------------------------------------"
    echo "build-exe: **EE** make failed"
  else
    # make succeeds, save the executables
    echo "build-exe: Compile successful for ${MAINS[@]}"
    saved_list=''
    for target in ${TARGETS[*]}; do
      [ -z "$target" ] && continue
      [ -s "$target" ] || continue
      saved_list+=" $target"
      if [ $save_exec -eq 1 ]; then
        # Also save targets on RUNPATH
        # This will create a link in the build dir that points to the executable on RUNPATH
        save $target $target
      fi
    done

    echo "build-exe: Build info file: $build_info"
    echo "build-exe: Build directory remains in $build_dir"
    exit
  fi
fi

# Return to the original dir and clean up
cd $invoke_dir
if [ $keep_build_dir -eq 1 ]; then
  echo "build-exe: Build directory remains in $build_dir"
else
  rm -fr $build_dir
fi
