#! /bin/sh

# Copyright (C) 2023-2025 mintsuki and contributors.
#
# Permission to use, copy, modify, and/or distribute this software
# for any purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
# USE OR PERFORMANCE OF THIS SOFTWARE.

set -e

export LC_ALL=C

IFS=" ""	"'
'

if [ -z "$ARCHITECTURE" ]; then
    echo 'error: $ARCHITECTURE must be specified'
    exit 1
fi

case "$ARCHITECTURE" in
    arm)
        TRIPLET=${ARCHITECTURE}-unknown-none-eabi
        ;;
    *)
        TRIPLET=${ARCHITECTURE}-unknown-none-elf
        ;;
esac

check_component() {
    if eval [ "x\$WANT_$1" = "xyes" ]; then
        printf "checking for $2 ($ARCHITECTURE)... "
        if ! command -v $2 >/dev/null 2>&1; then
            echo "no"
            return 1
        fi
        echo "yes"
        eval "_$1"="$2"
    fi
}

validate_lang() {
    CC_MACHINE="$($1 -dumpmachine | sed 's/-.*//g')"

    if [ "x$CC_MACHINE" = "xamd64" ]; then
        CC_MACHINE=x86_64
    fi

    if [ "x$CC_MACHINE" = "xx86_64" ]; then
        case "$ARCHITECTURE" in
            i?86)
                CC_MACHINE="$ARCHITECTURE"
                ;;
        esac
    fi

    if ! [ "x$CC_MACHINE" = "x$ARCHITECTURE" ] || ! echo | $1 -x $2 -c - -o /dev/null >/dev/null 2>&1; then
        return 1
    fi
}

validate_cc() {
    validate_lang "$1" c
}

validate_cxx() {
    validate_lang "$1" c++
}

unset_toolchain() {
    unset _FREESTANDING_ADDR2LINE
    unset _FREESTANDING_AR
    unset _FREESTANDING_CC
    unset _FREESTANDING_CXX
    unset _FREESTANDING_CXXFILT
    unset _FREESTANDING_LD
    unset _FREESTANDING_NM
    unset _FREESTANDING_OBJCOPY
    unset _FREESTANDING_OBJDUMP
    unset _FREESTANDING_RANLIB
    unset _FREESTANDING_READELF
    unset _FREESTANDING_SIZE
    unset _FREESTANDING_STRINGS
    unset _FREESTANDING_STRIP
}

unset_toolchain

TOOLCHAIN_VALID=no

# First check if we have LLVM
if [ -z "$FREESTANDING_TOOLCHAIN" ] || [ "x$FREESTANDING_TOOLCHAIN" = "xllvm" ]; then while true; do
    if [ -z "$FREESTANDING_ADDR2LINE" ]; then check_component FREESTANDING_ADDR2LINE llvm-addr2line || break; fi
    if [ -z "$FREESTANDING_AR" ]; then check_component FREESTANDING_AR llvm-ar || break; fi
    if [ -z "$FREESTANDING_CC" ]; then check_component FREESTANDING_CC clang || break; fi
    if [ -z "$FREESTANDING_CXX" ]; then check_component FREESTANDING_CXX clang++ || break; fi
    if [ -z "$FREESTANDING_CXXFILT" ]; then check_component FREESTANDING_CXXFILT llvm-cxxfilt || break; fi
    if [ -z "$FREESTANDING_LD" ]; then check_component FREESTANDING_LD ld.lld || break; fi
    if [ -z "$FREESTANDING_NM" ]; then check_component FREESTANDING_NM llvm-nm || break; fi
    if [ -z "$FREESTANDING_OBJCOPY" ]; then check_component FREESTANDING_OBJCOPY llvm-objcopy || break; fi
    if [ -z "$FREESTANDING_OBJDUMP" ]; then check_component FREESTANDING_OBJDUMP llvm-objdump || break; fi
    if [ -z "$FREESTANDING_RANLIB" ]; then check_component FREESTANDING_RANLIB llvm-ranlib || break; fi
    if [ -z "$FREESTANDING_READELF" ]; then check_component FREESTANDING_READELF llvm-readelf || break; fi
    if [ -z "$FREESTANDING_SIZE" ]; then check_component FREESTANDING_SIZE llvm-size || break; fi
    if [ -z "$FREESTANDING_STRINGS" ]; then check_component FREESTANDING_STRINGS llvm-strings || break; fi
    if [ -z "$FREESTANDING_STRIP" ]; then check_component FREESTANDING_STRIP llvm-strip || break; fi

    if [ -z "$FREESTANDING_CC" ] && [ "x$WANT_FREESTANDING_CC" = "xyes" ]; then
        _FREESTANDING_CC="clang -target $TRIPLET"
        validate_cc "$_FREESTANDING_CC" || break
    fi
    if [ -z "$FREESTANDING_CXX" ] && [ "x$WANT_FREESTANDING_CXX" = "xyes" ]; then
        _FREESTANDING_CXX="clang++ -target $TRIPLET"
        validate_cxx "$_FREESTANDING_CXX" || break
    fi

    TOOLCHAIN_VALID=yes
    break
