#!/bin/bash
#------------------------------------------------------------------------------
# =========                 |
# \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\    /   O peration     |
#   \\  /    A nd           | www.openfoam.com
#    \\/     M anipulation  |
#------------------------------------------------------------------------------
#     Copyright (C) 2011-2016 OpenFOAM Foundation
#     Copyright (C) 2017-2020 OpenCFD Ltd.
#------------------------------------------------------------------------------
# License
#     This file is part of OpenFOAM.
#
#     OpenFOAM is free software: you can redistribute it and/or modify it
#     under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 3 of the License, or
#     (at your option) any later version.
#
#     OpenFOAM 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 GNU General Public License
#     for more details.
#
#     You should have received a copy of the GNU General Public License
#     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
#
# Script
#     wmake
#
# Description
#     General, easy to use make system for multi-platform development
#     with support for local and network parallel compilation.
#
#     This updated wmake supports out-of-tree object and dependency files to
#     avoid the clutter which accumulates in the source-tree with the original
#     wmake system.  Now when building the OpenFOAM package both the object and
#     dependency files are now located in a tree with the same structure as the
#     source tree but in the platforms/$WM_OPTIONS sub-directory of
#     $WM_PROJECT_DIR.
#
#     When building user libraries and applications which are not located in the
#     OpenFOAM source-tree the object and dependency files are located in a tree
#     with the same structure as the source tree but in the Make/$WM_OPTIONS
#     sub-directory.
#
#     The disadvantage of the out-of-tree compilation is that the dependency
#     files are harder to find but are sometimes useful to study which header
#     files are included.  For those who need access to the dependency files the
#     new wdep script is provided to locate them.  See the wdep script header or
#     run:
#         wdep -h
#
# See also
#     wmakeLnInclude, wmakeLnIncludeAll, wmakeCollect, wdep, wrmdep, wrmo,
#     wclean, wcleanPlatform, wcleanLnIncludeAll
#
#------------------------------------------------------------------------------
Script="${0##*/}"           # Use 'Script' for error messages in wmakeFunctions
. "${0%/*}/scripts/wmakeFunctions"              # Source wmake functions

# Print help to stdout so that it can be captured
printHelp() {
    cat<<HELP_HEAD

Usage: $Script [OPTION] [dir]
       $Script [OPTION] target [dir [MakeDir]]
options:
  -s | -silent      Quiet mode (does not echo commands)
  -a | -all         wmake all sub-directories, running Allwmake if present
  -q | -queue       wmakeCollect sub-directories, running Allwmake if present
  -k | -keep-going  Keep going even when errors occur (-non-stop)
  -j | -jN | -j N   Compile using all or specified N cores/hyperthreads
  -update           Update lnInclude dirs, dep files, remove deprecated files/dirs
HELP_HEAD

if [ -n "$1" ]
then
cat<<HELP_FULL
  -debug            Define c++DBUG='-DFULLDEBUG -g -O0' as override
  -no-scheduler     Disable scheduled parallel compilation
  -show-api         Print api value
  -show-ext-so      Print shared library extension (with '.' separator)
  -show-c           Print C compiler value
  -show-cflags      Print C compiler flags
  -show-cxx         Print C++ compiler value
  -show-cxxflags    Print C++ compiler flags
  -show-cflags-arch     The C compiler arch flag (eg, -m64 etc)
  -show-cxxflags-arch   The C++ compiler arch flag (eg, -m64 etc)
  -show-compile-c   Same as '-show-c -show-cflags'
  -show-compile-cxx Same as '-show-cxx -show-cxxflags'
  -show-path-c      Print path to C compiler
  -show-path-cxx    Print path to C++ compiler
HELP_FULL
fi

cat<<HELP_TAIL_COMMON
  -pwd              Print root directory containing a Make/ directory
  -version | --version  Print version (same as -show-api)
  -h | -help        Display short help and exit
  -help-full        Display full help and exit

A general, easy-to-use make system for multi-platform development
with support for local and network parallel compilation.

HELP_TAIL_COMMON

if [ -n "$1" ]
then
cat<<HELP_TAIL_FULL
Makefile targets:   platforms/linux64GccDPInt32Opt/.../fvMesh.o (for example)
Special targets:
  all | queue       Same as -all | -queue options
  exe               Compile statically linked executable
  lib               Compile statically linked archive lib (.a)
  libo              Compile statically linked lib (.o)
  libso             Compile dynamically linked lib (.so)
  dep               Compile lnInclude and dependencies only
  updatedep         Compile dependencies only (in case of broken dependencies)
  objects           Compile but not link

Environment
  FOAM_EXTRA_CXXFLAGS

HELP_TAIL_FULL
else
cat<<HELP_TAIL_BRIEF
Some special targets (see -help-full for details):
  all | queue       Same as -all | -queue options
  exe               Executables
  lib libo libso    Libraries

HELP_TAIL_BRIEF
fi
    exit 0 # clean exit
}


