#!/bin/bash
# SCRIPT_PURPOSE: Example framework for default build script.
# DUE_VERSION_COMPATIBILITY_TRACKING=1.0.0

# Copyright 2019,2020 Cumulus Networks, Inc.  All rights reserved.
#
#  SPDX-License-Identifier:     MIT

# Have last command in a pipe to fail return error code.
# This prevents use of tee from hiding fails.
set -o pipefail

# Set top level directory to be where we are now
if [ "$gTOP_DIR" = "" ];then
    gTOP_DIR=$(pwd)
fi

# if command line args for later reference
INVOKED_WITH="$*"

# Hold any build errors. They can be masked by git reset
RETURN_CODE="0"

# if this is set as the first argument, enable debug trace
if [ "$1" = "--script-debug" ];then
    set -x
    echo "$0 Enabling --script-debug "
fi

# Somewhat formatted status messages
function fxnPP()
{
    echo "== $*"
}

function fxnWARN()
{
    echo ""
    echo "## Warning:  $*"
    echo ""
}
# A universal error checking function. Invoke as:
# fxnEC <command line> || exit 1
# Example:  fxnEC cp ./foo /home/bar || exit 1
function fxnEC ()
{

    # actually run the command
    "$@"

    # save the status so it doesn't get overwritten
    status=$?
    # Print calling chain (BASH_SOURCE) and lines called from (BASH_LINENO) for better debug
    if [ $status -ne 0 ];then
        #echo "ERROR [ $status ] in ${BASH_SOURCE[1]##*/}, line #${BASH_LINENO[0]}, calls [ ${BASH_LINENO[*]} ] command: \"$*\"" 1>&2
        echo "ERROR [ $status ] in $(caller 0), calls [ ${BASH_LINENO[*]} ] command: \"$*\"" 1>&2
    fi

    return $status
}

# Standardized error messaging
# Line numbers are more of a suggestion than a rule
function fxnERR
{
    # Print script name, and line original macro was on.
    printf "ERROR at $(caller 0)  :  %s\\n" "$1"
    echo ""
}

# Print messages with an offset for improved visibility.
# MSG_SPACER can be used as needed for
MSG_SPACER=" ---- "
function fxnMSG ()
{
    echo "$MSG_SPACER"
    if [ "$1" = "" ];then
        return 0
    fi
    echo "${MSG_SPACER}$1 "
    echo "$MSG_SPACER"

}

function fxnHelp()
{
    echo "Usage  : $(basename "$0"): [options] [--cbuild | --default | --build-command <args> ]"
    echo "  Script to support building for the container's target."
    echo ""
    echo "  Build targets:"
    echo "   -c|--cbuild <args>          Default container build REPLACE_THIS build instructions"
	echo "                               <args> is a list of additional build commands. Must be last argument."
	echo "      --default                Try to build with default settings."
    echo "      --build-command <args>   Do environment prep and run <args>. Must be last argument on the line."
    echo "      --help-build-targets     More detailed description of the above options."	
    echo ""
    echo "  Build options:"
    echo "   -j|--jobs <#>               Number of parallel builds to use. Defaults to the number of CPU cores."
    echo "   --use-directory <dir>       cd to <dir> before trying to build."
    echo "   --prebuild-script <scr>     Run script at container path <scr> before starting build. Pass 'NONE' to ignore."
    echo "   --script-debug              Enable -x if passed as first argument."
    echo ""
    echo ""
    echo "  More information:"
    echo "   --quiet                    Suppress output."
    echo "   --verbose                  More info."
    echo "   --help                     This message"
    echo "   --help-examples            Print possible configurations."
    echo "   --version                  Version of this script."
    echo ""
}

#
# A more detailed breakdown on what exactly gets run with which option.
#
function fxnHelpBuildTargets()
{
    echo ""
    echo "duebuild use examples for specifying how to build REPLACE_WITH_TARGET"
    echo ""
	echo "In all these examples, duebuild will:"
	echo " - REPLACE WITH ANY PRECONFIGURATION"
	echo " - And then apply the build command."
	echo ""
	echo " Examples"
    echo "  DUE command:   due --build"
    echo "  duebuild runs: duebuild --default"
    echo "  Build command: REPLACE"
    echo ""
    echo "  DUE command:   due --build --cbuild"
    echo "  duebuild runs: duebuild --cbuild"
    echo "  Build command: REPLACE"
    echo ""
    echo "  DUE command:   due --build --cbuild REPLACE WITH EXAMPLE ARGUMENTS"
    echo "  duebuild runs: duebuild --cbuild REPLACE WITH EXAMPLE ARGUMENTS"
    echo "  Build command: REPLACE"
    echo ""
    echo "  DUE command:   due --build --build-command make all"
    echo "  duebuild runs: duebuild --build-command make all"
    echo "  Build command: make all"
    echo ""
	
}	
# Demonstrate cases of script usage
function fxnHelpExamples()
{
    echo ""
    echo " Examples:"
    echo ""
    echo "  Build."
    echo "   $0 --cbuild "
	echo ""
	echo "  Build default - a simple/standard build case."
    echo "   $0 --cbuild --default"
	echo "  Pass additional arguments to build."
	echo "   $0 --cbuild  REPLACE_THIS build example"
    echo ""
}

