#!/bin/bash
##############################################################################
# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://github.com/llnl/spack
# Please also see the LICENSE file for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License (as
# published by the Free Software Foundation) version 2.1, February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
#
# Spack compiler wrapper script.
#
# Compiler commands go through this compiler wrapper in Spack builds.
# The compiler wrapper is a thin layer around the standard compilers.
# It enables several key pieces of functionality:
#
# 1. It allows Spack to swap compilers into and out of builds easily.
# 2. It adds several options to the compile line so that spack
#    packages can find their dependencies at build time and run time:
#      -I           arguments for dependency /include directories.
#      -L           arguments for dependency /lib directories.
#      -Wl,-rpath   arguments for dependency /lib directories.
#

# This is an array of environment variables that need to be set before
# the script runs. They are set by routines in spack.build_environment
# as part of spack.package.Package.do_install().
parameters=(
    SPACK_PREFIX
    SPACK_ENV_PATH
    SPACK_DEBUG_LOG_DIR
    SPACK_COMPILER_SPEC
    SPACK_CC_RPATH_ARG
    SPACK_CXX_RPATH_ARG
    SPACK_F77_RPATH_ARG
    SPACK_FC_RPATH_ARG
    SPACK_SHORT_SPEC
)

# The compiler input variables are checked for sanity later:
#   SPACK_CC, SPACK_CXX, SPACK_F77, SPACK_FC
# Debug flag is optional; set to "TRUE" for debug logging:
#   SPACK_DEBUG
# Test command is used to unit test the compiler script.
#   SPACK_TEST_COMMAND
# Dependencies can be empty for pkgs with no deps:
#   SPACK_DEPENDENCIES

# die()
# Prints a message and exits with error 1.
function die {
    echo "$@"
    exit 1
}

for param in ${parameters[@]}; do
    if [[ -z ${!param} ]]; then
        die "Spack compiler must be run from Spack! Input '$param' is missing."
    fi
done

# Figure out the type of compiler, the language, and the mode so that
# the compiler script knows what to do.
#
# Possible languages are C, C++, Fortran 77, and Fortran 90.
# 'command' is set based on the input command to $SPACK_[CC|CXX|F77|F90]
#
# 'mode' is set to one of:
#    vcheck  version check
#    cpp     preprocess
#    cc      compile
#    as      assemble
#    ld      link
#    ccld    compile & link

command=$(basename "$0")
comp="CC"
case "$command" in
    cpp)
        mode=cpp
        ;;
    cc|c89|c99|gcc|clang|icc|pgcc|xlc)
        command="$SPACK_CC"
        language="C"
        comp="CC"
        ;;
    c++|CC|g++|clang++|icpc|pgc++|xlc++)
        command="$SPACK_CXX"
        language="C++"
        comp="CXX"
        ;;
    f90|fc|f95|gfortran|ifort|pgfortran|xlf90|nagfor)
        command="$SPACK_FC"
        language="Fortran 90"
        comp="FC"
        ;;
    f77|gfortran|ifort|pgfortran|xlf|nagfor)
        command="$SPACK_F77"
        language="Fortran 77"
        comp="F77"
        ;;
    ld)
        mode=ld
        ;;
    *)
        die "Unkown compiler: $command"
        ;;
esac

# If any of the arguments below are present, then the mode is vcheck.
# In vcheck mode, nothing is added in terms of extra search paths or
# libraries.
if [[ -z $mode ]]; then
    for arg in "$@"; do
        if [[ $arg == -v || $arg == -V || $arg == --version || $arg == -dumpversion ]]; then
            mode=vcheck
            break
    fi
    done
fi

# Finish setting up the mode.
if [[ -z $mode ]]; then
    mode=ccld
    for arg in "$@"; do
        if [[ $arg == -E ]]; then
            mode=cpp
            break
        elif [[ $arg == -S ]]; then
            mode=as
            break
        elif [[ $arg == -c ]]; then
            mode=cc
            break
        fi
    done
fi

# Set up rpath variable according to language.
eval rpath=\$SPACK_${comp}_RPATH_ARG

# Dump the version and exit if we're in testing mode.
if [[ $SPACK_TEST_COMMAND == dump-mode ]]; then
    echo "$mode"
    exit
fi

