#!/bin/sh
#
# Copyright 2011
# Kaz Kylheku <kaz@kylheku.com>
# Vancouver, Canada
# All rights reserved.
#
# BSD License:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#   1. Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#   2. Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in
#      the documentation and/or other materials provided with the
#      distribution.
#   3. The name of the author may not be used to endorse or promote
#      products derived from this software without specific prior
#      written permission.
#
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#
# Save command line in a way that can be re-run.
# This takes care of spaces, but if there are shell-meta characters
# in the arguments, oops.
#

set -u

cmdline=
for arg in "$0" "$@" ; do
  [ -n "$cmdline" ] && cmdline="$cmdline "
  case $arg in
  *" "* | " "* | *" " )
    cmdline=$cmdline$(printf "\"%s\"" "$arg")
    ;;
  * )
    cmdline=$cmdline$arg
    ;;
  esac
done

#
# Parse configuration variables
#
while [ $# -gt 0 ] ; do
  case $1 in
  --no-* )
    var=${1#--no-}
    val=
    ;;
  --*=* )
    var=${1%%=*}
    var=${var#--}
    val=${1#*=}
    ;;
  --*= )
    var=${1%%=*}
    var=${var#--}
    val=
    ;; --* )
    var=${1#--}
    val=y
    ;;
  *=* )
    var=${1%%=*}
    val=${1#*=}
    ;;
  *= )
    var=${1%%=*}
    val=
    ;;
  * )
    printf "$0: '$1' doesn't look like a configuration variable assignment\n"
    printf "$0: use --help to get help\n"
    exit 1
  esac

  if ! printf $var | grep -q -E '^[A-Za-z_][A-Za-z0-9_]*$' ; then
    printf "$0: '$var' isn't a proper configuration variable name\n"
    exit 1
  fi

  eval "$var='$val'"
  shift
done

#
# Establish default values for any variables that are not specified
# on the command line. The default derivations from prefix are in
# Make syntax. They go verbatim into the generated config.make.
# This way they can be overridden more flexibly at make time.
#

#
# non-config
#
help=${help-}

#
# config
#
prefix=${prefix-/usr/local}
install_prefix=${install_prefix-}
bindir=${datadir-'$(prefix)/bin'}
datadir=${datadir-'$(prefix)/share/txr'}
mandir=${mandir-'$(prefix)/share/man'}
cross=${cross-}
compiler_prefix=${compiler_prefix-}
ccname=${ccname-gcc}
cc=${cc-'$(cross)$(compiler_prefix)$(ccname)'}
intptr=${intptr-}
tool_prefix=${tool_prefix-}
lex=${lex-'$(cross)$(tool_prefix)flex'}
lexlib=${lexlib--lfl}
yaccname_given=${yaccname+yes}
yaccname=${yaccname-}
yacc_given=${yacc+yes}
yacc=${yacc-'$(cross)$(tool_prefix)$(yaccname)'}
nm=${nm-'$(cross)$(tool_prefix)nm'}
opt_flags=${opt_flags--O2}
lang_flags=${lang_flags--ansi -D_POSIX_C_SOURCE=2}
diag_flags=${diag_flags--Wall -Wmissing-prototypes -Wstrict-prototypes}
debug_flags=${debug_flags--g}
inline=${inline-}
platform_flags=${platform_flags-}
remove_flags=${remove_flags-}
lex_dbg_flags=${lex_dbg_flags-}
txr_dbg_opts=${txr_dbg_opts---gc-debug}
valgrind=${valgrind-}
lit_align=${lit_align-}

#
# If --help was given (or --help=<nonempty> or help=<nonempty>) then
# print help and exit. The termination status is failed, to indicate
# that configuration was not done.
#

if [ -n "$help" ] ; then
cat <<!

usage: $0 { variable=value }*

The configure script prepares txr program for compilation and installation.
To configure a program means to establish the values of make variables
which influence how the software is built, where it is installed.
These variables can also influence what features are present in the
software, and can determine various defaults for those behaviors which are
dynamically configurable when the software is run.