done; fi

if [ "$TOOLCHAIN_VALID" = "no" ]; then
    if [ "x$FREESTANDING_TOOLCHAIN" = "xllvm" ]; then
        echo "error: could not detect LLVM toolchain"
        exit 1
    fi

    unset_toolchain
fi

if [ "x$FREESTANDING_TOOLCHAIN" = "xgnu" ]; then
    unset FREESTANDING_TOOLCHAIN
fi

# Now try a prefixed GCC toolchain
ENFORCE_TOOLCHAIN=no
if [ "$TOOLCHAIN_VALID" = "no" ]; then
    if [ -z "$FREESTANDING_TOOLCHAIN" ]; then
        FREESTANDING_TOOLCHAIN="$TRIPLET"
    else
        ENFORCE_TOOLCHAIN=yes
    fi

    FIRST_LOOP=1
    for i in $FREESTANDING_TOOLCHAIN \
             $(echo $FREESTANDING_TOOLCHAIN | sed -E 's/-unknown-none-(elf|eabi)/-none-\1/g') \
             $(echo $FREESTANDING_TOOLCHAIN | sed -E 's/-unknown-none-(elf|eabi)/-\1/g') \
             $(echo $FREESTANDING_TOOLCHAIN | sed -E 's/-unknown-none-(elf|eabi)/-unknown-linux-gnu/g') \
             $(echo $FREESTANDING_TOOLCHAIN | sed -E 's/-unknown-none-(elf|eabi)/-pc-linux-gnu/g') \
             $(echo $FREESTANDING_TOOLCHAIN | sed -E 's/-unknown-none-(elf|eabi)/-linux-gnu/g') \
    ; do
        if [ "$FIRST_LOOP" = 0 ] && [ "$ENFORCE_TOOLCHAIN" = "yes" ]; then
            break
        fi
        FIRST_LOOP=0

        if [ -z "$FREESTANDING_ADDR2LINE" ]; then check_component FREESTANDING_ADDR2LINE $i-addr2line || continue; fi
        if [ -z "$FREESTANDING_AR" ]; then check_component FREESTANDING_AR $i-ar || continue; fi
        if [ -z "$FREESTANDING_CC" ]; then check_component FREESTANDING_CC $i-gcc || continue; fi
        if [ -z "$FREESTANDING_CXX" ]; then check_component FREESTANDING_CXX $i-g++ || continue; fi
        if [ -z "$FREESTANDING_CXXFILT" ]; then check_component FREESTANDING_CXXFILT $i-c++filt || continue; fi
        if [ -z "$FREESTANDING_LD" ]; then check_component FREESTANDING_LD $i-ld || continue; fi
        if [ -z "$FREESTANDING_NM" ]; then check_component FREESTANDING_NM $i-nm || continue; fi
        if [ -z "$FREESTANDING_OBJCOPY" ]; then check_component FREESTANDING_OBJCOPY $i-objcopy || continue; fi
        if [ -z "$FREESTANDING_OBJDUMP" ]; then check_component FREESTANDING_OBJDUMP $i-objdump || continue; fi
        if [ -z "$FREESTANDING_RANLIB" ]; then check_component FREESTANDING_RANLIB $i-ranlib || continue; fi
        if [ -z "$FREESTANDING_READELF" ]; then check_component FREESTANDING_READELF $i-readelf || continue; fi
        if [ -z "$FREESTANDING_SIZE" ]; then check_component FREESTANDING_SIZE $i-size || continue; fi
        if [ -z "$FREESTANDING_STRINGS" ]; then check_component FREESTANDING_STRINGS $i-strings || continue; fi
        if [ -z "$FREESTANDING_STRIP" ]; then check_component FREESTANDING_STRIP $i-strip || continue; fi

        if [ -z "$FREESTANDING_CC" ] && [ "x$WANT_FREESTANDING_CC" = "xyes" ]; then
            validate_cc "$_FREESTANDING_CC" || continue
        fi
        if [ -z "$FREESTANDING_CXX" ] && [ "x$WANT_FREESTANDING_CXX" = "xyes" ]; then
            validate_cxx "$_FREESTANDING_CXX" || continue
        fi

        TOOLCHAIN_VALID=yes
        break
    done