# Set an exit trap to log completion.
function fxnExit()
{

    local returnCode="$?"

    if [ "$returnCode" = "0" ];then
        echo "Done - $0 [ $INVOKED_WITH ]"
    else
        echo "ERROR - $0 [ $INVOKED_WITH ] failed with return code [ $returnCode ]"
    fi

    return $returnCode
}
trap 'fxnExit $RETURN_CODE' EXIT

#
# Include script libraries for consistency, fxnPP, fxnEC, etc
#

# Clearly print what was passed, and any variables set.
# This makes debugging after the fact way easier
function fxnPrintConfig()
{
    echo " ______________________________________________________________________"
    echo "|"
    echo "| $0"
    echo "| Invoked with:      $INVOKED_WITH"
    if [ "$DO_DEFAULT" != "" ];then
        echo "| Building with default settings"
    fi
    if [ "$RAW_BUILD_COMMAND" != "" ];then
        echo "| build command:     $RAW_BUILD_COMMAND"
    else
        echo "| build command:      REPLACE"
    fi	
    echo "|"
    echo "| Build dir            [ $(pwd) ]"
    echo "| Build attempts       [ $BUILD_ATTEMPTS ]"
    if [ "$PREBUILD_SCRIPT" != "" ];then
        echo "| Pre build script      [ $PREBUILD_SCRIPT ]"
    fi
    if [ "$DO_BUILD" = "TRUE" ];then
        echo "| Build jobs           [ $BUILD_JOBS ]"
        echo "| Build start at       [ $(date) ]"
    fi
    echo "|_____________________________________________________________________"
}




# Function to build whatever the target is
function fxnDoBuild()
{

    while [ $(( BUILD_ATTEMPTS > 0 )) = 1 ]; do
        fxnMSG "Building in [ $BUILD_TARGET_DIR_NAME ] with [ $BUILD_ARGS ].  Attempt [ $BUILD_ATTEMPTS ]. "

        BUILD_ATTEMPTS=$(( BUILD_ATTEMPTS - 1 ))
        echo ""

        # And BUILD_ARGS was set

		if [ "$RAW_BUILD_COMMAND" != "" ];then
            failMsg="$RAW_BUILD_COMMAND failed with error "
            fxnMSG "Building in [ $(pwd) ] with [ $RAW_BUILD_COMMAND ].  Attempt [ $BUILD_ATTEMPTS ]. "
			# Run a non-default build command after setup
            echo "bash -c  $RAW_BUILD_COMMAND"
            bash -c " $RAW_BUILD_COMMAND"
        else
            failMsg="dpkg-buildpackage $BUILD_ARGS failed with error "
            fxnMSG "Building in [ $(pwd) ] with [ $BUILD_ARGS ].  Attempt [ $BUILD_ATTEMPTS ]. "
            # And ADDITIONAL_BUILD_ARGS was set
            echo "bash -c  'default build command goes here'"
			#  bash -c " default build command "
        fi

        bash -c " echo 'Build command goes here in duebuild script.'"
        result="$?"

        if [ "$result" != "0" ];then

            case $result in

                * )
                    # Retry until the tries run out.
                    if [ "$BUILD_ATTEMPTS" = "0" ];then
                        fxnERR "Build $BUILD_ARGS failed with [ $result ]"
                        exit $result
                    fi
            esac
        fi

    done
    echo ""


    fxnMSG "Built [ $BUILD_TARGET_DIR_NAME ] - list follows:"

    echo ""
}


#
# set any default variables
#
# provide a version for use in upgrades
SCRIPT_VERSION="1.0"

# Default to one build attempt
BUILD_ATTEMPTS="1"

# If nproc found, default to ALL THE CORES!
# Else try 4 cores.
BUILD_JOBS=$(nproc 2>/dev/null || echo "4" )

BUILD_TARGET_DIR_NAME="REPLACE_THIS build dir"
##################################################
#                                                #
# MAIN  - script processing starts here          #
#                                                #
##################################################



if [ "$#" = "0" ];then
    # Require an argument for action.
    # Always trigger help messages on no action.
    fxnHelp
    exit 0
fi

# Track this. --jobs and -j* by themselves don't count as a non-default
#  build command.
TOTAL_COMMAND_LINE_ARGS="$#"

