#
# Copyright (C) 2011 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#  This file contains various shell function definitions that can be
#  used to either build a static and shared libraries from sources, or
#  generate a Makefile to do it in parallel.
#

_BUILD_TAB=$(echo " " | tr ' ' '\t')

builder_command ()
{
    if [ -z "$_BUILD_MK" ]; then
        if [ "$VERBOSE2" = "yes" ]; then
            echo "$@"
        fi
        "$@"
    else
        echo "${_BUILD_TAB}${_BUILD_HIDE}$@" >> $_BUILD_MK
    fi
}


builder_log ()
{
    if [ "$_BUILD_MK" ]; then
        echo "${_BUILD_TAB}${_BUILD_HIDE}echo $@" >> $_BUILD_MK
    else
        log "$@"
    fi
}

# $1: Build directory
# $2: Optional Makefile name
builder_begin ()
{
    _BUILD_DIR_NEW=
    _BUILD_DIR=$1
    if [ ! -d "$_BUILD_DIR" ]; then
        mkdir -p "$_BUILD_DIR"
        fail_panic "Can't create build directory: $_BUILD_DIR"
        _BUILD_DIR_NEW=true
    else
        rm -rf "$_BUILD_DIR/*"
        fail_panic "Can't cleanup build directory: $_BUILD_DIR"
    fi
    _BUILD_TARGETS=
    _BUILD_PREFIX=
    _BUILD_MK=$2
    if [ -n "$_BUILD_MK" ]; then
        log "Creating temporary build Makefile: $_BUILD_MK"
        rm -f $_BUILD_MK &&
        echo "# Auto-generated by $0 - do not edit!" > $_BUILD_MK
        echo ".PHONY: all" >> $_BUILD_MK
        echo "all:" >> $_BUILD_MK
    fi
    # HIDE is used to hide the Makefile output, unless --verbose --verbose
    # is used.
    if [ "$VERBOSE2" = "yes" ]; then
        _BUILD_HIDE=""
    else
        _BUILD_HIDE=@
    fi

    builder_begin_module
}

# $1: Variable name
# out: Variable value
_builder_varval ()
{
    eval echo "\$$1"
}

_builder_varadd ()
{
    local _varname="$1"
    local _varval="$(_builder_varval $_varname)"
    shift
    if [ -z "$_varval" ]; then
        eval $_varname=\"$@\"
    else
        eval $_varname=\$$_varname\" $@\"
    fi
}


builder_set_prefix ()
{
    _BUILD_PREFIX="$@"
}

builder_begin_module ()
{
    _BUILD_CC=
    _BUILD_CXX=
    _BUILD_AR=
    _BUILD_C_INCLUDES=
    _BUILD_CFLAGS=
    _BUILD_CXXFLAGS=
    _BUILD_LDFLAGS_BEGIN_SO=
    _BUILD_LDFLAGS_END_SO=
    _BUILD_LDFLAGS_BEGIN_EXE=
    _BUILD_LDFLAGS_END_EXE=
    _BUILD_LDFLAGS=
    _BUILD_BINPREFIX=
    _BUILD_DSTDIR=
    _BUILD_SRCDIR=.
    _BUILD_OBJECTS=
    _BUILD_STATIC_LIBRARIES=
    _BUILD_SHARED_LIBRARIES=
}

builder_set_binprefix ()
{
    _BUILD_BINPREFIX=$1
    _BUILD_CC=${1}gcc
    _BUILD_CXX=${1}g++
    _BUILD_AR=${1}ar
}

builder_set_binprefix_llvm ()
{
    _BUILD_BINPREFIX=$1
    _BUILD_CC=${1}clang
    _BUILD_CXX=${1}clang++
}

builder_set_builddir ()
{
    _BUILD_DIR=$1
}

builder_set_srcdir ()
{
    _BUILD_SRCDIR=$1
}

builder_set_dstdir ()
{
    _BUILD_DSTDIR=$1
}

builder_ldflags ()
{
    _builder_varadd _BUILD_LDFLAGS "$@"
}

builder_ldflags_exe ()
{
    _builder_varadd _BUILD_LDFLAGS_EXE "$@"
}

