#!/usr/bin/env bash
# Copyright 2010-2013 RethinkDB, all rights reserved.

# This script detects the build settings local to the local build
# environment and saves them in the config.mk file.
#
# See ./configure --help for more information.

set -u

# Lists of dependencies and versions
init () {
    min_gcc_version=$(read_version 4.4.3)
    min_clang_version=0
    min_icc_version=0
    min_lessc_version="1.3.1"
    min_coffee_version="1.6.2"

    please_fetch_list='handlebars coffee lessc browserify proto2js'

    required_libs="protobuf v8 termcap"
    other_libs="unwind tcmalloc_minimal"
    all_libs="$required_libs $other_libs"
    support_libs="unwind tcmalloc_minimal v8 protobuf"
    default_static="tcmalloc_minimal"

    web_assets_deps="lessc coffee handlebars browserify proto2js"
    npm_deps="$web_assets_deps"
    required_bin_deps="protoc"
    support_bin_deps="$required_bin_deps $web_assets_deps npm"
    optional_bin_deps="wget curl"
    support_deps="$support_bin_deps $support_libs"
    bin_deps="cxx $support_bin_deps $optional_bin_deps"
    all_deps="$bin_deps $all_libs"

    allowed_arg_vars="CXXFLAGS LDFLAGS"
}

# The main configuration steps
configure () {
    require "Bash"
    show "$BASH_VERSION"
    if contains "$please_fetch_list" protoc; then
        please_fetch_list="$please_fetch_list protobuf"
    fi
    if contains "$please_fetch_list" protobuf; then
        please_fetch_list="$please_fetch_list protoc"
    fi
    for var in $allowed_arg_vars; do
        local val
        if lookup "$arg_vars" $var val; then
            require $var
            var $var "$val"
        fi
    done
    var LIB_SEARCH_PATHS $(for path in $lib_paths; do echo " -L$path"; done)
    require "Operating System"
    var OS $(uname)
    require "OS Version"
    show "`uname -msr`"
    case "$OS" in
        Darwin)
            with_tcmalloc=false ;
            var PTHREAD_LIBS "" ;;
        Linux)
            var PTHREAD_LIBS -pthread ;;
        *) error "unsupported operating system: $OS" ;;
    esac
    require "Without tcmalloc"
    boolvar NO_TCMALLOC not $with_tcmalloc
    require "Build client drivers"
    boolvar BUILD_DRIVERS $enable_drivers
    require "Architecture"
    var GCC_ARCH $(uname -m | grep '^[A-Za-z0-9_]*$' | head -n 1)
    var GCC_ARCH_REDUCED $(echo "$GCC_ARCH" | sed -e 's/^i[56]86$$/i486/g')
    var DEB_ARCH $(echo "${GCC_ARCH_REDUCED:-}" | sed -e 's/^x86_64$$/amd64/g')
    require "Use ccache"
    boolvar USE_CCACHE $use_ccache
    check_cxx
    if [ "$COMPILER" = CLANG ]; then
        require stdlib
        case "$OS" in
            Darwin) var_append LDFLAGS -lc++
                    var_append CXXFLAGS -stdlib=libc++ ;;
            *) var_append LDFLAGS -lstdc++ ;;
        esac
    fi
    require "Precompiled web assets"
    if [[ -z "$enable_precompiled_web" ]]; then
        if test -d precompiled/web; then
            enable_precompiled_web=true
        else
            enable_precompiled_web=false
        fi
    fi
    boolvar USE_PRECOMPILED_WEB_ASSETS $enable_precompiled_web
    if ! $enable_precompiled_web; then
        for bin in $web_assets_deps; do
            require_dep $bin
            check_bin $bin
        done
    fi
    for bin in $required_bin_deps; do
        require_dep $bin
        check_bin $bin
    done
    for bin in $optional_bin_deps; do
        optional_dep $bin
        check_bin $bin
    done
    if any "$fetch_list" contains "$npm_deps"; then
        require_dep npm
        check_bin npm
    fi
    build_try_lib_paths
    for lib in $required_libs; do
        check_lib $lib
    done
    check_v8_pre_3_19
    if [[ $NO_TCMALLOC = 0 ]] ; then
        check_lib tcmalloc_minimal
        if contains "$fetch_list" tcmalloc_minimal; then
            check_lib unwind
        fi
    else
        var TCMALLOC_MINIMAL_LIBS ""
    fi
    test_protobuf
    boolvar STATIC_V8 contains "$static_libs" v8
    var FETCH_LIST "$fetch_list"
    boolvar FETCH_INTERNAL_TOOLS $allow_fetch
    require Installation prefix
    var PREFIX ${arg_prefix:-/usr/local}
    require Configuration prefix
    var SYSCONFDIR ${arg_sysconfdir:-$PREFIX/etc}
    require Runtime data prefix
    var LOCALSTATEDIR ${arg_localstatedir:-$PREFIX/var}
}

