#!/usr/bin/env bash
set -e

# This script must be able to run with bash-3.1, so it can't use
# associative arrays. Instead, it emulates them using 'eval'. It
# can however use indexed arrays, supported since at least bash-3.0.

# The names of the br2-external trees, once validated.
declare -a BR2_EXT_NAMES

# URL to manual for help in converting old br2-external trees.
# Escape '#' so that make does not consider it a comment.
MANUAL_URL='https://buildroot.org/manual.html\#br2-external-converting'

main() {
    local OPT OPTARG
    local br2_ext outputdir

    while getopts :d: OPT; do
        case "${OPT}" in
        d)  outputdir="${OPTARG}";;
        :)  error "option '%s' expects a mandatory argument\n" "${OPTARG}";;
        \?) error "unknown option '%s'\n" "${OPTARG}";;
        esac
    done
    # Forget options; keep only positional args
    shift $((OPTIND-1))

    if [ -z "${outputdir}" ]; then
        error "no output directory specified (-d)\n"
    fi

    # Trap any unexpected error to generate a meaningful error message
    trap "error 'unexpected error while generating ${ofile}\n'" ERR

    do_validate ${@//:/ }

    mkdir -p "${outputdir}"
    do_mk "${outputdir}"
    do_kconfig "${outputdir}"
}

