#! /bin/sh
# Simple configure script for Core Lightning.

set -e

CONFIGURATOR=ccan/tools/configurator/configurator
CONFIG_VAR_FILE=config.vars
CONFIG_HEADER=ccan/config.h
BASE_WARNFLAGS="-Wall -Wundef -Wmissing-prototypes -Wmissing-declarations -Wstrict-prototypes -Wold-style-definition -Werror"

OS=$(uname -s)
ARCH=$(uname -m)
if [ "$OS-$ARCH" = "Darwin-arm64" ]; then
CPATH=/opt/homebrew/include
LIBRARY_PATH=/opt/homebrew/lib
export PKG_CONFIG_PATH=/opt/homebrew/opt/sqlite/lib/pkgconfig
else
CPATH=/usr/local/lib
LIBRARY_PATH=/usr/local/lib
export PKG_CONFIG_PATH=/usr/local/opt/sqlite/lib/pkgconfig
fi

: ${PKG_CONFIG=pkg-config}

# You can set PG_CONFIG in the environment to direct configure to call
# a specific 'pg_config' binary. If you set it to an empty string, then
# PostgreSQL support will be explicitly disabled, even if a 'pg_config'
# binary exists in your PATH. If you leave it unset, then the following
# line enables the automagic detection that most users want.
: ${PG_CONFIG=pg_config}