Configuration variables are recorded in a file called config.make. This is a
GNU makefile, and consequently uses the GNU make syntax. It is included in the
main Makefile by an include statement.

The configure script is flexible. It allows variables to be entered in any
of these forms:

Canonical:

   variable=value      Defines the given variable as having the given value.
   variable=           Defines the variable as having an empty value.
                       An empty value serves as boolean false.

Long-option style:

   --variable=value    Same as 'variable=value', but resembles a GNU-style
                       long option.
   --variable          Same as 'variable=y'.
   --no-variable       Same as 'variable='.

No variables are required. The configure script establishes default values
for any variables which are needed by the build, but which are not specified
on the command line.

After running $0, check that the config.make contents are sane.

The following variables are supported.  Note that make variable syntax may
be used in paths. Default values are shown in [square brackets].

prefix [$prefix]

  Specifies root directory where the software will ultimately be installed and
  run from.

install_prefix [$install_prefix]

  Specifies an extra path prefix that will be prepended to all paths during
  installation, which allows the software to be installed in a temporary
  directory for packaging. This variable becomes the \$(DESTDIR)
  variable in the config.make makefile.

bindir [$bindir]

  Specifies where the program executable will be installed.

datadir [$datadir]

  Specifies where read-only program data is to be stored.

mandir [$mandir]

  Specifies the directory where to install man pages.

cross [$cross]

  Specifies the root of a cross-compiling toolchain.
  This becomes the \$(cross) variable in the config.make makefile, and by
  default will be added as a prefix to all of the toolchain commands.
  It should include the trailing slash, unless the \$compiler_prefix
  and \$tool_prefix variables take care of this by providing a leading slash.

compiler_prefix [$compiler_prefix]

  Specifies a prefix to be added to the compiler command.
  This is added to the \$(cross) prefix. This can include some path name
  components, and a name fragment. For instance, if
  \$cross is "/cross/toolchain/" and \$compiler_prefix is
  "bin/mips-linux-" then the compiler command, unless otherwise
  specified, will be "/cross/toolchain/bin/mips-linux-gcc".

ccname [$ccname]

  Specifies just the name of the compiler front-end program, without the path.
  The following variable, cc, specifies the full name.

cc [$cc]

  Specifies the name of the toolchain front-end driver command to use for
  compiling C sources to object files, and for linking object files to
  executables. This becomes the CC variable in config.make.

intptr [$intptr]

  Specifies the name of the C integer type wide enough such that a pointer
  value can be converted to it. If this is blank, the configure script
  will try to auto detect it.

lit_align [$lit_align]

  Specifies alignment for wide string literals.  This is guessed
  from the size of the wchar_t type. If your wchar_t type is two byte wide, but
  wide literals are aligned to four bytes, then you should specify this.  This
  will eliminate some kludges in the program. There is no easy way to check
  for this withut generating and running a C program, which is unfriendly
  for cross-compiling!

inline [$inline]

  Specifies the syntax for defining an inline function, in such
  a way that the function definition can be included into multiple
  translation units without clashes.

  If blank, an attempt is made to auto-detect this which
  falls back on "static".

tool_prefix [$tool_prefix]

  Specifies a prefix to be added to tool commands other than the
  compiler, like lex and yacc, in addition to \$cross.

lex [$lex]

  Specifies the program to use for compiling lex scanners to C.
  This must be compatible with GNU flex, since flex extensions are used.

lexlib [$lexlib]

  Specifies the linker flag to use for linking the lex library.

yaccname [$yaccname]

  Specifies just the name of the yacc program without the path.
  The following variable, yacc, specifies the full name.
  If blank, the choice yacc program will be auto-detected.

yacc [$yacc]

  Specifies the program to use for compiling yacc scanners to C.

nm [$nm]

  Specifies the nm program for dumping symbols from an object file.

opt_flags [$opt_flags]

  Specifies optimization flags to use for compiling and linking
  C sources.