builder_cflags ()
{
    _builder_varadd _BUILD_CFLAGS "$@"
}

builder_cxxflags ()
{
    _builder_varadd _BUILD_CXXFLAGS "$@"
}

builder_c_includes ()
{
    _builder_varadd _BUILD_C_INCLUDES "$@"
}

# $1: optional var to hold the original cflags before reset
builder_reset_cflags ()
{
    local _varname="$1"
    if [ -n "$_varname" ] ; then
        eval $_varname=\"$_BUILD_CFLAGS\"
    fi
    _BUILD_CFLAGS=
}

# $1: optional var to hold the original cxxflags before reset
builder_reset_cxxflags ()
{
    local _varname="$1"
    if [ -n "$_varname" ] ; then
        eval $_varname=\"$_BUILD_CXXFLAGS\"
    fi
    _BUILD_CXXFLAGS=
}

# $1: optional var to hold the original c_includes before reset
builder_reset_c_includes ()
{
    local _varname="$1"
    if [ -n "$_varname" ] ; then
        eval $_varname=\"$_BUILD_C_INCLUDES\"
    fi
    _BUILD_C_INCLUDES=
}

builder_link_with ()
{
    local LIB
    for LIB; do
        case $LIB in
            *.a)
                _builder_varadd _BUILD_STATIC_LIBRARIES $LIB
                ;;
            *.so)
                _builder_varadd _BUILD_SHARED_LIBRARIES $LIB
                ;;
            *)
                echo "ERROR: Unknown link library extension: $LIB"
                exit 1
        esac
    done
}

builder_sources ()
{
    local src srcfull obj cc cflags text
    if [ -z "$_BUILD_DIR" ]; then
        panic "Build directory not set!"
    fi
    if [ -z "$_BUILD_CC" ]; then
        _BUILD_CC=${CC:-gcc}
    fi
    if [ -z "$_BUILD_CXX" ]; then
        _BUILD_CXX=${CXX:-g++}
    fi
    for src in "$@"; do
        srcfull=$_BUILD_SRCDIR/$src
        if [ ! -f "$srcfull" ]; then
            echo "ERROR: Missing source file: $srcfull"
            exit 1
        fi
        obj=$src
        cflags="$_BUILD_CFLAGS"
        for inc in $_BUILD_C_INCLUDES; do
            cflags=$cflags" -I$inc"
        done
        cflags=$cflags" -I$_BUILD_SRCDIR"
        case $obj in
            *.c)
                obj=${obj%%.c}
                text="C"
                cc=$_BUILD_CC
                ;;
            *.cpp)
                obj=${obj%%.cpp}
                text="C++"
                cc=$_BUILD_CXX
                cflags="$cflags $_BUILD_CXXFLAGS"
                ;;
            *.cc)
                obj=${obj%%.cc}
                text="C++"
                cc=$_BUILD_CXX
                cflags="$cflags $_BUILD_CXXFLAGS"
                ;;
            *.S|*.s)
                obj=${obj%%.$obj}
                text="ASM"
                cc=$_BUILD_CC
                ;;
            *)
                echo "Unknown source file extension: $obj"
                exit 1
                ;;
        esac

        # Source file path can include ../ path items, ensure
        # that the generated object do not back up the output
        # directory by translating them to __/
        obj=$(echo "$obj" | tr '../' '__/')

        # Ensure we have unwind tables in the generated machine code
        # This is useful to get good stack traces
        cflags=$cflags" -funwind-tables"

        obj=$_BUILD_DIR/$obj.o
        if [ "$_BUILD_MK" ]; then
            echo "$obj: $srcfull" >> $_BUILD_MK
        fi
        builder_log "${_BUILD_PREFIX}$text: $src"
        builder_command mkdir -p $(dirname "$obj")
        builder_command $NDK_CCACHE $cc -c -o "$obj" "$srcfull" $cflags
        fail_panic "Could not compile ${_BUILD_PREFIX}$src"
        _BUILD_OBJECTS=$_BUILD_OBJECTS" $obj"
    done
}