# Report error and exit
die()
{
    exec 1>&2
    echo
    echo "Error encountered:"
    while [ "$#" -ge 1 ]; do echo "    $1"; shift; done
    echo
    echo "See '${0##*/} -help' for usage"
    echo
    exit 1
}

# Default make is the "make" in the path
make="make"


#------------------------------------------------------------------------------

# Set nCores to the number of cores on the machine
nCores=0
allCores()
{
    nCores=$(getconf _NPROCESSORS_ONLN 2>/dev/null) || nCores=1
    : ${nCores:=1}
}


#------------------------------------------------------------------------------
# Parse arguments and options
#------------------------------------------------------------------------------

# Default to compiling the local target only
unset all update optDebug optShow optPrintRootDir

while [ "$#" -gt 0 ]
do
    case "$1" in
        -help-f*)   # Full help
            printHelp -full
            ;;
        -h | -help*) # Short help
            printHelp
            ;;
        -s | -silent)
            export WM_QUIET=true
            ;;
        -debug)
            optDebug="-DFULLDEBUG -g -O0"
            ;;
        -show-api | -show-ext-so | \
        -show-compile-c   | -show-c   | -show-cflags   | -show-cflags-arch | \
        -show-compile-cxx | -show-cxx | -show-cxxflags | -show-cxxflags-arch )
            $make -f $WM_DIR/makefiles/info "${1#-show-}"
            optShow=true
            ;;
        -show-path-c | -show-path-cxx )
            command -v $($make -f $WM_DIR/makefiles/info "${1#-show-path-}")
            optShow=true
            ;;
        -a | -all | all)
            all=all
            ;;
        -q | -queue | queue)
            all=queue
            ;;
        # Parallel compilation on all cores (or specified number of cores)
        -j)
            nCores=0
            test $# -ge 2 && expr $2 + 1 > /dev/null 2>&1 \
                && shift && nCores=$1

            [ "$nCores" = 0 ] && allCores
            export WM_NCOMPPROCS=$nCores
            echo "Compiling enabled on $WM_NCOMPPROCS cores" 1>&2
            ;;
        # Parallel compilation on specified number of cores
        -j[1-9]*)
            export WM_NCOMPPROCS=${1#-j}
            echo "Compiling enabled on $WM_NCOMPPROCS cores" 1>&2
            ;;
        # Keep going, ignoring errors
        -k | -keep-going | -non-stop)
            export WM_CONTINUE_ON_ERROR=true
            ;;
        # Disable scheduled parallel compilation
        -no-scheduler)
            unset WM_SCHEDULER
            ;;
        # Print root directory containing a Make/ directory and exit
        -pwd)
            optPrintRootDir=true
            ;;
        # Meant to be used following a pull, this will:
        # - remove dep files that depend on deleted files;
        # - remove stale dep files;
        # - update lnInclude directories;
        # - remove empty directories, along with deprecated object directories
        #   and respective binaries.
        -update)
            update=true
            : ${all:=all}  # implies 'all', unless previous set to 'queue' etc.
            ;;
        -version | --version)
            $make -f $WM_DIR/makefiles/info api
            optShow=true
            break;
            ;;
        --)
            shift
            break
            ;;
        -*)
            die "unknown option: '$1'"
            ;;
        *)
            break
            ;;
    esac
    shift
done

if [ "$optShow" = true ]
then
    exit 0
fi


#------------------------------------------------------------------------------
# Check environment variables
#------------------------------------------------------------------------------

checkEnv

