#
# 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-compiler
# @brief Chain into foreign C/C++ build systems
#
# The <lit>chain-compiler</lit> module extends the
# <modref>chain-generic</modref> module to integrate
# non-MakeKit C/C++ components into your project.
# See <modref>chain-autotools</modref> for integrating
# autotools-based components in particular.
#>

DEPENDS="compiler chain-generic"

### section configure

#<
# @brief Chain into C/C++ component with foreign build system
# @usage CONFIGURE=cfunc BUILD=bfunc
# @option HEADERS=headers Specifies a list of header files
# that will be generated by the component.  Each will be
# taken as relative to <varref>MK_INCLUDEDIR</varref>
# unless given as an absolute path.
# @option LIBS=libs Specifies libraries installed by the component.
# Each library should be specified as its base name with no
# file extension or <lit>lib</lit> prefix.  Each name may be
# followed by a colon (<lit>:</lit>) and a version
# specifier as accepted by <funcref>mk_library</funcref>.
# @option DLOS=dlos Specifies dynamically-loadable objects which will
# be installed by the component.  Since these need not be placed
# in the usual library directory, each should be specified as a complete
# path to a <lit>.la</lit> file.  A version number may be attached to
# each as in <param>libs</param>.
# @option PROGRAMS=programs Specifies a list of program binaries which
# will be generated by the component.  Each will be taken to be relative
# to <varref>MK_BINDIR</varref> unless given as an absolute path.
# @option LIBDEPS=libdeps Specifies libraries the component
# depends on
# @option HEADERDEPS=headerdeps Specifies headers the component
# depends on
# @option CPPFLAGS=flags Additional C preprocessor flags
# @option CFLAGS=flags Additional C compiler flags
# @option CXXFLAGS=flags Additional C++ compiler flags
# @option LDFLAGS=flags Additional linker flags
# @option ... Any option supported by <funcref>mk_chain_generic</funcref>.
#
# Chains into a component with a foreign build system using
# <funcref>mk_chain_generic</funcref>, but supports additional features
# to integrate with the <lit>compiler</lit> module.
#
# The default stage function used is <funcref>mk_chain_compiler_stage</funcref>.
# This function understands how to generate <lit>.la</lit> files for components
# that do not create their own.  On platforms that support fat binaries, it
# will automatically merge binaries from separate builds.
#
# You must still provide your own configure and build functions.
# If chaining into an autotools project, consider using
# <funcref>mk_chain_autotools</funcref> instead.
#>
mk_chain_compiler()
{
    mk_push_vars \
        NAME SOURCEDIR TARGETS DEPS BUILDDEPS CONFIGURE BUILD \
        HEADERS LIBS PROGRAMS DLOS LIBDEPS HEADERDEPS \
        CPPFLAGS CFLAGS CXXFLAGS LDFLAGS PASSVARS \
        LINK_TARGETS \
        STAGE="mk_chain_compiler_stage" \
        MULTIARCH="$MK_MULTIARCH" \
        HEADER EXT LIB DLO PROGRAM \
        VERSION MAJOR MINOR MICRO LINKS SONAME \
        link_target stamp targets deps
    mk_parse_params

    PASSVARS="$PASSVARS CPPFLAGS CFLAGS CXXFLAGS LDFLAGS LINK_TARGETS"

    # Process and merge deps
    mk_unquote_list "$LIBDEPS"
    for LIB in
    do
        if _mk_contains "$LIB" ${MK_INTERNAL_LIBS}
        then
            mk_quote "${MK_LIBDIR}/lib${LIB}.la"
            DEPS="$DEPS $result"
        fi
    done

    mk_unquote_list "$HEADERDEPS"
    for HEADER
    do
        if _mk_contains "$HEADER" ${MK_INTERNAL_HEADERS}
        then
            mk_resolve_header "$HEADER"
            mk_quote "$result"
            DEPS="$DEPS $result"
        fi
    done

    # Process and merge targets
    mk_unquote_list "$HEADERS"
    for HEADER
    do
        MK_INTERNAL_HEADERS="$MK_INTERNAL_HEADERS $HEADER"

        mk_resolve_header "$HEADER"
        mk_quote "$result"

        TARGETS="$TARGETS $result"
    done

    EXT="$MK_LIB_EXT"
    mk_unquote_list "$LIBS"
    for LIB
    do
        MK_INTERNAL_LIBS="$MK_INTERNAL_LIBS ${LIB%%:*}"

        case "$LIB" in
            *:*)
                VERSION="${LIB#*:}"
                LIB="${LIB%%:*}"
                _mk_library_process_version
                ;;
            *)
                mk_quote "lib${LIB%%:*}${MK_LIB_EXT}"
                LINKS="$result"
                ;;
        esac

        mk_unquote_list "$LINKS"
        mk_resolve_target "${MK_LIBDIR}/$1"
        mk_quote "$result"
        link_target="$result"
        deps="$result"
        TARGETS="$TARGETS $result"
        shift

        for link
        do
            mk_quote "${MK_LIBDIR}/$link"
            deps="$deps $result"
            mk_quote "${MK_LIBDIR}/$link:$link_target"
            TARGETS="$TARGETS $result"
        done

        mk_quote "${MK_LIBDIR}/lib${LIB}.la:$deps"
        TARGETS="$TARGETS $result"
        LINK_TARGETS="$LINK_TARGETS $link_target"

        mk_resolve_file "${MK_LIBDIR}/lib${LIB}.la"
        mk_varname "$result"
        mk_set "${result}_LINKS" "$LINKS"
        mk_set "${result}_EXT" "$MK_LIB_EXT"
        PASSVARS="$PASSVARS ${result}_LINKS ${result}_EXT"
    done

    EXT="$MK_DLO_EXT"
    mk_unquote_list "$DLOS"
    for DLO
    do
        mk_dirname "$DLO"
        _dlodir="$result"
        mk_basename "$DLO"
        DLO="$result"

        case "$DLO" in
            *:*)
                VERSION="${DLO#*:}"
                DLO="${DLO%%:*}"
                _mk_dlo_process_version
                ;;
            *)
                mk_quote "${DLO%.la}${MK_DLO_EXT}"
                LINKS="$result"
                ;;
        esac

        mk_unquote_list "$LINKS"

        mk_resolve_target "$_dlodir/$1"
        mk_quote "$result"
        link_target="$result"
        deps="$result"
        TARGETS="$TARGETS $result"
        shift

        for link
        do
            mk_quote "$_dlodir/$link"
            deps="$deps $result"
            mk_quote "$_dlodir/$link:$link_target"
            TARGETS="$TARGETS $result"
        done

        mk_quote "$_dlodir/$DLO:$deps"
        TARGETS="$TARGETS $result"
        LINK_TARGETS="$LINK_TARGETS $link_target"

        mk_resolve_file "$_dlodir/$DLO"
        mk_varname "$result"
        mk_set "${result}_LINKS" "$LINKS"
        mk_set "${result}_EXT" "$MK_LIB_EXT"
        PASSVARS="$PASSVARS ${result}_LINKS ${result}_EXT"
    done

    mk_unquote_list "$PROGRAMS"
    for PROGRAM
    do
        case "$PROGRAM" in
            /*|@*)
                mk_resolve_target "$PROGRAM"
                ;;
            *)
                mk_resolve_target "$MK_BINDIR/$PROGRAM"
                ;;
        esac

        link_target="$result"
        mk_quote "$link_target"
        TARGETS="$TARGETS $result"
        LINK_TARGETS="$LINK_TARGETS $result"
    done

    mk_chain_generic \
        SOURCEDIR="$SOURCEDIR" \
        NAME="$NAME" \
        TARGETS="$TARGETS" \
        DEPS="$DEPS" \
        BUILDDEPS="$BUILDDEPS" \
        CONFIGURE="$CONFIGURE" \
        BUILD="$BUILD" \
        PASSVARS="$PASSVARS" \
        STAGE="$STAGE" \
        MULTIARCH="$MULTIARCH"

    stamp="$result"

    mk_unquote_list "$LINK_TARGETS"
    for link_target
    do
        mk_run_link_target_posthooks "$link_target"
    done

    mk_pop_vars
}

### section build

_mk_chain_synth_la()
{
    mk_varname "$1"
    var="$result"
    mk_get "${var}_LINKS"
    links="$result"
    mk_get "${var}_EXT"
    ext="$result"

    mk_run_script link MODE=la EXT="$ext" LINKS="$links" "$1"
}

_mk_chain_is_link_target()
{
    _needle="$1"
    mk_unquote_list "$LINK_TARGETS"
    _mk_contains "@$_needle" "$@"
}

_mk_chain_combine()
{
    # $1 = target
    # ... = sources
    if [ "$MK_HOST_OS" = darwin ]
    then
        mk_mkdirname "$1"
        mk_run_or_fail lipo -create -output "$@"
    else
        mk_fail "don't know how to combine binaries for $MK_HOST_OS"
    fi
}

#<
# @brief C/C++-oriented stage function for chaining
# @usage target outdirs...
#
# This is the default stage function used by
# <funcref>mk_chain_compiler</funcref>.  It understands
# how to generate missing <lit>.la</lit> files and merge
# multiple outputs into one binary on fat binary systems.
#>
mk_chain_compiler_stage()
{
    target="$1"
    shift

    case "$target" in
        *.la)
            found=false
            for outdir
            do
                if [ -e "$outdir${target#$MK_STAGE_DIR}" ]
                then
                    found=true
                    break
                fi
            done

            if $found
            then
                mk_chain_generic_stage "$target" "$@"
            else
                _mk_chain_synth_la "$target" "$@"
            fi
            ;;
        *)
            if _mk_chain_is_link_target "$target"
            then
                if [ "$MK_SYSTEM" = host -a "$MK_HOST_MULTIARCH" = combine ]
                then
                    sources=""
                    for outdir
                    do
                        if [ -f "$outdir${target#$MK_STAGE_DIR}" ]
                        then
                            mk_quote "$outdir${target#$MK_STAGE_DIR}"
                            sources="$sources $result"
                        fi
                    done
                    if [ -z "$sources" ]
                    then
                        mk_fail "${target#$MK_STAGE_DIR} not found in output"
                    fi
                    mk_unquote_list "$sources"
                    _mk_chain_combine "$target" "$@"
                else
                    mk_chain_generic_stage "$target" "$@"
                fi
                mk_run_link_posthooks "$target"
            else
                mk_chain_generic_stage "$target" "$@"
            fi
            ;;
    esac
}