usage_with_default()
{
    if [ $# = 4 ]; then
	if [ "$2" = 1 ]; then
	    DEF=$3
	else
	    DEF=$4
	fi
    else
	DEF=$2
    fi
    echo "  $1 (default $DEF)"
}

# Given DEBUGBUILD, what COPTFLAGS do we default to.
default_coptflags()
{
    if [ "$1" = 0 ]; then
	echo "-Og"
    fi
}

# Given COPTFLAGS, HAVE_GCC and HAVE_MODERN_GCC, what CWARNFLAGS to default to?
default_cwarnflags()
{
    F=$BASE_WARNFLAGS
    # Clang doesn't like -Wno-maybe-uninitialized, but doesn't seem
    # to give spurious warnings, either.
    if [ "$2" = 1 ]; then
	# With old gccs, or optimization != -O3, we need to suppress some warnings.
	if [ -n "${1##*-O3*}" ] || [ "$3" != "1" ]; then
	    F="$F -Wno-maybe-uninitialized"
	fi
	# Recent clang understands that, but at least our MacOS CI doesn't, so keep
	# this gcc-only.
	F="$F -Wshadow=local"
    fi
    echo "$F"
}

default_pytest()
{
    # Since we just checked that we have python3 we give that one the
    # most priority and then fall back to some common aliases.
    PYTEST_BINS="python3 -m pytest,pytest,py.test,pytest3,pytest-3"
    IFS=','
    for p in $PYTEST_BINS; do
	# If it is a combined command such as `python3 -m pytest` we
	# want to only call which on the executable
	exe=$(echo "$p" | awk '{print $1}')
	# shellcheck disable=SC2086
	if [ "$(which $exe)" != "" ] ; then
	    "$p" --version 2>&1 | grep -q "pytest" || continue
	    echo "$p"
	    return
	fi
    done

    PYTHON_BINS="python python3"
    for p in $PYTHON_BINS; do
        if [ "$(which $p)" != "" ] ; then
            $p --version 2>&1 | grep -q "Python 3." || continue
            if $p -c "import pytest" 2>/dev/null ; then
                echo "$p -m pytest"
                return
            fi
        fi
    done
}

check_command()
{
    name="$1"
    shift 1

    echo -n "checking for $name... "
    if "$@" >/dev/null 2>&1 </dev/null; then
	echo 'found'
	return 0
    fi
    echo 'not found'
    return 1
}

default_valgrind_setting()
{
    # Valgrind must accept all these options (might exit with error 7 though
    # if /bin/true leaks mem on your system!)
    if valgrind -q --error-exitcode=7 --track-origins=yes --leak-check=full --show-reachable=yes --errors-for-leak-kinds=all /bin/true >/dev/null 2>&1 || [ $? = 7 ]; then
	echo 1
    else
	echo 0
    fi
}

default_rust_setting()
{
    if cargo --version > /dev/null 2>&1 && rustfmt --version >/dev/null 2>&1; then
	echo 1
    else
	echo 0
    fi
}

set_defaults()
{
    # Default values, loaded from environment or canned.
    # Note that ":-" means substitute if empty or unset, "-" means only if unset
    # which matters since you might explicitly set of these blank.
    PREFIX=${PREFIX:-/usr/local}
    CC=${CC:-cc}
    CDEBUGFLAGS=${CDEBUGFLAGS--std=gnu11 -g -fstack-protector-strong}
    DEVELOPER=${DEVELOPER:-0}
    DEBUGBUILD=${DEBUGBUILD:-0}
    # --enable-developer forces debug build
    [ "$DEVELOPER" = 0 ] || DEBUGBUILD=1
    COMPAT=${COMPAT:-1}
    STATIC=${STATIC:-0}
    CLANG_COVERAGE=${CLANG_COVERAGE:-0}
    ASAN=${ASAN:-0}
    UBSAN=${UBSAN:-0}
    FUZZING=${FUZZING:-0}
    FUZZFLAGS=""
    CSANFLAGS=""
    if [ "$ASAN" != 0 ]; then
	CSANFLAGS="$CSANFLAGS -fsanitize=address"
	if [ "$DEBUGBUILD" != 0 ]; then
	    CSANFLAGS="$CSANFLAGS -fno-sanitize-recover=address"
	fi
    fi
    if [ "$UBSAN" != 0 ]; then
	CSANFLAGS="$CSANFLAGS -fsanitize=undefined"
	if [ "$DEBUGBUILD" != 0 ]; then
	    CSANFLAGS="$CSANFLAGS -fno-sanitize-recover=undefined"
	fi
    fi
    if [ "$FUZZING" != 0 ]; then
	FUZZFLAGS="-fsanitize=fuzzer-no-link"
	CSANFLAGS="$CSANFLAGS $FUZZFLAGS"
    fi
    echo CSANFLAGS = $CSANFLAGS
    PYTEST=${PYTEST-$(default_pytest)}
    COPTFLAGS=${COPTFLAGS-$(default_coptflags "$DEBUGBUILD")}
    CONFIGURATOR_CC=${CONFIGURATOR_CC-$CC}
    VALGRIND=${VALGRIND:-$(default_valgrind_setting)}
    TEST_NETWORK=${TEST_NETWORK:-regtest}
    RUST=${RUST:-$(default_rust_setting)}
}

usage()
{
    echo "Usage: ./configure [--reconfigure] [setting=value] [options]"
    echo "If --reconfigure is specified, $CONFIG_VAR_FILE will set defaults."
    echo "Default settings:"
    set_defaults
    DEFAULT_COPTFLAGS="$(default_coptflags $DEBUGBUILD)"
    # We assume we have a modern gcc.
    DEFAULT_CWARNFLAGS="$(default_cwarnflags ""$DEFAULT_COPTFLAGS"" 1 1)"
    usage_with_default "CC" "$CC"
    usage_with_default "CWARNFLAGS" "$DEFAULT_CWARNFLAGS"
    usage_with_default "COPTFLAGS" "$DEFAULT_COPTFLAGS"
    usage_with_default "CDEBUGFLAGS" "$CDEBUGFLAGS"
    usage_with_default "CONFIGURATOR_CC" "${CONFIGURATOR_CC:-$CC}"
    echo "    To override compile line for configurator itself"
    usage_with_default "PYTEST" "$PYTEST"
    usage_with_default "VALGRIND" "$VALGRIND"

    echo "Options include:"
    usage_with_default "--prefix=" "$PREFIX"
    echo "    Prefix for make install"
    usage_with_default "--enable/disable-developer" "$DEVELOPER" "enable" "disable"
    echo "    Developer mode, good for testing"
    usage_with_default "--enable/disable-debug" "$DEBUGBUILD" "enable" "disable"
    echo "    Extra debug checks in the build, good for testing"
    usage_with_default "--enable/disable-compat" "$COMPAT" "enable" "disable"
    echo "    Compatibility mode, good to disable to see if your software breaks"
    usage_with_default "--enable/disable-valgrind" "(autodetect)"
    echo "    Run tests with Valgrind"
    usage_with_default "--enable/disable-static" "$STATIC" "enable" "disable"
    echo "    Static link sqlite3 and zlib libraries"
    usage_with_default "--enable/disable-coverage" "$CLANG_COVERAGE" "enable" "disable"
    echo "    Compile with Clang coverage instrumentation"
    usage_with_default "--enable/disable-address-sanitizer" "$ASAN" "enable" "disable"
    echo "    Compile with address-sanitizer"
    usage_with_default "--enable/disable-ub-sanitizer" "$UBSAN" "enable" "disable"
    echo "    Compile with undefined behaviour sanitizer"
    usage_with_default "--enable/disable-fuzzing" "$FUZZING" "enable" "disable"
    echo "    Compile with fuzzing"
    usage_with_default "--enable/disable-rust" "$RUST" "enable" "disable"
    echo "    Compile with Rust support"
    exit 1
}

add_var()
{
    if [ -n "$2" ]; then
        echo "Setting $1... $2"
    else
        echo "$1 not found"
    fi
    echo "$1=$2" >> $CONFIG_VAR_FILE.$$
    [ -z "$3" ] || echo "#define $1 $2" >> "$3"
}

for opt in "$@"; do
    case "$opt" in
	--reconfigure)
	    # Figure out what defaulT COPTFLAGS was for this config.vars
	    DEFAULT_COPTFLAGS=
	    # Set from values if not already set.
	    while IFS='=' read VAR VAL; do
		if eval [ -z \${$VAR+x} ]; then eval $VAR=\"$VAL\"; fi
		# If they had an old config, it might set DEVELOPER.
		if [ "$VAR" = DEVELOPER ]; then
		    DEFAULT_COPTFLAGS=$(default_coptflags "$VAL")
		    DEBUGBUILD="$VAL"
		    VAR=DEBUGBUILD
		fi
		if [ "$VAR" = DEBUGBUILD ]; then
		   DEFAULT_COPTFLAGS=$(default_coptflags "$VAL")
		fi
	    done < $CONFIG_VAR_FILE
	    # If we were those defaults, unset so we get new defaults in
	    # case DEBUGBUILD has changed.
	    if [ x"$COPTFLAGS" = x"$DEFAULT_COPTFLAGS" ]; then
		unset COPTFLAGS
	    fi
	    ;;
	CC=*) CC="${opt#CC=}";;
	CONFIGURATOR_CC=*) CONFIGURATOR_CC="${opt#CONFIGURATOR_CC=}";;
	CWARNFLAGS=*) CWARNFLAGS="${opt#CWARNFLAGS=}";;
	CDEBUGFLAGS=*) CDEBUGFLAGS="${opt#CDEBUGFLAGS=}";;
	COPTFLAGS=*) COPTFLAGS="${opt#COPTFLAGS=}";;
	PYTEST=*) PYTEST="${opt#PYTEST=}";;
	--prefix=*) PREFIX="${opt#--prefix=}";;
	--enable-developer) DEVELOPER=1;;
	--disable-developer) DEVELOPER=0;;
	--enable-debugbuild) DEBUGBUILD=1;;
	--disable-debugbuild) DEBUGBUILD=0;;
	--enable-compat) COMPAT=1;;
	--disable-compat) COMPAT=0;;
	--enable-valgrind) VALGRIND=1;;
	--disable-valgrind) VALGRIND=0;;
	--enable-static) STATIC=1;;
	--disable-static) STATIC=0;;
	--enable-coverage) CLANG_COVERAGE=1;;
	--disable-coverage) CLANG_COVERAGE=0;;
	--enable-address-sanitizer) ASAN=1;;
	--disable-address-sanitizer) ASAN=0;;
	--enable-ub-sanitizer) UBSAN=1;;
	--disable-ub-sanitize) UBSAN=0;;
	--enable-fuzzing) FUZZING=1;;
	--disable-fuzzing) FUZZING=0;;
	--enable-rust) RUST=1;;
	--disable-rust) RUST=0;;
	--help|-h) usage;;
	*)
	    echo "Unknown option '$opt'" >&2
	    usage
	    ;;
    esac