# Entry point
main () {
    init

    if test -e configure.default; then
        echo "* Reading arguments from 'configure.default'"
        default_args=$(cat configure.default)
        echo "* Prepending the following arguments:" $default_args
    fi

    read_args ${default_args:-} "$@"

    echo "* Detecting system configuration"

    trap "show error; echo '* Aborting configure'" EXIT

    write "# Automatically generated by $0" 3> "$config"
    write "# Command line: $@" 3>> "$config"
    write "CONFIGURE_STATUS := started" 3>> "$config"
    write "CONFIGURE_ERROR := " 3>> "$config"
    write "CONFIGURE_COMMAND_LINE := $(echo ${default_args:-}) $@" 3>> "$config"
    configure 3>> "$config"

    trap - EXIT

    if ! $failed; then
        write "CONFIGURE_STATUS := success" 3>> "$config"
        echo "* Wrote configuration to $config"
    else
        write "CONFIGURE_STATUS := failed" 3>> "$config"
        echo "* Aborting configure"
        exit 1
    fi

}

# Parse the command line arguments
read_args () {
    local no_arg
    local has_arg
    local arg
    local option
    local dep

    exit_on_error=true
    config=config.mk
    failed=false

    allow_fetch=false
    force_paths='= '
    can_show=false
    required=false
    arg_vars=$'\1\2'
    lib_paths=
    static_libs=$default_static
    arg_prefix=
    arg_sysconfdir=
    arg_localstatedir=
    with_tcmalloc=true
    enable_drivers=false
    enable_precompiled_web=
    use_ccache=false

    while [[ $# -ne 0 ]]; do
        arg=${1#*=}
        if [[ "$arg" = "$1" ]]; then
            no_arg=shift
            if [[ $# -eq 1 ]]; then
                has_arg="error_missing_arg $(quote "$1")"
            else
                arg=$2
                has_arg='shift 2'
            fi
        else
            no_arg="error_no_arg $(quote "$1")"
            has_arg=shift
        fi
        option="${1%%=*}"
        case "$option" in
            --debug-configure) $no_arg; set -x ;;
            --config) $has_arg; config=$arg ;;
            --continue) $no_arg; exit_on_error=false ;;
            --allow-fetch) $no_arg; allow_fetch=true ;;
            --fetch) $has_arg
                allow_fetch=true
                if [[ "$arg" = "all" ]]; then
                    please_fetch_list="$support_deps"
                elif contains "$support_deps" "$arg"; then
                    please_fetch_list="$please_fetch_list $arg"
                else
                    die "Don't know how to fetch '$arg'"
                fi ;;
            --static) $has_arg
                if [[ "$arg" = none ]]; then
                    static_libs=
                elif [[ "$arg" = all ]]; then
                    static_libs=$all_libs
                elif contains "$all_libs" "$arg"; then
                    static_libs="$static_libs $arg"
                else
                    die "Unknown library: $arg"
                fi ;;
            --dynamic) $has_arg
                if [[ "$arg" = none ]]; then
                    static_libs=$all_libs
                elif [[ "$arg" = all ]]; then
                    static_libs=
                elif contains "$all_libs" "$arg"; then
                    static_libs=$(remove "$static_libs" "$arg")
                else
                    die "Unknown library: $arg"
                fi ;;
            --lib-path) $has_arg; lib_paths="$lib_paths $arg" ;;
            --with-tcmalloc) $no_arg; with_tcmalloc=true ;;
            --without-tcmalloc) $no_arg; with_tcmalloc=false ;;
            --enable-drivers) $no_arg; enable_drivers=true ;;
            --disable-drivers) $no_arg; enable_drivers=false ;;
            --enable-precompiled-web) $no_arg; enable_precompiled_web=true ;;
            --disable-precompiled-web) $no_arg; enable_precompiled_web=false ;;
            --ccache) $no_arg; use_ccache=true ;;
            --prefix) $has_arg; arg_prefix=$arg ;;
            --sysconfdir) $has_arg; arg_sysconfdir=$arg ;;
            --localstatedir) $has_arg; arg_localstatedir=$arg ;;
            --help) show_help; exit ;;
            -*) die "Unknown option '$option'" ;;
            *) if contains "$all_deps" "$(lc $option)"; then
                 $has_arg
                 force_paths="$force_paths $(lc $option)=$arg "
               elif contains "$allowed_arg_vars" "$option"; then
                 $has_arg
                 arg_vars="$arg_vars"$'\2'"$option"$'\1'"$arg"
               else
                 die "Unknown variable argument: $option"
               fi ;;
        esac
    done
}

