#!/bin/sh
# A script-only build utility like autotools
#
# 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.
#
# Copyright (C) 2022-present, TBOOX Open Source Group.
#
# @author      ruki
# @homepage    https://github.com/xmake-io/xmake.sh
#

#-----------------------------------------------------------------------------
# some constants
#
xmake_sh_projectdir=$(X= cd -- "$(dirname -- "$0")" && pwd -P)
xmake_sh_buildir="build"
xmake_sh_version="1.0.5"
xmake_sh_verbose=false
xmake_sh_diagnosis=false
xmake_sh_copyright="Copyright (C) 2022-present Ruki Wang, tboox.org, xmake.io."
xmake_sh_makefile="${xmake_sh_projectdir}/Makefile"

#-----------------------------------------------------------------------------
# some helper functions
#
raise() {
    echo "$@" 1>&2 ; exit 1
}

vprint() {
    if "${xmake_sh_verbose}"; then
        echo "$@"
    fi
}

dprint() {
    if "${xmake_sh_diagnosis}"; then
        echo "$@"
    fi
}

# show and escape string instead of `echo -e`, because sh does not support it
print() {
    printf "${@}\n"
}

wprint() {
    if "${xmake_sh_verbose}"; then
        printf "warning: ${@}\n"
    fi
}

# test empty string
test_z() {
    if test "x${1}" = "x"; then
        return 0
    fi
    return 1
}

# test non-empty string
test_nz() {
    if test "x${1}" != "x"; then
        return 0
    fi
    return 1
}

# test string is equal
test_eq() {
    if test "x${1}" = "x${2}"; then
        return 0
    fi
    return 1
}

# test string is not equal
test_nq() {
    if test "x${1}" != "x${2}"; then
        return 0
    fi
    return 1
}

string_toupper() {
    _ret=$(echo "$1" | tr '[a-z]' '[A-Z]')
}

string_tolower() {
    _ret=$(echo "$1" | tr '[A-Z]' '[a-z]')
}

string_replace() {
    _ret=$(echo "$1" | sed "s/${2}/${3}/g")
}

# we avoid use `cut` command, because it's slow
string_split() {
    local str="${1}"
    local sep="${2}"
    local idx="${3}"
    local oldifs="${IFS}"
    IFS="${sep}"
    set -- ${str}
    if test_nz "${idx}"; then
        case "${idx}" in
            1) _ret="$1";;
            2) _ret="$2";;
            3) _ret="$3";;
            4) _ret="$4";;
            5) _ret="$5";;
            6) _ret="$6";;
        esac
    else
        _ret="$1"
        _ret2="$2"
        _ret3="$3"
        _ret4="$4"
        _ret5="$5"
        _ret6="$6"
    fi
    IFS="${oldifs}"
}

# does contain sub-string?
# e.g.
# str="src/*.cpp"
# string_contains "$str" "src"
string_contains() {
    case "${1}" in
        *${2}*) return 0;;
        *) return 1;;
    esac
    return 1
}

# does contain "*"?
string_contains_star() {
    case "${1}" in
        *\**) return 0;; # bash
        *'*'*) return 0;; # csh
        *) return 1;;
    esac
    return 1
}

# does contain "**"?
string_contains_star2() {
    case "${1}" in
        *\*\**) return 0;; # bash
        *'**'*) return 0;; # csh
        *) return 1;;
    esac
    return 1
}

# does startswith sub-string?
# e.g.
# str="src/*.cpp"
# string_startswith "$str" "src"
string_startswith() {
    case "${1}" in
        ${2}*) return 0;;
        *) return 1;;
    esac
    return 1
}

# duplicate characters
# e.g. string_dupch 10 "." => ...........
string_dupch() {
    local count=${1}
    local ch=${2}
    printf %${count}s | tr " " "${ch}"
}

# replace file content
_io_replace_file() {
    local infile="${1}"
    local outfile="${2}"
    local patterns="${3}"
    sed "/./ {${patterns}}" "${infile}" > "${outfile}"
}

# try remove file or directory
_os_tryrm() {
    if test -f "${1}"; then
        rm "${1}"
    elif test -d "${1}"; then
        rm -r "${1}"
    fi
}

# get temporary file
# https://github.com/xmake-io/xmake/issues/5464
_os_tmpfile() {
    _ret=$(mktemp "${TMPDIR-/tmp}/tmp.XXXXXXXX")
}

# try run program
_os_runv() {
    if ${xmake_sh_diagnosis}; then
        ${@}
    else
        ${@} >/dev/null 2>&1
    fi
    local ok=$?
    if test "${ok}" -ne "0"; then
        return 1
    fi
    return 0
}

# try run program and get output
_os_iorunv() {
    _os_tmpfile
    local tmpfile="${_ret}"
    ${@} >"${tmpfile}" 2>&1
    local ok=$?
    if test "${ok}" -ne "0"; then
        _ret=""
    else
        local result=$(cat "${tmpfile}")
        _ret="${result}"
    fi
    _os_tryrm "${tmpfile}"
}

# find file in the given directory
# e.g. _os_find . xmake.sh
_os_find() {
    local dir="${1}"
    local name="${2}"
    local depth="${3}"
    if test_nz "${depth}"; then
        _ret=$(find "${dir}" -maxdepth "${depth}" -mindepth "${depth}" -type f -name "${name}")
    else
        _ret=$(find "${dir}" -type f -name "${name}")
    fi
}

# get date, "%Y%m%d%H%M" -> 202212072222
# Use deterministic timestamp from SOURCE_DATE_EPOCH if available
# https://reproducible-builds.org/docs/source-date-epoch/
_os_date() {
    if test_z "${SOURCE_DATE_EPOCH}"; then
        _ret=$(date +"${1}")
    else
        # Use GNU date options first, then fallback to BSD's, and finally fallback to current time.
        _ret=$(date -u -d "@$SOURCE_DATE_EPOCH" +"${1}" 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +"${1}" 2>/dev/null || date +"${1}")
    fi
}

# we avoid use `basename`, because it's slow
path_filename() {
    local path="${1}"
    if test_eq "${path}" "/"; then
        _ret="/"
    else
        _ret="${path##*/}"
    fi
}

path_extension() {
    path_filename "${1}"; local filename="${_ret}"
    _ret=".${filename##*.}"
}

path_basename() {
    path_filename "${1}"; local filename="${_ret}"
    _ret="${filename%.*}"
}

# we avoid use `dirname -- ${1}`, because it's too slow
path_directory() {
    local path="${1}"
    if test_z "${path}"; then
        raise "invalid empty path in path_directory()."
    fi
    path="${path%/}"
    local dir="${path%/*}"
    if string_startswith "${path}" "/"; then
        if test_z "${dir}"; then
            dir="/"
        fi
    else
        dir="${dir#/}"
        if test_z "${dir}"; then
            dir="."
        fi
    fi
    _ret="${dir}"
}

# e.g. path_filename_fromdir "/tmp/file" "/tmp" -> "file"
path_filename_fromdir() {
    _ret="${1#${2}/}"
}

path_is_absolute() {
    if string_startswith "${1}" "/"; then
        return 0
    fi
    return 1
}

# get relative path, e.g $(path_relative ${rootdir} ${absolute_path}`
path_relative() {
    local source="${1}"
    local target="${2}"
    if test_z "${source}" || test_z "${target}"; then
        raise "invalid empty path in path_relative()"
    fi

    # patch missing "./"
    source=${source#./}
    source=${source#.}
    target=${target#./}
    target=${target#.}
    if test_z "${source}"; then
        _ret="${target}"
        return
    fi

    # find common path
    local result=""
    local common_part=$source
    while test_eq "${target#$common_part}" "${target}"; do
        # no match, means that candidate common part is not correct
        # go up one level (reduce common part)
        path_directory "${common_part}"; common_part="${_ret}"
        # and record that we went back, with correct / handling
        if test_z "${result}"; then
            result=".."
        else
            result="../${result}"
        fi
    done

    if test_eq "${common_part}" "/"; then
        # special case for root (no common path)
        result="${result}/"
    fi

    # since we now have identified the common part,
    # compute the non-common part
    local forward_part="${target#$common_part}"

    # and now stick all parts together
    if test_nz "${result}" && test_nz "${forward_part}"; then
        result="${result}${forward_part}"
    elif test_nz "${forward_part}"; then
        result="${forward_part#*/}"
    fi

    # same directory?
    if test_z "${result}" && test_eq "${source}" "${target}"; then
        result="."
    fi

    _ret="${result}"
}

path_sourcekind() {
    local sourcekind=""
    case "${1}" in
        *.cpp) sourcekind="cxx";;
        *.cc) sourcekind="cxx";;
        *.c) sourcekind="cc";;
        *.ixx) sourcekind="cxx";;
        *.mm) sourcekind="mxx";;
        *.m) sourcekind="mm";;
        *.S) sourcekind="as";;
        *.s) sourcekind="as";;
        *.asm) sourcekind="as";;
        *) raise "unknown sourcekind for ${1}" ;;
    esac
    _ret="${sourcekind}"
}

path_toolname() {
    local toolname=""
    case "${1}" in
        *-gcc) toolname="gcc";;
        */gcc) toolname="gcc";;
        gcc) toolname="gcc";;
        gcc-*) toolname="gcc";;
        */gcc-*) toolname="gcc";;
        *-g++) toolname="gxx";;
        */g++) toolname="gxx";;
        g++) toolname="gxx";;
        g++-*) toolname="gxx";;
        */g++-*) toolname="gxx";;
        xcrun*clang++) toolname="clangxx";;
        xcrun*clang) toolname="clang";;
        *-clang++) toolname="clangxx";;
        */clang++) toolname="clangxx";;
        clang++) toolname="clangxx";;
        clang++-*) toolname="clangxx";;
        */clang++-*) toolname="clangxx";;
        *-clang) toolname="clang";;
        */clang) toolname="clang";;
        clang) toolname="clang";;
        clang-*) toolname="clang";;
        */clang-*) toolname="clang";;
        */emcc) toolname="emcc";;
        emcc) toolname="emcc";;
        */em++) toolname="emxx";;
        em++) toolname="emxx";;
        */cosmocc) toolname="cosmocc";;
        cosmocc) toolname="cosmocc";;
        */cosmoc++) toolname="cosmocxx";;
        cosmoc++) toolname="cosmocxx";;
        *-ar) toolname="ar";;
        */ar) toolname="ar";;
        ar) toolname="ar";;
        */emar) toolname="emar";;
        emar) toolname="emar";;
        */cosmoar) toolname="cosmoar";;
        cosmoar) toolname="cosmoar";;
        cc) toolname="gcc";;
        */cc) toolname="gcc";;
        c++) toolname="gxx";;
        */c++) toolname="gxx";;
        tcc) toolname="tcc";;
        */tcc) toolname="tcc";;
        *) raise "unknown tool ${1}";;
    esac
    _ret="${toolname}"
}

# get flag name from toolkind, e.g. cc => cflags, cxx => cxxflags
_get_flagname() {
    local toolkind="${1}"
    local flagname=""
    case "${toolkind}" in
        cc) flagname="cflags";;
        cxx) flagname="cxxflags";;
        as) flagname="asflags";;
        mm) flagname="mflags";;
        mxx) flagname="mxxflags";;
        ar) flagname="arflags";;
        sh) flagname="shflags";;
        ld) flagname="ldflags";;
        *) raise "unknown toolkind(${toolkind})!" ;;
    esac
    _ret="${flagname}"
}

# is enabled? true, yes, y
_is_enabled() {
    local value=${1}
    if test_eq "${value}" "true"; then
        return 0
    elif test_eq "${value}" "yes"; then
        return 0
    elif test_eq "${value}" "y"; then
        return 0
    fi
    return 1
}

# deduplicate string list
# .e.g "hello world hello how are you world" -> hello world how are you
_dedup() {
    _ret=$(echo "${1}" | awk '{for (i = 1; i <= NF; ++i) if (!seen[$i]++) printf $i " "}')
}

# deduplicate string list from the reverse order
# .e.g "hello world hello how are you world" -> hello how are you world
_dedup_reverse() {
    local result=""
    local list=""
    local item=""
    list=$(echo "${1}" | awk '{for (i = NF; i > 0; --i) if (!seen[$i]++) printf $i " "}')
    for item in ${list}; do
        result="${item} ${result}"
    done
    _ret="${result}"
}

#-----------------------------------------------------------------------------
# map functions
#

# define map, @note we can not use bash/declare to define map, because sh does not support it.
#
# _map "options"
# _map_set "options" "key1" "value1"
# _map_set "options" "key2" "value2"
# _map_set "options" "key2" "value3"
# _map_set "options" "key3" "value3"
# _map_set "options" "key4" "__empty__"
# _map_set "options" "key4" "__empty__"
# _map_count "options"; _count="${_ret}"
# _map_keys "options"; _keys="${_ret}"
# echo ${_count}
# for key in ${_keys}; do
#     _map_get "options" ${key}; value="{_ret}"
#     echo ${key} "->" ${value}
# done
#
# echo "------"
# _map_remove "options" "key3"
# _map_count "options"; _count="${_ret}"
# _map_keys "options"; _keys="${_ret}"
# echo ${_count}
# for key in ${_keys}; do
#     _map_get "options" ${key}; value="{_ret}"
#     echo ${key} "->" ${value}
# done
#
_map() {
    local name=${1}
#    eval _map_${name}_count=0
#    eval _map_${name}_keys=""
}

# because the shell is slow, we have to temporarily
# disable some of the map features for performance.
#
#_map_count() {
#    local name=${1}
#    local count=$(eval echo \$_map_${name}_count)
#    _ret="${count}"
#}

_map_get() {
    local name="${1}"
    local key="${2}"
    _ret=$(eval echo \$_map_${name}_value_${key})
    if test_eq "${_ret}" "__empty__"; then
        _ret=""
    fi
}

_map_has() {
    local name="${1}"
    local key="${2}"
    local value=""
    value=$(eval echo \$_map_${name}_value_${key})
    if test_nz "${value}"; then
        return 0
    fi
    return 1
}

_map_set() {
    local name="${1}"
    local key="${2}"
    local value="${3}"
#    if ! _map_has ${name} ${key}; then
#        _map_count "options"; local count="${_ret}"
#        eval _map_${name}_count=$((${count} + 1))
#        local keys=$(eval echo \$_map_${name}_keys)
#        keys="${keys} ${key}"
#        eval _map_${name}_keys=\${keys}
#    fi
    eval _map_${name}_value_${key}=\${value}
}

#_map_remove() {
#    local name="${1}"
#    local key="${2}"
#    if _map_has ${name} ${key}; then
#        _map_count "options"; local count="${_ret}"
#        eval _map_${name}_count=$((${count} - 1))
#        eval _map_${name}_value_${key}=""
#        local keys=$(eval echo \$_map_${name}_keys)
#        local keys_new=""
#        local k=""
#        for k in ${keys}; do
#            if test_nq "${k}" "${key}"; then
#                keys_new="${keys_new} ${k}"
#            fi
#        done
#        eval _map_${name}_keys=\${keys_new}
#    fi
#}

#_map_keys() {
#    local name="${1}"
#    local keys=$(eval echo \$_map_${name}_keys)
#    _ret="${keys}"
#}

#-----------------------------------------------------------------------------
# detect default environments
#

# detect hosts
os_host=`uname`
string_tolower ${os_host}; os_host="${_ret}"
if echo "${os_host}" | grep cygwin >/dev/null 2>&1; then
    os_host="cygwin"
fi
if echo "${os_host}" | grep msys >/dev/null 2>&1; then
    os_host="msys"
fi
if echo "${os_host}" | grep mingw >/dev/null 2>&1; then
    os_host="msys"
fi
if echo "${os_host}" | grep darwin >/dev/null 2>&1; then
    os_host="macosx"
fi
if echo "${os_host}" | grep linux >/dev/null 2>&1; then
    os_host="linux"
fi
if echo "${os_host}" | grep freebsd >/dev/null 2>&1; then
    os_host="freebsd"
fi
if echo "${os_host}" | grep bsd >/dev/null 2>&1; then
    os_host="bsd"
fi
if echo "${os_host}" | grep Haiku >/dev/null 2>&1; then
    os_host="haiku"
fi

# determining host
# e.g.
# if is_host "linux" "macosx"; then
#     ...
# fi
is_host() {
    local host=""
    for host in $@; do
        if test_eq "${os_host}" "${host}"; then
            return 0
        fi
    done
    return 1
}

# detect host architecture
os_arch=`uname -m | tr '[A-Z]' '[a-z]'`
if test_eq "${os_arch}" "i686"; then
    os_arch="i386"
elif test_eq "${os_arch}" "aarch64" || test_eq "${os_arch}" "arm64"; then
    os_arch="arm64"
elif string_contains "${os_arch}" "armv7"; then
    os_arch="armv7"
elif string_contains "${os_arch}" "arm"; then
    os_arch="arm"
elif string_contains "${os_arch}" "power macintosh"; then
    os_arch="ppc"
fi

# set the default target platform
_target_plat_default=${os_host}
if is_host "msys"; then
    _target_plat_default="mingw"
elif is_host "freebsd"; then
    _target_plat_default="bsd"
elif test_nz "${EMSDK}"; then
    _target_plat_default="wasm"
fi

# set the default target architecture
_target_arch_default=${os_arch}
if is_host "msys" && test_nz "${MSYSTEM_CARCH}"; then
    _target_arch_default="${MSYSTEM_CARCH}"
elif test_nz "${TERMUX_ARCH}"; then
    _target_arch_default="${TERMUX_ARCH}"
elif test_nz "${EMSDK}"; then
    _target_arch_default="wasm32"
fi
if test_eq "${_target_arch_default}" "i686"; then
    _target_arch_default="i386"
elif test_eq "${_target_arch_default}" "aarch64" || test_eq "${_target_arch_default}" "arm64"; then
    _target_arch_default="arm64"
