#
# Copyright (c) Brian Koropoff
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the MakeKit project nor the names of its
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#

#<
# @module chain-generic
# @brief Chain into foreign build systems
#
# The <lit>chain-generic</lit> module allows you to chain
# into the build system of a foreign component embedded in your
# project.  This is a generic module designed for any
# component; see <modref>chain-compiler</modref> for C/C++
# components and <modref>chain-autotools</modref> for
# autotools components.
#>

DEPENDS="core"

### section configure

_mk_chain_generic()
{
    _mk_slashless_name "${NAME}_${CANONICAL_SYSTEM}"
    name="$result"
    builddir="${result}_build"
    outdir="${result}_output"
    mk_resolve_target "$builddir"
    builddir="$result"
    mk_resolve_target "$outdir"
    outdir="$result"

    mk_add_clean_target "$builddir"
    mk_add_clean_target "$outdir"

    mk_target \
        SYSTEM="$SYSTEM" \
        TARGET=".${name}_configure" \
        DEPS="$DEPS" \
        _mk_chain_configure %PASSVARS '$@' "$SOURCEDIR" "$builddir"

    mk_quote "$result"

    mk_target \
        SYSTEM="$SYSTEM" \
        TARGET=".${name}_build" \
        DEPS="$BUILDDEPS $result" \
        _mk_chain_build %PASSVARS MAKE='$(MAKE)' MFLAGS='$(MFLAGS)' \
        '$@' "$builddir" "$outdir"
}

#<
# @brief Chain into component with foreign build system
# @usage TARGETS=targets CONFIGURE=cfunc BUILD=bfunc
# @option SOURCEDIR=dir Specifies that source files for
# the component are found in <param>dir</param> relative
# to the current <lit>MakeKitBuild</lit> file.  Defaults
# to '<lit>.</lit>'.
# @option TARGETS=targets Specifies a list of targets
# that will be generated by the component.  As an extension
# to the usual target syntax (see <funcref>mk_resolve_target</funcref>),
# you may suffix each target with a <lit>:</lit> and a list
# of targets it depends on.  This is useful for specifying
# interdependencies between targets generated by the component.
# Note that these lists within a list require an additional level of
# shell quoting.
# @option DEPS=deps Specifies a list of target dependencies
# before the component can be configured
# @option BUILDDEPS=bdeps Specifies a list of target dependencies
# needed to build but not configure the component
# @option CONFIGURE=cfunc Specifies a function which will be
# invoked with the source directory and build directory
# as its two arguments.  It should prepare the build directory
# for building the component (e.g. by running a <lit>configure</lit>
# script or similar).
# @option BUILD=bfunc Specifies a function which will be invoked
# with the build directory and output directory as its two arguments.
# It should build and install any build products into the output directory
# (e.g. by running <lit>make</lit> and <lit>make DESTDIR=... install</lit>).
# @option STAGE=sfunc Specifies a function which will be invoked
# with one of the targets in <param>targets</param> as its first
# parameter and a list of output directories as its subsequent
# parameters.  It should generate the target from the output directories
# in a sensible way (e.g. simply copying it).  This defaults
# to <funcref>mk_chain_generic_stage</funcref>.
# @option PASSVARS=vars Specifies a list of variables whose current
# values will be captured and made available to <param>cfunc</param>,
# <param>bfunc</param>, and <param>sfunc</param> when they are invoked.
#
# This function provides a generic mechanism to chain into a bundled
# component with a foreign build system.  You must specify a list of
# targets which will be generated and at least two functions to carry
# out the actual build.  The first, <param>cfunc</param>, should
# make preparations for the component to be built in a dedicated build
# directory.  The second, <param>bfunc</param>, should build the component
# and install it into a dedicated output directory.  You may optionally
# specify your own <param>sfunc</param> which transfers the installed
# files from the output directories into the staging area, but the
# default will work fine as long as the output directory has the same
# layout as the staging area.
#
# On systems that support "fat" binaries, and when targeting the
# host system (but not a particular ISA), the component will be
# be configured and built for each ISA.  A custom <param>sfunc</param>
# can be used to decide how to to create each target from the multiple
# outputs.
#
# This function is very generic.  If you wish to build projects
# which invoke the C or C++ compiler, consider using
# <funcref>mk_chain_compiler</funcref> instead.
#
# This function sets <var>result</var> to a target that
# depends on all other targets it defined.
#>
mk_chain_generic()
{
    mk_push_vars \
        SOURCEDIR NAME TARGETS DEPS BUILDDEPS \
        CONFIGURE BUILD STAGE="mk_chain_generic_stage" PASSVARS \
        SYSTEM="$MK_SYSTEM" CANONICAL_SYSTEM \
        MULTIARCH="$MK_MULTIARCH" \
        builddir outdir deps \
        outdirs stamps temp varfile
    mk_parse_params

    if [ -z "$NAME" ]
    then
        if [ -n "$SOURCEDIR" ]
        then
            NAME="${MK_SUBDIR:+${MK_SUBDIR#/}/}$SOURCEDIR"
        elif [ -n "$MK_SUBDIR" ]
        then
            NAME="${MK_SUBDIR#/}"
        else
            NAME="$PROJECT_NAME"
        fi
    fi

    mk_resolve_target "${SOURCEDIR:-.}"
    SOURCEDIR="$result"

    # Dump pass-through variables to file
    PASSVARS="$PASSVARS NAME CONFIGURE BUILD STAGE"
    _mk_slashless_name ".${NAME}_${SYSTEM}_vars"
    mk_resolve_file "$result"
    varfile="$result"

    {
        for temp in ${PASSVARS}
        do
            mk_get "$temp"
            mk_quote "$result"
            echo "$temp=$result"
        done
    } > "${varfile}.new" || mk_fail "could not write $varfile"

    if diff "${varfile}" "${varfile}.new" >/dev/null 2>&1
    then
        mk_safe_rm "${varfile}.new"
    else
        mk_run_or_fail mv -f "${varfile}.new" "$varfile"
    fi

    mk_add_configure_output "@$varfile"
    DEPS="$DEPS @$varfile"
    PASSVARS="$varfile"

    if [ "$MK_SYSTEM" = "host" -a "$MULTIARCH" = "combine" ]
    then
        parts=""

        for _isa in ${MK_HOST_ISAS}
        do
            SYSTEM="host/$_isa"
            CANONICAL_SYSTEM="$SYSTEM"

            _mk_chain_generic "$@"
            mk_quote "$result"
            stamps="$stamps $result"
            outdirs="$outdirs $outdir"
        done
    else
        mk_canonical_system "$SYSTEM"
        CANONICAL_SYSTEM="$result"

        _mk_chain_generic "$@"
        mk_quote "$result"
        stamps="$stamps $result"
        outdirs="$outdirs $outdir"
    fi

    DEPS=""
    mk_unquote_list "$TARGETS"
    for temp
    do
        deps=""
        case "$temp" in
            *:*)
                deps="${temp#*:}"
                temp="${temp%%:*}"
                ;;
        esac

        mk_target \
            TARGET="$temp" \
            DEPS="$stamps $deps" \
            _mk_chain_stage %PASSVARS '$@' "*$outdirs"
        mk_quote "$result"
        DEPS="$DEPS $result"
    done

    _mk_slashless_name ".${NAME}_${SYSTEM}_targets"
    mk_target \
        TARGET="$result" \
        DEPS="$DEPS" \
        mk_run_or_fail touch '$@'

    mk_pop_vars
}