lang_flags [$lang_flags]

  Specifies compiler flags which control the C language dialect and standard
  conformance in the language and header files. The txr program is written
  in C90, and requires POSIX and possibly other extensions.

diag_flags [$diag_flags]

  Specifies compiler flags for obtaining extra diagnostics.

debug_flags [$debug_flags]

  Specifies flags for requesting that debugging information be
  retained in the compile and link.

platform_flags [$platform_flags]

  Specify additional compiler flags for anything else, such as CPU tuning,
  target ABI selection, code generation options, et cetera.

remove_flags [$remove_flags]

  This is a negative otpion. Any flags mentioned in this variable
  will be removed from any of the other compiler flags options above.
  The flags may contain GNU Make patterns.

lex_dbg_flags [$lex_dbg_flags]

  Specifies debug flags to be passed to lex, perhaps to generate a debugging
  scanner.

txr_dbg_opts [$txr_dbg_opts]

  Specifies debug flags to pass to the txr program during the execution
  of "make tests".

valgrind [$valgrind]

  Use --valgrind to to build txr with valgrind integration.
  Valgrind integration means that when the program is running under valgrind,
  it advises valgrind about stack memory locations accessed by the garbage
  collector, to suppress diagnostics about uninitialized accesses.
!
  exit 1
fi

#
# Variables are read, --help wasn't given, so let's configure!
#


txr_ver=041

#
# The all important banner.
#

if [ $txr_ver ] ; then
  banner_text=$(printf " Configuring txr %s " "$txr_ver")
else
  banner_text=" Configuring txr (unknown version) "
fi
banner_box=$(printf "%.${#banner_text}s\n" \
		    "-------------------------------------------")
printf "+%s+\n|%s|\n+%s+\n" $banner_box "$banner_text" $banner_box

#
# From here on in, we bail if any command fails.
#

set -e

#
# Check for GNU make
#

printf "Checking for GNU Make ... "

output=$(make --version 2>&1)
set -- $output

if [ $1 != "GNU" -o $2 != "Make" ] ; then
  printf "missing\n"
  exit 1
fi

make_version=$3

save_ifs=$IFS ; IFS=. ; set -- $make_version ; IFS=$save_ifs

if [ $1 -lt 3 -o \( $1 -eq 3 -a $2 -lt 80 \) ] ; then
  printf "too old (%s found, 3.80 or newer needed)\n" $make_version
  exit 1
else
  printf "yes (%s found)\n" $make_version
fi

#
# Verify sanity of --prefix and other directories.
#

printf "Checking installation paths:\n"