elif string_contains "${_target_arch_default}" "armv7"; then
    _target_arch_default="armv7"
elif string_contains "${_target_arch_default}" "arm"; then
    _target_arch_default="arm"
fi

# set the default target mode
_target_mode_default="release"

# set the default target kind
_target_kind_default="static"

# set the default project generator and build program
if is_host "freebsd" "bsd"; then
    project_generator="gmake"
    _make_program_default="gmake"
    _ninja_program_default="ninja"
elif is_host "msys" "cygwin"; then
    project_generator="gmake"
    _make_program_default="make.exe"
    _ninja_program_default="ninja.exe"
else
    project_generator="gmake"
    _make_program_default="make"
    _ninja_program_default="ninja"
fi

# set the default directories
if test -d "/usr/local"; then
    _install_prefix_default="/usr/local"
elif test -d "/usr"; then
    _install_prefix_default="/usr"
fi
_install_bindir_default="\${prefix}/bin"
_install_libdir_default="\${prefix}/lib"
_install_includedir_default="\${prefix}/include"

# determining target platform
# e.g.
# if is_plat "linux" "macosx"; then
#     ...
# fi
is_plat() {
    local plat=""
    for plat in $@; do
        if test_eq "${_target_plat}" "${plat}"; then
            return 0
        fi
    done
    return 1
}

# determining target architecture
# e.g.
# if is_arch "x86_64" "i386"; then
#     ...
# fi
is_arch() {
    local arch=""
    for arch in $@; do
        if test_eq "${_target_arch}" "${arch}"; then
            return 0
        fi
    done
    return 1
}

# determining target mode
# e.g.
# if is_mode "release"; then
#     ...
# fi
is_mode() {
    local mode=""
    for mode in $@; do
        if test_eq "${_target_mode}" "${mode}"; then
            return 0
        fi
    done
    return 1
}

# determining target kind
# e.g.
# if is_kind "release"; then
#     ...
# fi
is_kind() {
    local kind=""
    for kind in $@; do
        if test_eq "${_target_kind}" "${kind}"; then
            return 0
        fi
    done
    return 1
}

# determining target toolchain
# e.g.
# if is_toolchain "clang"; then
#     ...
# fi
is_toolchain() {
    local toolchain=""
    for toolchain in $@; do
        if test_eq "${_target_toolchain}" "${toolchain}"; then
            return 0
        fi
    done
    return 1
}

#-----------------------------------------------------------------------------
# project configuration apis
#

# set project name
set_project() {
    _xmake_sh_project_name="${1}"
}

# include the given xmake.sh file or directory
# e.g. includes "src" "tests"
includes() {
    local path=""
    for path in $@; do
        if test -f "${path}"; then
            path_directory "${path}"; xmake_sh_scriptdir="${_ret}"
            . "${path}"
        else
            local xmake_sh_scriptdir_cur=${xmake_sh_scriptdir}
            if test "x${xmake_sh_scriptdir}" != "x"; then
                xmake_sh_scriptdir="${xmake_sh_scriptdir_cur}/${path}"
                . "${xmake_sh_scriptdir}/xmake.sh"
            else
                . "${xmake_sh_projectdir}/${path}/xmake.sh"
            fi
            xmake_sh_scriptdir=${xmake_sh_scriptdir_cur}
        fi
    done
}

#-----------------------------------------------------------------------------
# some helper functions
#

# split flags
_split_flags() {
    string_replace "${1}" ":" " "
}

# get abstract flag for gcc/clang
_get_abstract_flag_for_gcc_clang() {
    local toolkind="${1}"
    local toolname="${2}"
    local itemname="${3}"
    local value="${4}"
    local flag=""
    case "${itemname}" in
        defines)
            string_replace "${value}" '"' '\\\"'; value="${_ret}"
            flag="-D${value}"
            ;;
        undefines) flag="-U${value}";;
        includedirs) flag="-I${value}";;
        linkdirs) flag="-L${value}";;
        links) flag="-l${value}";;
        syslinks) flag="-l${value}";;
        frameworks) flag="-framework ${value}";;
        frameworkdirs) flag="-F${value}";;
        rpathdirs)
            if is_plat "macosx"; then
                string_replace "${value}" "\$ORIGIN" "@loader_path"; value="${_ret}"
                flag="-Xlinker -rpath -Xlinker ${value}"
            else
                # escape $ORIGIN in makefile, TODO we need also handle it for ninja
                string_replace "${value}" "@loader_path" '$$ORIGIN'; value="${_ret}"
                if is_plat "bsd"; then
                    flag="-Wl,-zorigin -Wl,-rpath='${value}'"
                else
                    flag="-Wl,-rpath='${value}'"
                fi
            fi
            ;;
        symbols)
            if test_eq "${value}" "debug"; then
                flag="-g"
            elif test_eq "${value}" "hidden"; then
                flag="-fvisibility=hidden"
            fi
            ;;
        strip)
            if test_eq "${value}" "debug"; then
                flag="-Wl,-S"
            elif test_eq "${value}" "all"; then
                if is_plat "macosx"; then
                    flag="-Wl,-x -Wl,-dead_strip"
                else
                    flag="-s"
                fi
            fi
            ;;
        warnings)
            if test_eq "${value}" "all" || test_eq "${value}" "more" || test_eq "${value}" "less"; then
                flag="-Wall"
            elif test_eq "${value}" "allextra"; then
                flag="-Wall -Wextra"
            elif test_eq "${value}" "error"; then
                flag="-Werror"
            elif test_eq "${value}" "everything"; then
                flag="-Wall -Wextra"
            elif test_eq "${value}" "none"; then
                flag="-w"
            fi
            ;;
        optimizes)
            if test_eq "${value}" "fast"; then
                flag="-O1"
            elif test_eq "${value}" "faster"; then
                flag="-O2"
            elif test_eq "${value}" "fastest"; then
                flag="-O3"
            elif test_eq "${value}" "smallest"; then
                if test_eq "${toolname}" "clang" || test_eq "${toolname}" "clangxx"; then
                    flag="-Oz"
                else
                    flag="-Os"
                fi
            elif test_eq "${value}" "aggressive"; then
                flag="-Ofast"
            elif test_eq "${value}" "none"; then
                flag="-O0"
            fi
            ;;
        languages)
            if test_eq "${toolkind}" "cc" || test_eq "${toolkind}" "mm"; then
                case "${value}" in
                    ansi) flag="-ansi";;
                    c89) flag="-std=c89";;
                    gnu89) flag="-std=gnu89";;
                    c99) flag="-std=c99";;
                    gnu99) flag="-std=gnu99";;
                    c11) flag="-std=c11";;
                    gnu11) flag="-std=gnu11";;
                    c17) flag="-std=c17";;
                    gnu17) flag="-std=gnu17";;
                esac
            elif test_eq "${toolkind}" "cxx" || test_eq "${toolkind}" "mxx"; then
                case "${value}" in
                    cxx98) flag="-std=c++98";;
                    c++98) flag="-std=c++98";;
                    gnuxx98) flag="-std=gnu++98";;
                    gnu++98) flag="-std=gnu++98";;

                    cxx11) flag="-std=c++11";;
                    c++11) flag="-std=c++11";;
                    gnuxx11) flag="-std=gnu++11";;
                    gnu++11) flag="-std=gnu++11";;

                    cxx14) flag="-std=c++14";;
                    c++14) flag="-std=c++14";;
                    gnuxx14) flag="-std=gnu++14";;
                    gnu++14) flag="-std=gnu++14";;

                    cxx17) flag="-std=c++17";;
                    c++17) flag="-std=c++17";;
                    gnuxx17) flag="-std=gnu++17";;
                    gnu++17) flag="-std=gnu++17";;

                    cxx1z) flag="-std=c++1z";;
                    c++1z) flag="-std=c++1z";;
                    gnuxx1z) flag="-std=gnu++1z";;
                    gnu++1z) flag="-std=gnu++1z";;

                    cxx2a) flag="-std=c++2a";;
                    c++2a) flag="-std=c++2a";;
                    gnuxx2a) flag="-std=gnu++2a";;
                    gnu++2a) flag="-std=gnu++2a";;

                    cxx20) flag="-std=c++20";;
                    c++20) flag="-std=c++20";;
                    gnuxx20) flag="-std=gnu++20";;
                    gnu++20) flag="-std=gnu++20";;
                    cxx*) raise "unknown language value(${value})!" ;;
                    c++*) raise "unknown language value(${value})!" ;;
                esac
            fi
            ;;
        *) raise "unknown itemname(${itemname})!" ;;
    esac
    _ret="${flag}"
}

# get abstract flags
_get_abstract_flags() {
    local toolkind="${1}"
    local toolname="${2}"
    local itemname="${3}"
    local values="${4}"
    local flags=""
    local value=""
    for value in ${values}; do
        local flag=""
        case "${toolname}" in
            gcc) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            gxx) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            clang) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            clangxx) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            emcc) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            emxx) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            cosmocc) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            cosmocxx) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            tcc) _get_abstract_flag_for_gcc_clang "${toolkind}" "${toolname}" "${itemname}" "${value}"; flag="${_ret}";;
            *) raise "unknown toolname(${toolname})!" ;;
        esac
        if test_nz "${flag}"; then
            flags="${flags} ${flag}"
        fi
    done
    _ret="${flags}"
}

#-----------------------------------------------------------------------------
# option configuration apis
#

# define option
option() {
    local name="${1}"
    local description="${2}"
    local default=${3}
    _xmake_sh_option_current="${name}"
    if ! ${_loading_options}; then
        if test_nz "${description}"; then
            _xmake_sh_option_current=""
        fi
        return
    fi
    if ! _map_has "options" "${name}_name"; then
        _xmake_sh_options="${_xmake_sh_options} ${name}"
    fi
    _map_set "options" "${name}_name" "${name}"
    _map_set "options" "${name}_description" "${description}"
    _map_set "options" "${name}_default" "${default}"
    # we end option if it's just one line
    if test_nz "${description}"; then
        _xmake_sh_option_current=""
    fi
    return 0
}
option_end() {
    _xmake_sh_option_current=""
}
_map "options"

# has the given option?
_has_option() {
    local name=${1}
    if _map_has "options" "${name}_name"; then
        return 0
    fi
    return 1
}

# get the given option item
_get_option_item() {
    local name=${1}
    local key=${2}
    _map_get "options" "${name}_${key}"
}

# set the given option item
_set_option_item() {
    local name=${1}
    local key=${2}
    shift
    shift
    if test_nz "${name}"; then
        _map_set "options" "${name}_${key}" "${@}"
    else
        raise "please call set_${key}(${@}) in the option scope!"
    fi
}

# add values to the given option item
_add_option_item() {
    local name=${1}
    local key=${2}
    shift
    shift
    if test_nz "${name}"; then
        _map_get "options" "${name}_${key}"; local values="${_ret}"
        values="${values} ${@}"
        _map_set "options" "${name}_${key}" "${values}"
    else
        raise "please call add_${key}(${@}) in the option scope!"
    fi
}

# get the give option value
_get_option_value() {
    local name=${1}
    _get_option_item "${name}" "value"
    if test "x${_ret}" = "x"; then
        _get_option_item "${name}" "default"
    fi
}

# set the give option value
_set_option_value() {
    local name=${1}
    local value=${2}
    _set_option_item "${name}" "value" "${value}"
}

# this option need checking?
_option_need_checking() {
    local name="${1}"
    _get_option_item "${name}" "default"; local default="${_ret}"
    if test_nz "${default}"; then
        return 1
    fi
    _get_option_item "${name}" "cfuncs"; local cfuncs="${_ret}"
    _get_option_item "${name}" "cxxfuncs"; local cxxfuncs="${_ret}"
    _get_option_item "${name}" "cincludes"; local cincludes="${_ret}"
    _get_option_item "${name}" "cxxincludes"; local cxxincludes="${_ret}"
    _get_option_item "${name}" "ctypes"; local ctypes="${_ret}"
    _get_option_item "${name}" "cxxtypes"; local cxxtypes="${_ret}"
    _get_option_item "${name}" "csnippets"; local csnippets="${_ret}"
    _get_option_item "${name}" "cxxsnippets"; local cxxsnippets="${_ret}"
    _get_option_item "${name}" "links"; local links="${_ret}"
    _get_option_item "${name}" "syslinks"; local syslinks="${_ret}"
    if test_nz "${cfuncs}" || test_nz "${cxxfuncs}" ||
       test_nz "${cincludes}" || test_nz "${cxxincludes}" ||
       test_nz "${ctypes}" || test_nz "${cxxtypes}" ||
       test_nz "${csnippets}" || test_nz "${cxxsnippets}" ||
       test_nz "${links}" || test_nz "${syslinks}"; then
        return 0
    fi
    return 1
}

# get options for the help menu
_get_options_for_menu() {
    local options=""
    local name=""
    for name in ${_xmake_sh_options}; do
        _get_option_item "${name}" "showmenu"; local showmenu="${_ret}"
        if _is_enabled "${showmenu}"; then
            options="${options} ${name}"
        elif test_z "${showmenu}" && ! _option_need_checking "${name}"; then
            options="${options} ${name}"
        fi
    done
    _ret="${options}"
}

# get options for checking
_get_options_for_checking() {
    local options=""
    local name=""
    for name in ${_xmake_sh_options}; do
        _get_option_item "${name}" "showmenu"; local showmenu="${_ret}"
        if test_z "${showmenu}" && _option_need_checking "${name}"; then
            options="${options} ${name}"
        fi
    done
    _ret="${options}"
}

# get abstract flags in option
_get_option_abstract_flags() {
    local name="${1}"
    local toolkind="${2}"
    local toolname="${3}"
    local itemname="${4}"
    local values="${5}"
    if test_z "${values}"; then
        _get_option_item "${name}" "${itemname}"; values="${_ret}"
    fi
    _get_abstract_flags "${toolkind}" "${toolname}" "${itemname}" "${values}"
}

# is config for option
is_config() {
    if ! ${_loading_targets}; then
        return 1
    fi
    local name=${1}
    local value=${2}
    _get_option_value "${name}"; local value_cur="${_ret}"
    if test_eq "${value_cur}" "${value}"; then
        return 0
    fi
    return 1
}

# has config for option
has_config() {
    if ! ${_loading_targets}; then
        return 1
    fi
    local name=${1}
    _get_option_value "${name}"; local value_cur="${_ret}"
    if _is_enabled ${value_cur}; then
        return 0
    fi
    return 1
}

# set config for option, we can use it to modify option status when loading targets
set_config() {
    local name=${1}
    local value=${2}
    _set_option_value "${name}" "${value}"
}

# set showmenu in option
set_showmenu() {
    if ! ${_loading_options}; then
        return
    fi
    local show="${1}"
    _set_option_item "${_xmake_sh_option_current}" "showmenu" "${show}"
}

# set description in option
set_description() {
    if ! ${_loading_options}; then
        return
    fi
    local description="${1}"
    _set_option_item "${_xmake_sh_option_current}" "description" "${description}"
}

# add cfuncs in option
add_cfuncs() {
    if ! ${_loading_options}; then
        return
    fi
    _add_option_item "${_xmake_sh_option_current}" "cfuncs" "${@}"
}

# add cxxfuncs in option
add_cxxfuncs() {
    if ! ${_loading_options}; then
        return
    fi
    _add_option_item "${_xmake_sh_option_current}" "cxxfuncs" "${@}"
}

# add cincludes in option
add_cincludes() {
    if ! ${_loading_options}; then
        return
    fi
    _add_option_item "${_xmake_sh_option_current}" "cincludes" "${@}"
}

# add cxxincludes in option
add_cxxincludes() {
    if ! ${_loading_options}; then
        return
    fi
    _add_option_item "${_xmake_sh_option_current}" "cxxincludes" "${@}"
}

# add ctypes in option
add_ctypes() {
    if ! ${_loading_options}; then
        return
    fi
    _add_option_item "${_xmake_sh_option_current}" "ctypes" "${@}"
}

# add cxxtypes in option
add_cxxtypes() {
    if ! ${_loading_options}; then
        return
    fi
    _add_option_item "${_xmake_sh_option_current}" "cxxtypes" "${@}"
}

# add csnippets in option
add_csnippets() {
    if ! ${_loading_options}; then
        return
    fi
    local csnippets="${1}"
    _add_option_item "${_xmake_sh_option_current}" "csnippets" "${csnippets}"
}

# add cxxsnippets in option
add_cxxsnippets() {
    if ! ${_loading_options}; then
        return
    fi
    local cxxsnippets="${1}"
    _add_option_item "${_xmake_sh_option_current}" "cxxsnippets" "${cxxsnippets}"
}

# before_check in option
before_check() {
    if ! ${_loading_options}; then
        return
    fi
    local funcname="${1}"
    _add_option_item "${_xmake_sh_option_current}" "before_check" "${funcname}"
}

#-----------------------------------------------------------------------------
# target configuration apis
#

# define target
target() {
    local name="${1}"
    _xmake_sh_target_current="${name}"
    if ! ${_loading_targets}; then
        return
    fi
    if ! _map_has "targets" "${name}_name"; then
        _xmake_sh_targets="${_xmake_sh_targets} ${name}"
    fi
    _map_set "targets" "${name}_name" "${name}"
    return 0
}
target_end() {
    _xmake_sh_target_current=""
}
_map "targets"

# has the given target?
_has_target() {
    local name=${1}
    if _map_has "targets" "${name}_name"; then
        return 0
    fi
    return 1
}

# has the given target item
_has_target_item() {
    local name=${1}
    local key=${2}
    if _map_has "targets" "${name}_${key}"; then
        return 0
    elif _map_has "targets" "__root_${key}"; then
        return 0
    fi
    return 1
}

# get the given target item
_get_target_item() {
    local name=${1}
    local key=${2}
    _map_get "targets" "${name}_${key}"
    local values="${_ret}"
    if _map_has "targets" "__root_${key}"; then
        _map_get "targets" "__root_${key}"; local root_values="${_ret}"
        if test_nz "${values}"; then
            values="${root_values} ${values}"
        else
            values="${root_values}"
        fi
    fi
    _ret="${values}"
}