fi

if [ "$TOOLCHAIN_VALID" = "no" ]; then
    if [ "$ENFORCE_TOOLCHAIN" = "yes" ]; then
        echo "error: requested toolchain not valid"
        exit 1
    fi

    unset_toolchain
fi

# If $CC is passed, try a toolchain based on the triplet of $CC
if ! [ -z "$CC" ] && [ "$TOOLCHAIN_VALID" = "no" ]; then while true; do
    CC_TRIPLET="$($CC -dumpmachine)"

    if [ -z "$FREESTANDING_ADDR2LINE" ]; then check_component FREESTANDING_ADDR2LINE $CC_TRIPLET-addr2line || break; fi
    if [ -z "$FREESTANDING_AR" ]; then check_component FREESTANDING_AR $CC_TRIPLET-ar || break; fi
    if [ -z "$FREESTANDING_CC" ]; then check_component FREESTANDING_CC $CC_TRIPLET-gcc || break; fi
    if [ -z "$FREESTANDING_CXX" ]; then check_component FREESTANDING_CXX $CC_TRIPLET-g++ || break; fi
    if [ -z "$FREESTANDING_CXXFILT" ]; then check_component FREESTANDING_CXXFILT $CC_TRIPLET-c++filt || break; fi
    if [ -z "$FREESTANDING_LD" ]; then check_component FREESTANDING_LD $CC_TRIPLET-ld || break; fi
    if [ -z "$FREESTANDING_NM" ]; then check_component FREESTANDING_NM $CC_TRIPLET-nm || break; fi
    if [ -z "$FREESTANDING_OBJCOPY" ]; then check_component FREESTANDING_OBJCOPY $CC_TRIPLET-objcopy || break; fi
    if [ -z "$FREESTANDING_OBJDUMP" ]; then check_component FREESTANDING_OBJDUMP $CC_TRIPLET-objdump || break; fi
    if [ -z "$FREESTANDING_RANLIB" ]; then check_component FREESTANDING_RANLIB $CC_TRIPLET-ranlib || break; fi
    if [ -z "$FREESTANDING_READELF" ]; then check_component FREESTANDING_READELF $CC_TRIPLET-readelf || break; fi
    if [ -z "$FREESTANDING_SIZE" ]; then check_component FREESTANDING_SIZE $CC_TRIPLET-size || break; fi
    if [ -z "$FREESTANDING_STRINGS" ]; then check_component FREESTANDING_STRINGS $CC_TRIPLET-strings || break; fi
    if [ -z "$FREESTANDING_STRIP" ]; then check_component FREESTANDING_STRIP $CC_TRIPLET-strip || break; fi

    if [ -z "$FREESTANDING_CC" ] && [ "x$WANT_FREESTANDING_CC" = "xyes" ]; then
        validate_cc "$_FREESTANDING_CC" || break
    fi
    if [ -z "$FREESTANDING_CXX" ] && [ "x$WANT_FREESTANDING_CXX" = "xyes" ]; then
        validate_cxx "$_FREESTANDING_CXX" || break
    fi

    TOOLCHAIN_VALID=yes
    break
done; fi