done

# Now fill in any unset vars.
set_defaults

# We assume warning flags don't affect congfigurator that much!
echo -n "Compiling $CONFIGURATOR..."
$CC ${CWARNFLAGS-$BASE_WARNFLAGS} $CDEBUGFLAGS $COPTFLAGS -o $CONFIGURATOR $CONFIGURATOR.c
echo "done"

if [ "$ASAN" = "1" ]; then
    if [ "$VALGRIND" = "1" ]; then
	echo "Address sanitizer (ASAN) and valgrind cannot be enabled at the same time"
	exit 1
    fi
fi

if [ "$CLANG_COVERAGE" = "1" ]; then
	case "$CC" in
		(*"clang"*)
			;;
		(*)
			echo "Clang coverage requires building with CC=clang."
			exit 1
			;;
	esac
fi

if [ "$FUZZING" = "1" ]; then
	case "$CC" in
		(*"clang"*)
			;;
		(*)
			echo "Fuzzing is currently only supported with clang."
			exit 1
			;;
	esac
fi


SQLITE3_CFLAGS=""
SQLITE3_LDLIBS="-lsqlite3"
if command -v "${PKG_CONFIG}" >/dev/null; then
    SQLITE3_CFLAGS="$("${PKG_CONFIG}" --silence-errors --cflags sqlite3 || :)"
    SQLITE3_LDLIBS="$("${PKG_CONFIG}" --silence-errors --libs sqlite3 || :)"