for name in prefix bindir datadir mandir; do
  eval path="\$install_prefix\${$name}"
  printf "\$(install_prefix)\$(%s)=%s ... " $name "$path"
  test_access=y
  case "$path" in
  " "* | *" "* | *" " )
    printf "incorrect (contains spaces)\n"
    exit 1
    ;;
  -* )
    printf "incorrect (resembles a command option)\n"
    exit 1
    ;;
  *'$('* )
    # It's a make expression; can't test it
    test_access=
    ;;
  /* )
    ;;
  * )
    printf "incorrect (must be absolute path)\n"
    exit 1
    ;;
  esac

  if [ $test_access ] ; then
    test_prefix=$path

    while true ; do
      if [ -e $test_prefix ] ; then
	if [ ! -d $test_prefix ] ; then
	  printf "incorrect ('%s' is not a directory)!\n" $test_prefix
	  exit 1
	fi
	if [ ! -w $test_prefix ] ; then
	  printf "okay\n  (but no write access to '%s'\n" $test_prefix

	  printf "   so 'make install' will require root privileges)\n"
	else
	  printf "okay\n"
	fi
	break
      fi
      test_prefix=$(dirname $test_prefix)
    done
  else
    printf "okay\n  (make variable derivation)\n"
  fi
done

#
# First, we have to figure out whether we are configured straight
# in the source directory, or whether we are in a separate build directory.
# In the latter case, we set up a symbolic link to the Makefile.
#
source_dir="$(dirname $0)"

#
# Compute an absolute path to the source directory.
#
top_srcdir="$(cd "$source_dir" ; pwd -P)"

printf "Checking source directory %s ..." "$top_srcdir"

case "$top_srcdir" in
" "* | *" "* | *" " )
  printf " bad (contains spaces)\n"
  exit 1
  ;;
* )
  printf " okay\n"
  ;;
esac

if [ "$source_dir" != "." ] ; then
  printf "symlinking Makefile -> $source_dir/Makefile\n"
  ln -sf "$source_dir/Makefile" .
else
  printf "warning: its recommended to build in a separate directory\n"
fi

gen_config_make()
{
  cat > config.make <<!
#
# Make include file automatically generated by $0.
# Changes to this file are lost when the above is re-run.
#

# absolute path to source code directory
top_srcdir := $top_srcdir

# ultimate installation prefix, where the
# application will be run.
prefix := $prefix

# packaging installation prefix, where the
# application may be temporarily installed
# for creating pre-compiled packages,
# e.g. for an operating system distro.
DESTDIR := $install_prefix

# executable directory
bindir := $bindir

# read-only data directory
datadir := $datadir

# man page directory
mandir := $mandir

# cross compiler toolchain root directory
cross := $cross

# compiler name
ccname = $ccname

# name of yacc program
yaccname = $yaccname

# prefix for compiler command
compiler_prefix := $compiler_prefix

# prefix for non-compiler toolchain commands
tool_prefix := $tool_prefix

CC := $cc
LEX := $lex
LEXLIB := $lexlib
YACC := $yacc
NM := $nm

OPT_FLAGS := $opt_flags
LANG_FLAGS := $lang_flags
DIAG_FLAGS := $diag_flags
DBG_FLAGS := $debug_flags
PLATFORM_FLAGS := $platform_flags
REMOVE_FLAGS := $remove_flags
LEX_DBG_FLAGS := $lex_dbg_flags
TXR_DBG_OPTS := $txr_dbg_opts
!
}

#
# Before doing some other tests, we need a config.make
#

printf "generating config.make ... "
gen_config_make
printf "\n"

#
# Start config.h header
#

cat <<! > config.h
/*
 * Header file automatically generated by $0.
 * Tweaking this file may seem like a good temporary workaround
 * than tweak the file, but you probably should fix the script
 * to do the job. In any case, be aware that you will lose your
 * changes if you re-run $0.
 */
!

#
# Check C compiler sanity
#
printf "Checking whether your C compiler can make a simple executable ... "

cat > conftest.c <<!
#include <stdio.h>
int main(void)
{
  printf("Hello, world!\n");
  return 0;
}
!

rm -f conftest
if ! make conftest > conftest.err 2>&1 || ! [ -x conftest ] ; then
  printf "failed\n\n"
  printf "Errors from compilation: \n\n"
  cat conftest.err
  exit 1
fi

rm -f conftest
printf "okay\n"

#
# Check what kind of C type we have for integers wider than long,
# if any.
#
printf "Checking what C type we have for integers wider than \"long\" ... "

for try_type in int64 __int64 "long long" ; do
  cat > conftest.c <<!
$try_type value;
!
  rm -f conftest.o
  if make conftest.o > conftest.err 2>&1 ; then
    longlong=$try_type
    break
  fi
done

if [ -n "$longlong" ] ; then
  printf '"%s"\n' "$longlong"
  printf "#define HAVE_LONGLONG_T 1\n" >> config.h
  printf "typedef $longlong longlong_t;\n" >> config.h
else
  printf "none\n"
fi

printf "Checking what C integer type can hold a pointer ... "

if [ -z "$intptr" ] ; then
  cat > conftest.c <<!