# Check that at least one of the real commands was actually selected,
# otherwise we don't know what to execute.
if [[ -z $command ]]; then
    die "ERROR: Compiler '$SPACK_COMPILER_SPEC' does not support compiling $language programs."
fi

if [[ $mode == vcheck ]]; then
    exec ${command} "$@"
fi

# Darwin's linker has a -r argument that merges object files together.
# It doesn't work with -rpath.
# This variable controls whether they are added.
add_rpaths=true
if [[ $mode == ld && "$SPACK_SHORT_SPEC" =~ "darwin" ]]; then
    for arg in "$@"; do
        if [[ $arg == -r ]]; then
            add_rpaths=false
            break
        fi
    done
fi

# Save original command for debug logging
input_command="$@"
args=("$@")

# Read spack dependencies from the path environment variable
IFS=':' read -ra deps <<< "$SPACK_DEPENDENCIES"
for dep in "${deps[@]}"; do
    # Prepend include directories
    if [[ -d $dep/include ]]; then
        if [[ $mode == cpp || $mode == cc || $mode == as || $mode == ccld ]]; then
            args=("-I$dep/include" "${args[@]}")
        fi
    fi

    # Prepend lib and RPATH directories
    if [[ -d $dep/lib ]]; then
        if [[ $mode == ccld ]]; then
            $add_rpaths && args=("$rpath$dep/lib" "${args[@]}")
            args=("-L$dep/lib" "${args[@]}")
        elif [[ $mode == ld ]]; then
            $add_rpaths && args=("-rpath" "$dep/lib" "${args[@]}")
            args=("-L$dep/lib" "${args[@]}")
        fi
    fi

    # Prepend lib64 and RPATH directories
    if [[ -d $dep/lib64 ]]; then
        if [[ $mode == ccld ]]; then
            $add_rpaths && args=("$rpath$dep/lib64" "${args[@]}")
            args=("-L$dep/lib64" "${args[@]}")
        elif [[ $mode == ld ]]; then
            $add_rpaths && args=("-rpath" "$dep/lib64" "${args[@]}")
            args=("-L$dep/lib64" "${args[@]}")
        fi
    fi
done

# Include all -L's and prefix/whatever dirs in rpath
if [[ $mode == ccld ]]; then
    $add_rpaths && args=("$rpath$SPACK_PREFIX/lib64" "${args[@]}")
    $add_rpaths && args=("$rpath$SPACK_PREFIX/lib"   "${args[@]}")
elif [[ $mode == ld ]]; then
    $add_rpaths && args=("-rpath" "$SPACK_PREFIX/lib64" "${args[@]}")
    $add_rpaths && args=("-rpath" "$SPACK_PREFIX/lib"   "${args[@]}")
fi

#
# Unset pesky environment variables that could affect build sanity.
#
unset LD_LIBRARY_PATH
unset LD_RUN_PATH
unset DYLD_LIBRARY_PATH

#
# Filter '.' and Spack environment directories out of PATH so that
# this script doesn't just call itself
#
IFS=':' read -ra env_path <<< "$PATH"
IFS=':' read -ra spack_env_dirs <<< "$SPACK_ENV_PATH"
spack_env_dirs+=("" ".")
PATH=""
for dir in "${env_path[@]}"; do
    addpath=true
    for env_dir in "${spack_env_dirs[@]}"; do
        if [[ $dir == $env_dir ]]; then
            addpath=false
            break
        fi
    done
    if $addpath; then
        PATH="${PATH:+$PATH:}$dir"
    fi
done
export PATH

full_command=("$command" "${args[@]}")

# In test command mode, write out full command for Spack tests.
if [[ $SPACK_TEST_COMMAND == dump-args ]]; then
    echo "${full_command[@]}"
    exit
elif [[ -n $SPACK_TEST_COMMAND ]]; then
    die "ERROR: Unknown test command"
fi

#
# Write the input and output commands to debug logs if it's asked for.
#
if [[ $SPACK_DEBUG == TRUE ]]; then
    input_log="$SPACK_DEBUG_LOG_DIR/spack-cc-$SPACK_SHORT_SPEC.in.log"
    output_log="$SPACK_DEBUG_LOG_DIR/spack-cc-$SPACK_SHORT_SPEC.out.log"
    echo "[$mode] $command $input_command" >> $input_log
    echo "[$mode] ${full_command[@]}" >> $output_log
fi

exec "${full_command[@]}"