# set the given target item
_set_target_item() {
    local name=${1}
    local key=${2}
    shift
    shift
    if test_nz "${name}"; then
        _map_set "targets" "${name}_${key}" "${@}"
    else
        _map_set "targets" "__root_${key}" "${@}"
    fi
}

# add values to the given target item
_add_target_item() {
    local name=${1}
    local key=${2}
    shift
    shift
    if test_nz "${name}"; then
        _map_get "targets" "${name}_${key}"; local values="${_ret}"
        values="${values} ${@}"
        _map_set "targets" "${name}_${key}" "${values}"
    else
        _map_get "targets" "__root_${key}"; local values="${_ret}"
        values="${values} ${@}"
        _map_set "targets" "__root_${key}" "${values}"
    fi
}

# is default?
_is_target_default() {
    local name="${1}"
    if _has_target_item "${name}" "default"; then
        _get_target_item "${target}" "default"; local default="${_ret}"
        if _is_enabled ${default}; then
            return 0
        fi
        return 1
    fi
    return 0
}

# get target basename
_get_target_basename() {
    local name="${1}"
    _get_target_item "${name}" "basename"; local basename="${_ret}"
    if test_z "${basename}"; then
        basename="${name}"
    fi
    _ret="${basename}"
}

# get target extension
_get_target_extension() {
    local name="${1}"
    local extension=""
    if _has_target_item "${name}" "extension"; then
        _get_target_item "${name}" "extension"; extension="${_ret}"
    elif is_plat "mingw"; then
        _get_target_item "${name}" "kind"; local kind="${_ret}"
        if test "x${kind}" = "xbinary"; then
            extension=".exe"
        elif test "x${kind}" = "xstatic"; then
            extension=".a"
        elif test "x${kind}" = "xshared"; then
            extension=".dll"
        fi
    else
        _get_target_item "${name}" "kind"; local kind="${_ret}"
        if test "x${kind}" = "xstatic"; then
            extension=".a"
        elif test "x${kind}" = "xshared"; then
            if is_plat "macosx"; then
                extension=".dylib"
            else
                extension=".so"
            fi
        fi
    fi
    _ret="${extension}"
}

# get target prefixname
_get_target_prefixname() {
    local name="${1}"
    local prefixname=""
    if _has_target_item "${name}" "prefixname"; then
        _get_target_item "${name}" "prefixname"; prefixname="${_ret}"
    elif is_plat "mingw"; then
        _get_target_item "${name}" "kind"; local kind="${_ret}"
        if test "x${kind}" = "xstatic"; then
            prefixname="lib"
        elif test "x${kind}" = "xshared"; then
            prefixname="lib"
        fi
    else
        _get_target_item "${name}" "kind"; local kind="${_ret}"
        if test "x${kind}" = "xstatic"; then
            prefixname="lib"
        elif test "x${kind}" = "xshared"; then
            prefixname="lib"
        fi
    fi
    _ret="${prefixname}"
}

# get target filename
_get_target_filename() {
    local name="${1}"
    _get_target_item "${name}" "filename"; local filename="${_ret}"
    if test_z "${filename}"; then
        _get_target_basename "${name}"; local basename="${_ret}"
        _get_target_extension "${name}"; local extension="${_ret}"
        _get_target_prefixname "${name}"; local prefixname="${_ret}"
        filename="${prefixname}${basename}${extension}"
    fi
    _ret="${filename}"
}

# get target soname
# @see https://github.com/tboox/tbox/issues/214
#
# set_version "1.0.1" "" "1" -> libfoo.so.1, libfoo.1.dylib
# set_version "1.0.1" "" "A" -> libfoo.so.A, libfoo.A.dylib
_get_target_soname() {
    local soname=""
    local name="${1}"
    _get_target_item "${name}" "kind"; local targetkind="${_ret}"
    if test_eq "${targetkind}" "shared" && is_plat "macosx" "linux" "bsd"; then
        _get_target_item "${name}" "version"; local version="${_ret}"
        _get_target_item "${name}" "version_soname"; local version_soname="${_ret}"
        if test_nz "${version}" && test_nz "${version_soname}"; then
            _get_target_filename "${name}"; soname="${_ret}"
            _get_target_extension "${name}"; local extension="${_ret}"
            if test_eq "${extension}" ".dylib"; then
                path_basename "${soname}"; local basename="${_ret}"
                soname="${basename}.${version_soname}${extension}"
            else
                soname="${soname}.${version_soname}"
            fi
        fi
    fi
    _ret="${soname}"
}

# get target directory
_get_targetdir() {
    local name="${1}"
    _get_target_item "${name}" "targetdir"; local targetdir="${_ret}"
    if test_z "${targetdir}"; then
        targetdir="${xmake_sh_buildir}/${_target_plat}/${_target_arch}/${_target_mode}"
    fi
    _ret="${targetdir}"
}

# get target object directory
_get_target_objectdir() {
    local name="${1}"
    _get_target_item "${name}" "objectdir"; local objectdir="${_ret}"
    if test_z "${objectdir}"; then
        objectdir="${xmake_sh_buildir}/.objs/${name}/${_target_plat}/${_target_arch}/${_target_mode}"
    fi
    _ret="${objectdir}"
}

# get target file path
_get_target_file() {
    local name="${1}"
    _get_target_filename "${name}"; local filename="${_ret}"
    _get_targetdir "${name}"; local targetdir="${_ret}"
    local targetfile="${targetdir}/${filename}"
    _ret="${targetfile}"
}

# get target librarydeps
_get_target_librarydeps_impl() {
    local name="${1}"
    local librarydeps=""
    local dep=""
    _get_target_item "${name}" "deps"; local deps="${_ret}"
    for dep in ${deps}; do
        _get_target_item "${dep}" "kind"; local dep_kind="${_ret}"
        if test_eq "${dep_kind}" "static" || test_eq "${dep_kind}" "shared"; then
            librarydeps="${librarydeps} ${dep}"
            _get_target_librarydeps_impl "${dep}"; local dep_librarydeps="${_ret}"
            if test_nz "${dep_librarydeps}"; then
                librarydeps="${librarydeps} ${dep_librarydeps}"
            fi
        fi
    done
    _ret="${librarydeps}"
}

_get_target_librarydeps() {
    local name="${1}"
    _get_target_item "${name}" "librarydeps"; local librarydeps="${_ret}"
    if test_z "${librarydeps}" && test_nq "${librarydeps}" "__none__"; then
        _get_target_librarydeps_impl "${name}"; librarydeps="${_ret}"
        if test_nz "${librarydeps}"; then
            _dedup_reverse "${librarydeps}"; librarydeps="${_ret}"
            _set_target_item "${name}" "librarydeps" "${librarydeps}"
        else
            _set_target_item "${name}" "librarydeps" "__none__"
        fi
    fi
    if test_eq "${librarydeps}" "__none__"; then
        librarydeps=""
    fi
    _ret="${librarydeps}"
}

# get sourcefiles in target
_get_target_sourcefiles() {
    local name="${1}"
    _get_target_item "${name}" "files"
}

# get objectfile in target
_get_target_objectfile() {
    local name="${1}"
    local sourcefile="${2}"
    local extension=".o"
    if is_plat "mingw"; then
        extension=".obj"
    fi
    _get_target_objectdir "${name}"; local objectdir="${_ret}"
    local objectfile="${objectdir}/${sourcefile}${extension}"
    _ret="${objectfile}"
}

# get objectfiles in target
_get_target_objectfiles() {
    local name="${1}"
    _get_target_sourcefiles "${name}"; local sourcefiles="${_ret}"
    local objectfiles=""
    local sourcefile=""
    for sourcefile in ${sourcefiles}; do
        _get_target_objectfile "${name}" "${sourcefile}"; local objectfile="${_ret}"
        objectfiles="${objectfiles} ${objectfile}"
    done
    _ret="${objectfiles}"
}

# get abstract flags in target
_get_target_abstract_flags() {
    local name="${1}"
    local toolkind="${2}"
    local toolname="${3}"
    local itemname="${4}"
    local values="${5}"
    if test_z "${values}"; then

        # get values from target
        _get_target_item "${name}" "${itemname}"; values="${_ret}"

        # get values from target deps
        _get_target_librarydeps "${name}"; local deps="${_ret}"
        local dep=""
        for dep in ${deps}; do
            _get_target_item "${dep}" "kind"; local dep_kind="${_ret}"
            if test_eq "${dep_kind}" "static" || test_eq "${dep_kind}" "shared"; then
                _get_target_item "${dep}" "${itemname}_public"; local depvalues="${_ret}"
                if test_nz "${depvalues}"; then
                    values="${values} ${depvalues}"
                fi
            fi
        done
    fi
    if test_nz "${values}"; then
        _get_abstract_flags "${toolkind}" "${toolname}" "${itemname}" "${values}"
    else
        _ret=""
    fi
}

# get toolchain flags for ar in target
_get_target_toolchain_flags_for_ar() {
    _ret="-cr"
}

# get toolchain flags for gcc in target
_get_target_toolchain_flags_for_gcc() {
    local name="${1}"
    local toolkind="${2}"
    local flags=""
    if is_arch "i386"; then
        flags="${flags} -m32"
    fi
    _get_target_item "${name}" "kind"; local targetkind="${_ret}"
    if test_eq "${targetkind}" "shared"; then
        if test_eq "${toolkind}" "sh"; then
            flags="${flags} -shared -fPIC"
        elif test_eq "${toolkind}" "cc" || test_eq "${toolkind}" "cxx"; then
            flags="${flags} -fPIC"
        fi
        # @see https://github.com/tboox/tbox/issues/214
        if test_eq "${toolkind}" "sh"; then
            _get_target_soname "${name}"; local soname="${_ret}"
            if test_nz "${soname}"; then
                if is_plat "macosx"; then
                    flags="${flags} -Wl,-install_name,${soname}"
                else
                    flags="${flags} -Wl,-soname,${soname}"
                fi
            fi
        fi
    fi
    _ret="${flags}"
}

# get toolchain flags for clang in target
_get_target_toolchain_flags_for_clang() {
    local name="${1}"
    local toolkind="${2}"
    local flags="-Qunused-arguments"
    if is_arch "i386"; then
        flags="${flags} -m32"
    fi
    _get_target_item "${name}" "kind"; local targetkind="${_ret}"
    if test_eq "${targetkind}" "shared"; then
        if test_eq "${toolkind}" "sh"; then
            flags="${flags} -shared -fPIC"
        elif test_eq "${toolkind}" "cc" || test_eq "${toolkind}" "cxx"; then
            flags="${flags} -fPIC"
        fi
        # @see https://github.com/tboox/tbox/issues/214
        if test_eq "${toolkind}" "sh"; then
            _get_target_soname "${name}"; local soname="${_ret}"
            if test_nz "${soname}"; then
                if is_plat "macosx"; then
                    flags="${flags} -Wl,-install_name,${soname}"
                else
                    flags="${flags} -Wl,-soname,${soname}"
                fi
            fi
        fi
    fi
    if is_plat "macosx"; then
        _os_iorunv "xcrun" "-sdk" "macosx" "--show-sdk-path"; local sdkdir="${_ret}"
        if test_nz "${sdkdir}"; then
            flags="${flags} -isysroot \"${sdkdir}\""
        fi
    fi
    _ret="${flags}"
}

# get toolchain flags for tcc in target
_get_target_toolchain_flags_for_tcc() {
    local name="${1}"
    local toolkind="${2}"
    local flags=""
    if is_arch "i386"; then
        flags="${flags} -m32"
    fi
    _get_target_item "${name}" "kind"; local targetkind="${_ret}"
    if test_eq "${targetkind}" "shared"; then
        if test_eq "${toolkind}" "sh"; then
            flags="${flags} -shared -fPIC"
        elif test_eq "${toolkind}" "cc" || test_eq "${toolkind}" "cxx"; then
            flags="${flags} -fPIC"
        fi
        # @see https://github.com/tboox/tbox/issues/214
        if test_eq "${toolkind}" "sh"; then
            _get_target_soname "${name}"; local soname="${_ret}"
            if test_nz "${soname}"; then
                if is_plat "macosx"; then
                    flags="${flags} -Wl,-install_name,${soname}"
                else
                    flags="${flags} -Wl,-soname,${soname}"
                fi
            fi
        fi
    fi
    if is_plat "macosx"; then
        _os_iorunv "xcrun" "-sdk" "macosx" "--show-sdk-path"; local sdkdir="${_ret}"
        if test_nz "${sdkdir}"; then
            flags="${flags} -isysroot \"${sdkdir}\""
        fi
    fi
    _ret="${flags}"
}

# get toolchain flags in target
_get_target_toolchain_flags() {
    local name="${1}"
    local toolkind="${2}"
    local toolname="${3}"
    local flags=""
    case "${toolname}" in
        gcc) _get_target_toolchain_flags_for_gcc "${name}" "${toolkind}"; flags="${_ret}";;
        gxx) _get_target_toolchain_flags_for_gcc "${name}" "${toolkind}"; flags="${_ret}";;
        clang) _get_target_toolchain_flags_for_clang "${name}" "${toolkind}"; flags="${_ret}";;
        clangxx) _get_target_toolchain_flags_for_clang "${name}" "${toolkind}"; flags="${_ret}";;
        emcc) _get_target_toolchain_flags_for_clang "${name}" "${toolkind}"; flags="${_ret}";;
        emxx) _get_target_toolchain_flags_for_clang "${name}" "${toolkind}"; flags="${_ret}";;
        cosmocc) _get_target_toolchain_flags_for_gcc "${name}" "${toolkind}"; flags="${_ret}";;
        cosmocxx) _get_target_toolchain_flags_for_gcc "${name}" "${toolkind}"; flags="${_ret}";;
        tcc) _get_target_toolchain_flags_for_tcc "${name}" "${toolkind}"; flags="${_ret}";;
        ar) _get_target_toolchain_flags_for_ar "${name}" "${toolkind}"; flags="${_ret}";;
        emar) _get_target_toolchain_flags_for_ar "${name}" "${toolkind}"; flags="${_ret}";;
        cosmoar) _get_target_toolchain_flags_for_ar "${name}" "${toolkind}"; flags="${_ret}";;
        *) raise "unknown toolname(${toolname})!" ;;
    esac
    _ret="${flags}"
}

# get compiler flags in target
_get_target_compiler_flags() {
    local name="${1}"
    local toolkind="${2}"
    _get_toolchain_toolset "${_target_toolchain}" "${toolkind}"; local program="${_ret}"
    path_toolname "${program}"; local toolname="${_ret}"
    local result=""

    # get toolchain flags
    _get_target_toolchain_flags "${name}" "${toolkind}" "${toolname}"; local toolchain_flags="${_ret}"
    if test_nz "${toolchain_flags}"; then
        result="${result} ${toolchain_flags}"
    fi

    # get abstract flags
    local itemnames="symbols optimizes warnings languages defines undefines includedirs frameworkdirs frameworks"
    local itemname=""
    for itemname in ${itemnames}; do
        _get_target_abstract_flags "${name}" "${toolkind}" "${toolname}" "${itemname}"; local flags="${_ret}"
        if test_nz "${flags}"; then
            result="${result} ${flags}"
        fi
    done

    # get raw flags, e.g. add_cflags, add_cxxflags
    _get_flagname "${toolkind}"; local flagname="${_ret}"
    _get_target_item "${name}" "${flagname}"; local flags="${_ret}"
    # get flags from target deps
    _get_target_librarydeps "${name}"; local deps="${_ret}"
    local dep=""
    for dep in ${deps}; do
        _get_target_item "${dep}" "kind"; local dep_kind="${_ret}"
        if test_eq "${dep_kind}" "static" || test_eq "${dep_kind}" "shared"; then
            _get_target_item "${dep}" "${flagname}_public"; local depflags="${_ret}"
            if test_nz "${depflags}"; then
                flags="${flags} ${depflags}"
            fi
        fi
    done
    if test_nz "${flags}"; then
        result="${result} ${flags}"
    fi
    if test_eq "${flagname}" "cflags" || test_eq "${flagname}" "cxxflags"; then
        _get_target_item "${name}" "cxflags"; flags="${_ret}"
        if test_nz "${flags}"; then
            result="${result} ${flags}"
        fi
    elif test_eq "${flagname}" "mflags" || test_eq "${flagname}" "mxxflags"; then
        _get_target_item "${name}" "mxflags"; flags="${_ret}"
        if test_nz "${flags}"; then
            result="${result} ${flags}"
        fi
    fi

    # get flags from environments, e.g. $CFLAGS, $CXXFLAGS
    if test_nz "${CPPFLAGS}"; then
        result="${result} ${CPPFLAGS}"
    fi
    if test_eq "${flagname}" "cflags" && test_nz "${CFLAGS}"; then
        result="${result} ${CFLAGS}"
    fi
    if test_eq "${flagname}" "cxxflags" && test_nz "${CXXFLAGS}"; then
        result="${result} ${CXXFLAGS}"
    fi
    if test_eq "${flagname}" "mflags" && test_nz "${MFLAGS}"; then
        result="${result} ${MFLAGS}"
    fi
    if test_eq "${flagname}" "mxxflags" && test_nz "${MXXFLAGS}"; then
        result="${result} ${MXXFLAGS}"
    fi
    _ret="${result}"
}