### section build

_mk_chain_configure()
{
    mk_parse_params
    mk_safe_source "$PASSVARS"

    # $1 = stamp
    # $2 = sourcedir
    # $3 = builddir

    mk_msg_domain "configure"
    mk_msg "begin $NAME ($MK_CANONICAL_SYSTEM)"

    mk_safe_rm "$3"
    mk_mkdir "$3"

    "$CONFIGURE" "$2" "$3"

    mk_run_or_fail touch "$1"
    mk_msg "end $NAME ($MK_CANONICAL_SYSTEM)"
}

_mk_chain_build()
{
    mk_parse_params
    mk_safe_source "$PASSVARS"

    # $1 = stamp
    # $2 = builddir
    # $3 = outputdir

    mk_msg_domain "build"
    mk_msg "begin $NAME ($MK_CANONICAL_SYSTEM)"

    mk_safe_rm "$3"
    mk_mkdir "$3"

    "$BUILD" "$2" "$3"

    mk_run_or_fail touch "$1"

    mk_msg "end $NAME ($MK_CANONICAL_SYSTEM)"
}

_mk_chain_stage()
{
    mk_parse_params
    mk_safe_source "$PASSVARS"

    # $1 = target
    # ... = output dirs

    mk_msg_domain "stage"
    mk_pretty_path "$1"
    mk_msg "$result"

    "$STAGE" "$@"
}

#<
# @brief Generic stage function for chaining
# @usage target outdirs...
#
# This is the default stage function used by
# <funcref>mk_chain_generic</funcref>.  It generates
# <param>target</param> by looking for it in each
# directory in <param>outdirs</param> in turn and copying
# from the first one to contain it.  If it is not found
# in any, it aborts.
#>
mk_chain_generic_stage()
{
    target="$1"
    shift
    if [ "${MK_SYSTEM%/*}" = "host" ]
    then
        rel="${target#$MK_STAGE_DIR}"
    else
        rel="${MK_ROOT_DIR}/$target"
    fi

    for outdir
    do
        if [ -e "$outdir$rel" -o -h "$outdir$rel" ]
        then
            mk_safe_rm "$target"
            mk_mkdirname "$target"
            mk_run_or_fail mk_clone "$outdir$rel" "$target"
            return
        fi
    done

    mk_fail "could not find in build output: $rel"
}