#include "config.h"
char sizeof_ptr[sizeof (char *)];
char sizeof_short[sizeof (short)];
char sizeof_int[sizeof (int)];
char sizeof_long[sizeof (long)];
#ifdef HAVE_LONGLONG_T
char sizeof_longlong_t[sizeof (longlong_t)];
#endif
!
  rm -f conftest.o conftest.syms

  if ! make conftest.syms > conftest.err 2>&1 ; then
    printf "failed\n\n"

    printf "Errors from compilation: \n\n"
    cat conftest.err
    exit 1
  fi

  sizeof_ptr=0
  sizeof_short=0
  sizeof_int=0
  sizeof_long=0
  sizeof_longlong_t=0

  while read symbol type offset size ; do
    eval "size=$(( 0$size + 0 ))"
    symbol=${symbol#_}
    if [ "$type" = "C" ] ; then
      size=$offset
    fi
    case "$symbol" in
    sizeof* )
      eval $(printf "%s=%d\n" "$symbol" "$size")
      ;;
    esac
  done < conftest.syms

  rm -f conftest.syms conftest.o

  if [ $sizeof_ptr -eq 0 ] ; then
    printf "failed\n"
    exit 1
  fi

  if [ $sizeof_ptr -eq $sizeof_short ] ; then
    intptr="short"
  elif [ $sizeof_ptr -eq $sizeof_int ] ; then
    intptr="int"
  elif [ $sizeof_ptr -eq $sizeof_long ] ; then
    intptr="long"
  elif [ $sizeof_ptr -eq $sizeof_long_long_t ] ; then
    intptr="longlong_t"
  fi

  if [ -z "$intptr" ] ; then
    printf "failed\n"
    exit 1
  fi
fi

printf '"%s"\n' "$intptr"
printf "typedef $intptr int_ptr_t;\n" >> config.h
intptr_max_expr="((((($intptr) 1 << $((sizeof_ptr * 8 - 2))) - 1) << 1) + 1)"
printf "#define INT_PTR_MAX %s\n" "$intptr_max_expr" >> config.h
printf "#define INT_PTR_MIN (-INT_PTR_MAX)\n" >> config.h

#
# Alignment of wchar_t
#
# What we really want to know is the alignment of wide string literals
# like L"wide literal".
#
# We make pessimistic assumption that the size of the wchar_t type is this
# alignment.
#
# There is no easy way to get the information without running a compiled
# program.
#

printf "Conservatively guessing the alignment of wide literals ... "

if [ -z "$lit_align" ] ; then
  cat > conftest.c <<!
#include <wchar.h>
char sizeof_wchar_t[sizeof (wchar_t)];
!
  rm -f conftest.o conftest.syms

  if ! make conftest.syms > conftest.err 2>&1 ; then
    printf "failed\n\n"

    printf "Errors from compilation: \n\n"
    cat conftest.err
    exit 1
  fi

  sizeof_wchar_t=0

  while read symbol type offset size ; do
    eval "size=$(( 0$size + 0 ))"
    symbol=${symbol#_}
    if [ "$type" = "C" ] ; then
      size=$offset
    fi
    case "$symbol" in
    sizeof* )
      eval $(printf "%s=%d\n" "$symbol" "$size")
      ;;
    esac
  done < conftest.syms

  rm -f conftest.syms conftest.o

  if [ $sizeof_wchar_t -eq 0 ] ; then
    printf "failed\n"
    exit 1
  fi

  lit_align=$sizeof_wchar_t
fi

printf "%d\n" "$lit_align"
printf "#define LIT_ALIGN %d\n" "$lit_align" >> config.h

#
# Inline functions
# 

printf "Checking how to declare inline functions ... "

if [ -z "$inline" ] ; then
  for inline in \
    "inline" "static inline" "extern inline" \
    "__inline__" "static __inline__" "extern __inline__" \
    "static"
  do
    cat > conftest1.c <<!
$inline int func(void)
{
  return 0;
}

int main(void)
{
  return func();
}
!
    cat > conftest2.c <<!
$inline int func(void)
{
  return 0;
}
!
    rm -f conftest2
    if ! make conftest2 > conftest.err 2>&1 ; then
	continue
    fi
    break
  done
fi

