#!/bin/sh

myname="${0##*/}"

VERSION=$( cat .version )
DATE=$( date +%Y%m%d )

PREFIX_DEFAULT=/usr/local

BINDIR_set=
LIBDIR_set=
DOCDIR_set=
MANDIR_set=
PROG_PFX=
PROG_SFX=
PROG_SED=
LOCAL_set=
FORCE=

do_quit=

# Simply print the error message, and exit. Obvious, he?
do_error() {
    printf "${myname}: ${@}\n"
    exit 1
}

# Given an option string and the following argument,
# echoes the value of the option.
# If --var=val => echoes val and returns 0, meaning second arg was not consumed
# If --var val => echoes val and returns non null, meaning second arg was used
get_optval(){
    case "$1" in
        --*=?*)
            printf "${1#*=}"
            return 0
            ;;
        *)
            printf "${2}"
            return 1
            ;;
    esac
}

# The set_xxx functions will set the corresponding configuration variable
# They return 0 if second arg was not consumed, and non-zero if it was consumed.
set_prefix() {
    PREFIX="$( get_optval "$1" "$2" )"
}
set_bindir() {
    BINDIR_set=1
    BINDIR="$( get_optval "$1" "$2" )"
}
set_libdir() {
    LIBDIR_set=1
    LIBDIR="$( get_optval "$1" "$2" )"
}
set_docdir() {
    DOCDIR_set=1
    DOCDIR="$( get_optval "$1" "$2" )"
}
set_mandir() {
    MANDIR_set=1
    MANDIR="$( get_optval "$1" "$2" )"
}
set_program_prefix() {
    PROG_PFX="$( get_optval "$1" "$2" )"
}
set_program_suffix() {
    PROG_SFX="$( get_optval "$1" "$2" )"
}
set_program_transform_name() {
    PROG_SED="$( get_optval "$1" "$2" )"
}
set_tool() {
    local var_name="${1%%=*}"
    var_name="${var_name#--with-}"
    eval ${var_name}="\$( get_optval "$1" "$2" )"
}

# var_list is a list of variables, each one holding a path to a
# tool, either detected by ./configure, or specified by the user.
var_list=""
kconfig_list=""

# This function adds a variable name to the above list of variable names.
# $1: the name of the variable to add to the list
add_to_var_list() {
    local v
    for v in ${var_list}; do
        [ "${v}" = "${1}" ] && return 0
    done
    var_list="${var_list} ${1}"
}
add_to_kconfig_list() {
    local k
    for k in ${kconfig_list}; do
        [ "${k}" = "${1}" ] && return 0
    done
    kconfig_list="${kconfig_list} ${1}"
}