# Lastly, try a toolchain made up of unprefixed utilities
if [ "$TOOLCHAIN_VALID" = "no" ]; then while true; do
    unset_toolchain

    if [ -z "$FREESTANDING_ADDR2LINE" ]; then check_component FREESTANDING_ADDR2LINE addr2line || break; fi
    if [ -z "$FREESTANDING_AR" ]; then check_component FREESTANDING_AR ar || break; fi
    if [ -z "$FREESTANDING_CC" ]; then check_component FREESTANDING_CC gcc || break; fi
    if [ -z "$FREESTANDING_CXX" ]; then check_component FREESTANDING_CXX g++ || break; fi
    if [ -z "$FREESTANDING_CXXFILT" ]; then check_component FREESTANDING_CXXFILT c++filt || break; fi
    if [ -z "$FREESTANDING_LD" ]; then check_component FREESTANDING_LD ld || break; fi
    if [ -z "$FREESTANDING_NM" ]; then check_component FREESTANDING_NM nm || break; fi
    if [ -z "$FREESTANDING_OBJCOPY" ]; then check_component FREESTANDING_OBJCOPY objcopy || break; fi
    if [ -z "$FREESTANDING_OBJDUMP" ]; then check_component FREESTANDING_OBJDUMP objdump || break; fi
    if [ -z "$FREESTANDING_RANLIB" ]; then check_component FREESTANDING_RANLIB ranlib || break; fi
    if [ -z "$FREESTANDING_READELF" ]; then check_component FREESTANDING_READELF readelf || break; fi
    if [ -z "$FREESTANDING_SIZE" ]; then check_component FREESTANDING_SIZE size || break; fi
    if [ -z "$FREESTANDING_STRINGS" ]; then check_component FREESTANDING_STRINGS strings || break; fi
    if [ -z "$FREESTANDING_STRIP" ]; then check_component FREESTANDING_STRIP strip || break; fi

    if [ -z "$FREESTANDING_CC" ] && [ "x$WANT_FREESTANDING_CC" = "xyes" ]; then
        validate_cc "$_FREESTANDING_CC" || break
    fi
    if [ -z "$FREESTANDING_CXX" ] && [ "x$WANT_FREESTANDING_CXX" = "xyes" ]; then
        validate_cxx "$_FREESTANDING_CXX" || break
    fi

    TOOLCHAIN_VALID=yes
    break
done; fi

if [ "$TOOLCHAIN_VALID" = "no" ]; then
    echo "error: could not find any valid toolchain for target '$ARCHITECTURE'"
    exit 1
fi

custom_check_fail() {
    echo "error: custom toolchain component '$1' not valid"
    exit 1
}

if ! [ -z "$FREESTANDING_ADDR2LINE" ]; then check_component FREESTANDING_ADDR2LINE "$FREESTANDING_ADDR2LINE" || custom_check_fail "$FREESTANDING_ADDR2LINE"; fi
if ! [ -z "$FREESTANDING_AR" ]; then check_component FREESTANDING_AR "$FREESTANDING_AR" || custom_check_fail "$FREESTANDING_AR"; fi
if ! [ -z "$FREESTANDING_CC" ]; then check_component FREESTANDING_CC "$FREESTANDING_CC" || custom_check_fail "$FREESTANDING_CC"; fi
if ! [ -z "$FREESTANDING_CXX" ]; then check_component FREESTANDING_CXX "$FREESTANDING_CXX" || custom_check_fail "$FREESTANDING_CXX"; fi
if ! [ -z "$FREESTANDING_CXXFILT" ]; then check_component FREESTANDING_CXXFILT "$FREESTANDING_CXXFILT" || custom_check_fail "$FREESTANDING_CXXFILT"; fi
if ! [ -z "$FREESTANDING_LD" ]; then check_component FREESTANDING_LD "$FREESTANDING_LD" || custom_check_fail "$FREESTANDING_LD"; fi
if ! [ -z "$FREESTANDING_NM" ]; then check_component FREESTANDING_NM "$FREESTANDING_NM" || custom_check_fail "$FREESTANDING_NM"; fi
if ! [ -z "$FREESTANDING_OBJCOPY" ]; then check_component FREESTANDING_OBJCOPY "$FREESTANDING_OBJCOPY" || custom_check_fail "$FREESTANDING_OBJCOPY"; fi
if ! [ -z "$FREESTANDING_OBJDUMP" ]; then check_component FREESTANDING_OBJDUMP "$FREESTANDING_OBJDUMP" || custom_check_fail "$FREESTANDING_OBJDUMP"; fi
if ! [ -z "$FREESTANDING_RANLIB" ]; then check_component FREESTANDING_RANLIB "$FREESTANDING_RANLIB" || custom_check_fail "$FREESTANDING_RANLIB"; fi
if ! [ -z "$FREESTANDING_READELF" ]; then check_component FREESTANDING_READELF "$FREESTANDING_READELF" || custom_check_fail "$FREESTANDING_READELF"; fi
if ! [ -z "$FREESTANDING_SIZE" ]; then check_component FREESTANDING_SIZE "$FREESTANDING_SIZE" || custom_check_fail "$FREESTANDING_SIZE"; fi
if ! [ -z "$FREESTANDING_STRINGS" ]; then check_component FREESTANDING_STRINGS "$FREESTANDING_STRINGS" || custom_check_fail "$FREESTANDING_STRING"; fi
if ! [ -z "$FREESTANDING_STRIP" ]; then check_component FREESTANDING_STRIP "$FREESTANDING_STRIP" || custom_check_fail "$FREESTANDING_STRIP"; fi