builder_static_library ()
{
    local lib libname
    libname=$1
    if [ -z "$_BUILD_DSTDIR" ]; then
        panic "Destination directory not set"
    fi
    lib=$_BUILD_DSTDIR/$libname
    lib=${lib%%.a}.a
    if [ "$_BUILD_MK" ]; then
        _BUILD_TARGETS=$_BUILD_TARGETS" $lib"
        echo "$lib: $_BUILD_OBJECTS" >> $_BUILD_MK
    fi
    if [ -z "${_BUILD_AR}" ]; then
        _BUILD_AR=${AR:-ar}
    fi
    builder_log "${_BUILD_PREFIX}Archive: $libname"
    rm -f "$lib"
    builder_command ${_BUILD_AR} crsD "$lib" "$_BUILD_OBJECTS"
    fail_panic "Could not archive ${_BUILD_PREFIX}$libname objects!"
}

builder_host_static_library ()
{
    local lib libname
    libname=$1
    if [ -z "$_BUILD_DSTDIR" ]; then
        panic "Destination directory not set"
    fi
    lib=$_BUILD_DSTDIR/$libname
    lib=${lib%%.a}.a
    if [ "$_BUILD_MK" ]; then
        _BUILD_TARGETS=$_BUILD_TARGETS" $lib"
        echo "$lib: $_BUILD_OBJECTS" >> $_BUILD_MK
    fi
    if [ -z "$BUILD_AR" ]; then
        _BUILD_AR=${AR:-ar}
    fi
    builder_log "${_BUILD_PREFIX}Archive: $libname"
    rm -f "$lib"
    builder_command ${_BUILD_AR} crsD "$lib" "$_BUILD_OBJECTS"
    fail_panic "Could not archive ${_BUILD_PREFIX}$libname objects!"
}

builder_shared_library ()
{
    local lib libname suffix
    libname=$1
    suffix=$2
    if [ -z "$suffix" ]; then
        suffix=".so"
    fi
    lib=$_BUILD_DSTDIR/$libname
    lib=${lib%%${suffix}}${suffix}
    if [ "$_BUILD_MK" ]; then
        _BUILD_TARGETS=$_BUILD_TARGETS" $lib"
        echo "$lib: $_BUILD_OBJECTS" >> $_BUILD_MK
    fi
    builder_log "${_BUILD_PREFIX}SharedLibrary: $libname"

    # Important: -lgcc must appear after objects and static libraries,
    #            but before shared libraries for Android. It doesn't hurt
    #            for other platforms.
    builder_command ${_BUILD_CXX} \
        -Wl,-soname,$(basename $lib) \
        -Wl,-shared,-Bsymbolic \
        $_BUILD_LDFLAGS_BEGIN_SO \
        $_BUILD_OBJECTS \
        $_BUILD_STATIC_LIBRARIES \
        -lgcc \
        $_BUILD_SHARED_LIBRARIES \
        -lc -lm \
        $_BUILD_LDFLAGS \
        $_BUILD_LDFLAGS_END_SO \
        -o $lib
    fail_panic "Could not create ${_BUILD_PREFIX}shared library $libname"
}

# Same as builder_shared_library, but do not link the default libs
builder_nostdlib_shared_library ()
{
    local lib libname suffix
    libname=$1
    suffix=$2
    if [ -z "$suffix" ]; then
        suffix=".so"
    fi
    lib=$_BUILD_DSTDIR/$libname
    lib=${lib%%${suffix}}${suffix}
    if [ "$_BUILD_MK" ]; then
        _BUILD_TARGETS=$_BUILD_TARGETS" $lib"
        echo "$lib: $_BUILD_OBJECTS" >> $_BUILD_MK
    fi
    builder_log "${_BUILD_PREFIX}SharedLibrary: $libname"

    builder_command ${_BUILD_CXX} \
        -Wl,-soname,$(basename $lib) \
        -Wl,-shared,-Bsymbolic \
        $_BUILD_LDFLAGS_BEGIN_SO \
        $_BUILD_OBJECTS \
        $_BUILD_STATIC_LIBRARIES \
        $_BUILD_SHARED_LIBRARIES \
        $_BUILD_LDFLAGS \
        $_BUILD_LDFLAGS_END_SO \
        -o $lib
    fail_panic "Could not create ${_BUILD_PREFIX}shared library $libname"
}