# get linker flags in target
_get_target_linker_flags() {
    local name="${1}"
    local toolkind="${2}"
    _get_toolchain_toolset "${_target_toolchain}" "${toolkind}"; local program="${_ret}"
    path_toolname "${program}"; local toolname="${_ret}"
    local result=""

    # get toolchain flags
    _get_target_toolchain_flags "${name}" "${toolkind}" "${toolname}"; local toolchain_flags="${_ret}"
    if test_nz "${toolchain_flags}"; then
        result="${result} ${toolchain_flags}"
    fi

    # get flags from target deps
    _get_target_librarydeps "${name}"; local deps="${_ret}"
    local dep=""
    for dep in ${deps}; do
        _get_target_item "${dep}" "kind"; local dep_kind="${_ret}"
        if test_eq "${dep_kind}" "static" || test_eq "${dep_kind}" "shared"; then
            _get_targetdir "${dep}"; local dep_targetdir="${_ret}"
            _get_target_basename "${dep}"; local dep_basename="${_ret}"
            _get_target_abstract_flags "${dep}" "${toolkind}" "${toolname}" "linkdirs" "${dep_targetdir}"; local linkdirs_flags="${_ret}"
            _get_target_abstract_flags "${dep}" "${toolkind}" "${toolname}" "links" "${dep_basename}"; local links_flags="${_ret}"
            if test_eq "${dep_kind}" "shared"; then
                local rpathdir="@loader_path"
                _get_targetdir "${name}"; local targetdir="${_ret}"
                path_relative "${targetdir}" "${dep_targetdir}"; local subdir="${_ret}"
                if test_nz "${subdir}"; then
                    rpathdir="${rpathdir}/${subdir}"
                fi
                _get_target_abstract_flags "${dep}" "${toolkind}" "${toolname}" "rpathdirs" "${rpathdir}"; local rpathdirs_flags="${_ret}"
                result="${result} ${rpathdirs_flags}"
            fi
            result="${result} ${linkdirs_flags} ${links_flags}"
        fi
    done

    # get abstract flags
    local itemnames="strip frameworkdirs linkdirs links rpathdirs frameworks syslinks"
    local itemname=""
    for itemname in ${itemnames}; do
        _get_target_abstract_flags "${name}" "${toolkind}" "${toolname}" "${itemname}"; local flags="${_ret}"
        if test_nz "${flags}"; then
            result="${result} ${flags}"
        fi
    done

    # get raw flags, e.g. add_ldflags, add_shflags
    _get_flagname "${toolkind}"; local flagname="${_ret}"
    _get_target_item "${name}" "${flagname}"; local flags="${_ret}"
    # get flags from target deps
    for dep in ${deps}; do
        _get_target_item "${dep}" "kind"; local dep_kind="${_ret}"
        if test_eq "${dep_kind}" "static" || test_eq "${dep_kind}" "shared"; then
            _get_target_item "${dep}" "${flagname}_public"; local depflags="${_ret}"
            if test_nz "${depflags}"; then
                flags="${flags} ${depflags}"
            fi
        fi
    done
    if test_nz "${flags}"; then
        result="${result} ${flags}"
    fi

    # get flags from environments, e.g. $LDFLAGS
    if test_eq "${flagname}" "ldflags" && test_nz "${LDFLAGS}"; then
        result="${result} ${LDFLAGS}"
    fi
    if test_eq "${flagname}" "shflags" && test_nz "${LDFLAGS}"; then
        result="${result} ${LDFLAGS}"
    fi
    _ret="${result}"
}

# get archiver flags in target
_get_target_archiver_flags() {
    local name="${1}"
    local toolkind="${2}"
    _get_toolchain_toolset "${_target_toolchain}" "${toolkind}"; local program="${_ret}"
    path_toolname "${program}"; local toolname="${_ret}"
    local result=""

    # get toolchain flags
    _get_target_toolchain_flags "${name}" "${toolkind}" "${toolname}"; local toolchain_flags="${_ret}"
    if test_nz "${toolchain_flags}"; then
        result="${result} ${toolchain_flags}"
    fi

    # get raw flags, e.g. add_arflags
    _get_flagname "${toolkind}"; local flagname="${_ret}"
    _get_target_item "${name}" "${flagname}"; local flags="${_ret}"
    if test_nz "${flags}"; then
        result="${result} ${flags}"
    fi

    _ret="${result}"
}

# get target flags
_get_target_flags() {
    local name="${1}"
    local toolkind="${2}"
    local flags=""
    if test_eq "${toolkind}" "sh"; then
        _get_target_linker_flags "${name}" "${toolkind}"; flags="${_ret}"
    elif test_eq "${toolkind}" "ld"; then
        _get_target_linker_flags "${name}" "${toolkind}"; flags="${_ret}"
    elif test_eq "${toolkind}" "ar"; then
        _get_target_archiver_flags "${name}" "${toolkind}"; flags="${_ret}"
    else
        _get_target_compiler_flags "${name}" "${toolkind}"; flags="${_ret}"
    fi
    _ret="${flags}"
}

# add file paths in target
_add_target_filepaths() {
    local key="$1"
    shift
    # we need avoid escape `*` automatically in for-loop
    local file=""
    string_replace "${@}" "\*" "?"; local list="${_ret}"
    if test_eq "${key}" "files"; then
        for file in ${list}; do
            path_sourcekind "${file}"; local sourcekind="${_ret}"
            _targets_toolkinds="${_targets_toolkinds} ${sourcekind}"
        done
    fi
    for file in ${list}; do
        string_replace "${file}" "?" "*"; file="${_ret}"
        if ! path_is_absolute "${file}"; then
            file="${xmake_sh_scriptdir}/${file}"
        fi
        local files=""
        if string_contains_star2 "${file}"; then
            path_directory "${file}"; local dir="${_ret}"
            path_filename_fromdir "${file}" "${dir}"; local name="${_ret}"
            _os_find "${dir}" "${name}"; files="${_ret}"
        elif string_contains_star "${file}"; then
            path_directory "${file}"; local dir="${_ret}"
            path_filename_fromdir "${file}" "${dir}"; local name="${_ret}"
            _os_find "${dir}" "${name}" 1; files="${_ret}"
        else
            files="${file}"
        fi
        for file in ${files}; do
            path_relative "${xmake_sh_projectdir}" "${file}"; file="${_ret}"
            _add_target_item "${_xmake_sh_target_current}" "${key}" "${file}"
        done
    done
}

# add install paths in target
_add_target_installpaths() {
    local key="$1"
    local filepattern="${2}"
    local prefixdir="${3}"
    local filename=${4}

    # get root directory, e.g. "src/foo/(*.h)" -> "src/foo"
    local rootdir=""
    if string_contains "${filepattern}" "("; then
        string_split "${filepattern}" "(" 1; rootdir="${_ret}"
        rootdir=${rootdir%/}
        if ! path_is_absolute "${rootdir}"; then
            rootdir="${xmake_sh_scriptdir}/${rootdir}"
        fi
        path_relative "${xmake_sh_projectdir}" "${rootdir}"; rootdir="${_ret}"
        rootdir=${rootdir%/}
    fi

    # remove (), e.g. "src/(*.h)" -> "src/*.h"
    string_replace ${filepattern} "(" ""; filepattern="${_ret}"
    string_replace ${filepattern} ")" ""; filepattern="${_ret}"

    # get real path
    if ! path_is_absolute "${filepattern}"; then
        filepattern="${xmake_sh_scriptdir}/${filepattern}"
    fi
    local files=""
    if string_contains_star2 "${filepattern}"; then
        path_directory "${filepattern}"; local dir="${_ret}"
        path_filename_fromdir "${filepattern}" "${dir}"; local name="${_ret}"
        _os_find "${dir}" "${name}"; files="${_ret}"
    elif string_contains_star "${filepattern}"; then
        path_directory "${filepattern}"; local dir="${_ret}"
        path_filename_fromdir "${filepattern}" "${dir}"; local name="${_ret}"
        _os_find "${dir}" "${name}" 1; files="${_ret}"
    else
        files="${filepattern}"
    fi
    for file in ${files}; do
        path_relative "${xmake_sh_projectdir}" "${file}"; file="${_ret}"
        _add_target_item "${_xmake_sh_target_current}" "${key}" "${file}:${rootdir}:${prefixdir}:${filename}"
    done
}

# set target file path
_set_target_filepath() {
    local key="${1}"
    local path="${2}"
    if ! path_is_absolute "${path}"; then
        path="${xmake_sh_scriptdir}/${path}"
    fi
    path_relative ${xmake_sh_projectdir} "${path}"; path="${_ret}"
    _set_target_item "${_xmake_sh_target_current}" "${key}" "${path}"
}

# set kind in target
set_kind() {
    if ! ${_loading_targets}; then
        return
    fi
    local kind=${1}
    _set_target_item "${_xmake_sh_target_current}" "kind" "${kind}"
    case "${kind}" in
        binary) _targets_toolkinds="${_targets_toolkinds} ld";;
        static) _targets_toolkinds="${_targets_toolkinds} ar";;
        shared) _targets_toolkinds="${_targets_toolkinds} sh";;
        *) raise "unknown target kind ${kind}";;
    esac
}

# set version in target
set_version() {
    if ! ${_loading_targets}; then
        return
    fi
    local version="${1}"
    local version_build="${2}"
    local version_soname="${3}"
    _set_target_item "${_xmake_sh_target_current}" "version" "${version}"
    _set_target_item "${_xmake_sh_target_current}" "version_build" "${version_build}"
    _set_target_item "${_xmake_sh_target_current}" "version_soname" "${version_soname}"
}

# set default in target
set_default() {
    local default=${1}
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        _set_target_item "${_xmake_sh_target_current}" "default" "${default}"
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _set_option_item "${_xmake_sh_option_current}" "default" "${default}"
    fi
}

# set configvar in target
set_configvar() {
    if ! ${_loading_targets}; then
        return
    fi
    local name="${1}"
    local value="${2}"
    _set_target_item "${_xmake_sh_target_current}" "configvar_${name}" "${value}"
    _add_target_item "${_xmake_sh_target_current}" "configvars" "${name}"
}

# set filename in target
set_filename() {
    if ! ${_loading_targets}; then
        return
    fi
    local filename="${1}"
    _set_target_item "${_xmake_sh_target_current}" "filename" "${filename}"
}

# set basename in target
set_basename() {
    if ! ${_loading_targets}; then
        return
    fi
    local basename="${1}"
    _set_target_item "${_xmake_sh_target_current}" "basename" "${basename}"
}

# set extension in target
set_extension() {
    if ! ${_loading_targets}; then
        return
    fi
    local extension=${1}
    _set_target_item "${_xmake_sh_target_current}" "extension" "${extension}"
}

# set prefixname in target
set_prefixname() {
    if ! ${_loading_targets}; then
        return
    fi
    local prefixname=${1}
    _set_target_item "${_xmake_sh_target_current}" "prefixname" "${prefixname}"
}

# set target directory
set_targetdir() {
    if ! ${_loading_targets}; then
        return
    fi
    _set_target_filepath "targetdir" "${1}"
}

# set target object directory
set_objectdir() {
    if ! ${_loading_targets}; then
        return
    fi
    _set_target_filepath "objectdir" "${1}"
}

# set target config directory
set_configdir() {
    if ! ${_loading_targets}; then
        return
    fi
    _set_target_filepath "configdir" "${1}"
}

# set target install directory
set_installdir() {
    if ! ${_loading_targets}; then
        return
    fi
    _set_target_filepath "installdir" "${1}"
}

# add deps in target
add_deps() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_item "${_xmake_sh_target_current}" "deps" "${@}"
}

# add files in target
add_files() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_filepaths "files" "$@"
}

# add install files in target
add_installfiles() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_installpaths "installfiles" "$@"
}

# add header files in target
add_headerfiles() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_installpaths "headerfiles" "$@"
}

# add config files in target
add_configfiles() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_filepaths "configfiles" "$@"
}

# add defines in target
add_defines() {
    local define=""
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        local public=false
        for define in $@; do
            if test_nq "${define}" "{public}"; then
                _add_target_item "${_xmake_sh_target_current}" "defines" "${define}"
            else
                public=true
            fi
        done
        if ${public}; then
            for define in $@; do
                if test_nq "${define}" "{public}"; then
                    _add_target_item "${_xmake_sh_target_current}" "defines_public" "${define}"
                fi
            done
        fi
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "defines" "${@}"
    fi
}

# add undefines in target
add_undefines() {
    local undefine=""
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        local public=false
        for undefine in $@; do
            if test_nq "${undefine}" "{public}"; then
                _add_target_item "${_xmake_sh_target_current}" "undefines" "${undefine}"
            else
                public=true
            fi
        done
        if ${public}; then
            for undefine in $@; do
                if test_nq "${undefine}" "{public}"; then
                    _add_target_item "${_xmake_sh_target_current}" "undefines_public" "${undefine}"
                fi
            done
        fi
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "undefines" "${@}"
    fi
}

# add includedirs in target
add_includedirs() {
    local public=false
    local dir=""
    for dir in $@; do
        if test_nq "${dir}" "{public}"; then
            if ! path_is_absolute "${dir}"; then
                dir="${xmake_sh_scriptdir}/${dir}"
            fi
            if string_startswith "${dir}" "${xmake_sh_projectdir}"; then
                path_relative ${xmake_sh_projectdir} "${dir}"; dir="${_ret}"
            fi
            if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
                _add_target_item "${_xmake_sh_target_current}" "includedirs" "${dir}"
            elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
                _add_option_item "${_xmake_sh_option_current}" "includedirs" "${dir}"
            fi
        else
            public=true
        fi
    done
    if ${public}; then
        for dir in $@; do
            if test_nq "${dir}" "{public}"; then
                if ! path_is_absolute "${dir}"; then
                    dir="${xmake_sh_scriptdir}/${dir}"
                fi
                if string_startswith "${dir}" "${xmake_sh_projectdir}"; then
                    path_relative ${xmake_sh_projectdir} "${dir}"; dir="${_ret}"
                fi
                if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
                    _add_target_item "${_xmake_sh_target_current}" "includedirs_public" "${dir}"
                fi
            fi
        done
    fi
}

# add links in target
add_links() {
    local link=""
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        local public=false
        for link in $@; do
            if test_nq "${link}" "{public}"; then
                _add_target_item "${_xmake_sh_target_current}" "links" "${link}"
            else
                public=true
            fi
        done
        if ${public}; then
            for link in $@; do
                if test_nq "${link}" "{public}"; then
                    _add_target_item "${_xmake_sh_target_current}" "links_public" "${link}"
                fi
            done
        fi
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "links" "${@}"
    fi
}

# add syslinks in target
add_syslinks() {
    local syslink=""
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        local public=false
        for syslink in $@; do
            if test_nq "${syslink}" "{public}"; then
                _add_target_item "${_xmake_sh_target_current}" "syslinks" "${syslink}"
            else
                public=true
            fi
        done
        if ${public}; then
            for syslink in $@; do
                if test_nq "${syslink}" "{public}"; then
                    _add_target_item "${_xmake_sh_target_current}" "syslinks_public" "${syslink}"
                fi
            done
        fi
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "syslinks" "${@}"
    fi
}

# add linkdirs in target
add_linkdirs() {
    local dir=""
    local public=false
    for dir in $@; do
        if test_nq "${dir}" "{public}"; then
            if ! path_is_absolute "${dir}"; then
                dir="${xmake_sh_scriptdir}/${dir}"
            fi
            if string_startswith "${dir}" "${xmake_sh_projectdir}"; then
                path_relative ${xmake_sh_projectdir} "${dir}"; dir="${_ret}"
            fi
            if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
                _add_target_item "${_xmake_sh_target_current}" "linkdirs" "${dir}"
            elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
                _add_option_item "${_xmake_sh_option_current}" "linkdirs" "${dir}"
            fi
        else
            public=true
        fi
    done
    if ${public}; then
        for dir in $@; do
            if test_nq "${dir}" "{public}"; then
                if ! path_is_absolute "${dir}"; then
                    dir="${xmake_sh_scriptdir}/${dir}"
                fi
                if string_startswith "${dir}" "${xmake_sh_projectdir}"; then
                    path_relative ${xmake_sh_projectdir} "${dir}"; dir="${_ret}"
                fi
                if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
                    _add_target_item "${_xmake_sh_target_current}" "linkdirs_public" "${dir}"
                fi
            fi
        done
    fi
}

# add rpathdirs in target
add_rpathdirs() {
    if ! ${_loading_targets}; then
        return
    fi
    local dir=""
    for dir in $@; do
        if ! path_is_absolute "${dir}"; then
            dir="${xmake_sh_scriptdir}/${dir}"
        fi
        path_relative ${xmake_sh_projectdir} "${dir}"; dir="${_ret}"
        _add_target_item "${_xmake_sh_target_current}" "rpathdirs" "${dir}"
    done
}

# add frameworks in target
add_frameworks() {
    local framework=""
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        local public=false
        for framework in $@; do
            if test_nq "${framework}" "{public}"; then
                _add_target_item "${_xmake_sh_target_current}" "frameworks" "${framework}"
            else
                public=true
            fi
        done
        if ${public}; then
            for framework in $@; do
                if test_nq "${framework}" "{public}"; then
                    _add_target_item "${_xmake_sh_target_current}" "frameworks_public" "${framework}"
                fi
            done
        fi
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "frameworks" "${@}"
    fi
}

# add frameworkdirs in target
add_frameworkdirs() {
    local dir=""
    for dir in $@; do
        if ! path_is_absolute "${dir}"; then
            dir="${xmake_sh_scriptdir}/${dir}"
        fi
        path_relative ${xmake_sh_projectdir} "${dir}"; dir="${_ret}"
        if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
            _add_target_item "${_xmake_sh_target_current}" "frameworkdirs" "${dir}"
        elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
            _add_option_item "${_xmake_sh_option_current}" "frameworkdirs" "${dir}"
        fi
    done
}

# set strip in target
set_strip() {
    if ! ${_loading_targets}; then
        return
    fi
    local strip=${1}
    _set_target_item "${_xmake_sh_target_current}" "strip" "${strip}"
}

# set symbols in target
set_symbols() {
    if ! ${_loading_targets}; then
        return
    fi
    local symbols="${1}"
    _set_target_item "${_xmake_sh_target_current}" "symbols" "${symbols}"
}