# Description of some of the dependencies
dep_descrs=':
cxx:C++ Compiler
protoc:Protobuf compiler
npm:Node.js package manager
lessc:LESS css
coffee:CoffeeScript
handlebars:Handlebars
unwind:libunwind
tcmalloc_minimal:Google Perf Tools library
v8:v8 javascript engine
protobuf:Protobuf library'

# Output of --help
show_help () {
    cat <<EOF
Configure a RethinkDB build from source

Usage: $0 [arguments]

  --help                  Display this help
  --config <file>         Output file (default config.mk)
  --continue              Do not stop after encountering an error

  --allow-fetch           Allow fetching missing dependencies
  --lib-path <dir>        Add dir to the library search path
  <var>=<val>             Set the value of a variable
                            CXXFLAGS  C++ compiler arguments
                            LDFLAGS   C++ linker arguments
  --fetch <dep>           Force fetching <dep>.
  <dep>=<path>            Library or executable path. <dep> can be
EOF
    for dep in $all_deps; do
        local padded="$dep                        "
        local descr
        lookup "$dep_descrs" $dep descr || descr=
        echo "                            ${padded:0:24} $descr"
    done
    cat <<EOF
  --static <lib>          Statically link some libraries. <lib> is a library name, 'all' or 'none'
  --dynamic <lib>         Dynamically link some libraries. <lib> is a library name, 'all' or 'none'
  --prefix <dir>          Installation prefix. Defaults to /usr/local
  --sysconfdir <dir>      Configuration prefix. Defaults to /usr/local/etc
  --localstatedir <dir>   Runtime data prefix. Defaults to /usr/local/var
  --with-<module>
  --without-<module>      Enable or disable <module>. <module> can be one of
                            tcmalloc     Build with tcmalloc. (Default: enabled)
  --enable-<option>
  --disable-<option>      Enable or disable a build option
                            drivers          Build the client drivers (default: false)
                            precompiled-web  Use precompiled web assets located in precompiled/web (default: autodetect)
  --ccache                Speed up the build using ccache
EOF
}

# Quote for re-use in the shell
# quote 'a b' -> 'a\ b'
quote () {
    printf %q "$1"
}

# Some error messages
error_no_arg () { die "option ${1%%=*} does not take any arguments"; }
error_missing_arg () { die "option ${1%%=*} takes an argument"; }