fi

POSTGRES_INCLUDE=""
POSTGRES_LDLIBS=""
if command -v "${PG_CONFIG}" >/dev/null; then
    POSTGRES_INCLUDE="-I$("${PG_CONFIG}" --includedir)"
    POSTGRES_LDLIBS="-L$("${PG_CONFIG}" --libdir) -lpq"
fi

# Clean up on exit.
trap "rm -f $CONFIG_VAR_FILE.$$" 0

$CONFIGURATOR --extra-tests --autotools-style --var-file=$CONFIG_VAR_FILE.$$ --header-file=$CONFIG_HEADER.$$ --configurator-cc="$CONFIGURATOR_CC" --wrapper="$CONFIGURATOR_WRAPPER" "$CC" ${CWARNFLAGS-$BASE_WARNFLAGS} $CDEBUGFLAGS $COPTFLAGS $CSANFLAGS -I$CPATH -L$LIBRARY_PATH $SQLITE3_CFLAGS $POSTGRES_INCLUDE <<EOF

var=HAVE_GOOD_LIBSODIUM
desc=libsodium with IETF chacha20 variants
style=DEFINES_EVERYTHING|EXECUTE|MAY_NOT_COMPILE
link=-lsodium
code=
#include <sodium.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
	crypto_secretstream_xchacha20poly1305_state crypto_state;
	unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
	unsigned char data[] = { 1, 2, 3 };

	crypto_secretstream_xchacha20poly1305_init_push(&crypto_state, header,
	                                                data);
	printf("%p\n", crypto_aead_chacha20poly1305_ietf_encrypt);
	printf("%d\n", crypto_aead_chacha20poly1305_ietf_NPUBBYTES);
	return 0;
}
/*END*/
var=HAVE_SQLITE3_EXPANDED_SQL
desc=sqlite3_expanded_sql
style=DEFINES_EVERYTHING|EXECUTE|MAY_NOT_COMPILE
link=$SQLITE3_LDLIBS
code=
#include <sqlite3.h>
#include <stdio.h>

int main(void)
{
	printf("%p\n", sqlite3_expanded_sql);
	return 0;
}
/*END*/
var=HAVE_SQLITE3
desc=sqlite3
style=DEFINES_EVERYTHING|EXECUTE|MAY_NOT_COMPILE
link=$SQLITE3_LDLIBS
code=
#include <sqlite3.h>
#include <stdio.h>

int main(void)
{
	printf("%p\n", sqlite3_prepare_v2);
	return 0;
}
/*END*/
var=HAVE_POSTGRES
desc=postgres
style=DEFINES_EVERYTHING|EXECUTE|MAY_NOT_COMPILE
link=$POSTGRES_LDLIBS
code=
#include <libpq-fe.h>
#include <stdio.h>