#
# Gather arguments and set action flags for processing after
# all parsing is done. The only functions that should get called
# from here are ones that take no arguments.
while [[ $# -gt 0 ]]
do
    term="$1"

    case $term in

        --script-debug )
            # Catch the debug flag here
            echo "[ $0 ] Script debug is ON"
            ;;

        --use-directory )
            # as package builds put the build products in the directory above
            # the source, the build may have been started a level above and
            # will have to go into that directory.
            BUILD_DIR="$2"
            shift
            ;;

		# Take --build as a synonym for --cbuild, although --cbuild is clearer.
        -c | --cbuild | --build  )
            # Default to build everything in container context.
            # skip over --cbuild	
            DO_BUILD="TRUE"
			if [ "$2" != "" ];then
				shift
				# The rest of the arguments passed should be given verbatim to
				# whatever the build command is.
				ADDITIONAL_BUILD_ARGS="$*"
			fi
            ;;

        --build-command )
            # Default to build everything in container context
            DO_BUILD="TRUE"
            if [ "$2" != "" ];then
                # More arguments?
                # skip over --build-command
                shift
                # The rest of the arguments passed should be given verbatim to
                # whatever the build command is.
                RAW_BUILD_COMMAND="$*"
            fi
            break
            ;;
		
		--default )
			DO_DEFAULT="TRUE"
			# --default is an exported hook that can be be called by DUE 
			# when this container is run. The intent is to do a very 
			# basic build operation to demonstrate functionality
			# and hopefully cover common cases.
			# For Debian package build, the --cbuild option conveniently 
			# does all this, so use it.
			DO_BUILD="TRUE"
			;;

        -j* )
            # number of build job threads
            BUILD_JOBS="${term#-j}"
            # If only jobs were specified, do default build
            if [ "$TOTAL_COMMAND_LINE_ARGS" = 1 ];then
                DO_DEFAULT="TRUE"
                DO_BUILD="TRUE"
            fi
            ;;

        --jobs )
            # number of build job threads
            BUILD_JOBS="$2"
            if [ "$2" = "" ];then
                fxnERR "--jobs requires a #"
                exit 1
            fi
            # If only jobs were specified, do default build
            if [ "$TOTAL_COMMAND_LINE_ARGS" = 2 ];then
                DO_DEFAULT="TRUE"
                DO_BUILD="TRUE"
            fi
            shift
            ;;

		
        --prebuild-script )
            # Run this before starting package build. Probably contains commands
            # to generate ./debian/* files
            # Allow the option of it just being a placeholder if 'NONE' is passed.
            if [ "$2" != "NONE" ];then
                PREBUILD_SCRIPT="$2"
                shift
            fi
            ;;
		
        --build-attempts )
            # Sometimes the first try isn't the charm.
            BUILD_ATTEMPTS="$2"
            if [ "$2" = "" ];then
                fxnERR "--build-attempts requires a number. Ex: --build-attempts 2.  Exiting."
                exit 1
            fi
            shift
            ;;

        --version )
            # Track version for upgrade purposes
            echo "$SCRIPT_VERSION"
            exit 0
            ;;

        -h|--help)
            fxnHelp
            exit 0
            ;;

        --help-examples)
            # Show examples of script invocation
            fxnHelpExamples
            exit 0
            ;;

        --help-build-targets)
            # Examples of what gets invoked for each build option.
            fxnHelpBuildTargets
            exit 0
            ;;

        --verbose )
            # Unused for now
            DO_VERBOSE="TRUE"
            ;;

        --quiet )
            # Unused for now
            DO_QUIET="TRUE"
            ;;

        *)
            fxnHelp
            echo "Unrecognized option [ $term ]. Exiting"
            exit 1
            ;;

    esac
    shift # skip over argument

done

# Building from top level if not specified otherwise
if [ "$BUILD_DIR" = "" ];then
    BUILD_DIR="$gTOP_DIR"
fi


# Arguments passed to the dpkg build. This gets
# appended by fxnParseBuildArgs
if [ "$DO_BUILD" = "TRUE" ];then

    if [ "$ADDITIONAL_BUILD_ARGS" != "" ];then
        # User is overriding defaults.
        # take it literally with no defaults.
        BUILD_ARGS="$ADDITIONAL_BUILD_ARGS"
    fi

fi

# Add parallel build
if [ "$BUILD_JOBS" != "" ];then
    BUILD_ARGS+=" -j${BUILD_JOBS} "
fi


if [ "$BUILD_DIR" != "" ];then
    if [ ! -e "$BUILD_DIR" ];then
        fxnERR "--use-directory [ $BUILD_DIR ] does not exist in $(pwd)"
        exit 1
    fi
    fxnEC cd "$BUILD_DIR" || exit 1
fi

#
# Dump what's being run
#
fxnPrintConfig

if [ "$PREBUILD_SCRIPT" != "" ];then
    fxnHeader "Running pre build script [ $PREBUILD_SCRIPT ]"
    bash "$PREBUILD_SCRIPT"
fi

#
# Take actions now that all arguments have been passed.
#

if [ "$DO_BUILD" = "TRUE" ];then

	BUILD_LOG_FILE="example-build.log"
    #
    # actually do the build
    #

    fxnPP "Building with dev version $DEV_PACKAGE_VERSION"
    fxnDoBuild  2>&1 | tee -a  "$BUILD_LOG_FILE"
fi    

# Preserve exit code through exit trap
RETURN_CODE=${PIPESTATUS[0]}




if [ "$RETURN_CODE" = "0" ];then
        fxnPP "Success"
		# Add post build actions
        fxnPP "Build finished at [ $(date) ]"
else
    fxnERR "Build FAILED with return code [ $RETURN_CODE ] at [ $(date) ]"


exit "$RETURN_CODE"