# error <message>
# Try to generate an error
# If $delayed_errors, then save the error for later
# If not $required, turn it into a warning
error () {
    local req=$required
    if ${delay_errors:-false}; then
	delayed_error="$*"
	return
    fi
    local type
    if $req; then
        show "error"
        type=Error
    else
        show "no"
        type=Warning
    fi
    if $req || ! ${ignore_warnings:-false}; then
        echo "* $type: $*" >&2
    fi
    write "CONFIGURE_ERROR := $*"
    if $req; then
        echo "${error_details:-}" | head -n 5
        if $exit_on_error; then
            exit 1
        fi
    fi
    failed=$req
}

# Like error, but fatal
die () {
    show error
    required=true
    error "$@"
    exit 1
}

# not_found <name>
# Generate a not found error
not_found () {
    if $required; then
	if contains "$support_deps" "`lc $1`"; then
            error "missing $1. Install it, specify the full path with $(uc $1)= or run ./configure with --allow-fetch"
	else
            error "missing $1. Install it or specify the full path with $(uc $1)="
	fi
    else
	show no
    fi
}

# write <line>
# Write to the config file
write () {
    echo "$*" >&3
}

# show_descr <description>
# Describe what the script is looking for next
# A call to show will complete the line
show_descr () {
    if ${can_show:-false}; then
	show_no
    fi
    local padded="$*:                               "
    echo -n "${padded:0:32}"
    write "# $*"
    can_show=true
    error_details=
}

# Like 'show no', but show the delayed error if there is one
show_no () {
    local req=$required
    local err=$delayed_error
    show no
    if $req; then
	error "${err:-${1:-} not found}"
    elif [[ -n "$delayed_error" ]]; then
	error "$err"
    fi
}

# show <value>
# Display the value that was found
show () {
    if $can_show; then
        echo "$*"
        can_show=false
    fi
    delayed_error=
    delay_errors=false
}

# Like show_descr, but set $required to true
require () {
    required=true
    show_descr "$@"
}

# Like show_descr, but set $required to false
optional () {
    required=false
    show_descr "$@"
}

# var <name> <value>
# Set the value of a variable in the config file
var () {
    local name=$1
    shift
    local val=$*
    show "$val"
    write "$name := $val"
    eval "$name=$(quote "$val")"
}

# macro <name> <value>
# Set the value of a macro variable in the config file
macro () {
    local name=$1
    shift
    local val=$*
    show "$val"
    write "$name = $val"
}

# var_append <name> <value>
# Append a value to a variable in to the config file
var_append () {
    local name=$1
    shift
    local val=$*
    show "$val"
    write "$name += $val"
    eval "$name=\${$name:-}\ $(quote "$val")"
}

# boolvar <name> <command> <arguments ...>
# Run the command and set the variable to its boolean return value
boolvar () {
    local name=$1
    shift
    if "$@"; then
        show yes
        var $name 1
    else
        show no
        var $name 0
    fi
}

# Find a C++ compiler that rethinkdb supports
check_cxx () {
    require "C++ Compiler"
    local force_CXX
    lookup "$force_paths" cxx force_CXX || :
    local test_CXX=${force_CXX:-${CXX:-c++}}
    local full_CXX=$(which "$test_CXX" 2>/dev/null)
    if [[ ! -x "$full_CXX" ]]; then
        if [[ -n ${force_CXX:-} ]]; then
            error "unable to run $force_CXX"
            return
        fi
        full_CXX=$(which g++ 2>/dev/null || which clang 2>/dev/null || which icc 2>/dev/null)
        if [[ -z "$full_CXX" ]]; then
            not_found CXX
            return
        fi
    fi
    local description=$($full_CXX --version 2>/dev/null)$($full_CXX --help 2>/dev/null)
    local version_string=$(echo "$description" | extract_version_string)
    local type=$(echo "$description" | egrep -io 'gcc|g\+\+|clang|icc' | head -n 1)
    case "$(uc $type)" in
        GCC|G++) min_version=$min_gcc_version
                 type=GCC ;;
        CLANG) min_version=$min_clang_version ;;
        ICC) min_version=$min_icc_version
             type=INTEL ;;
        *) show "unknown"
           error "Could not determine C++ compiler type of $full_CXX (gcc, clang or icc is required)"
           return
    esac
    if [[ -z "$version_string" ]]; then
        show "$(uc $type) (unknown version)"
        error "Could not determine C++ compiler version (>= $(write_version $min_version) required)"
        return
    else
        test_version_ge $(uc $type) $(read_version "$version_string") $min_version || return
    fi

    show "$(uc $type) $version_string ($full_CXX)"
    var COMPILER "$(uc $type)"
    var CXX "$full_CXX"
}