if ! [ -z "$FREESTANDING_CC" ] && [ "x$WANT_FREESTANDING_CC" = "xyes" ]; then
    if ! validate_cc "$_FREESTANDING_CC"; then
        echo "error: C compiler '$FREESTANDING_CC' does not support specified architecture '$ARCHITECTURE'"
        exit 1
    fi
fi
if ! [ -z "$FREESTANDING_CXX" ] && [ "x$WANT_FREESTANDING_CXX" = "xyes" ]; then
    if ! validate_cxx "$_FREESTANDING_CXX"; then
        echo "error: C++ compiler '$FREESTANDING_CXX' does not support specified architecture '$ARCHITECTURE'"
        exit 1
    fi
fi

unset FREESTANDING_CC_IS_CLANG
if [ "x$WANT_FREESTANDING_CC" = "xyes" ]; then
    FREESTANDING_CC_IS_CLANG=no

    if $_FREESTANDING_CC --version 2>&1 | grep -q '^Target: '; then
        FREESTANDING_CC_IS_CLANG=yes
    fi
fi

# Output the final makefile chunk
cat 1>&2 <<EOF
${FREESTANDING_TOOLCHAIN_PREFIX}ADDR2LINE${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_ADDR2LINE
${FREESTANDING_TOOLCHAIN_PREFIX}AR${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_AR
${FREESTANDING_TOOLCHAIN_PREFIX}CC${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_CC
${FREESTANDING_TOOLCHAIN_PREFIX}CXX${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_CXX
${FREESTANDING_TOOLCHAIN_PREFIX}CXXFILT${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_CXXFILT
${FREESTANDING_TOOLCHAIN_PREFIX}LD${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_LD
${FREESTANDING_TOOLCHAIN_PREFIX}NM${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_NM
${FREESTANDING_TOOLCHAIN_PREFIX}OBJCOPY${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_OBJCOPY
${FREESTANDING_TOOLCHAIN_PREFIX}OBJDUMP${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_OBJDUMP
${FREESTANDING_TOOLCHAIN_PREFIX}RANLIB${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_RANLIB
${FREESTANDING_TOOLCHAIN_PREFIX}READELF${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_READELF
${FREESTANDING_TOOLCHAIN_PREFIX}SIZE${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_SIZE
${FREESTANDING_TOOLCHAIN_PREFIX}STRINGS${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_STRINGS
${FREESTANDING_TOOLCHAIN_PREFIX}STRIP${FREESTANDING_TOOLCHAIN_SUFFIX} := $_FREESTANDING_STRIP

${FREESTANDING_TOOLCHAIN_PREFIX}CC${FREESTANDING_TOOLCHAIN_SUFFIX}_IS_CLANG := $FREESTANDING_CC_IS_CLANG
EOF