# A function to test for required tools/headers/libraries
# Return 0 (true) if found, !0 (false) if not found
#
# $*: [prog|inc|lib]=<name[ name...]>
#     the name(s) of tool(s) to test for
#     mandatory
#       eg: prog=bash   prog="curl wget"
# $*: var=<var_name>
#     the name of the variable to test and set
#     optional
#       eg: var=bash    if ${bash} is set and non-null, use that,
#                       else check for bash and set bash=$(which bash)
# $*: ver=<regexp>
#     for each 'prog', test if $(prog --version) matches 'regexp'
#     optional
#       eg: ver='^GNU bash, version [34]\.'
# $*: lib_exts=<extension[ extension...]>
#     the list of allowed library extension
#     mandatory
#       eg: lib_exts="so dylib"     lib_exts="so dylib a"
# $*: err=<error_message>
#     the error message to print if tool is missing
#     optional, defaults to: '${prog}: none found'
#       eg: err="'bash' 3.x or above was not found"
#     Note: err may be printed by caller, not us
# $*: kconfig=<var_name>
#     the name of a variable to pass down to kconfig if
#     the prog/inc/lib was found
#     optional, defaults to none
#       eg: kconfig=has_libncurses
# $*: skip=[y|n|]
#     if set to 'y', skip the test, but still register the
#     kconfig and var variables; if 'n' or empty, do the
#     test.
#     optional, default to 'n'
#       eg: skip="${static_link_ko}"
check_for() {
    local lib_exts
    local skip
    local val
    local item
    local where
    local status
    local ext

    # Note: prog/inc/lib and var/kconfig/ver/err are set here,
    # but declared by the caller (because it needs it)
    for item in "${@}"; do
        case "${item}" in
            prog=*|inc=*|lib=*|var=*|ver=*|err=*|kconfig=*|lib_exts=*|skip=*)
                eval ${item%%=*}=\"${item#*=}\"
                ;;
            *)  do_error "check_for: incorrect parameters: '${item}'";;
        esac
    done

    case "${prog}:${inc}:${lib}" in
        ?*:?*:|?*::?*|:?*:?*|?*:?*:?*)
            if [ -n "${var}" ]; then
                do_error "check_for: the use of var is not compatible with passing several of [prog|inc|lib] at once"
            fi
            ;;
        ::) do_error "check_for: [prog|inc|lib] is mandatory";;
    esac

    if [ -n "${var}" ]; then
        add_to_var_list "${var}"
    fi
    if [ -n "${kconfig}" ]; then
        add_to_kconfig_list "${kconfig}"
    fi

    if [ "${skip}" = "y" ]; then
        return 0
    fi

    if [ -n "${prog}" ]; then
        for item in ${prog}; do
            printf "Checking for '${item}'... "
            if [ -n "${var}" ]; then
                eval val="\${${var}}"
                if [ -n "${val}" ]; then
                    status="${val} (cached)\n"
                    where="${val}"
                    break
                fi
            fi
            where="$( which "${item}" 2>/dev/null )"
            if [ -z "${where}" ]; then
                printf "no\n"
                continue
            elif [ -n "${ver}" ]; then
                str=$( LC_ALL=C "${where}" --version 2>&1   \
                       |grep -E "${ver}"                    \
                       |head -n 1
                     )
                if [ -z "${str}" ]; then
                    printf "no\n"
                    unset where
                    continue
                fi
            fi
            status="${where}"
            break
        done
        if [ -z "${status}" ]; then
            return 1
        fi
        printf "${status}\n"
        unset status
    fi

    if [ -n "${inc}" ]; then
        for item in ${inc}; do
            printf "Checking for '${item}'... "
            if printf "#include \"${item}\"" |gcc -x c -c - -o /dev/null >/dev/null 2>&1; then
                where="${item}"
                status=yes
                break;
            fi
            printf "no\n"
        done
        if [ -z "${status}" ]; then
            return 1
        fi
        printf "${status}\n"
        unset status
    fi

    if [ -n "${lib}" ]; then
        if [ -z "${lib_exts}" ]; then
            do_error "check_for: no library extension specified for '${lib}'"
        fi
        for item in ${lib}; do
            for ext in ${lib_exts}; do
                printf "Checking for '${item}.${ext}'... "
                where="$( gcc -print-file-name="${item}.${ext}" )"
                if [ "${where}" != "${item}.${ext}" ]; then
                    where="$( readlink "${where}" )"
                    status=yes
                    break 2;
                fi
                printf "no\n"
            done
        done
        if [ -z "${status}" ]; then
            return 1
        fi
        printf "${status}\n"
        unset status
    fi

    if [ -n "${var}" ]; then
        eval ${var}='"'"${where}"'"'
    fi
    if [ -n "${kconfig}" ]; then
        eval ${kconfig}=y
    fi
}

# This function checks for a tool, and aborts if not found
# See check_for(), above, for how to call has_or_abort
has_or_abort() {
    # We declare these 6 variables here, although they are
    # set in check_for(), called below
    local prog inc lib
    local var ver err kconfig

    if ! check_for "$@"; then
        printf " * A mandatory dependency is missing, or version mis-match:\n"
        printf " * - ${err:-${prog}${inc}${lib}: none found}\n"
        if [ -n "${var}" ]; then
            printf " * --> You can give the path to this tool using: --with-${var}=PATH\n"
        fi
        printf "\n"
        # Bail out if --force is not specified
        [ -z "${FORCE}" ] && do_error "Bailing out..."
        printf "<*                                          *>\n"
        printf "<*            FORCE in action:              *>\n"
        printf "<* Continuing despite missing pre-requisite *>\n"
        printf "<*          Prepare for breakage            *>\n"
        printf "<*                                          *>\n"
        printf "\n"
    fi
}

# This function checks for a tool, and warns if not found
# See check_for(), above, for how to call has_or_abort
# Note: if err is not set, then no error message is printed
has_or_warn() {
    # We declare these 6 variables here, although they are
    # set in check_for(), called below
    local prog inc lib
    local var ver err kconfig

    if ! check_for "$@"; then
        printf " * An optional dependency is missing, some features will be disabled"
        printf "${err:+:\n * - ${err}}\n"
        if [ -n "${var}" ]; then
            printf " * --> You can give the path to this tool using: --with-${var}=PATH\n"
        fi
    fi
}