builder_host_shared_library ()
{
    local lib libname
    libname=$1
    lib=$_BUILD_DSTDIR/$libname
    lib=${lib%%.so}.so
    if [ "$_BUILD_MK" ]; then
        _BUILD_TARGETS=$_BUILD_TARGETS" $lib"
        echo "$lib: $_BUILD_OBJECTS" >> $_BUILD_MK
    fi
    builder_log "${_BUILD_PREFIX}SharedLibrary: $libname"

    if [ -z "$_BUILD_CXX" ]; then
        _BUILD_CXX=${CXX:-g++}
    fi

    # Important: -lgcc must appear after objects and static libraries,
    #            but before shared libraries for Android. It doesn't hurt
    #            for other platforms.
    builder_command ${_BUILD_CXX} \
        -shared -s \
        $_BUILD_OBJECTS \
        $_BUILD_STATIC_LIBRARIES \
        $_BUILD_SHARED_LIBRARIES \
        $_BUILD_LDFLAGS \
        -o $lib
    fail_panic "Could not create ${_BUILD_PREFIX}shared library $libname"
}

builder_host_executable ()
{
    local exe exename
    exename=$1
    exe=$_BUILD_DSTDIR/$exename$HOST_EXE
    if [ "$_BUILD_MK" ]; then
        _BUILD_TARGETS=$_BUILD_TARGETS" $exe"
        echo "$exe: $_BUILD_OBJECTS" >> $_BUILD_MK
    fi
    builder_log "${_BUILD_PREFIX}Executable: $exename$HOST_EXE"

    if [ -z "$_BUILD_CXX" ]; then
        _BUILD_CXX=${CXX:-g++}
    fi

    # Important: -lgcc must appear after objects and static libraries,
    #            but before shared libraries for Android. It doesn't hurt
    #            for other platforms.
    builder_command ${_BUILD_CXX} \
        -s \
        $_BUILD_OBJECTS \
        $_BUILD_STATIC_LIBRARIES \
        $_BUILD_SHARED_LIBRARIES \
        $_BUILD_LDFLAGS \
        -o $exe
    fail_panic "Could not create ${_BUILD_PREFIX}executable $libname"
}


builder_end ()
{
    if [ "$_BUILD_MK" ]; then
        echo "all: $_BUILD_TARGETS" >> $_BUILD_MK
        run make -j$NUM_JOBS -f $_BUILD_MK
        fail_panic "Could not build project!"
    fi

    if [ "$_BUILD_DIR_NEW" ]; then
        log2 "Cleaning up build directory: $_BUILD_DIR"
        rm -rf "$_BUILD_DIR"
        _BUILD_DIR_NEW=
    fi
}