# Validates the br2-external trees passed as arguments. Makes each of
# them canonical and store them in the global arrays BR2_EXT_NAMES
# and BR2_EXT_PATHS.
#
# Note: since this script is always first called from Makefile context
# to generate the Makefile fragment before it is called to generate the
# Kconfig snippet, we're sure that any error in do_validate will be
# interpreted in Makefile context. Going up to generating the Kconfig
# snippet means that there were no error.
#
do_validate() {
    local br2_ext

    if [ ${#} -eq 0 ]; then
        # No br2-external tree is valid
        return
    fi

    for br2_ext in "${@}"; do
        do_validate_one "${br2_ext}"
    done
}

do_validate_one() {
    local br2_ext="${1}"
    local br2_name br2_desc n d

    if [ ! -d "${br2_ext}" ]; then
        error "'%s': no such file or directory\n" "${br2_ext}"
    fi
    if [ ! -r "${br2_ext}" -o ! -x "${br2_ext}" ]; then
        error "'%s': permission denied\n" "${br2_ext}"
    fi
    if [ ! -f "${br2_ext}/external.desc" ]; then
        error "'%s': does not have a name (in 'external.desc'). See %s\n" \
            "${br2_ext}" "${MANUAL_URL}"
    fi
    br2_name="$(sed -r -e '/^name: +(.*)$/!d; s//\1/' "${br2_ext}/external.desc")"
    if [ -z "${br2_name}" ]; then
        error "'%s/external.desc': does not define the name\n" "${br2_ext}"
    fi
    # Only ASCII chars in [A-Za-z0-9_] are permitted
    n="$(sed -r -e 's/[A-Za-z0-9_]//g' <<<"${br2_name}" )"
    if [ -n "${n}" ]; then
        # Escape '$' so that it gets printed
        error "'%s': name '%s' contains invalid chars: '%s'\n" \
            "${br2_ext}" "${br2_name//\$/\$\$}" "${n//\$/\$\$}"
    fi
    eval d="\"\${BR2_EXT_PATHS_${br2_name}}\""
    if [ -n "${d}" ]; then
        error "'%s': name '%s' is already used in '%s'\n" \
            "${br2_ext}" "${br2_name}" "${d}"
    fi
    br2_desc="$(sed -r -e '/^desc: +(.*)$/!d; s//\1/' "${br2_ext}/external.desc")"
    if [ ! -f "${br2_ext}/external.mk" ]; then
        error "'%s/external.mk': no such file or directory\n" "${br2_ext}"
    fi
    if [ ! -f "${br2_ext}/Config.in" ]; then
        error "'%s/Config.in': no such file or directory\n" "${br2_ext}"
    fi

    # Register this br2-external tree, use an absolute canonical path
    br2_ext="$( cd "${br2_ext}"; pwd )"
    BR2_EXT_NAMES+=( "${br2_name}" )
    eval BR2_EXT_PATHS_${br2_name}="\"\${br2_ext}\""
    eval BR2_EXT_DESCS_${br2_name}="\"\${br2_desc:-\${br2_name}}\""
}

# Generate the .mk snippet that defines makefile variables
# for the br2-external tree
do_mk() {
    local outputdir="${1}"
    local br2_name br2_desc br2_ext

    {
        printf '#\n# Automatically generated file; DO NOT EDIT.\n#\n'
        printf '\n'

        printf 'BR2_EXTERNAL ?='
        for br2_name in "${BR2_EXT_NAMES[@]}"; do
            eval br2_ext="\"\${BR2_EXT_PATHS_${br2_name}}\""
            printf ' %s' "${br2_ext}"
        done
        printf '\n'

        printf 'BR2_EXTERNAL_NAMES = \n'
        printf 'BR2_EXTERNAL_DIRS = \n'
        printf 'BR2_EXTERNAL_MKS = \n'

        if [ ${#BR2_EXT_NAMES[@]} -eq 0 ]; then
            printf '\n'
            printf '# No br2-external tree defined.\n'
            return
        fi

        for br2_name in "${BR2_EXT_NAMES[@]}"; do
            eval br2_desc="\"\${BR2_EXT_DESCS_${br2_name}}\""
            eval br2_ext="\"\${BR2_EXT_PATHS_${br2_name}}\""
            printf '\n'
            printf 'BR2_EXTERNAL_NAMES += %s\n' "${br2_name}"
            printf 'BR2_EXTERNAL_DIRS += %s\n' "${br2_ext}"
            printf 'BR2_EXTERNAL_MKS += %s/external.mk\n' "${br2_ext}"
            printf 'export BR2_EXTERNAL_%s_PATH = %s\n' "${br2_name}" "${br2_ext}"
            printf 'export BR2_EXTERNAL_%s_DESC = %s\n' "${br2_name}" "${br2_desc}"
        done
    } >"${outputdir}/.br2-external.mk"
}

# Generate the kconfig snippets for the br2-external tree.
do_kconfig() {
    local outputdir="${1}"
    local br2_name br2_desc br2_ext br2
    local -a items

    items=(
        paths
        menus
        toolchains
        jpeg
        openssl
    )

    for br2 in "${items[@]}"; do
        {
            printf '#\n# Automatically generated file; DO NOT EDIT.\n#\n'
            printf '\n'
            if [ ${#BR2_EXT_NAMES[@]} -eq 0 ]; then
                printf '# No br2-external tree defined.\n'
            fi
        } >"${outputdir}/.br2-external.in.${br2}"
    done
    if [ ${#BR2_EXT_NAMES[@]} -eq 0 ]; then
        return
    fi

    printf 'menu "External options"\n\n' >>"${outputdir}/.br2-external.in.menus"

    for br2_name in "${BR2_EXT_NAMES[@]}"; do
        eval br2_desc="\"\${BR2_EXT_DESCS_${br2_name}}\""
        eval br2_ext="\"\${BR2_EXT_PATHS_${br2_name}}\""

        {
            printf 'config BR2_EXTERNAL_%s_PATH\n' "${br2_name}"
            printf '\tstring\n'
            printf '\tdefault "%s"\n' "${br2_ext}"
            printf '\n'
        } >>"${outputdir}/.br2-external.in.paths"

        {
            if [ ${#BR2_EXT_NAMES[@]} -gt 1 ]; then
                printf 'menu "%s"\n' "${br2_desc}"
            fi
            printf 'comment "%s (in %s)"\n' "${br2_desc}" "${br2_ext}"
            printf 'source "%s/Config.in"\n' "${br2_ext}"
            if [ ${#BR2_EXT_NAMES[@]} -gt 1 ]; then
                printf 'endmenu # %s\n' "${br2_name}"
            fi
            printf '\n'
        } >>"${outputdir}/.br2-external.in.menus"

        if [ -f "${br2_ext}/provides/toolchains.in" ]; then
            printf 'comment "Toolchains from: %s"\n' "${br2_desc}"
            printf 'source "%s/provides/toolchains.in"\n' "${br2_ext}"
            printf '\n'
        else
            printf '# No toolchain from: %s\n\n' "${br2_desc}"
        fi >>"${outputdir}/.br2-external.in.toolchains"

        if [ -f "${br2_ext}/provides/jpeg.in" ]; then
            printf 'comment "jpeg from: %s"\n' "${br2_desc}"
            printf 'source "%s/provides/jpeg.in"\n' "${br2_ext}"
            printf '\n'
        else
            printf '# No jpeg from: %s\n\n' "${br2_desc}"
        fi >>"${outputdir}/.br2-external.in.jpeg"

        if [ -f "${br2_ext}/provides/openssl.in" ]; then
            printf 'comment "openssl from: %s"\n' "${br2_desc}"
            printf 'source "%s/provides/openssl.in"\n' "${br2_ext}"
            printf '\n'
        else
            printf '# No openssl from: %s\n\n' "${br2_desc}"
        fi >>"${outputdir}/.br2-external.in.openssl"
    done

    printf 'endmenu\n' >>"${outputdir}/.br2-external.in.menus"
}

error() { local fmt="${1}"; shift; printf "BR2_EXTERNAL_ERROR = ${fmt}" "${@}"; exit 1; }

my_name="${0##*/}"
main "${@}"