do_help() {
    cat <<__EOF__
\`configure' configures crosstool-NG-${VERSION} to adapt to many kind of systems.

USAGE: ./configure [OPTION]...

Defaults for the options are specified in brackets.

Configuration:
  -h, --help              display this help and exit
      --force             force configure to continue, even in case
                          some pre-requisites are missing

Installation directories:
  --prefix=PREFIX         install files in PREFIX [${PREFIX_DEFAULT}]
  --local                 don't install, and use current directory

By default, \`make install' will install all the files in
\`${PREFIX_DEFAULT}/bin', \`${PREFIX_DEFAULT}/lib' etc.  You can specify
an installation prefix other than \`${PREFIX_DEFAULT}' using \`--prefix',
for instance \`--prefix=\${HOME}'.

For better control, use the options below.
Note: options marked as \`ignored' are recognised, but not acted upon, as
they make no sense for crosstool-NG, or they are not implemented yet.

Fine tuning of the installation directories:
  --bindir=DIR            user executables [PREFIX/bin]
  --libdir=DIR            object code libraries [PREFIX/lib]
  --docdir=DIR            info documentation [PREFIX/share/doc]
  --mandir=DIR            man documentation [PREFIX/share/man]
  --infodir=DIR           info documentation [DATAROOTDIR/info] (ignored)
  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
                          (ignored)
  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc] (ignored)
  --localstatedir=DIR     modifiable single-machine data [PREFIX/var] (ignored)

Program names:
  --program-prefix=PREFIX            prepend PREFIX to installed program names
  --program-suffix=SUFFIX            append SUFFIX to installed program names
  --program-transform-name=PROGRAM   run sed PROGRAM on installed program names

System types:
  --build=BUILD     configure for building on BUILD [guessed] (ignored)
  --host=HOST       cross-compile to build programs to run on HOST [BUILD]
                    (ignored)

Optional Features:
  --enable-shared[=PKGS]  build shared libraries [default=yes] (ignored)
  --enable-static[=PKGS]  build static libraries [default=yes] (ignored)

Optional Packages:
  --with-install=PATH     Specify the full PATH to GNU install
  --with-make=PATH        Specify the full PATH to GNU make >= 3.80
  --with-grep=PATH        Specify the full PATH to GNU grep
  --with-sed=PATH         Specify the full PATH to GNU sed
  --with-bash=PATH        Specify the full PATH to bash >= 3.0
__EOF__
}

#---------------------------------------------------------------------
# Set user's options

while [ $# -ne 0 ]; do
    case "$1" in
        --local)    LOCAL_set="y"; shift;;
        --prefix*)  set_prefix "$1" "$2" && shift || shift 2;;
        --bindir*)  set_bindir "$1" "$2" && shift || shift 2;;
        --libdir*)  set_libdir "$1" "$2" && shift || shift 2;;
        --docdir*)  set_docdir "$1" "$2" && shift || shift 2;;
        --mandir*)  set_mandir "$1" "$2" && shift || shift 2;;
        --with-*)   set_tool   "$1" "$2" && shift || shift 2;;
        --program-prefix=*|--program-prefix)
                    set_program_prefix "$1" "$2" && shift || shift 2
                    ;;
        --program-suffix=*|--program-suffix)
                    set_program_suffix "$1" "$2" && shift || shift 2
                    ;;
        --program-transform-name=*|--program-transform-name)
                    set_program_transform_name "$1" "$2" && shift || shift 2
                    ;;
        --force)    FORCE=1; shift;;
        --help|-h)  do_help; exit 0;;
        # Skip, auto-stuff compatibility
        --build=*|--host=*|--infodir=*|--datadir=*|--sysconfdir=*|--localstatedir=*) shift;;
        --build|--host|--infodir|--datadir|--sysconfdir|--localstatedir)             shift 2;;
        --enable-shared|--disable-shared|--enable-static|--disable-static)           shift;;
        *)          printf "Unrecognised option: '${1}'\n"; do_help; exit 1;;
    esac
done

# Use defaults
[ -z "${PREFIX}" ] && set_prefix "" "${PREFIX_DEFAULT}"

# Special case when installing locally
if [ "${LOCAL_set}" = "y" ]; then
    set_prefix "" "$( pwd )"
    set_bindir "" "$( pwd )"
    set_libdir "" "$( pwd )"
    set_docdir "" "$( pwd )/docs"
    set_mandir "" "$( pwd )/docs"
    set_program_prefix "" ""
    set_program_suffix "" ""
    set_program_transform_name "" ""
fi

#---------------------------------------------------------------------
# Some sanity checks, now

# We check for grep and sed manually, because they are used in check_for()
printf "Checking for 'grep'... "
if [ -n "${grep}" ]; then
    printf "${grep} (cached)\n"
else
    grep="$( which grep 2>/dev/null )"
    if [ -z "${grep}" ]; then
        printf "not found\n"
    else
        printf "${grep}\n"
        printf "Checking whether '${grep}' supports -E... "
        if echo 'foo' |"${grep}" -E 'foo' >/dev/null 2>&1; then
            printf "yes\n"
        else
            printf "no\n"
            grep=
        fi
    fi
fi
if [ -z "${grep}" ]; then
    printf "Either you are missing entirely the needed tool,\n"
    printf "or the version you have is too old.\n"
    printf "You can give the path to this tool using: --with-grep=PATH\n"
    do_error "Bailing out..."
fi
add_to_var_list grep

printf "Checking for 'sed'... "
if [ -n "${sed}" ]; then
    printf "${sed} (cached)\n"
else
    sed="$( which sed 2>/dev/null )"
    if [ -z "${sed}" ]; then
        printf "not found\n"
    else
        printf "${sed}\n"
        printf "Checking whether '${sed}' supports -i and -e... "
        touch .ct-ng.sed.test
        if "${sed}" -r -i -e 's/foo/bar/' .ct-ng.sed.test >/dev/null 2>&1; then
            printf "yes\n"
        else
            printf "no\n"
            sed=
        fi
        rm -f .ct-ng.sed.test
    fi
fi
if [ -z "${sed}" ]; then
    printf "Either you are missing entirely the needed tool,\n"
    printf "or the version you have is too old.\n"
    printf "You can give the path to this tool using: --with-sed=PATH\n"
    do_error "Bailing out..."
fi
add_to_var_list sed

# The regular list of tools we can now easily check for
has_or_abort prog=bash                              \
             var=bash                               \
             ver='^GNU bash, version (3\.[1-9]|4)'  \
             err="'bash' 3.1 or above was not found"
has_or_abort prog=cut
has_or_abort prog=install var=install
has_or_abort prog=make                                  \
             var=make                                   \
             ver='^GNU Make (3.[89][[:digit:]]|[4-9])'  \
             err="GNU 'make' 3.80 or above was not found"
has_or_abort prog=gcc
has_or_abort prog="awk gawk" ver='^GNU Awk' err="GNU 'awk' was not found"
has_or_abort prog=bison
has_or_abort prog=flex
has_or_abort prog=makeinfo
has_or_abort prog=automake                                                      \
             ver='\(GNU automake\) (1\.[[:digit:]]{2,}|[2-9][[:digit:]]*\.)'    \
             err="'automake' 1.10 or above was not found"
has_or_abort prog=libtool                                                                           \
             var=libtool                                                                            \
             ver='\(GNU libtool.*\) (2[[:digit:]]*\.|1\.6[[:digit:]]*\.|1\.5\.[2-9][[:digit:]]+)'   \
             err="'libtool' 1.5.26 or above was not found"
has_or_abort prog=libtoolize                                                                        \
             var=libtoolize                                                                         \
             ver='\(GNU libtool.*\) (2[[:digit:]]*\.|1\.6[[:digit:]]*\.|1\.5\.[2-9][[:digit:]]+)'   \
             err="'libtoolize' 1.5.26 or above was not found"
has_or_abort prog=stat
has_or_abort prog="curl"
has_or_abort prog=patch
has_or_abort prog=tar
has_or_abort prog=gzip
has_or_abort prog=bzip2
has_or_warn  prog=xz                                        \
             kconfig=has_xzutils                            \
             err="xz-compressed tarballs will not be used"
has_or_warn  prog=lzma                                      \
             kconfig=has_lzma                               \
             skip="${has_xzutils}"                          \
             err="lzma-compressed tarballs will not be used"
has_or_abort prog=readlink
has_or_abort prog=objcopy var=objcopy
has_or_abort prog=objdump var=objdump
has_or_abort prog=readelf var=readelf
has_or_abort prog=patch var=patch
has_or_warn  prog=cvs                                                   \
             kconfig=has_cvs                                            \
             err="it will not be possible to use newlib cvs snapshots"
has_or_warn  prog=svn                               \
             kconfig=has_svn                        \
             err="subversion is required to download eglibc"

# Library checks
libs_exts="so dylib a"

ncurses_hdrs="ncurses/ncurses.h ncurses/curses.h ncurses.h curses.h"
ncurses_libs="libncursesw libncurses libcurses"
has_or_abort lib="${ncurses_libs}"                                          \
             lib_exts="${libs_exts}"                                        \
             inc="${ncurses_hdrs}"                                          \
             err="The 'ncurses' library is needed fo the menuconfig frontend"

#---------------------------------------------------------------------
# Compute the version string

# If this version is n hg clone, try to get the revision number
# If we can't get the revision number, use date
printf "\nComputing version string... "

# Pass the version to the version helper script, if present, to compute
# a local version string, if needed.
if [ -f version.sh -a -x version.sh ]; then
    V="$( ./version.sh "${VERSION}" 2>/dev/null |head -n 1 )"
fi

# If the script returns an empty string, revert to using the version
# we just computed, above.
if [ -n "${V}" ]; then
    VERSION="${V}"
else
    case "${VERSION}" in
        *+hg|hg)
            rev_id="$( hg log -r . --template '{branch}-{node|short}\n' \
                              2>/dev/null                               \
                       || true                                          \
                     )"
            VERSION="${VERSION}+${rev_id:-unknown-$( date +%Y%m%d.%H%M%S )}"
            ;;
    esac
fi

# Arrange to have no / in the directory name, no need to create an
# arbitrarily deep directory structure
VERSION="$( printf "${VERSION}" |"${sed}" -r -e 's:/+:_:g;' )"

printf "${VERSION}\n"

#---------------------------------------------------------------------
# Compute and check install paths

# Now we have the version string, we can build up the paths
[ -z "${BINDIR_set}" ] && BINDIR="${PREFIX}/bin"
[ -z "${LIBDIR_set}" ] && LIBDIR="${PREFIX}/lib"
[ -z "${DOCDIR_set}" ] && DOCDIR="${PREFIX}/share/doc"
[ -z "${MANDIR_set}" ] && MANDIR="${PREFIX}/share/man"

# Install support files in our own sub-dir, so as not to mangle (system)
# files and dirs, but only if not --local
if [ -z "${LOCAL_set}" ]; then
    LIBDIR="${LIBDIR}/ct-ng-${VERSION}"
    DOCDIR="${DOCDIR}/ct-ng-${VERSION}"
fi

# Check that install PATHs are absolute
for p in BIN LIB DOC MAN; do
    var="${p}DIR"
    eval v='"${'"${var}"'}"'
    case "${v}" in
        /*) ;;
        *)  do_error "'${var}' is not an absolute path: '${v}'";;
    esac
done
case "${PROG_PFX}" in
    */*)    do_error "program prefix '${PROG_PFX}' contains a '/'";;
esac
case "${PROG_SFX}" in
    */*)    do_error "program suffix '${PROG_SFX}' contains a '/'";;
esac

#---------------------------------------------------------------------
# That's all, folks!

printf "Building up Makefile... "
var_sed="$( for var_name in ${var_list}; do
                eval echo 's,@@${var_name}@@,${'"${var_name}"'},g'
            done
          )"
kconfig_sed="s/@@KCONFIG@@/$( for k_name in ${kconfig_list}; do
                                  eval printf \"${k_name}=\${${k_name}} \"
                              done
                            )/"
"${sed}" -r -e "s,@@BINDIR@@,${BINDIR},g"       \
            -e "s,@@LIBDIR@@,${LIBDIR},g"       \
            -e "s,@@DOCDIR@@,${DOCDIR},g"       \
            -e "s,@@MANDIR@@,${MANDIR},g"       \
            -e "s,@@PROG_PFX@@,${PROG_PFX},g"   \
            -e "s,@@PROG_SFX@@,${PROG_SFX},g"   \
            -e "s,@@PROG_SED@@,${PROG_SED},g"   \
            -e "s,@@VERSION@@,${VERSION},g"     \
            -e "s,@@DATE@@,${DATE},g"           \
            -e "s,@@LOCAL@@,${LOCAL_set},g"     \
            -e "${var_sed}"                     \
            -e "${kconfig_sed}"                 \
         Makefile.in                            \
         >Makefile
echo "done"

cat <<__EOF__

crosstool-NG configured as follows:
  PREFIX='${PREFIX}'
  BINDIR='${BINDIR}'
  LIBDIR='${LIBDIR}'
  DOCDIR='${DOCDIR}'
  MANDIR='${MANDIR}'
  PROG_PFX='${PROG_PFX}'
  PROG_SFX='${PROG_SFX}'
  PROG_SED='${PROG_SED}'

Now run:
  make
__EOF__
if [ "${LOCAL_set}" != "y" ]; then
    printf "  make install\n"
fi