printf '"%s"\n' "$inline"
printf "#define INLINE $inline\n" >> config.h

#
# Valgrind
#

if [ -n "$valgrind" ] ; then
  printf "Checking valgrind API availability ... "

  cat > conftest.c <<!
#include <valgrind/memcheck.h>

#ifdef VALGRIND_DO_CLIENT_REQUEST

int main(void)
{
  return 0;
}

#else
syntax error
#endif
!
  rm -rf conftest
  if ! make conftest > conftest.err 2>&1 || ! [ -x conftest ] ; then
    printf "failed\n\n"
    printf "Errors from compilation: \n\n"
    cat conftest.err
    exit 1
  fi

  printf "okay\n"
  printf "#define HAVE_VALGRIND\n" >> config.h
fi

#
# Yacc tests
#

printf "Checking for yacc program ... "

if [ -z "$yacc_given" -a -z "$yaccname_given" ] ; then
  rm -f conftest.yacc
  for yaccname in "yacc" "byacc" "bison -y" "" ; do
    yaccpath=$(make yaccname="$yaccname" conftest.yacc)
    if command -v $yaccpath > /dev/null ; then
      break;
    fi
  done

  if [ -z "$yaccname" ] ; then
    printf "not found\n"
    exit 1
  fi

  printf '"%s"\n' "$yaccpath"
else
  yaccpath=$(make conftest.yacc)
  case $yaccpath in
  *bison )
    printf "error\n\n"
    printf "GNU Bison needs -y to behave like yacc\n\n"
    printf "This needs to be specified in the --yaccname or --yacc option\n\n"
    exit 1
    ;;
  * )
    if ! command -v $yaccpath > /dev/null ; then
      printf "not found\n\n"
      exit 1
    fi
    printf "given\n"
    ;;
  esac
fi

#
# sys/wait.h
#

printf "Checking whether we have <sys/wait.h> ... "

cat > conftest.c <<!
#include <sys/wait.h>

int main(void)
{
  return 0;
}
!
rm -f conftest
if ! make conftest > conftest.err 2>&1 || ! [ -x conftest ] ; then
  printf "no\n"
else
  printf "yes\n"
  printf "#define HAVE_SYS_WAIT 1\n" >> config.h
fi

#
# environ
#

printf "Checking whether we have environ ... "

cat > conftest.c <<!
int main(void)
{
  extern char **environ;
  puts(environ[0]);
  return 0;
}
!
rm -f conftest
if ! make conftest > conftest.err 2>&1 || ! [ -x conftest ] ; then
  printf "no\n"
else
  printf "yes\n"
  printf "#define HAVE_ENVIRON 1\n" >> config.h
fi

#
# GetEnvironmentStrings
#

printf "Checking whether we have GetEnvironmentStrings ... "

cat > conftest.c <<!
#include <windows.h>

int main(void)
{
  WCHAR *ptr = GetEnvironmentStringsW();
  return 0;
}
!
rm -f conftest
if ! make conftest > conftest.err 2>&1 || ! [ -x conftest ] ; then
  printf "no\n"
else
  printf "yes\n"
  printf "#define HAVE_GETENVIRONMENTSTRINGS 1\n" >> config.h
fi

#
# Clean up
#

rm -f conftest conftest.[co] conftest.{err,syms}
rm -f conftest2 conftest[12].[oc]

#
# Regenerate config.make
#

printf "regenerating config.make ... "
gen_config_make
printf "\n"

#
# Save configuration in config.log
#
cat > config.log <<!

Configured on $(date) using

   $cmdline

!
#
# Parting message
#
cat <<!

The configuration seems to have been successful. That doesn't mean it's
correct!  Please check the above output for any problems, and verify that the
contents of the generated files config.make and config.h are sane for the
target platform.

The next step is to build the program with make.

If that is successful, please follow the INSTALL guide.

Usually, most users just need to "make tests" and "make install",
possibly switching to superuser for "make install" if the prefix
points to a privileged location like /usr/local/.

!