# When compiling anything but a standalone exe WM_PROJECT and WM_PROJECT_DIR
# must be set
[ "$1" = exe -o \( "$WM_PROJECT" -a "$WM_PROJECT_DIR" \) ] || {
    exec 1>&2
    echo "$Script error:"
    echo "    environment variable \$WM_PROJECT or \$WM_PROJECT_DIR not set"
    echo "    while building project library"
    exit 1
}


#------------------------------------------------------------------------------
# Setup parallel compilation
#------------------------------------------------------------------------------

if [ -n "$WM_NCOMPPROCS" ]
then
    parOpt="-j $WM_NCOMPPROCS"

    if [ "$WM_NCOMPPROCS" -gt 1 -a -z "$MAKEFLAGS" ]
    then
        make="$make --no-print-directory $parOpt"
    fi
fi


#------------------------------------------------------------------------------
# Check arguments and change to the directory in which to run wmake.
# The variables 'targetType' and 'MakeDir' are considered global
#------------------------------------------------------------------------------

unset targetType
MakeDir=Make

unset dir

if [ -n "$optPrintRootDir" ]
then
    if [ $# -ge 1 ]
    then
        if [ -d "$1" ]
        then
            dir="${1%/}"
        elif [ -f "$1" ]
        then
            dir="${1%/*}"
            : "${dir:=.}"
            if [ "$dir" = "$1" ]
            then
                dir="."
            fi
        else
            echo "$Script error: not a file or directory" 1>&2
            exit 1
        fi

        cd "$dir" 2>/dev/null || {
            echo "$Script error: could not change to directory '$dir'" 1>&2
            exit 1
        }
    fi

    # Locate target with Make/ directory
    if dir="$(findTarget .)"
    then
        (cd "$dir" && pwd -L)
        exit 0
    else
        exit 2
    fi

else

    if [ $# -ge 1 ]
    then
        if [ -d "$1" ]
        then
            dir="${1%/}"
        elif [ -f "$1" ]
        then
            dir="${1%/*}"
            : "${dir:=.}"
            if [ "$dir" = "$1" ]
            then
                dir="."
            fi
        else
            targetType="$1"
        fi

        # Specified directory name:
        [ $# -ge 2 ] && dir="${2%/}"

        # Specified alternative name for the Make sub-directory:
        [ $# -ge 3 ] && MakeDir="${3%/}"

        if [ -n "$dir" ]
        then
            cd $dir 2>/dev/null || {
                echo "$Script error: could not change to directory '$dir'" 1>&2
                exit 1
            }
        elif [ -f "$MakeDir/files" ]
        then
            dir="(${PWD##*/})"  # Implicit directory information
        fi

        # Print command
        echo "$Script $targetType${targetType:+ }$dir"
        unset dir
    fi
fi

unset dir


#------------------------------------------------------------------------------
# Recurse the source tree to update all
#------------------------------------------------------------------------------

if [ "$update" = true ]
then
    wrmdep -update
    wrmdep -old
    wmakeLnIncludeAll -update $parOpt
    wclean empty
    export WM_UPDATE_DEPENDENCIES=yes
fi


#------------------------------------------------------------------------------
# Recurse the source tree to compile "all" targets
#------------------------------------------------------------------------------

if [ "$all" = all ]
then
    if [ -e Allwmake ]
    then
        ./Allwmake -fromWmake ${optDebug:+-debug} $targetType
        exit $?
    fi

    # Find all the sub-directories containing a 'Make' directory
    # (xargs is just used to flatten the list)
    FOAM_APPS=$(
        for d in *
        do [ -d "$d" -a "$d" != Optional -a "$d" != Make ] && echo "$d"
        done | xargs)

    if [ -n "$FOAM_APPS" ]
    then
        # Compile all applications in sub-directories
        $make ${WM_CONTINUE_ON_ERROR:+-k} \
              -f $WM_DIR/makefiles/apps \
              ${optDebug:+c++DBUG="$optDebug"} \
              TARGET="$targetType" FOAM_APPS="$FOAM_APPS"
        makeExitCode=$?
    else
        makeExitCode=0 # For fall-through
    fi

    # Exit if current directory does not contains a 'Make' directory or
    # an error was previously encountered
    if [ ! -d "$MakeDir" -o $makeExitCode -ne 0 ]
    then
        exit $makeExitCode
    fi
fi


#------------------------------------------------------------------------------
# Recurse the source tree to compile "all" targets using wmakeCollect
#------------------------------------------------------------------------------

if [ "$all" = queue ]
then
    [ "$update" = true ] || wmakeLnIncludeAll $parOpt

    (
        export WM_COLLECT_DIR="$WM_PROJECT_DIR/build/${WM_OPTIONS}/${PWD////_}"
        export WM_SCHEDULER="$WM_DIR/wmakeCollect"
        trap '$WM_SCHEDULER -kill' TERM INT
        "$WM_SCHEDULER" -clean \
     && wmake -all objects   \
     && "$WM_SCHEDULER"
    ) && wmake -all ${optDebug:+-debug}
    exit $?
fi


#------------------------------------------------------------------------------
# Search up the directory tree for the Make sub-directory,
# check the existence of the 'files' file and build there if present
#------------------------------------------------------------------------------

cdSource


#------------------------------------------------------------------------------
# Transform options
#------------------------------------------------------------------------------

# Transform no option to "libso" if that looks appropriate or remove it
# so that the call to make builds the application
if [ -z "$targetType" ]
then
    if grep -qe '^ *LIB *=' "$MakeDir/files" 2>/dev/null
    then
        targetType=libso
    fi
elif grep -qe '^ *EXE *=' "$MakeDir/files" 2>/dev/null
then
    # Application. Remove any nonsense targetType
    case "$targetType" in
    lib*)
        unset targetType
        ;;
    esac
fi


#------------------------------------------------------------------------------
# Spawn a sub-shell and unset MAKEFLAGS in that sub-shell to avoid
# files and options being built in parallel
#------------------------------------------------------------------------------

objectsDir=$MakeDir/$WM_OPTIONS
case "$PWD" in
("$WM_PROJECT_DIR"/*)
    buildPath=$WM_PROJECT_DIR/build/${WM_OPTIONS}
    objectsDir=$buildPath$(echo $PWD | sed s%$WM_PROJECT_DIR%% )
    ;;
esac

(
    unset MAKEFLAGS
    mkdir -p $objectsDir

    # Pre-build the $WM_OPTIONS/options file
    # which is included when building the $WM_OPTIONS/files file
    $make -s -f $WM_DIR/makefiles/files \
        MAKE_DIR=$MakeDir OBJECTS_DIR=$objectsDir $objectsDir/options

    $make -s -f $WM_DIR/makefiles/files \
        MAKE_DIR=$MakeDir OBJECTS_DIR=$objectsDir
)


#------------------------------------------------------------------------------
# Check the $objectsDir/sourceFiles file was created successfully
#------------------------------------------------------------------------------

[ -r $objectsDir/sourceFiles ] || {
    echo "$Script error: file '$objectsDir/sourceFiles'" \
         "could not be created in $PWD" 1>&2
    exit 1
}


#------------------------------------------------------------------------------
# Make the dependency files
#------------------------------------------------------------------------------

# For libraries create lnInclude, but only if 'LIB' is declared in 'Make/files'
case "$targetType" in
(lib | libo | libso | dep)
    if grep -qe '^ *LIB *=' "$MakeDir/files" 2>/dev/null
    then
        $make -s -f $WM_DIR/makefiles/general \
            MAKE_DIR=$MakeDir OBJECTS_DIR=$objectsDir lnInclude
    fi
    ;;
esac


#------------------------------------------------------------------------------
# When WM_UPDATE_DEPENDENCIES is set, use forced dependency files update
#------------------------------------------------------------------------------

if [ -n "$WM_UPDATE_DEPENDENCIES" ]
then

    $make -f $WM_DIR/makefiles/general \
        MAKE_DIR=$MakeDir OBJECTS_DIR=$objectsDir updatedep
    makeExitCode=$?

    [ $makeExitCode -eq 0 ] || exit $makeExitCode
fi


#------------------------------------------------------------------------------
# Make the dependency files or object files and link
#------------------------------------------------------------------------------

exec $make -f $WM_DIR/makefiles/general \
     ${optDebug:+c++DBUG="$optDebug"} \
     MAKE_DIR=$MakeDir OBJECTS_DIR=$objectsDir $targetType

exit 0 # clean exit

#------------------------------------------------------------------------------