# foo --version | extract_version_string
# find a version string in the output of foo --version
extract_version_string () {
    egrep -o '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n 1
}

# read_version "1.2.3" -> 10203
# read_version "2.14" -> 21400
read_version () {
    local one=${1%%.*}
    local rest=${1#*.}
    local two=${rest%%.*}
    rest=${rest#*.}
    local three=${rest%%.*}
    if [[ "$three" != "$rest" ]]; then three=0; fi
    printf %d%02d%02d "$one" "$two" "$three"
}

# write_version "10203" -> "1.2.3"
write_version () {
    local v=$(printf %06d "$1")
    printf %d.%d.%d ${v:0:2} ${v:2:2} ${v:4:2}
}

# test_version_ge <name> <version> <min version>
test_version_ge () {
    if ! [[ $2 -ge $3 ]]; then
        show "$1 $(write_version $2)"
        error "$1 $(write_version $2) is too old. At least $1 $(write_version $3) is required"
        return 1
    fi
}

# test_version_le <name> <version> <max version>
test_version_le () {
    if ! [[ $2 -le $3 ]]; then
        show "$1 $(write_version $2)"
        error "$1 $(write_version $2) is too recent. At most $1 $(write_version $3) is required"
        return 1
    fi
}

# test_bin_version <name> <path>
test_bin_version () {
    local min_var=min_$1_version
    local min=${!min_var:-}
    local max_var=max_$1_version
    local max=${!max_var:-}
    local invalid_var=invalid_$1_versions
    local invalid=${!invalid_var:-}
    test -z "$min$max$invalid" && return 0
    local output
    local ver
    if output=$("$2" --version) &&
       ver=$(echo "$output" | extract_version_string) &&
       test -n "$ver" ;
    then
        local version=$(read_version "$ver")
        test -z "$min" || test_version_ge $1 $version $(read_version $min) || return 1
        test -z "$max" || test_version_le $1 $version $(read_version $max) || return 1
        if contains "$invalid" "$ver" ; then
            show "$ver"
            error "$1 ($ver) is not supported"
            return 1
        fi
    else
        error "Could not determine $1 version"
        return 1
    fi
}

# contains <haystack> <needle>
# Test if the space-separated list haystack contains needle
contains () {
    local d=" $1 "
    [[ "${d% $2 *}" != "$d" ]]
}

# remove <list> <item>
# remove an item from a space-separated list
remove () {
    if contains "$1" "$2"; then
        local d=" $1 "
        echo -n "${d%% $2 *} ${d#* $2 }"
    else
        echo -n "$1"
    fi
}

# any "<list>" <command>
# Test if the command is true for any element of the space seperated list
any () {
    local list=$1
    shift
    for x in $list; do
        if "$@" $x; then
            return 0
        fi
    done
    return 1
}

# lookup <dict> <key> [var]
# dict is an assoc-list composed of two seperators followed by a list of pairs
# eg: ':|foo:bar|test:123|baz:quux' or '= a=b c=d'
lookup () {
    local _val=${1#*${1:1:1}$2${1:0:1}}
    if [[ "$_val" = "$1" ]]; then
        unset ${3:-$2}
        return 1
    fi
    eval "${3:-$2}=$(quote "${_val%%${1:1:1}*}")"
}

# check_bin <name>
# Check for a binary
check_bin () {
    local force_bin
    lookup "$force_paths" $1 force_bin || force_bin=
    if test -z "$force_bin" && contains "$please_fetch_list" $1; then
        fetch_bin $1
        return
    fi
    local ucbin=$(uc $1)
    local bin=${force_bin:-${!ucbin:-$1}}
    bin=$(which "$bin" 2>/dev/null)
    if [[ ! -x "$bin" ]]; then
        if [[ -n "${force_bin:-}" ]]; then
            bin=$force_bin
        elif fetch_allowed $1; then
            fetch_bin $1
            return
        else
            not_found $(uc $1)
            return
        fi
    fi
    local version
    test_bin_version $1 "$bin" || return
    var "$(uc $1)" "$bin"
}

# List of dependencies that make will fetch
fetch_list=

# fetch_bin <name>
# Instruct make to fetch and build the binary
fetch_bin () {
    show fetch
    macro $(uc $1) "\$(TC_$(uc $1)_INT_EXE)"
    fetch_list="$fetch_list $1"
}

# fetch_lib <name>
# Instruct make to fetch and build the library
fetch_lib () {
    show fetch
    var_append $(uc $1)_LIBS "\$($(uc $1)_INT_LIB)"
    macro $(uc $1) "\$($(uc $1)_INT_LIB)"
    fetch_list="$fetch_list $1"
}

# require_dep <name>
# Like require, but looks up the description for name
require_dep () {
    local descr
    lookup "$dep_descrs" $1 descr || descr=$1
    require $descr
}

# optional_dep <name>
# Like optional, but looks up the description for name
optional_dep () {
    local descr
    lookup "$dep_descrs" $1 descr || descr=$1
    optional $descr
}

# An assoc-list of c++ code to test if a library works
lib_test_code=':~
~termcap:
#include <termcap.h>
int main(){ tgetent(0, "xterm"); return 0; }
~'

# An assoc-list of possible library aliases
lib_alias=':~termcap:ncurses'

# List the paths where static libraries can be found into the try_lib_paths variable
build_try_lib_paths () {
    cc_paths=$($CXX -print-search-dirs 2>/dev/null | grep ^libraries | sed -e 's/^libraries[: =]*//; s/:/ /g')
    LDCONFIG=$(PATH="$PATH:/sbin:/usr/sbin" which ldconfig 2>/dev/null)
    if [[ -n "$LDCONFIG" ]]; then
        ld_paths=$($LDCONFIG -v -N -X 2>/dev/null | grep -v '^	' | sed -e 's/:$//' )
    else
        ld_paths="/usr/lib /usr/local/lib" # Default ld search paths on OSX
    fi
    try_lib_paths="$lib_paths $cc_paths $ld_paths"
    try_lib_paths=$(for path in $try_lib_paths; do
            test -d "$path" && niceabspath "$path"
        done | sort -u)
}

# check_lib <name>
# Check for the presence of a library and set the correct make flags for it
# WISHLIST: Properly detect the presence of the headers and check that the
#           installed version is compatible. Try using pkg-config.
check_lib () {
    local path
    if contains "$please_fetch_list" $1; then
        require_dep $1
        fetch_lib $1
        return
    fi
    if lookup "$force_paths" $1 path; then
        require_dep $1
        check_lib_compile "$path" $1
        $can_show && show_no "$path"
        return
    fi
    local check
    if contains "$static_libs" $1; then
        static_info=" (static)"
        check=check_static_lib
    else
        static_info=
        check=check_dyn_lib
    fi
    require "$1$static_info"
    delay_errors=true
    local aliases
    lookup "$lib_alias" $1 aliases || aliases=
    for lib in $1 $aliases; do
	$check $lib $1
	$can_show || return
    done
    if fetch_allowed $1; then
        fetch_lib $1
    else
        show_no "lib$1$static_info"
    fi
}

check_dyn_lib () {
    # TODO: pkg_config
    check_lib_compile -l$1 $2
}

# Test if the c++ compiler knows about the library
check_lib_compile () {
    local bfile=`dirname $0`/mk/gen/check_$2
    local code
    if ! lookup "$lib_test_code" $2 code; then
        code='int main(){}'
    fi
    echo "$code" > "$bfile.cc"
    if "$CXX" ${CXXFLAGS:-} ${LDFLAGS:-} ${LIB_SEARCH_PATHS:-} "$bfile.cc" $1 -o "$bfile.out" 1>"$bfile.log" 2>&1; then
	var_append $(uc $2)_LIBS $1
    else
        error_details="${error_details:-}"$(cat "$bfile.log")
    fi
}

check_static_lib () {
    # TODO: pkg-config
    local path=$(static_lib_lookup $1)
    if [[ -n "$path" ]]; then
        var_append $(uc $2)_LIBS "$path"
    fi
}

# Find a static library and print it's path
static_lib_lookup () {
    for path in $try_lib_paths; do
	local l=$path/lib$1.a
	test -f $l && echo $l && return 0
    done
}

# Some utility functions
# When bash 4 becomes ubiquitous, we can use ${//}, ${^^} and ${,,}
uc () { echo "$*" | tr '[:lower:]' '[:upper:]'; }
lc () { echo "$*" | tr '[:upper:]' '[:lower:]'; }
subst () { echo "${1%%$2*}$3${1#*$2}"; }

# bash's ! as a command
not () {
    ! "$@"
}

# Make the path absolute and prettier (with no /..)
niceabspath () {
    if [[ -d "$1" ]]; then
        (cd "$1" && pwd) && return
    fi
    local dir=$(dirname "$1")
    if [[ -d "$dir" ]] && dir=$(cd "$dir" && pwd); then
        echo "$dir/$(basename "$1")" | sed 's|^//|/|'
        return
    fi
    if [[ "${1:0:1}" = / ]]; then
        echo "$1"
    else
        echo "$(pwd)/$1"
    fi
}

test_protobuf () {
    require "Test protobuf"
    if any "protoc protobuf" contains "$fetch_list"; then
        show fetch
        return
    fi
    if ! test -e "$PROTOC"; then
        required=false
        error "$PROTOC does not exist"
        return
    fi
    local pbdir=`dirname $0`/mk/gen/protoc/
    mkdir -p "$pbdir"
    echo 'message Foo { enum Bar { Baz = 1; } }' > "$pbdir/test.proto"
    echo 'int main(){ return 0; }' > "$pbdir/main.cc"
    local out
    if ! out=$(
            "$PROTOC" "$pbdir/test.proto" --cpp_out=. 2>&1 &&
            "$CXX" "$pbdir/test.pb.cc" "$pbdir/main.cc" -I "`dirname $0`" $PROTOBUF_LIBS $PTHREAD_LIBS ${CXXFLAGS:-} ${LDFLAGS:-} ${LIB_SEARCH_PATHS:-} -o "$pbdir/a.out" 2>&1)
    then
        error_details="$out"
        error "Unable to compile sample protobuf file. Try running ./configure with the --fetch protoc option"
        return
    fi
    show ok
}

fetch_allowed () {
     (contains "$npm_deps" $1 ||
         (contains "$support_deps" $1 && $allow_fetch)) || return 1
}

check_v8_pre_3_19 () {
    if contains "$fetch_list" v8; then
        boolvar V8_PRE_3_19 false
        return
    fi
    require "Use pre-3.19 v8 API"
    local tmpfile=`dirname $0`/mk/gen/check_v8_pre_3_19
    cat > "$tmpfile.cc" <<EOF
#include <v8.h>
int main() {
    v8::Local<v8::Context> lc = v8::Context::New(v8::Isolate::GetCurrent());
    v8::Persistent<v8::Context> ps;
    ps.Reset(v8::Isolate::GetCurrent(), lc);
    v8::Local<v8::Context> lh = v8::Local<v8::Context>::New(v8::Isolate::GetCurrent(), ps);
}
EOF
    if
        "$CXX" ${CXXFLAGS:-} ${LDFLAGS:-} ${LIB_SEARCH_PATHS:-} "$tmpfile.cc" \
            $V8_LIBS $PTHREAD_LIBS -o "$tmpfile.out" 1>"$tmpfile.log" 2>&1;
    then
        boolvar V8_PRE_3_19 false
    else
        boolvar V8_PRE_3_19 true
    fi
}

# Call the main command with the command line arguments
main "$@" 3>/dev/null