# set languages in target
set_languages() {
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        _set_target_item "${_xmake_sh_target_current}" "languages" "${@}"
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _set_option_item "${_xmake_sh_option_current}" "languages" "${@}"
    fi
}

# set warnings in target
set_warnings() {
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        _set_target_item "${_xmake_sh_target_current}" "warnings" "${@}"
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _set_option_item "${_xmake_sh_option_current}" "warnings" "${@}"
    fi
}

# set optimizes in target
set_optimizes() {
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        _set_target_item "${_xmake_sh_target_current}" "optimizes" "${@}"
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _set_option_item "${_xmake_sh_option_current}" "optimizes" "${@}"
    fi
}

# add cflags in target
add_cflags() {
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        _add_target_item "${_xmake_sh_target_current}" "cflags" "${@}"
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "cflags" "${@}"
    fi
}

# add cxflags in target
add_cxflags() {
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        _add_target_item "${_xmake_sh_target_current}" "cxflags" "${@}"
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "cxflags" "${@}"
    fi
}

# add cxxflags in target
add_cxxflags() {
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        _add_target_item "${_xmake_sh_target_current}" "cxxflags" "${@}"
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "cxxflags" "${@}"
    fi
}

# add asflags in target
add_asflags() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_item "${_xmake_sh_target_current}" "asflags" "${@}"
}

# add mflags in target
add_mflags() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_item "${_xmake_sh_target_current}" "mflags" "${@}"
}

# add mxflags in target
add_mxflags() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_item "${_xmake_sh_target_current}" "mxflags" "${@}"
}

# add mxxflags in target
add_mxxflags() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_item "${_xmake_sh_target_current}" "mxxflags" "${@}"
}

# add ldflags in target
add_ldflags() {
    if ${_loading_targets} && test_z "${_xmake_sh_option_current}"; then
        _add_target_item "${_xmake_sh_target_current}" "ldflags" "${@}"
    elif ${_loading_options} && test_nz "${_xmake_sh_option_current}"; then
        _add_option_item "${_xmake_sh_option_current}" "ldflags" "${@}"
    fi
}

# add shflags in target
add_shflags() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_item "${_xmake_sh_target_current}" "shflags" "${@}"
}

# add arflags in target
add_arflags() {
    if ! ${_loading_targets}; then
        return
    fi
    _add_target_item "${_xmake_sh_target_current}" "arflags" "${@}"
}

# add options in target
add_options() {
    if ! ${_loading_targets}; then
        return
    fi
    local name=""
    local public=false
    for name in $@; do
        if test_nq "${name}" "{public}"; then
            public=true
            break
        fi
    done
    for name in $@; do
        if has_config "${name}"; then
            local itemname=""
            local itemnames="includedirs linkdirs links defines cflags cxflags cxxflags ldflags"
            for itemname in ${itemnames}; do
                _get_option_item "${name}" "${itemname}"; local values="${_ret}"
                if test_nz "${values}"; then
                    _add_target_item "${_xmake_sh_target_current}" "${itemname}" "${values}"
                    if $public; then
                        _add_target_item "${_xmake_sh_target_current}" "${itemname}_public" "${values}"
                    fi
                fi
            done
        fi
    done
}

# before_install in target
before_install() {
    if ! ${_loading_targets}; then
        return
    fi
    local funcname="${1}"
    _add_target_item "${_xmake_sh_target_current}" "before_install" "${funcname}"
}

# after_install in target
after_install() {
    if ! ${_loading_targets}; then
        return
    fi
    local funcname="${1}"
    _add_target_item "${_xmake_sh_target_current}" "after_install" "${funcname}"
}

#-----------------------------------------------------------------------------
# toolchain configuration apis
#

# define toolchain
toolchain() {
    local name="${1}"
    _xmake_sh_toolchain_current="${name}"
    if ! ${_loading_toolchains}; then
        return
    fi
    _xmake_sh_toolchains="${_xmake_sh_toolchains} ${name}"
    _map_set "toolchains" "${name}_name" "${name}"
    return 0
}
toolchain_end() {
    _xmake_sh_toolchain_current=""
}
_map "toolchains"

# has the given toolchain?
_has_toolchain() {
    local name=${1}
    if _map_has "toolchains" "${name}_name"; then
        return 0
    fi
    return 1
}

# get the given toolchain item
_get_toolchain_item() {
    local name=${1}
    local key=${2}
    _map_get "toolchains" "${name}_${key}"
}

# set the given toolchain item
_set_toolchain_item() {
    local name=${1}
    local key=${2}
    local value="${3}"
    if test_nz "${name}"; then
        _map_set "toolchains" "${name}_${key}" "${value}"
    else
        raise "please set toolchain in the toolchain scope!"
    fi
}

# get the give toolchain toolset
_get_toolchain_toolset() {
    local name=${1}
    local kind=${2}
    _get_toolchain_item "${name}" "toolset_${kind}"
}

# set the give toolchain toolset
_set_toolchain_toolset() {
    local name=${1}
    local kind=${2}
    local programs="${3}"
    _set_toolchain_item "${name}" "toolset_${kind}" "${programs}"
}

# add the give toolchain toolset
_add_toolchain_toolset() {
    local name=${1}
    local kind=${2}
    local program="${3}"
    _get_toolchain_item "${name}" "toolset_${kind}"; local programs="${_ret}"
    if test_nz "${programs}"; then
        programs="${programs}:${program}"
    else
        programs="${program}"
    fi
    _set_toolchain_item "${name}" "toolset_${kind}" "${programs}"
}

# set toolset in toolchain
set_toolset() {
    if ! ${_loading_toolchains}; then
        return
    fi
    local kind=${1}
    shift
    local idx=0
    while test $# != 0; do
        local program="${1}"
        local key="${kind}"
        if test_nq "${idx}" "0"; then
            key="${key}_${idx}"
        fi
        _set_toolchain_toolset "${_xmake_sh_toolchain_current}" "${key}" "${program}"
        idx=$((idx+1))
        shift
    done
}

#-----------------------------------------------------------------------------
# load options
#

# load options and toolchains
_load_options_and_toolchains() {
    _loading_options=true
    _loading_toolchains=true
    _loading_targets=false
    local file=${xmake_sh_projectdir}/xmake.sh
    if test -f "${file}"; then
        includes "${file}"
    else
        # include all xmake.sh files in next sub-directories
        local files=`find ${xmake_sh_projectdir} -maxdepth 2 -mindepth 2 -name "xmake.sh"`
        for file in ${files}; do
            includes "${file}"
        done
    fi
}
_load_options_and_toolchains

# show option usage
_show_options_usage() {
    _get_options_for_menu; local options="${_ret}"
    for name in ${options}; do
        _get_option_item "${name}" "description"; local description="${_ret}"
        _get_option_item "${name}" "default"; local default="${_ret}"
        string_toupper ${name}; local head="--${name}="${_ret}""
        local headsize=${#head}
        local tail="${description}"
        if test "x${default}" != "x"; then
            local defval=${default}
            if test "x${defval}" = "xtrue"; then
                defval="yes"
            elif test "x${defval}" = "xfalse"; then
                defval="no"
            fi
            tail="${tail} (default: ${defval})"
        fi
        local width=24
        local padding_width=$((${width} - ${headsize}))
        local padding=$(string_dupch ${padding_width} " ")
        echo "  ${head}${padding}${tail}"
    done
}

# show configure usage
_show_usage() {
echo '
Usage: '"$0"' [<options>...]
Options: [defaults in brackets after descriptions]
Common options:
  --help                  Print this message.
  --version               Only print version information.
  --verbose               Display more information.
  --diagnosis             Display lots of diagnosis information.

  --generator=GENERATOR   Set the project generator. (default: '"${project_generator}"')
                            - gmake
                            - ninja
  --make=MAKE             Set the make program. (default: '"${_make_program_default}"')
  --ninja=NINJA           Set the Ninja program. (default: '"${_ninja_program_default}"')
  --plat=PLAT             Compile for the given platform. (default: '"${_target_plat_default}"')
                            - msys
                            - cross
                            - bsd
                            - mingw
                            - macosx
                            - linux
                            - wasm
  --arch=ARCH             Compile for the given architecture. (default: '"${_target_arch_default}"')
                            - msys: i386 x86_64
                            - cross: i386 x86_64 arm arm64 mips mips64 riscv riscv64 loong64 s390x ppc ppc64 sh4
                            - bsd: i386 x86_64
                            - mingw: i386 x86_64 arm arm64
                            - macosx: x86_64 arm64
                            - linux: i386 x86_64 armv7 armv7s arm64-v8a mips mips64 mipsel mips64el
  --mode=MODE             Set the given compilation mode. (default: '"${_target_mode_default}"')
                            - release
                            - debug
  --kind=KIND             Set the given target kind. (default: '"${_target_kind_default}"')
                            - static
                            - shared
                            - binary
  --toolchain=TOOLCHAIN   Set toolchain name.
                            - clang
                            - gcc
                            - emcc
                            - tinycc
                            - cosmocc
  --buildir=DIR           Set build directory. (default: '"${xmake_sh_buildir}"')

Autoconf options:
  --build=BUILD           Configure for building on BUILD [guessed]
  --host=HOST             Cross-compile to build programs to run on HOST [BUILD]
  --prefix=PREFIX         Set install files directory in tree rooted at PREFIX. (default: '"${_install_prefix_default}"')
  --bindir=DIR            Set install binaries directory in PREFIX/DIR. (default: '"${_install_bindir_default}"')
  --libdir=DIR            Set install libraries directory in PREFIX/DIR. (default: '"${_install_libdir_default}"')
  --includedir=DIR        Set install includes directory in PREFIX/DIR. (default: '"${_install_includedir_default}"')

Project options:
'"$(_show_options_usage)"'
'
    exit 1
}

# show xmake.sh version
_show_version() {
    echo "xmake.sh v${xmake_sh_version}, A script-only build utility like autotools"
    echo "${xmake_sh_copyright}"
    echo '                         _               _            '
    echo "    __  ___ __  __  __ _| | ______   ___| |__         "
    echo "    \ \/ / |  \/  |/ _  | |/ / __ \ / __| '_  \       "
    echo "     >  <  | \__/ | /_| |   <  ___/_\__ \ | | |       "
    echo "    /_/\_\_|_|  |_|\__ \|_|\_\____(_)___/_| |_|       "
    echo '                                     by ruki, xmake.io'
    echo '                                                      '
    echo '   👉  Manual: https://xmake.io/#/getting_started     '
    echo '   🙏  Donate: https://xmake.io/#/sponsor             '
    echo '                                                      '
    exit 2
}

# --foo=yes => foo
_parse_argument_name() {
    _ret=$(echo "${1#*--}" | sed "s/${2-=[^=]*}$//")
    string_replace "${_ret}" "-" "_"
}

# --foo=yes => yes
_parse_argument_value() {
    _ret=$(echo "$1" | sed "s/^${2-[^=]*=}//")
}

# parse input arguments
_handle_option() {
    _parse_argument_name ${1}; local name="${_ret}"
    _parse_argument_value ${1}; local value="${_ret}"
    if test_eq "${name}" "help"; then
        _show_usage
        return 0
    elif test_eq "${name}" "version"; then
        _show_version
        return 0
    elif test_eq "${name}" "verbose"; then
        xmake_sh_verbose=true
        return 0
    elif test_eq "${name}" "diagnosis"; then
        xmake_sh_diagnosis=true
        return 0
    elif test_eq "${name}" "plat"; then
        _target_plat=${value}
        return 0
    elif test_eq "${name}" "arch"; then
        _target_arch=${value}
        return 0
    elif test_eq "${name}" "mode"; then
        _target_mode=${value}
        return 0
    elif test_eq "${name}" "kind"; then
        _target_kind=${value}
        return 0
    elif test_eq "${name}" "toolchain"; then
        _target_toolchain=${value}
        return 0
    elif test_eq "${name}" "generator"; then
        project_generator=${value}
        return 0
    elif test_eq "${name}" "make"; then
        _make_program=${value}
        return 0
    elif test_eq "${name}" "ninja"; then
        _ninja_program=${value}
        return 0
    elif test_eq "${name}" "prefix"; then
        _install_prefix_default="${value}"
        return 0
    elif test_eq "${name}" "bindir"; then
        _install_bindir_default="${value}"
        return 0
    elif test_eq "${name}" "libdir"; then
        _install_libdir_default="${value}"
        return 0
    elif test_eq "${name}" "includedir"; then
        _install_includedir_default="${value}"
        return 0
    elif test_eq "${name}" "buildir"; then
        xmake_sh_buildir="${value}"
        return 0
    elif test_eq "${name}" "build"; then
        _autoconf_build_type="${value}"
        return 0
    elif test_eq "${name}" "host"; then
        _autoconf_host_type="${value}"
        return 0
    elif _has_option "${name}"; then
        _set_option_value "${name}" "${value}"
        return 0
    fi
    return 1
}
while test $# != 0; do
    if ! _handle_option ${1}; then
        wprint "unknown option: $1"
    fi
    shift
done

#-----------------------------------------------------------------------------
# handle some autoconf configurations
#

# parse triplet
# https://github.com/xmake-io/xmake/issues/3869
# e.g. i686-linux-gnu, aarch64-apple-darwin, x86_64-w64-mingw32, i686-redhat-linux-gnu
_parse_triplet() {
    local triplet="${1}"
    string_split "${triplet}" "-"
}

_get_arch_from_cpu() {
    local cpu="${1}"
    case "${cpu}" in
        i686) _ret="i386";;
        i386) _ret="i386";;
        x86_64) _ret="x86_64";;
        aarch64) _ret="arm64";;
        arm64) _ret="arm64";;
        arm*) _ret="arm";;
        *) _ret="${cpu}";;
    esac
}

_get_plat_from_vendor_os() {
    local vendor="${1}"
    local os="${2}"
    case "${vendor}" in
        linux)
            if string_contains "${os}" "android"; then
                _ret="android"
            else
                _ret="linux"
            fi
            ;;
        apple)
            if test_eq "${os}" "darwin"; then
                _ret="macosx"
            fi
            ;;
        w64) _ret="mingw";;
        *) _ret="${os}";;
    esac
}

_handle_autoconf_configs() {
    if test_z "${_autoconf_host_type}"; then
        _autoconf_host_type="${_autoconf_build_type}"
    fi

    if test_nz "${_autoconf_build_type}"; then
        _parse_triplet "${_autoconf_build_type}"; local cpu="${_ret}"; local vendor="${_ret2}"; local os="${_ret3}"
        _get_arch_from_cpu "${cpu}"
        if test_nz "${_ret}"; then
            os_arch="${_ret}"
        else
            wprint "unknown cpu: ${cpu} in --build=${value}"
        fi
        _get_plat_from_vendor_os "${vendor}" "${os}"
        if test_nz "${_ret}"; then
            os_host="${_ret}"
        else
            wprint "unknown vendor-os: ${vendor}-${os} in --build=${value}"
        fi
    fi

    if test_nz "${_autoconf_host_type}"; then
        _parse_triplet "${_autoconf_host_type}"; local cpu="${_ret}"; local vendor="${_ret2}"; local os="${_ret3}"
        _get_arch_from_cpu "${cpu}"
        if test_nz "${_ret}"; then
            _target_arch_default="${_ret}"
        else
            wprint "unknown cpu: ${cpu} in --host=${value}"
        fi
        _get_plat_from_vendor_os "${vendor}" "${os}"
        if test_nz "${_ret}"; then
            _target_plat_default="${_ret}"
        else
            wprint "unknown vendor-os: ${vendor}-${os} in --build=${value}"
        fi
    fi
}
_handle_autoconf_configs

#-----------------------------------------------------------------------------
# detect platform and toolchains
#

# envs toolchain
toolchain "envs"
    set_toolset "as" "$CC" "$CXX" "$AS"
    set_toolset "cc" "$CC"
    set_toolset "cxx" "$CC" "$CXX"
    set_toolset "mm" "$CC" "$CXX"
    set_toolset "mxx" "$CC" "$CXX"
    set_toolset "ld" "$CXX" "$CC" "$LD"
    set_toolset "sh" "$CXX" "$CC" "$LD"
    set_toolset "ar" "$AR" "ar"
toolchain_end

# clang toolchain
toolchain "clang"
    set_toolset "as" "clang"
    set_toolset "cc" "clang"
    set_toolset "cxx" "clang" "clang++"
    set_toolset "mm" "clang"
    set_toolset "mxx" "clang" "clang++"
    set_toolset "ld" "clang++" "clang"
    set_toolset "sh" "clang++" "clang"
    set_toolset "ar" "ar"
toolchain_end

# gcc toolchain
toolchain "gcc"
    set_toolset "as" "gcc"
    set_toolset "cc" "gcc"
    set_toolset "cxx" "gcc" "g++"
    set_toolset "mm" "gcc"
    set_toolset "mxx" "gcc" "g++"
    set_toolset "ld" "g++" "gcc"
    set_toolset "sh" "g++" "gcc"
    set_toolset "ar" "ar"
toolchain_end

# mingw toolchain (x86_64)
toolchain "x86_64_w64_mingw32"
    set_toolset "as" "x86_64-w64-mingw32-gcc"
    set_toolset "cc" "x86_64-w64-mingw32-gcc"
    set_toolset "cxx" "x86_64-w64-mingw32-gcc" "x86_64-w64-mingw32-g++"
    set_toolset "mm" "x86_64-w64-mingw32-gcc"
    set_toolset "mxx" "x86_64-w64-mingw32-gcc" "x86_64-w64-mingw32-g++"
    set_toolset "ld" "x86_64-w64-mingw32-g++" "x86_64-w64-mingw32-gcc"
    set_toolset "sh" "x86_64-w64-mingw32-g++" "x86_64-w64-mingw32-gcc"
    set_toolset "ar" "x86_64-w64-mingw32-ar" "ar"
toolchain_end