# Same as builder_begin, but to target Android with a specific ABI
# $1: ABI name (e.g. armeabi)
# $2: Build directory
# $3: Gcc version
# $4: Optional llvm version
# $5: Optional Makefile name
builder_begin_android ()
{
    local ABI BUILDDIR LLVM_VERSION MAKEFILE
    local ARCH PLATFORM SYSROOT FLAGS
    local CRTBEGIN_SO_O CRTEND_SO_O CRTBEGIN_EXE_SO CRTEND_SO_O
    local BINPREFIX GCC_TOOLCHAIN LLVM_TRIPLE
    if [ -z "$NDK_DIR" ]; then
        panic "NDK_DIR is not defined!"
    elif [ ! -d "$NDK_DIR/platforms" ]; then
        panic "Missing directory: $NDK_DIR/platforms"
    fi
    ABI=$1
    BUILDDIR=$2
    GCC_VERSION=$3
    LLVM_VERSION=$4
    MAKEFILE=$5
    ARCH=$(convert_abi_to_arch $ABI)
    PLATFORM=${2##android-}
    SYSROOT=$NDK_DIR/platforms/android-$PLATFORM/arch-$ARCH

    if [ "$(arch_in_unknown_archs $ARCH)" = "yes" ]; then
        LLVM_VERSION=$DEFAULT_LLVM_VERSION
    fi

    if [ -z "$LLVM_VERSION" ]; then
        BINPREFIX=$NDK_DIR/$(get_toolchain_binprefix_for_arch $ARCH $GCC_VERSION)
    else
        BINPREFIX=$NDK_DIR/$(get_llvm_toolchain_binprefix $LLVM_VERSION)
        GCC_TOOLCHAIN=`dirname $NDK_DIR/$(get_toolchain_binprefix_for_arch $ARCH $GCC_VERSION)`
        GCC_TOOLCHAIN=`dirname $GCC_TOOLCHAIN`
    fi

    SYSROOT=$NDK_DIR/$(get_default_platform_sysroot_for_arch $ARCH)

    CRTBEGIN_EXE_O=$SYSROOT/usr/lib/crtbegin_dynamic.o
    CRTEND_EXE_O=$SYSROOT/usr/lib/crtend_android.o

    CRTBEGIN_SO_O=$SYSROOT/usr/lib/crtbegin_so.o
    CRTEND_SO_O=$SYSROOT/usr/lib/crtend_so.o
    if [ ! -f "$CRTBEGIN_SO_O" ]; then
        CRTBEGIN_SO_O=$CRTBEGIN_EXE_O
    fi
    if [ ! -f "$CRTEND_SO_O" ]; then
        CRTEND_SO_O=$CRTEND_EXE_O
    fi

    builder_begin "$BUILDDIR" "$MAKEFILE"
    builder_set_prefix "$ABI "
    if [ -z "$LLVM_VERSION" ]; then
        builder_set_binprefix "$BINPREFIX"
    else
        builder_set_binprefix_llvm "$BINPREFIX"
        case $ABI in
            armeabi)
                LLVM_TRIPLE=armv5te-none-linux-androideabi
                ;;
            armeabi-v7a)
                LLVM_TRIPLE=armv7-none-linux-androideabi
                ;;
            x86)
                LLVM_TRIPLE=i686-none-linux-android
                ;;
            mips)
                LLVM_TRIPLE=mipsel-none-linux-android
                ;;
            *)
                LLVM_TRIPLE=le32-none-ndk
                GCC_TOOLCHAIN=
                CRTBEGIN_SO_O=
                CRTEND_SO_O=
                CRTBEGIN_EXE_O=
                CRTEND_EXE_O=
                FLAGS=-emit-llvm
                ;;
        esac
        builder_cflags "-target $LLVM_TRIPLE $FLAGS"
        builder_ldflags "-target $LLVM_TRIPLE $FLAGS"
        if [ ! -z $GCC_TOOLCHAIN ]; then
            builder_cflags "-gcc-toolchain $GCC_TOOLCHAIN"
            builder_ldflags "-gcc-toolchain $GCC_TOOLCHAIN"
        fi
    fi

    builder_cflags "--sysroot=$SYSROOT"
    builder_cxxflags "--sysroot=$SYSROOT"
    _BUILD_LDFLAGS_BEGIN_SO="--sysroot=$SYSROOT -nostdlib $CRTBEGIN_SO_O"
    _BUILD_LDFLAGS_BEGIN_EXE="--sysroot=$SYSROOT -nostdlib $CRTBEGIN_EXE_O"

    _BUILD_LDFLAGS_END_SO="$CRTEND_SO_O"
    _BUILD_LDFLAGS_END_EXE="$CRTEND_EXE_O"

    case $ABI in
        armeabi)
            if [ -z "$LLVM_VERSION" ]; then
                # add -minline-thumb1-jumptable such that gabi++/stlport/libc++ can be linked
                # with compiler-rt where helpers __gnu_thumb1_case_* (in libgcc.a) don't exist
                builder_cflags "-mthumb -minline-thumb1-jumptable"
            else
                builder_cflags "-mthumb"
            fi
            ;;
        armeabi-v7a)
            builder_cflags "-mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16"
            builder_ldflags "-march=armv7-a -Wl,--fix-cortex-a8"
            ;;
    esac
}

# $1: Build directory
# $2: Optional Makefile name
builder_begin_host ()
{
    prepare_host_build
    builder_begin "$1" "$2"
    builder_set_prefix "$HOST_TAG "
}