int main(void)
{
	printf("libpq version %d\n", PQlibVersion());
	return 0;
}
/*END*/
var=HAVE_GCC
desc=compiler is GCC
style=OUTSIDE_MAIN
code=
#ifndef __GNUC__
#error "Not GCC"
#endif
#ifdef __clang__
#error "clang"
#endif
/*END*/
var=HAVE_MODERN_GCC
desc=GCC version is 7 or above
style=OUTSIDE_MAIN
code=
#if __GNUC__ < 7
#error "Not modern GCC"
#endif
/*END*/
var=HAVE_PWRITEV
desc=pwritev() defined
style=DEFINES_EVERYTHING|EXECUTE|MAY_NOT_COMPILE
code=
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
	struct iovec iov[2];
	int fd = open("/dev/null", O_WRONLY);

	iov[0].iov_base = "hello";
	iov[0].iov_len = 5;
	iov[1].iov_base = " world";
	iov[1].iov_len = 6;
	if (pwritev(fd, iov, 2, 0) == 11)
		return 0;
	return 1;
}
/*END*/
EOF

if check_command 'python3-mako' python3 -c 'import mako'; then
    HAVE_PYTHON3_MAKO=1
else
    HAVE_PYTHON3_MAKO=0
fi

if check_command 'lowdown' lowdown; then
    HAVE_LOWDOWN=1
else
    HAVE_LOWDOWN=0
fi

if echo | check_command sha256sum sha256sum; then
    SHA256SUM=sha256sum
elif echo | check_command "shasum -a 256" shasum -a 256; then
    SHA256SUM="shasum -a 256"
elif echo | check_command sha256 sha256; then
    SHA256SUM=sha256
else
    echo "*** We need sha256sum, shasum -a 256, or sha256!" >&2
    exit 1
fi

# Now we can finally set our warning flags
if [ -z ${CWARNFLAGS+x} ]; then
    CWARNFLAGS=$(default_cwarnflags "$COPTFLAGS" \
		    $(sed -n 's/^HAVE_GCC=//p' < $CONFIG_VAR_FILE.$$) \
		    $(sed -n 's/^HAVE_MODERN_GCC=//p' < $CONFIG_VAR_FILE.$$) )
fi

add_var PREFIX "$PREFIX"
add_var CC "$CC"
add_var CONFIGURATOR_CC "$CONFIGURATOR_CC"
add_var CWARNFLAGS "$CWARNFLAGS"
add_var CDEBUGFLAGS "$CDEBUGFLAGS"
add_var COPTFLAGS "$COPTFLAGS"
add_var CSANFLAGS "$CSANFLAGS"
add_var FUZZFLAGS "$FUZZFLAGS"
add_var SQLITE3_CFLAGS "$SQLITE3_CFLAGS"
add_var SQLITE3_LDLIBS "$SQLITE3_LDLIBS"
add_var POSTGRES_INCLUDE "$POSTGRES_INCLUDE"
add_var POSTGRES_LDLIBS "$POSTGRES_LDLIBS"
add_var VALGRIND "$VALGRIND"
add_var DEVELOPER "$DEVELOPER" $CONFIG_HEADER.$$
add_var DEBUGBUILD "$DEBUGBUILD"
add_var COMPAT "$COMPAT" $CONFIG_HEADER.$$
add_var PYTEST "$PYTEST"
add_var STATIC "$STATIC"
add_var CLANG_COVERAGE "$CLANG_COVERAGE"
add_var ASAN "$ASAN"
add_var UBSAN "$UBSAN"
add_var TEST_NETWORK "$TEST_NETWORK"
add_var HAVE_PYTHON3_MAKO "$HAVE_PYTHON3_MAKO"
add_var HAVE_LOWDOWN "$HAVE_LOWDOWN"
add_var SHA256SUM "$SHA256SUM"
add_var FUZZING "$FUZZING"
add_var RUST "$RUST"

# Hack to avoid sha256 name clash with libwally: will be fixed when that
# becomes a standalone shared lib.
echo '#include "ccan_compat.h"' >> $CONFIG_HEADER.$$

# Now we set them all and check.
while IFS='=' read VAR VAL; do
    eval $VAR=\"$VAL\"
done < $CONFIG_VAR_FILE.$$

if [ "$HAVE_SQLITE3" = 0 -a "$HAVE_POSTGRES" = 0 ]; then
    # I have no database yet I must schema!)
    echo "*** We need a database, but neither sqlite3 nor postgres found" >&2
    exit 1
fi

mv $CONFIG_VAR_FILE.$$ $CONFIG_VAR_FILE
mv $CONFIG_HEADER.$$ $CONFIG_HEADER