# mingw toolchain (i686)
toolchain "i686_w64_mingw32"
    set_toolset "as" "i686-w64-mingw32-gcc"
    set_toolset "cc" "i686-w64-mingw32-gcc"
    set_toolset "cxx" "i686-w64-mingw32-gcc" "i686-w64-mingw32-g++"
    set_toolset "mm" "i686-w64-mingw32-gcc"
    set_toolset "mxx" "i686-w64-mingw32-gcc" "i686-w64-mingw32-g++"
    set_toolset "ld" "i686-w64-mingw32-g++" "i686-w64-mingw32-gcc"
    set_toolset "sh" "i686-w64-mingw32-g++" "i686-w64-mingw32-gcc"
    set_toolset "ar" "i686-w64-mingw32-ar" "ar"
toolchain_end

# aarch64 toolchain (aarch64)
toolchain "aarch64_linux_gnu"
    set_toolset "as" "aarch64-linux-gnu-gcc"
    set_toolset "cc" "aarch64-linux-gnu-gcc"
    set_toolset "cxx" "aarch64-linux-gnu-gcc" "aarch64-linux-gnu-g++"
    set_toolset "mm" "aarch64-linux-gnu-gcc"
    set_toolset "mxx" "aarch64-linux-gnu-gcc" "aarch64-linux-gnu-g++"
    set_toolset "ld" "aarch64-linux-gnu-g++" "aarch64-linux-gnu-gcc"
    set_toolset "sh" "aarch64-linux-gnu-g++" "aarch64-linux-gnu-gcc"
    set_toolset "ar" "aarch64-linux-gnu-ar" "ar"
toolchain_end

# emcc toolchain (wasm32)
toolchain "emcc"
    set_toolset "as" "emcc"
    set_toolset "cc" "emcc"
    set_toolset "cxx" "emcc" "em++"
    set_toolset "mm" "emcc"
    set_toolset "mxx" "emcc" "em++"
    set_toolset "ld" "em++" "emcc"
    set_toolset "sh" "em++" "emcc"
    set_toolset "ar" "emar" "ar"
toolchain_end

# cosmocc toolchain, e.g. ./configure --plat=linux --toolchain=cosmocc
toolchain "cosmocc"
    set_toolset "as" "cosmocc"
    set_toolset "cc" "cosmocc"
    set_toolset "cxx" "cosmocc" "cosmoc++"
    set_toolset "mm" "cosmocc"
    set_toolset "mxx" "cosmocc" "cosmoc++"
    set_toolset "ld" "cosmoc++" "cosmocc"
    set_toolset "sh" "cosmoc++" "cosmocc"
    set_toolset "ar" "cosmoar"
toolchain_end

# tinycc toolchain
toolchain "tinycc"
    set_toolset "as" "tcc" "gcc" "clang"
    set_toolset "cc" "tcc"
    set_toolset "cxx" "gcc" "g++" "clang" "clang++"
    set_toolset "mm" "gcc" "clang"
    set_toolset "mxx" "gcc" "g++" "clang" "clang++"
    set_toolset "ld" "tcc"
    set_toolset "sh" "tcc"
    set_toolset "ar" "tcc -ar" "ar"
toolchain_end

# check platform
_check_platform() {
    if test "x${_target_plat}" = "x"; then
        _target_plat=${_target_plat_default}
    fi
    if test "x${_target_arch}" = "x"; then
        _target_arch=${_target_arch_default}
    fi
    if test "x${_target_mode}" = "x"; then
        _target_mode=${_target_mode_default}
    fi
    if test "x${_target_kind}" = "x"; then
        _target_kind=${_target_kind_default}
    fi
    echo "checking for platform ... ${_target_plat}"
    echo "checking for architecture ... ${_target_arch}"
}

# get toolchain compile command for gcc/clang
_toolchain_compcmd_for_gcc_clang() {
    local program="${1}"
    local objectfile="${2}"
    local sourcefile="${3}"
    local flags="${4}"
    _ret="${program} -c ${flags} -o ${objectfile} ${sourcefile}"
}

# get toolchain link command for gcc/clang
_toolchain_linkcmd_for_gcc_clang() {
    local toolkind="${1}"
    local program="${2}"
    local binaryfile="${3}"
    local objectfiles="${4}"
    local flags="${5}"
    if test_eq "${toolkind}" "sh"; then
        flags="-shared -fPIC ${flags}"
    fi
    _ret="${program} -o ${binaryfile} ${objectfiles} ${flags}"
}

# get toolchain link command for ar
_toolchain_linkcmd_for_ar() {
    local toolkind="${1}"
    local program="${2}"
    local binaryfile="${3}"
    local objectfiles="${4}"
    local flags="${5}"
    _ret="${program} ${flags} ${binaryfile} ${objectfiles}"
}

# get toolchain compile command
_toolchain_compcmd() {
    local sourcekind="${1}"
    local objectfile="${2}"
    local sourcefile="${3}"
    local flags="${4}"
    _get_toolchain_toolset "${_target_toolchain}" "${sourcekind}"; local program="${_ret}"
    path_toolname "${program}"; local toolname="${_ret}"
    local compcmd=""
    case "${toolname}" in
        gcc) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        gxx) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        clang) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        clangxx) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        emcc) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        emxx) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        cosmocc) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        cosmocxx) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        tcc) _toolchain_compcmd_for_gcc_clang "${program}" "${objectfile}" "${sourcefile}" "${flags}"; compcmd="${_ret}";;
        *) raise "unknown toolname(${toolname})!" ;;
    esac
    _ret="${compcmd}"
}

# get toolchain link command
_toolchain_linkcmd() {
    local toolkind="${1}"
    local binaryfile="${2}"
    local objectfiles="${3}"
    local flags="${4}"
    _get_toolchain_toolset "${_target_toolchain}" "${toolkind}"; local program="${_ret}"
    path_toolname "${program}"; local toolname="${_ret}"
    case "${toolname}" in
        gcc) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        gxx) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        clang) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        clangxx) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        emcc) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        emxx) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        cosmocc) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        cosmocxx) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        tcc) _toolchain_linkcmd_for_gcc_clang "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        ar) _toolchain_linkcmd_for_ar "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        emar) _toolchain_linkcmd_for_ar "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        cosmoar) _toolchain_linkcmd_for_ar "${toolkind}" "${program}" "${binaryfile}" "${objectfiles}" "${flags}"; linkcmd="${_ret}";;
        *) raise "unknown toolname(${toolname})!" ;;
    esac
    _ret="${linkcmd}"
}

# try make
_toolchain_try_make() {
    local program="${1}"
    if _os_runv "${program}" "--version"; then
        return 0
    fi
    return 1
}

# try ninja
_toolchain_try_ninja() {
    local program="${1}"
    if _os_runv "${program}" "--version"; then
        return 0
    fi
    return 1
}

# try gcc
_toolchain_try_gcc() {
    if test "x${_toolchain_try_gcc_result}" = "xok"; then
        return 0
    elif test "x${_toolchain_try_gcc_result}" = "xno"; then
        return 1
    fi

    local kind="${1}"
    local program="${2}"
    if _os_runv "${program}" "--version"; then
        _toolchain_try_gcc_result="ok"
        return 0
    fi
    _toolchain_try_gcc_result="no"
    return 1
}

# try g++
_toolchain_try_gxx() {
    if test "x${_toolchain_try_gxx_result}" = "xok"; then
        return 0
    elif test "x${_toolchain_try_gxx_result}" = "xno"; then
        return 1
    fi

    local kind="${1}"
    local program="${2}"
    if _os_runv "${program}" "--version"; then
        _toolchain_try_gxx_result="ok"
        return 0
    fi
    _toolchain_try_gxx_result="no"
    return 1
}

# try clang
_toolchain_try_clang() {
    if test "x${_toolchain_try_clang_result}" = "xok"; then
        return 0
    elif test "x${_toolchain_try_clang_result}" = "xno"; then
        return 1
    fi

    local kind="${1}"
    local program="${2}"
    if _os_runv "${program}" "--version"; then
        _toolchain_try_clang_result="ok"
        return 0
    fi
    _toolchain_try_clang_result="no"
    return 1
}

# try clang++
_toolchain_try_clangxx() {
    if test "x${_toolchain_try_clangxx_result}" = "xok"; then
        return 0
    elif test "x${_toolchain_try_clangxx_result}" = "xno"; then
        return 1
    fi

    local kind="${1}"
    local program="${2}"
    if _os_runv "${program}" "--version"; then
        _toolchain_try_clangxx_result="ok"
        return 0
    fi
    _toolchain_try_clangxx_result="no"
    return 1
}

# try tcc
_toolchain_try_tcc() {
    if test "x${_toolchain_try_tcc_result}" = "xok"; then
        return 0
    elif test "x${_toolchain_try_tcc_result}" = "xno"; then
        return 1
    fi

    local kind="${1}"
    local program="${2}"
    if _os_runv "${program}" "-v"; then
        _toolchain_try_tcc_result="ok"
        return 0
    fi
    _toolchain_try_tcc_result="no"
    return 1
}

# try ar
_toolchain_try_ar() {
    local kind="${1}"
    local program="${2}"

    # generate the source file
    _os_tmpfile
    local tmpfile="${_ret}"
    local objectfile="${tmpfile}.o"
    local libraryfile="${tmpfile}.a"
    echo "" > "${objectfile}"

    # try linking it
    local ok=false
    if _os_runv "${program}" "-cr" "${libraryfile}" "${objectfile}"; then
        ok=true
    fi

    # remove files
    _os_tryrm "${objectfile}"
    _os_tryrm "${libraryfile}"
    if ${ok}; then
        return 0
    fi
    return 1
}

# try cosmoar
_toolchain_try_cosmoar() {
    if test "x${_toolchain_try_cosmoar_result}" = "xok"; then
        return 0
    elif test "x${_toolchain_try_cosmoar_result}" = "xno"; then
        return 1
    fi

    local kind="${1}"
    local program="${2}"
    if _os_runv "${program}" "--version"; then
        _toolchain_try_cosmoar_result="ok"
        return 0
    fi
    _toolchain_try_cosmoar_result="no"
    return 1
}

# try program
_toolchain_try_program() {
    local toolchain="${1}"
    local kind="${2}"
    local program="${3}"
    local ok=false
    path_toolname "${program}"; local toolname="${_ret}"
    case "${toolname}" in
        gcc) _toolchain_try_gcc "${kind}" "${program}" && ok=true;;
        gxx) _toolchain_try_gxx "${kind}" "${program}" && ok=true;;
        clang) _toolchain_try_clang "${kind}" "${program}" && ok=true;;
        clangxx) _toolchain_try_clangxx "${kind}" "${program}" && ok=true;;
        emcc) _toolchain_try_clang "${kind}" "${program}" && ok=true;;
        emxx) _toolchain_try_clangxx "${kind}" "${program}" && ok=true;;
        cosmocc) _toolchain_try_gcc "${kind}" "${program}" && ok=true;;
        cosmocxx) _toolchain_try_gxx "${kind}" "${program}" && ok=true;;
        tcc) _toolchain_try_tcc "${kind}" "${program}" && ok=true;;
        ar) _toolchain_try_ar "${kind}" "${program}" && ok=true;;
        emar) _toolchain_try_ar "${kind}" "${program}" && ok=true;;
        cosmoar) _toolchain_try_cosmoar "${kind}" "${program}" && ok=true;;
        *) raise "unknown toolname(${toolname})!" ;;
    esac
    if ${ok}; then
        vprint "checking for ${program} ... ok"
        return 0
    fi
    vprint "checking for ${program} ... no"
    return 1
}

# try toolset
_toolchain_try_toolset() {
    local toolchain=${1}
    local kind=${2}
    local description=${3}
    local indices="0 1 2 3 4 5"
    for idx in ${indices}; do
        local key="${kind}"
        if test_nq "${idx}" "0"; then
            key="${key}_${idx}"
        fi
        _get_toolchain_toolset "${toolchain}" "${key}"; local program="${_ret}"
        if test_nz "${program}"; then
            if _toolchain_try_program "${toolchain}" "${kind}" "${program}"; then
                _set_toolchain_toolset "${toolchain}" "${kind}" "${program}"
                echo "checking for the ${description} (${kind}) ... ${program}"
                return 0
            fi
        fi
    done
    return 1
}

# try toolchain
_toolchain_try() {
    local toolchain=${1}
    vprint "checking for $toolchain toolchain ..."
    if _toolchain_try_toolset "${toolchain}" "cc" "c compiler" &&
       _toolchain_try_toolset "${toolchain}" "cxx" "c++ compiler" &&
       _toolchain_try_toolset "${toolchain}" "as" "assembler" &&
       _toolchain_try_toolset "${toolchain}" "mm" "objc compiler" &&
       _toolchain_try_toolset "${toolchain}" "mxx" "objc++ compiler" &&
       _toolchain_try_toolset "${toolchain}" "ld" "linker" &&
       _toolchain_try_toolset "${toolchain}" "ar" "static library archiver" &&
       _toolchain_try_toolset "${toolchain}" "sh" "shared library linker"; then
        return 0
    fi
    return 1
}

# detect make
_toolchain_detect_make() {
    if test "x${_make_program}" = "x"; then
        _make_program=${_make_program_default}
    fi
    if _toolchain_try_make "${_make_program}"; then
        echo "checking for make ... ok"
    else
        echo "checking for make ... no"
        raise "make not found!"
    fi
}

# detect ninja
_toolchain_detect_ninja() {
    if test "x${_ninja_program}" = "x"; then
        _ninja_program=${_ninja_program_default}
    fi
    if _toolchain_try_ninja "${_ninja_program}"; then
        echo "checking for ninja ... ok"
    else
        echo "checking for ninja ... no"
        raise "ninja not found!"
    fi
}

# detect build backend
_toolchain_detect_backend() {
    if test "x${project_generator}" = "xgmake"; then
        _toolchain_detect_make
    elif test "x${project_generator}" = "xninja"; then
        _toolchain_detect_ninja
    fi
}

# detect toolchain
_toolchain_detect() {
    # detect build backend
    _toolchain_detect_backend

    # detect toolchains
    local toolchains="${1}"
    if test "x${toolchains}" = "x"; then
        if is_plat "macosx"; then
            toolchains="envs clang gcc"
        elif is_plat "mingw"; then
            if is_arch "i386"; then
                toolchains="i686_w64_mingw32"
            else
                toolchains="x86_64_w64_mingw32"
            fi
        elif is_plat "wasm"; then
            toolchains="emcc"
        elif is_plat "linux" && ! is_arch "${os_arch}"; then
            toolchains="envs"
            if is_arch "arm64"; then
                toolchains="${toolchains} aarch64_linux_gnu"
            fi
        else
            toolchains="envs gcc clang"
        fi
    fi
    for toolchain in ${toolchains}; do
        if _toolchain_try "$toolchain"; then
            _target_toolchain=${toolchain}
            break
        fi
    done
}

# check toolchain
_check_toolchain() {
    local toolchain=${_target_toolchain}
    _target_toolchain=""
    _toolchain_detect ${toolchain}

    if test "x${_target_toolchain}" != "x"; then
        echo "checking for toolchain ... ${_target_toolchain}"
    else
        echo "checking for toolchain ... no"
        raise "toolchain not found!"
    fi
}

# get function code
#
# sigsetjmp
# sigsetjmp((void*)0, 0)
#
_get_funccode() {
    local func="${1}"
    local code=""
    if string_contains "${func}" "("; then
        code="${func}"
    else
        code="typedef void (*func_t)(); volatile func_t p${func} = (func_t)${func}; while (p${func}) {break;};"
    fi
    _ret="${code}"
}

# generate cxsnippets source code
_generate_cxsnippets_sourcecode() {
    local funcs="${1}"
    local includes="${2}"
    local types="${3}"
    local snippets="${4}"

    local snippet_includes=""
    for include in $includes; do
        snippet_includes="${snippet_includes}#include \"${include}\"\n"
    done

    local snippet_types=""
    for type in $types; do
        string_replace "${type}" '[^a-zA-Z]' "_"; local typevar="${_ret}"
        snippet_types="${snippet_types}typedef ${type} __type_${typevar};\n"
    done

    local snippet_funcs=""
    for func in $funcs; do
        _get_funccode "${func}"; func="${_ret}"
        snippet_funcs="${snippet_funcs}${func}\n    "
    done

    local snippets_code=""
    if test_nz "${snippet_includes}"; then
        snippets_code="${snippets_code}${snippet_includes}\n"
    fi
    if test_nz "${snippet_types}"; then
        snippets_code="${snippets_code}${snippet_types}\n"
    fi
    if test_nz "${snippets}"; then
        snippets_code="${snippets_code}${snippets}\n"
    fi

    _ret='
'"${snippets_code}"'int main(int argc, char** argv) {
    '"${snippet_funcs}"'
    return 0;
}'
}

# check cxsnippets
_check_cxsnippets() {
    local name="${1}"
    local kind="${2}"
    _get_option_item "${name}" "${kind}funcs"; local funcs="${_ret}"
    _get_option_item "${name}" "${kind}includes"; local includes="${_ret}"
    _get_option_item "${name}" "${kind}types"; local types="${_ret}"
    _get_option_item "${name}" "${kind}snippets"; local snippets="${_ret}"
    if test_z "${funcs}" && test_z "${includes}" &&
       test_z "${types}" && test_z "${snippets}"; then
        return 0
    fi

    # get c/c++ extension
    local extension=".c"
    local sourcekind="cc"
    if test_eq "${kind}" "cxx"; then
        extension=".cpp"
        sourcekind="cxx"
    fi

    # generate source code
    _generate_cxsnippets_sourcecode "${funcs}" "${includes}" "${types}" "${snippets}"; local sourcecode="${_ret}"
    dprint "${sourcecode}"

    # generate the source file
    _os_tmpfile
    local tmpfile="${_ret}"
    local sourcefile="${tmpfile}${extension}"
    local objectfile="${tmpfile}.o"
    local binaryfile="${tmpfile}.bin"
    print "${sourcecode}" > "${sourcefile}"

    # try compiling it
    local ok=false
    if ! ${ok}; then
        local compflags=""
        _get_toolchain_toolset "${_target_toolchain}" "${sourcekind}"; local program="${_ret}"
        path_toolname "${program}"; local toolname="${_ret}"
        local itemnames="languages warnings optimizes defines undefines includedirs"
        for itemname in ${itemnames}; do
            _get_option_abstract_flags "${name}" "${sourcekind}" "${toolname}" "${itemname}"; local flags="${_ret}"
            if test_nz "${flags}"; then
                _split_flags "${flags}"; flags="${_ret}"
                compflags="${compflags} ${flags}"
            fi
        done
        local flagnames="cxflags"
        if test_eq "${sourcekind}" "cxx"; then
            flagnames="${flagnames} cxxflags"
        else
            flagnames="${flagnames} cflags"
        fi
        for flagname in $flagnames; do
            _get_option_item "${name}" "${flagname}"; local flags="${_ret}"
            if test_nz "${flags}"; then
                compflags="${compflags} ${flags}"
            fi
        done
        if test_eq "${sourcekind}" "cxx"; then
            if test_nz "${CXXFLAGS}"; then
                compflags="${compflags} ${CXXFLAGS}"
            fi
        else
            if test_nz "${CFLAGS}"; then
                compflags="${compflags} ${CFLAGS}"
            fi
        fi
        if test_nz "${CPPFLAGS}"; then
            compflags="${compflags} ${CPPFLAGS}"
        fi
        _toolchain_compcmd "${sourcekind}" "${objectfile}" "${sourcefile}" "${compflags}"; local compcmd="${_ret}"
        if ${xmake_sh_diagnosis}; then
            print "> ${compcmd}"
        fi
        if _os_runv ${compcmd}; then
            ok=true
        fi
    fi

    # try linking it
    _get_option_item "${name}" "links"; local links="${_ret}"
    _get_option_item "${name}" "syslinks"; local syslinks="${_ret}"
    _get_option_item "${name}" "ldflags"; local ldflags="${_ret}"
    if test_nz "${syslinks}"; then
        links="${links} ${syslinks}"
    fi
    if ${ok} && (test_nz "${links}" || test_nz "${ldflags}"); then
        local toolkind="ld"
        _get_toolchain_toolset "${_target_toolchain}" "${toolkind}"; local program="${_ret}"
        path_toolname "${program}"; local toolname="${_ret}"
        local itemnames="linkdirs links syslinks"
        local linkflags=""
        for itemname in ${itemnames}; do
            _get_option_abstract_flags "${name}" "${toolkind}" "${toolname}" "${itemname}"; local flags="${_ret}"
            if test_nz "${flags}"; then
                _split_flags "${flags}"; flags="${_ret}"
                linkflags="${linkflags} ${flags}"
            fi
        done
        _get_option_item "${name}" "ldflags"; local flags="${_ret}"
        if test_nz "${flags}"; then
            linkflags="${linkflags} ${flags}"
        fi
        if test_nz "${LDFLAGS}"; then
            linkflags="${linkflags} ${LDFLAGS}"
        fi
        _toolchain_linkcmd "${toolkind}" "${binaryfile}" "${objectfile}" "${linkflags}"; local linkcmd="${_ret}"
        if ${xmake_sh_diagnosis}; then
            print "> ${linkcmd}"
        fi
        if _os_runv ${linkcmd}; then
            ok=true
        else
            ok=false
        fi
    fi

    # trace
    if ${xmake_sh_verbose} || ${xmake_sh_diagnosis}; then
        if test_nz "${includes}"; then
            print "> checking for ${kind} includes(${includes})"
        fi
        if test_nz "${types}"; then
            print "> checking for ${kind} types(${types})"
        fi
        if test_nz "${funcs}"; then
            print "> checking for ${kind} funcs(${funcs})"
        fi
        if test_nz "${links}"; then
            print "> checking for ${kind} links(${links})"
        fi
    fi

    # remove files
    _os_tryrm "${sourcefile}"
    _os_tryrm "${objectfile}"
    _os_tryrm "${binaryfile}"
    if ${ok}; then
        return 0
    fi
    return 1
}

# check csnippets
_check_csnippets() {
    local name="${1}"
    if _check_cxsnippets "${name}" "c"; then
        return 0
    fi
    return 1
}

# check cxxsnippets
_check_cxxsnippets() {
    local name="${1}"
    if _check_cxsnippets "${name}" "cxx"; then
        return 0
    fi
    return 1
}

# check option
_check_option() {
    local name="${1}"
    _get_option_value "${name}"; local value="${_ret}"
    _get_option_item "${name}" "default"; local default="${_ret}"
    if test_nz "${value}"; then
        if _is_enabled "${value}"; then
            return 0
        else
            return 1
        fi
    elif test_nz "${default}"; then
        if _is_enabled "${default}"; then
            return 0
        else
            return 1
        fi
    else
        _get_option_item "${name}" "before_check"; local before_check="${_ret}"
        if test_nz "${before_check}"; then
            eval ${before_check}
        fi
        if _check_csnippets "${name}" && _check_cxxsnippets "${name}"; then
            return 0
        fi
    fi
    return 1
}

# check options
_check_options() {
    _get_options_for_checking; local options="${_ret}"
    for name in $options; do
        if _check_option "$name"; then
            echo "checking for ${name} .. ok"
            _set_option_value "${name}" true
        else
            echo "checking for ${name} .. no"
            _set_option_value "${name}" false
        fi
    done
}

# check all
_check_all() {
    _check_platform
    _check_toolchain
    _check_options
}
_check_all

#-----------------------------------------------------------------------------
# init builtin variables, e.g. add_headerfiles "${buildir}/config.h"
#
projectdir="${xmake_sh_projectdir}"
if path_is_absolute "${xmake_sh_buildir}"; then
    buildir="${xmake_sh_buildir}"
else
    buildir="${xmake_sh_projectdir}/${xmake_sh_buildir}"
fi
plat="${_target_plat}"
arch="${_target_arch}"
mode="${_target_mode}"
kind="${_target_kind}"

#-----------------------------------------------------------------------------
# load project targets
#

# load targets
_load_targets() {
    echo "analyzing project configuration .."
    _loading_options=false
    _loading_toolchains=false
    _loading_targets=true
    _xmake_sh_option_current=""
    _xmake_sh_target_current=""
    _xmake_sh_toolchain_current=""
    local file=${xmake_sh_projectdir}/xmake.sh
    if test -f "${file}"; then
        includes "${file}"
    else
        # include all xmake.sh files in next sub-directories
        _os_find "${xmake_sh_projectdir}" "xmake.sh" 2; local files="${_ret}"
        for file in ${files}; do
            includes "${file}"
        done
    fi
}
_load_targets

# get toolset kinds for all targets
# e.g. cc cxx as mm mxx ld sh ar
_get_targets_toolkinds() {
    if test_z "${_targets_toolkinds_dedup}"; then
        _dedup "${_targets_toolkinds}"; _targets_toolkinds_dedup="${_ret}"
    fi
    _ret="${_targets_toolkinds_dedup}"
}

#-----------------------------------------------------------------------------
# generate configfiles
#

# vprint config variable in `${name}`
_vprint_configvar_value() {
    local name="${1}"
    local value="${2}"
    vprint "  > replace ${name} -> ${value}"
}

# vprint config variable in `${define name}`
_vprint_configvar_define() {
    local name="${1}"
    local value="${2}"
    if test_z "${value}"; then
        vprint "  > replace ${name} -> /* #undef ${name} */"
    elif test_eq "${value}" "1" || test_eq "${value}" "true"; then
        vprint "  > replace ${name} -> #define ${name} 1"
    elif test_eq "${value}" "0" || test_eq "${value}" "false"; then
        vprint "  > replace ${name} -> /*#define ${name} 0*/"
    else
        vprint "  > replace ${name} -> #define ${name} ${value}"
    fi
}

# replace config variable in `${define name}`
_replace_configvar_define() {
    local name="${1}"
    local value="${2}"
    if test_z "${value}"; then
        _ret="s/\${define ${name}}/\/*#undef ${name}*\//g"
    elif test_eq "${value}" "1" || test_eq "${value}" "true"; then
        _ret="s/\${define ${name}}/#define ${name} 1/g"
    elif test_eq "${value}" "0" || test_eq "${value}" "false"; then
        _ret="s/\${define ${name}}/\/*#define ${name} 0*\//g"
    else
        _ret="s/\${define ${name}}/#define ${name} ${value}/g"
    fi
}

# replace config variable in `${name}`
_replace_configvar_value() {
    local name="${1}"
    local value="${2}"
    _ret="s@\${${name}}@${value}@g"
}

# generate configfile for the given target
_generate_configfile() {
    local target="${1}"
    local configfile_in="${2}"
    _get_target_item "${target}" "configdir"; local configdir="${_ret}"
    if test_z "${configdir}"; then
        path_directory configfile_in; configdir="${_ret}"
    fi
    if ! test -d "${configdir}"; then
        mkdir -p "${configdir}"
    fi
    path_basename "${configfile_in}"; local filename="${_ret}"
    local configfile="${configdir}/${filename}"
    echo "generating ${configfile} .."

    # replace builtin variables
    local patterns=""
    local target_os=""
    if is_plat "mingw"; then
        target_os="windows"
    else
        target_os="${_target_plat}"
    fi
    string_toupper ${target_os}; target_os="${_ret}"
    _vprint_configvar_value "OS" "${target_os}"
    _replace_configvar_value "OS" "${target_os}"; patterns="${_ret};${patterns}"

    # replace version
    _get_target_item "${target}" "version"; local version="${_ret}"
    _get_target_item "${target}" "version_build"; local version_build="${_ret}"
    string_split "${version}" "."
    local version_major="${_ret}"
    local version_minor="${_ret2}"
    local version_alter="${_ret3}"
    if test_nz "${version}"; then
        _vprint_configvar_value "VERSION" "${version}"
        _replace_configvar_value "VERSION" "${version}"; patterns="${_ret};${patterns}"
    fi
    if test_nz "${version_major}"; then
        _vprint_configvar_value "VERSION_MAJOR" "${version_major}"
        _replace_configvar_value "VERSION_MAJOR" "${version_major}"; patterns="${_ret};${patterns}"
    fi
    if test_nz "${version_minor}"; then
        _vprint_configvar_value "VERSION_MINOR" "${version_minor}"
        _replace_configvar_value "VERSION_MINOR" "${version_minor}"; patterns="${_ret};${patterns}"
    fi
    if test_nz "${version_alter}"; then
        _vprint_configvar_value "VERSION_ALTER" "${version_alter}"
        _replace_configvar_value "VERSION_ALTER" "${version_alter}"; patterns="${_ret};${patterns}"
    fi
    if test_nz "${version_build}"; then
        _os_date "${version_build}"; version_build="${_ret}"
        _vprint_configvar_value "VERSION_BUILD" "${version_build}"
        _replace_configvar_value "VERSION_BUILD" "${version_build}"; patterns="${_ret};${patterns}"
    fi

    # replace git variables
    local content=""
    content=$(cat "${configfile_in}")
    if string_contains "${content}" "GIT_"; then
        _os_iorunv "git" "describe" "--tags"; local git_tag="${_ret}"
        _vprint_configvar_value "GIT_TAG" "${git_tag}"
        _replace_configvar_value "GIT_TAG" "${git_tag}"; patterns="${_ret};${patterns}"

        _os_iorunv "git" "describe" "--tags" "--long"; local git_tag_long="${_ret}"
        _vprint_configvar_value "GIT_TAG_LONG" "${git_tag_long}"
        _replace_configvar_value "GIT_TAG_LONG" "${git_tag_long}"; patterns="${_ret};${patterns}"

        _os_iorunv "git" "rev-parse" "--abbrev-ref" "HEAD"; local git_branch="${_ret}"
        _vprint_configvar_value "GIT_BRANCH" "${git_branch}"
        _replace_configvar_value "GIT_BRANCH" "${git_branch}"; patterns="${_ret};${patterns}"

        _os_iorunv "git" "rev-parse" "--short" "HEAD"; local git_commit="${_ret}"
        _vprint_configvar_value "GIT_COMMIT" "${git_commit}"
        _replace_configvar_value "GIT_COMMIT" "${git_commit}"; patterns="${_ret};${patterns}"

        _os_iorunv "git" "rev-parse" "HEAD"; local git_commit_long="${_ret}"
        _vprint_configvar_value "GIT_COMMIT_LONG" "${git_commit_long}"
        _replace_configvar_value "GIT_COMMIT_LONG" "${git_commit_long}"; patterns="${_ret};${patterns}"

        _os_iorunv "log" "-1" "--date=format:%Y%m%d%H%M%S" "--format=%ad"; local git_commit_date="${_ret}"
        _vprint_configvar_value "GIT_COMMIT_DATE" "${git_commit_date}"
        _replace_configvar_value "GIT_COMMIT_DATE" "${git_commit_date}"; patterns="${_ret};${patterns}"
    fi

    # replace configvars in target
    local count=0
    local configfile_dst="${configfile}"
    _os_tmpfile; local tmpfile="${_ret}"
    cp "${configfile_in}" "${tmpfile}"
    _get_target_item "${target}" "configvars"; local configvars="${_ret}"
    for name in ${configvars}; do
        _get_target_item "${target}" "configvar_${name}"; local value="${_ret}"
        _vprint_configvar_define "${name}" "${value}"
        _vprint_configvar_value "${name}" "${value}"
        _replace_configvar_define "${name}" "${value}"; patterns="${_ret};${patterns}"
        _replace_configvar_value "${name}" "${value}"; patterns="${_ret};${patterns}"
        count=$((count + 1))

        # do replace
        if test_eq "$count" "10"; then
            _io_replace_file "${tmpfile}" "${configfile}" "${patterns}"
            local swapfile="${tmpfile}"
            tmpfile="${configfile}"
            configfile="${swapfile}"
            patterns=""
            count=0
        fi
    done

    # do replace (left)
    if test_nz "${patterns}"; then
        _io_replace_file "${tmpfile}" "${configfile}" "${patterns}"
        local swapfile="${tmpfile}"
        tmpfile="${configfile}"
        configfile="${swapfile}"
        patterns=""
        count=0
    fi

    # replace fallback
    patterns='s/${define \(.*\)}/\/*#undef \1*\//g;'
    _io_replace_file "${tmpfile}" "${configfile}" "${patterns}"
    if test_nq "${configfile}" "${configfile_dst}"; then
        cp "${configfile}" "${configfile_dst}"
    fi
    echo "${configfile_dst} is generated!"
}

# generate configfiles
_generate_configfiles() {
    for target in ${_xmake_sh_targets}; do
        _get_target_item "${target}" "configfiles"; local configfiles="${_ret}"
        for configfile in ${configfiles}; do
            _generate_configfile "${target}" "${configfile}"
        done
    done
}
_generate_configfiles

#-----------------------------------------------------------------------------
# generate gmake file
#

_gmake_begin() {
    echo "generating makefile .."
}

_gmake_add_header() {
    echo "# this is the build file for this project
# it is autogenerated by the xmake.sh build system.
# do not edit by hand.
" > "${xmake_sh_makefile}"
}

_gmake_add_switches() {
    echo "ifneq (\$(VERBOSE),1)" >> "${xmake_sh_makefile}"
    echo "VV=@" >> "${xmake_sh_makefile}"
    echo "endif" >> "${xmake_sh_makefile}"
    echo "" >> "${xmake_sh_makefile}"
    echo "ifeq (\$(PREFIX),)" >> "${xmake_sh_makefile}"
    echo "PREFIX=${_install_prefix_default}" >> "${xmake_sh_makefile}"
    echo "endif" >> "${xmake_sh_makefile}"
    echo "" >> "${xmake_sh_makefile}"
    echo "INSTALLDIR:=\$(DESTDIR)" >> "${xmake_sh_makefile}"
    echo "ifneq (\$(PREFIX),)" >> "${xmake_sh_makefile}"
    echo "ifneq (\$(INSTALLDIR),)" >> "${xmake_sh_makefile}"
    echo "PREFIX_:=\$(patsubst /%,%,\$(PREFIX))" >> "${xmake_sh_makefile}"
    echo "INSTALLDIR:=\$(INSTALLDIR)/\$(PREFIX_)" >> "${xmake_sh_makefile}"
    echo "else" >> "${xmake_sh_makefile}"
    echo "INSTALLDIR:=\$(PREFIX)" >> "${xmake_sh_makefile}"
    echo "endif" >> "${xmake_sh_makefile}"
    echo "endif" >> "${xmake_sh_makefile}"
    echo "" >> "${xmake_sh_makefile}"
}

_gmake_add_flags() {
    _get_targets_toolkinds; local kinds="${_ret}"
    for target in ${_xmake_sh_targets}; do
        for kind in ${kinds}; do
            _get_target_flags "${target}" "${kind}"; local flags="${_ret}"
            _get_flagname "${kind}"; local flagname="${_ret}"
            local key="${target}_${flagname}"
            echo "${key}=${flags}" >> "${xmake_sh_makefile}"
        done
        echo "" >> "${xmake_sh_makefile}"
    done
}

_gmake_add_toolchains() {
    _get_targets_toolkinds; local kinds="${_ret}"
    for kind in ${kinds}; do
        _get_toolchain_toolset "${_target_toolchain}" "${kind}"; local program="${_ret}"
        local key="${kind}"
        echo "${key}=${program}" >> "${xmake_sh_makefile}"
    done
    echo "" >> "${xmake_sh_makefile}"
}

_gmake_add_build_object_for_gcc_clang() {
    local kind="${1}"
    local sourcefile="${2}"
    local objectfile="${3}"
    local flagname="${4}"
    path_directory "${objectfile}"; local objectdir="${_ret}"
    print "\t@mkdir -p ${objectdir}" >> "${xmake_sh_makefile}"
    print "\t\$(VV)\$(${kind}) -c \$(${flagname}) -o ${objectfile} ${sourcefile}" >> "${xmake_sh_makefile}"
}

_gmake_add_build_object() {
    local target=${1}
    local sourcefile="${2}"
    local objectfile="${3}"
    path_sourcekind "${sourcefile}"; local sourcekind="${_ret}"
    _get_toolchain_toolset "${_target_toolchain}" "${sourcekind}"; local program="${_ret}"
    path_toolname "${program}"; local toolname="${_ret}"
    _get_flagname "${sourcekind}"; local flagname="${_ret}"
    flagname="${target}_${flagname}"
    echo "${objectfile}: ${sourcefile}" >> "${xmake_sh_makefile}"
    print "\t@echo compiling.${_target_mode} ${sourcefile}" >> "${xmake_sh_makefile}"
    case "${toolname}" in
        gcc) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        gxx) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        clang) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        clangxx) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        emcc) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        emxx) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        cosmocc) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        cosmocxx) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        tcc) _gmake_add_build_object_for_gcc_clang "${sourcekind}" "${sourcefile}" "${objectfile}" "${flagname}";;
        *) raise "unknown toolname(${toolname})!" ;;
    esac
    echo "" >> "${xmake_sh_makefile}"
}

_gmake_add_build_objects() {
    local target=${1}
    _get_target_sourcefiles "${target}"; local sourcefiles="${_ret}"
    for sourcefile in ${sourcefiles}; do
        _get_target_objectfile "${target}" "${sourcefile}"; local objectfile="${_ret}"
        _gmake_add_build_object "${target}" "${sourcefile}" "${objectfile}"
    done
}

_gmake_add_build_target_for_gcc_clang() {
    local kind="${1}"
    local targetfile="${2}"
    local objectfiles="${3}"
    local flagname="${4}"
    path_directory "${targetfile}"; local targetdir="${_ret}"
    print "\t@mkdir -p ${targetdir}" >> "${xmake_sh_makefile}"
    print "\t\$(VV)\$(${kind}) -o ${targetfile} ${objectfiles} \$(${flagname})" >> "${xmake_sh_makefile}"
}

_gmake_add_build_target_for_ar() {
    local kind="${1}"
    local targetfile="${2}"
    local objectfiles="${3}"
    local flagname="${4}"
    path_directory "${targetfile}"; local targetdir="${_ret}"
    print "\t@mkdir -p ${targetdir}" >> "${xmake_sh_makefile}"
    print "\t\$(VV)\$(${kind}) \$(${flagname}) ${flags} ${targetfile} ${objectfiles}" >> "${xmake_sh_makefile}"
}

_gmake_add_build_target() {
    local target=${1}
    _get_targetdir "${target}"; local targetdir="${_ret}"
    _get_target_file "${target}"; local targetfile="${_ret}"
    _get_target_item "${target}" "deps"; local deps="${_ret}"
    _get_target_objectfiles "${target}"; local objectfiles="${_ret}"

    # get linker
    _get_target_item "${target}" "kind"; local targetkind="${_ret}"
    local toolkind=""
    case "${targetkind}" in
        binary) toolkind="ld";;
        static) toolkind="ar";;
        shared) toolkind="sh";;
        *) raise "unknown targetkind(${targetkind})!" ;;
    esac
    _get_toolchain_toolset "${_target_toolchain}" "${toolkind}"; local program="${_ret}"
    path_toolname "${program}"; local toolname="${_ret}"

    # get linker flags
    _get_flagname "${toolkind}"; local flagname="${_ret}"
    flagname="${target}_${flagname}"

    # get depfiles
    local dep=""
    local depfiles=""
    for dep in ${deps}; do
        _get_target_file "${dep}"; local depfile="${_ret}"
        if test_nz "${depfiles}"; then
            depfiles="${depfiles} ${depfile}"
        else
            depfiles="${depfile}"
        fi
    done

    # link target
    echo "${target}: ${targetfile}" >> "${xmake_sh_makefile}"
    echo "${targetfile}: ${depfiles}${objectfiles}" >> "${xmake_sh_makefile}"
    print "\t@echo linking.${_target_mode} ${targetfile}" >> "${xmake_sh_makefile}"
    case "${toolname}" in
        gcc) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        gxx) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        clang) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        clangxx) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        emcc) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        emxx) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        cosmocc) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        cosmocxx) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        tcc) _gmake_add_build_target_for_gcc_clang "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        ar) _gmake_add_build_target_for_ar "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        emar) _gmake_add_build_target_for_ar "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        cosmoar) _gmake_add_build_target_for_ar "${toolkind}" "${targetfile}" "${objectfiles}" "${flagname}";;
        *) raise "unknown toolname(${toolname})!" ;;
    esac

    # @see https://github.com/tboox/tbox/issues/214
    if test_eq "${targetkind}" "shared"; then
        _get_target_item "${target}" "version"; local version="${_ret}"
        _get_target_soname "${target}"; local soname="${_ret}"
        if test_nz "${soname}" && test_nz "${version}"; then
            _get_target_filename "${target}"; local filename="${_ret}"
            _get_target_extension "${target}"; local extension="${_ret}"
            local targetfile_with_version="${targetdir}/${filename}.${version}"
            if test_eq "${extension}" ".dylib"; then
                path_basename "${filename}"; local basename="${_ret}"
                targetfile_with_version="${targetdir}/${basename}.${version}${extension}"
            fi
            local targetfile_with_soname="${targetdir}/${soname}"
            path_filename "${targetfile_with_version}"; local targetfilename_with_version="${_ret}"
            if test_nq "${soname}" "${filename}" && test_nq "${soname}" "${targetfilename_with_version}"; then
                print "\t@cp -p ${targetfile} ${targetfile_with_version}" >> "${xmake_sh_makefile}"
                print "\t@cd ${targetdir} && ln -sf ${targetfilename_with_version} ${soname} && ln -sf ${soname} ${filename}" >> "${xmake_sh_makefile}"
            fi
        fi
    fi

    # end
    echo "" >> "${xmake_sh_makefile}"

    # build objects
    _gmake_add_build_objects "${target}"
}

_gmake_add_build_targets() {
    local target=""
    local defaults=""
    for target in ${_xmake_sh_targets}; do
        if _is_target_default "${target}"; then
            defaults="${defaults} ${target}"
        fi
    done
    echo "default:${defaults}" >> "${xmake_sh_makefile}"
    echo "all:${_xmake_sh_targets}" >> "${xmake_sh_makefile}"
    echo ".PHONY: default all" >> "${xmake_sh_makefile}"
    echo "" >> "${xmake_sh_makefile}"
    for target in ${_xmake_sh_targets}; do
        _gmake_add_build_target "${target}"
    done
}

_gmake_add_build() {
    _gmake_add_build_targets
}

_gmake_add_run_target() {
    local target=${1}
    _get_targetdir "${target}"; local targetdir="${_ret}"
    _get_target_file "${target}"; local targetfile="${_ret}"
    if is_plat "macosx"; then
        print "\t@DYLD_LIBRARY_PATH=${targetdir} ${targetfile}" >> "${xmake_sh_makefile}"
    elif is_plat "linux" "bsd"; then
        print "\t@LD_LIBRARY_PATH=${targetdir} ${targetfile}" >> "${xmake_sh_makefile}"
    else
        print "\t@${targetfile}" >> "${xmake_sh_makefile}"
    fi
}

_gmake_add_run_targets() {
    local target=""
    local targets=""
    for target in ${_xmake_sh_targets}; do
        _get_target_item "${target}" "kind"; local kind="${_ret}"
        if test "x${kind}" = "xbinary"; then
            if _is_target_default "${target}"; then
                targets="${targets} ${target}"
            fi
        fi
    done
    echo "run:${targets}" >> "${xmake_sh_makefile}"
    for target in ${targets}; do
        _gmake_add_run_target "${target}"
    done
    echo "" >> "${xmake_sh_makefile}"
}

_gmake_add_run() {
    _gmake_add_run_targets
}

_gmake_add_clean_target() {
    local target=${1}
    local objectfile=""
    _get_target_file "${target}"; local targetfile="${_ret}"
    _get_target_objectfiles "${target}"; local objectfiles="${_ret}"
    print "\t@rm ${targetfile}" >> "${xmake_sh_makefile}"
    for objectfile in ${objectfiles}; do
        print "\t@rm ${objectfile}" >> "${xmake_sh_makefile}"
    done

    # @see https://github.com/tboox/tbox/issues/214
    _get_targetdir "${target}"; local targetdir="${_ret}"
    _get_target_item "${target}" "kind"; local targetkind="${_ret}"
    if test_eq "${targetkind}" "shared"; then
        _get_target_item "${target}" "version"; local version="${_ret}"
        _get_target_soname "${target}"; local soname="${_ret}"
        if test_nz "${soname}" && test_nz "${version}"; then
            _get_target_filename "${target}"; local filename="${_ret}"
            _get_target_extension "${target}"; local extension="${_ret}"
            local targetfile_with_version="${targetdir}/${filename}.${version}"
            if test_eq "${extension}" ".dylib"; then
                path_basename "${filename}"; local basename="${_ret}"
                targetfile_with_version="${targetdir}/${basename}.${version}${extension}"
            fi
            local targetfile_with_soname="${targetdir}/${soname}"
            print "\t@if test -f ${targetfile_with_soname}; then rm ${targetfile_with_soname}; fi" >> "${xmake_sh_makefile}"
            print "\t@if test -f ${targetfile_with_version}; then rm ${targetfile_with_version}; fi" >> "${xmake_sh_makefile}"
        fi
    fi
}

_gmake_add_clean_targets() {
    local target=""
    local targets=""
    for target in ${_xmake_sh_targets}; do
        if _is_target_default "${target}"; then
            targets="${targets} ${target}"
        fi
    done
    echo "clean:${targets}" >> "${xmake_sh_makefile}"
    for target in ${targets}; do
        _gmake_add_clean_target "${target}"
    done
    echo "" >> "${xmake_sh_makefile}"
}

_gmake_add_clean() {
    _gmake_add_clean_targets
}

_gmake_add_install_target() {
    local target=${1}
    _get_target_file "${target}"; local targetfile="${_ret}"
    path_filename "${targetfile}"; local filename="${_ret}"
    _get_target_item "${target}" "installdir"; local installdir="${_ret}"
    _get_target_item "${target}" "kind"; local targetkind="${_ret}"
    if test_z "${installdir}"; then
        installdir="\$(INSTALLDIR)"
    fi

    # before install
    _get_target_item "${target}" "before_install"; local before_install="${_ret}"
    if test_nz "${before_install}"; then
        eval ${before_install} "\${target}" "\${installdir}"
    fi

    # @see https://github.com/tboox/tbox/issues/214
    install_for_soname=false
    if test_eq "${targetkind}" "shared"; then
        _get_target_item "${target}" "version"; local version="${_ret}"
        _get_target_soname "${target}"; local soname="${_ret}"
        if test_nz "${soname}" && test_nz "${version}"; then
            _get_target_extension "${target}"; local extension="${_ret}"
            string_replace "${_install_libdir_default}" "\${prefix}" "${installdir}"; _install_libdir_default="${_ret}"
            local targetfile_with_version="${_install_libdir_default}/${filename}.${version}"
            if test_eq "${extension}" ".dylib"; then
                path_basename "${filename}"; local basename="${_ret}"
                targetfile_with_version="${_install_libdir_default}/${basename}.${version}${extension}"
            fi
            local targetfile_with_soname="${_install_libdir_default}/${soname}"
            path_filename "${targetfile_with_version}"; local targetfilename_with_version="${_ret}"
            if test_nq "${soname}" "${filename}" && test_nq "${soname}" "${targetfilename_with_version}"; then
                install_for_soname=true
            fi
        fi
    fi

    # install target file
    if test_eq "${targetkind}" "binary"; then
        string_replace "${_install_bindir_default}" "\${prefix}" "${installdir}"; _install_bindir_default="${_ret}"
        print "\t@echo installing ${targetfile} to ${_install_bindir_default}" >> "${xmake_sh_makefile}"
        print "\t@mkdir -p ${_install_bindir_default}" >> "${xmake_sh_makefile}"
        print "\t@cp -p ${targetfile} ${_install_bindir_default}/${filename}" >> "${xmake_sh_makefile}"
    elif ${install_for_soname}; then
        string_replace "${_install_libdir_default}" "\${prefix}" "${installdir}"; _install_libdir_default="${_ret}"
        print "\t@echo installing ${targetfile} to ${_install_libdir_default}" >> "${xmake_sh_makefile}"
        print "\t@mkdir -p ${_install_libdir_default}" >> "${xmake_sh_makefile}"
        print "\t@cp -p ${targetfile} ${targetfile_with_version}" >> "${xmake_sh_makefile}"
        print "\t@cd ${_install_libdir_default} && ln -sf ${targetfilename_with_version} ${soname} && ln -sf ${soname} ${filename}" >> "${xmake_sh_makefile}"
    elif test_eq "${targetkind}" "static" || test_eq "${targetkind}" "shared"; then
        string_replace "${_install_libdir_default}" "\${prefix}" "${installdir}"; _install_libdir_default="${_ret}"
        print "\t@echo installing ${targetfile} to ${_install_libdir_default}" >> "${xmake_sh_makefile}"
        print "\t@mkdir -p ${_install_libdir_default}" >> "${xmake_sh_makefile}"
        print "\t@cp -p ${targetfile} ${_install_libdir_default}/${filename}" >> "${xmake_sh_makefile}"
    fi

    # install header files
    _get_target_item "${target}" "headerfiles"; local headerfiles="${_ret}"
    if test_nz "${headerfiles}"; then
        string_replace "${_install_includedir_default}" "\${prefix}" "${installdir}"; _install_includedir_default="${_ret}"
        local srcheaderfile=""
        local includedir="${_install_includedir_default}"
        for srcheaderfile in ${headerfiles}; do
            string_split "${srcheaderfile}" ":"
            local srcheaderfile="${_ret}"
            local rootdir="${_ret2}"
            local prefixdir="${_ret3}"
            local filename="${_ret4}"
            if test_z "${filename}"; then
                path_filename "${srcheaderfile}"; filename="${_ret}"
            fi
            local dstheaderdir="${includedir}"
            if test_nz "${prefixdir}"; then
                dstheaderdir="${dstheaderdir}/${prefixdir}"
            fi
            local dstheaderfile="${dstheaderdir}/${filename}"
            if test_nz "${rootdir}"; then
                path_relative "${rootdir}" "${srcheaderfile}"; local subfile="${_ret}"
                dstheaderfile="${dstheaderdir}/${subfile}"
            fi
            path_directory "${dstheaderfile}"; dstheaderdir="${_ret}"
            print "\t@mkdir -p ${dstheaderdir}" >> "${xmake_sh_makefile}"
            print "\t@cp -p ${srcheaderfile} ${dstheaderfile}" >> "${xmake_sh_makefile}"
        done
    fi

    # install user files
    _get_target_item "${target}" "installfiles"; local installfiles="${_ret}"
    if test_nz "${installfiles}"; then
        local srcinstallfile=""
        for srcinstallfile in ${installfiles}; do
            string_split "${srcinstallfile}" ":"
            local srcinstallfile="${_ret}"
            local rootdir="${_ret2}"
            local prefixdir="${_ret3}"
            local filename="${_ret4}"
            if test_z "${filename}"; then
                path_filename "${srcinstallfile}"; filename="${_ret}"
            fi
            local dstinstalldir="${installdir}"
            if test_nz "${prefixdir}"; then
                dstinstalldir="${dstinstalldir}/${prefixdir}"
            fi
            local dstinstallfile="${dstinstalldir}/${filename}"
            if test_nz "${rootdir}"; then
                path_relative "${rootdir}" "${srcinstallfile}"; local subfile="${_ret}"
                dstinstallfile="${dstinstalldir}/${subfile}"
            fi
            path_directory "${dstinstallfile}"; dstinstalldir="${_ret}"
            print "\t@mkdir -p ${dstinstalldir}" >> "${xmake_sh_makefile}"
            print "\t@cp -p ${srcinstallfile} ${dstinstallfile}" >> "${xmake_sh_makefile}"
        done
    fi

    # after install
    _get_target_item "${target}" "after_install"; local after_install="${_ret}"
    if test_nz "${after_install}"; then
        eval ${after_install} "\${target}" "\${installdir}"
    fi
}

_gmake_add_install_targets() {
    local target=""
    local targets=""
    for target in ${_xmake_sh_targets}; do
        if _is_target_default "${target}"; then
            targets="${targets} ${target}"
        fi
    done
    echo "install:${targets}" >> "${xmake_sh_makefile}"
    for target in ${targets}; do
        _gmake_add_install_target "${target}"
    done
    echo "" >> "${xmake_sh_makefile}"
}

_gmake_add_install() {
    _gmake_add_install_targets
}

_gmake_done() {
    echo "makefile is generated!"
    if "${xmake_sh_diagnosis}"; then
        cat "${xmake_sh_makefile}"
    fi
}

# generate build file for gmake
_generate_for_gmake() {
    _gmake_begin
    _gmake_add_header
    _gmake_add_switches
    _gmake_add_toolchains
    _gmake_add_flags
    _gmake_add_build
    _gmake_add_clean
    _gmake_add_install
    _gmake_add_run
    _gmake_done
}

#-----------------------------------------------------------------------------
# generate ninja file
#

# generate build file for ninja
_generate_for_ninja() {
    raise "Ninja generator has been not supported!"
}

#-----------------------------------------------------------------------------
# generate build file
#

_generate_build_file() {
    if test_eq "${project_generator}" "gmake"; then
        _generate_for_gmake
    elif test_eq "${project_generator}" "ninja"; then
        _generate_for_ninja
    else
        raise "unknown generator: ${project_generator}"
    fi
}
_generate_build_file

