#/usr/bin/tclsh
# ^^^ help out editors which guess this file's content type.
#
# This is the main autosetup-compatible configure script for the
# SQLite project.
#
# This script should be kept compatible with JimTCL, a copy of which
# is included in this source tree as ./autosetup/jimsh0.c. The number
# of incompatibilities between canonical TCL and JimTCL is very low
# and alternative formulations of incompatible constructs have, so
# far, been easy to find.
#
# JimTCL: https://jim.tcl.tk
#


use cc cc-db cc-shared cc-lib hwaci-common pkg-config

# Are we cross-compiling?
set cross_compiling 0
if {[get-define host] ne [get-define build]} {
  set cross_compiling 1
} elseif {1
          && "nope" eq [get-env CC_FOR_BUILD "nope"]
          && [get-define CC] ne [get-define CC_FOR_BUILD]} {
  # Arguable/debatable...
  #
  # When _not_ cross-compiling and CC_FOR_BUILD is _not_ explcitely
  # specified, force CC_FOR_BUILD to be the same as CC, so that:
  #
  # ./configure CC=clang
  #
  # will use CC_FOR_BUILD=clang, instead of cc, for building in-tree
  # tools. This is based off of an email discussion and is thought to
  # be likely to cause less confusion than seeing 'cc' invocations
  # will when the user passes CC=clang.
  #
  # Sidebar: if we do this before the cc package is installed, it gets
  # reverted by that package. Ergo, the cc package init will tell the
  # user "Build C compiler...cc" shortly before we tell them:
  msg-result "Re-defining CC_FOR_BUILD to CC=[get-define CC]. To avoid this, explicitly pass CC_FOR_BUILD=..."
  define CC_FOR_BUILD [get-define CC]
}

# $DUMP_DEFINES_TXT is the file emitted by --dump-defines, intended
# only for build debugging and not part of the public build interface.
set DUMP_DEFINES_TXT ./config.defines.txt
# $DUMP_DEFINES_JSON is the autosetup counterpart of the historical
# "DEFS" var which was generated by the autotools in the pre-processed
# autotools builds (but not in the canonical tree). This is used by at
# least one high-profile client to extract build config info for use
# in compiling a scripting-language binding, so its name should not be
# arbitrarily changed.
set DUMP_DEFINES_JSON ./config.defines.json

########################################################################
# Regarding flag compatibility with the historical autotool configure
# script:
#
# A very long story made short, autosetup's --flag handling has
# some behaviors which make it impossible to implement 100% identical
# flags compared to the historical autotools build. The differences
# are documented here:
#
# 1) --debug is used by autosetup itself, so we have to rename it to
# --with-debug. We cannot use --enable-debug because that is, for
# autosetup, and alias for --debug=1.
#
# 2) In autosetup, all flags starting with (--enable, --disable) are
# forced to be booleans and receive special handling in how they're
# resolved. Because of that we have to rename:
#
#   2.1) --enable-tempstore[=no] to --with-tempstore[=no].
#
########################################################################
# A gentle introduction to flags handling in autosetup
#
# Reference: https://msteveb.github.io/autosetup/developer/
#
# All configure flags must be described in an 'options' call, which
# must appear very early on in this script. The general syntax is:
#
#  FLAG => {Help text}
#
# Where FLAG can have any of the following formats:
#
#   boolopt            => "a boolean option which defaults to disabled"
#   boolopt2=1         => "a boolean option which defaults to enabled"
#   stringopt:         => "an option which takes an argument, e.g. --stringopt=value"
#   stringopt2:=value  => "an option where the argument is optional and defaults to 'value'"
#   optalias booltopt3 => "a boolean with a hidden alias. --optalias is not shown in --help"
#
# Autosetup does no small amount of specialized handling for flags,
# especially booleans. Each bool-type --FLAG implicitly gets
# --enable-FLAG and --disable-FLAG forms, and explicitly adding flags
# with those prefixes will force them to be boolean flags. e.g. we
# define a flag "readline", which will be interpreted in one of two
# ways, depending on how it's invoked and how its default is defined:
#
#   --enable-readline ==> boolean true
#   --disable-readline ==> boolean false
#
# Trying to pass --readline or --readline=1 or --readline=0 will
# result in an "unrecognized option" error, despite the the options
# call listing the flag as "readline".
#
# The behavior described above can lead lead to some confusion when
# writing help text. For example:
#
#   options { json=1 {Disable JSON functions} }
#
# The reason the help text says "disable" is because a boolean option
# defaulting to true is, in the --help text, rendered as:
#
#   --disable-json          Disable JSON functions
#
# Whereas a bool flag which defaults to false will instead render as:
#
#   --enable-FLAG
#
# Non-boolean flags, in contrast, use the names specifically given to
# them in the 'options' invocation. e.g. "with-tcl" is the --with-tcl
# flag. Autosetup may, however, choose to automatically alter the help
# text, as demonstrated here:
#
#   options {
#    readline=1           => {Disable readline support}
#    with-readline-lib:   => {Readline library}
#    with-readline-inc:   => {Readline include paths}
#   }
#
#   $ ./configure --help | grep readline
#   --disable-readline      disable readline support
#   --with-readline-lib     specify readline library
#   --with-readline-inc     specify readline include paths
#
# Note that it prefixed and lower-case the help message. Whether
# that's a feature or a bug can be debated.
#
# Fetching values for flags:
#
#   booleans: use one of:
#     - [opt-bool FLAG] is autosetup's built-in command for this, but we
#       have some convenience variants:
#     - [hwaci-opt-truthy FLAG]
#     - [hwaci-opt-if-truthy FLAG {THEN} {ELSE}]
#
#   Non-boolean (i.e. string) flags:
#     - [opt-val FLAG]
#
########################################################################
options [subst {
  with-debug:=1        => {Enable debug build flags}
  with-tclsh:PATH      => {Full pathname of tclsh to use}
  with-tcl:DIR         => {Directory containing tclConfig.sh}
  tcl=1                => {Disable components which require TCL-dev}
  test-status          => {Enable status of tests}
  threadsafe=1         => {Disable mutexing}
  with-tempstore:=no   => {Use an in-ram database for temporary tables: never,no,yes,always}
  editline=0           => {BSD editline support}
  readline=1           => {Disable readline support}
  largefile=1          => {Disable large file support}
  with-readline-lib:   => {Readline library}
  with-readline-inc:   => {Readline include paths}
  with-linenoise:DIR   => {}
  amalgamation=1       => {Disable the amalgamation and instead build all files separately}
  load-extension=1     => {Disable loading of external extensions}
  math=1               => {Disable math functions}
  json=1               => {Disable JSON functions}
  all                  => {Enable FTS4, FTS5, Geopoly, RTree, Sessions}
  memsys5              => {Enable MEMSYS5}
  memsys3              => {Enable MEMSYS3}
  fts3                 => {Enable the FTS3 extension}
  fts4                 => {Enable the FTS4 extension}
  fts5                 => {Enable the FTS5 extension}
  update-limit         => {Enable the UPDATE/DELETE LIMIT clause}
  geopoly              => {Enable the GEOPOLY extension}
  rtree                => {Enable the RTREE extension}
  session              => {Enable the SESSION extension}
  gcov=0               => {Enable coverage testing using gcov}
  linemacros           => {Enable #line macros in the amalgamation.}
  with-wasi-sdk:=/opt/wasi-sdk
                       => {Top-most dir of the wasi-sdk for a WASI build}
  with-emsdk:DIR       => {Top-most dir of the Emscripten SDK installation}
  defines-json-include-lowercase=0
                       => {Include lower-case defines (primarily system paths) in $DUMP_DEFINES_JSON}
  dump-defines=0       => {Dump autosetup defines to $DUMP_DEFINES_TXT (for build debugging)}
}]

########################################################################
# Notes about certain historical flags:
#
#  --releasemode: libtool-specific (which we don't have now)
#
#
set srcdir $::autosetup(srcdir)
set top_srcdir [get-define abs_top_srcdir]
msg-result "srcdir = $srcdir"
msg-result "top_srcdir = $top_srcdir"
set PACKAGE_VERSION [readfile $::autosetup(srcdir)/VERSION]
msg-result "VERSION = $PACKAGE_VERSION"

define PACKAGE_NAME "sqlite"
define PACKAGE_URL {https://sqlite.org}
define PACKAGE_VERSION $PACKAGE_VERSION
define PACKAGE_STRING "[get-define PACKAGE_NAME] $PACKAGE_VERSION"
define PACKAGE_BUGREPORT [get-define PACKAGE_URL]/forum

define-append SQLITE_AUTOREMAKE cd $::autosetup(srcdir) && $top_srcdir/configure {*}$::autosetup(argv)

set outOfTreeBuild 0
if {![file exists sqlite3.pc.in]} {
  msg-result "This appears to be an out-of-tree build."
  set outOfTreeBuild 1
}

cc-check-tools ld ar

########################################################################
# The build process allows for using a cross-compiler.  But the default
# action is to target the same platform that we are running on.  The
# configure script needs to discover the following properties of the
# build and target systems:
#
#    srcdir
#
#        The is the name of the directory that contains the
#        "configure" shell script.  All source files are
#        located relative to this directory.
#
#    bindir
#
#        The name of the directory where executables should be
#        written by the "install" target of the makefile.
#
#    program_prefix
#
#        Add this prefix to the names of all executables that run
#        on the target machine.  Default: ""
#
#    ENABLE_SHARED
#
#        True if shared libraries should be generated.
#
#    BUILD_CC
#
#        The name of a command that is used to convert C
#        source files into executables that run on the build
#        platform.
#
#    BUILD_CFLAGS
#
#        Switches that the build compiler needs in order to construct
#        command-line programs.
#
#    BUILD_LIBS
#
#        Libraries that the build compiler needs in order to construct
#        command-line programs.
#
#    TCL_*
#
#        Lots of values are read in from the tclConfig.sh script,
#        if that script is available.  This values are used for
#        constructing and installing the TCL extension.
#
#    TARGET_READLINE_LIBS
#
#        This is the library directives passed to the target linker
#        that cause the executable to link against the readline library.
#        This might be a switch like "-lreadline" or pathnames of library
#        file like "../../src/libreadline.a".
#
#    TARGET_READLINE_INC
#
#        This variables define the directory that contain header
#        files for the readline library.  If the compiler is able
#        to find <readline.h> on its own, then this can be blank.

#
# OPT_FEATURE_FLAGS = -DSQLITE_OMIT/ENABLE flags.
define OPT_FEATURE_FLAGS {}
define OPT_SHELL {}; # CFLAGS for the sqlite3 CLI app

# Adds $args, if not empty, to OPT_FEATURE_FLAGS.
proc add-feature-flag {args} {
  if {"" ne $args} {
    define-append OPT_FEATURE_FLAGS {*}$args
  }
}
# add-feature-flag -DSQLITE_JUST_TESTING=3

# Adds $args, if not empty, to OPT_SHELL.
proc add-shell-opt {args} {
  if {"" ne $args} {
    define-append OPT_SHELL {*}$args
  }
}

hwaci-file-extensions
if {".exe" eq [get-define TARGET_EXEEXT]} {
  define SQLITE_OS_UNIX 0
  define SQLITE_OS_WIN 1
  # todo? add -DSQLITE_OS_WIN=1 to CFLAGS?
} else {
  define SQLITE_OS_UNIX 1
  define SQLITE_OS_WIN 0
  # todo? add -DSQLITE_OS_UNIX=1 to CFLAGS?
}

#########
# Programs needed
if {"" eq [hwaci-bin-define install]} {
  hwaci-warn "Cannot find install binary, so 'make install' will not work."
  # Reminder: we historically have ./install-sh in the source tree.
  # Can we not simply use that?
  #
  # define BIN_INSTALL "$top_srcdir/install-sh"
  #
  # Nope: it MOVES its source files over the target, which breaks the
  # installation in some cases, e.g. when libtclsqlite3.so is built in
  # response to 'make install' and libsqlite3.a is moved before
  # libtclsqlite3.so is linked.  It's easy to hack to use cp instead
  # of mv (simply replace the instcmd=... bit) but that won't retain
  # the source timestamp and permissions unless we use cp's -p flag,
  # which may not be portable enough.
}

########################################################################
# We differentiate between two C compilers: the one used for binaries
# which are to run on the build system (BUILD_CC, a.k.a. BCC) and the
# one used for compiling binaries for the target system (CC,
# a.k.a. TCC).  Normally they're the same, but they will differ when
# cross-compiling.
define BUILD_CC [get-define CC_FOR_BUILD]
define BUILD_CFLAGS [get-env BUILD_CFLAGS {-g}]
define ENABLE_SHARED 1
define HAVE_TCL 0

########################################################################
# Handle --with-wasi-sdk=DIR
#
# This must be early because it may change the toolchain and disable
# several config options.
proc hwaci-check-wasi-sdk {} {
  set wasiSdkDir [opt-val with-wasi-sdk] ; # ??? [lindex [opt-val with-wasi-sdk] end]
  define HAVE_WASI_SDK 0
  #puts "x wasiSdkDir=$wasiSdkDir foo=[lindex [opt-val with-wasi-sdk] end]"
  if {$wasiSdkDir eq ""} {
    return 0
  } elseif {$::cross_compiling} {
    hwaci-fatal "Cannot combine --with-wasi-sdk with cross-compilation"
  }
  msg-result "Checking WASI SDK directory \[$wasiSdkDir]... "
  #puts "prefix = [prefix $wasiSdkDir/bin {clang ld}]"
  hwaci-affirm-files-exist -v {*}[prefix "$wasiSdkDir/bin/" {clang wasm-ld}]
  msg-result "Using wasi-sdk clang, disabling: tcl, CLI shell, DLL, loadable extensions, threading"
  define HAVE_WASI_SDK 1
  define WASI_SDK_DIR $wasiSdkDir
  hwaci-opt-set load-extension 0; # ==> --disable-load-extension
  hwaci-opt-set threadsafe 0; # ==> --threadsafe=0
  hwaci-opt-set tcl 0; # ==> --disable-tcl
  define HAVE_TCL 0
  set cross_compiling 1
  define ENABLE_SHARED 0

  # Changing --host and --target have no effect here except to possibly
  # cause confusion. autoconf has finished processing them by this
  # point.
  #
  #  host_alias=wasm32-wasi
  #  target=wasm32-wasi
  #
  # Merely changing CC and LD to the wasi-sdk's is enough to get
  # sqlite3.o building in WASM format.
  # XXX CC="${wasiSdkDir}/bin/clang"
  # XXX LD="${wasiSdkDir}/bin/wasm-ld"
  # XXX RANLIB="${wasiSdkDir}/bin/llvm-ranlib"
  define CC "${wasiSdkDir}/bin/clang"
  define LD "${wasiSdkDir}/bin/wasm-ld"
  #define STRIP "${wasiSdkDir}/bin/strip"
  return 1
}; # hwaci-check-wasi-sdk
hwaci-check-wasi-sdk

#
# Enable large file support (if special flags are necessary)
cc-check-lfs

#
# Check for needed/wanted data types
cc-with {-includes stdint.h} \
  {cc-check-types int8_t int16_t int32_t int64_t intptr_t \
     uint8_t uint16_t uint32_t uint64_t uintptr_t}

#
# Check for needed/wanted functions
cc-check-functions gmtime_r isnan localtime_r localtime_s \
  malloc_usable_size strchrnul usleep utime pread pread64 pwrite pwrite64

hwaci-check-function-in-lib fdatasync rt
define LDFLAGS_FDATASYNC [get-define lib_fdatasync]
undefine lib_fdatasync

#
# Check for needed/wanted headers
cc-check-includes \
  sys/types.h sys/stat.h dlfcn.h unistd.h \
  stdlib.h malloc.h memory.h \
  string.h strings.h \
  inttypes.h

# These are optional for JimTCL but necessary if we want to use it for
# code generation:
cc-check-includes dirent.h sys/time.h

if {[cc-check-includes zlib.h] && [hwaci-check-function-in-lib deflate z]} {
  # TODO: port over the more sophisticated zlib search from the fossil auto.def
  define HAVE_ZLIB 1; # "-DSQLITE_HAVE_ZLIB=1"
  define LDFLAGS_ZLIB -lz
  # Note that -DSQLITE_HAVE_ZLIB=1 is handled separately from the
  # other feature flags in the autotools build. Do we need to emulate
  # that?
  add-shell-opt -DSQLITE_HAVE_ZLIB=1
} else {
  define HAVE_ZLIB 0
  define LDFLAGS_ZLIB ""
}

hwaci-define-if-opt-truthy amalgamation USE_AMALGAMATION \
  "Use amalgamation for builds?"

hwaci-define-if-opt-truthy gcov USE_GCOV "Use gcov?"

hwaci-define-if-opt-truthy test-status TSTRNNR_OPTS \
  "test-runner flags:" {--status} {}

hwaci-define-if-opt-truthy linemacros AMALGAMATION_LINE_MACROS \
  "Use #line macros in the amalgamation:"

msg-checking "Debug build? "

hwaci-if-opt-truthy with-debug {
  define SQLITE_DEBUG 1
  define TARGET_DEBUG {-g -DSQLITE_DEBUG=1 -DSQLITE_ENABLE_SELECTTRACE -DSQLITE_ENABLE_WHERETRACE -O0 -Wall}
  msg-result yes
} {
  define TARGET_DEBUG {-DNDEBUG}
  msg-result no
}

########################################################################
# TCL...
#
# hwaci-check-tcl performs most of the --with-tcl and --with-tclsh
# handling. Some related bits and pieces are performed before and
# after that function is called.
#
# Important [define]'d vars:
#
#  - HAVE_TCL indicates whether we have a tclsh suitable for building
#    the TCL SQLite extension and, by extension, the testing
#    infrastructure. This must only be 1 for environments where
#    tclConfig.sh can be found.
#
#  - TCLSH_CMD is the path to the canonical tclsh or "". It never
#    refers to jimtcl.
#
#  - TCL_CONFIG_SH is the path to tclConfig.sh or "".
#
#  - TCLLIBDIR is the dir to which libtclsqlite3 gets installed.
#
#  - TCLLIB_RPATH = the -rpath flag specific to libtclsqlite3, which
#    will usually differ from the rpath used by the rest of the lib.
#
#  - BTCLSH = the path to the tcl interpreter used for in-tree code
#    generation.  It may be jimtcl or the canonical tclsh but may not
#    be empty - this tree requires TCL to generated numerous
#    components.
#
define TCLSH_CMD {exit 1}
proc hwaci-check-tcl {} {
  # TODO: document the steps this is taking.
  global top_srcdir
  puts "Checking for a suitable tcl... "
  set optTcl [hwaci-opt-truthy tcl]
  set use_tcl $optTcl
  set with_tclsh [opt-val with-tclsh]
  set with_tcl [opt-val with-tcl]
  #puts "hwaci-check-tcl: use_tcl ${use_tcl}"
  #puts "hwaci-check-tcl: with_tclsh=${with_tclsh}"
  #puts "hwaci-check-tcl: with_tcl=$with_tcl"
  if {"" eq $with_tclsh && "" eq $with_tcl} {
    set with_tclsh [hwaci-first-bin-of tclsh9.0 tclsh8.6 tclsh]
  }
  #puts "hwaci-check-tcl: with_tclsh=${with_tclsh}"

  if {"" ne $with_tclsh} {
    if {![file isfile $with_tclsh]} {
      hwaci-fatal "TCL shell $with_tclsh is not a file"
    } elseif {![file-isexec $with_tclsh]} {
      hwaci-fatal "TCL shell $with_tclsh is not executable"
    } else {
      define TCLSH_CMD $with_tclsh
      msg-result "Using tclsh: $with_tclsh"
    }
    if {$use_tcl} {
      if {[catch {exec $with_tclsh $top_srcdir/tool/find_tclconfig.tcl} result] == 0} {
        set with_tcl $result
      }
      if {"" ne $with_tcl && [file isdir $with_tcl]} {
        msg-result "$with_tclsh recommends the tclConfig.sh from $with_tcl"
      } else {
        hwaci-warn "$with_tclsh is unable to recommand a tclConfig.sh"
        set use_tcl 0
      }
    }
  }

  set cfg ""
  set tclSubdirs {tcl9.0 tcl8.6 lib}
  while {1} {
    if {$use_tcl} {
      if {"" ne $with_tcl} {
        if {[file readable "${with_tcl}/tclConfig.sh"]} {
          set cfg "${with_tcl}/tclConfig.sh"
        } else {
          foreach i $tclSubdirs {
            if {[file readable "${with_tcl}/$i/tclConfig.sh"]} {
              set cfg "${with_tcl}/$i/tclConfig.sh"
              break
            }
          }
        }
        if {"" eq $cfg} {
          hwaci-fatal "No tclConfig.sh found under ${with_tcl}"
        }
      } else {
        # If we have not yet found a tclConfig.sh file, look in $libdir which is
        # set automatically by autosetup or by the --prefix command-line option.
        # See https://sqlite.org/forum/forumpost/e04e693439a22457
        set libdir [get-define libdir]
        if {[file readable "${libdir}/tclConfig.sh"]} {
          set cfg "${libdir}/tclConfig.sh"
        } else {
          foreach i $tclSubdirs {
            if {[file readable "${libdir}/$i/tclConfig.sh"]} {
              set cfg "${libdir}/$i/tclConfig.sh"
              break
            }
          }
        }
        if {![file readable $cfg]} {
          hwaci-warn {
            Cannot find a usable tclConfig.sh file.
            Use --with-tcl=DIR to specify a directory where tclConfig.sh can be found.
            SQLite does not use TCL internally, but TCL is required to build SQLite
            from canonical sources and TCL is required for testing.
          }
          break
        }
      }
      msg-result "Using tclConfig.sh: $cfg"
    } elseif {!$optTcl} {
      hwaci-warn "Unable to run tests because of --disable-tcl"
    } else {
      hwaci-warn "Unable to run tests because no tclConfig.sh file could be located"
    }
    break
  }

  define TCL_CONFIG_SH $cfg
  # The historical configure.ac sources tclConfig.sh so that it can
  # use the several TCL_... env vars. We obviously cannot do that from
  # TCL, so we apply a level of indirection which sources that script
  # then emits the pieces we're interested in as TCL code. If the
  # config is not available, this emits empty-string entries for the
  # various options we're interested in.
  eval [exec "${top_srcdir}/tool/tclConfigShToTcl.sh" "[get-define TCL_CONFIG_SH]"]
  #puts "hwaci-check-tcl: with_tclsh=$with_tclsh"
  #puts "hwaci-check-tcl: with_tcl=$with_tcl"
  #puts "hwaci-check-tcl: cfg=$cfg"
  #puts "hwaci-check-tcl: use_tcl ${use_tcl}"

  if {"" eq $with_tclsh} {
    set with_tclsh [get-define TCL_EXEC_PREFIX]/bin/tclsh[get-define TCL_VERSION]
    if {![file-isexec $with_tclsh]} {
      set with_tclsh2 [get-define TCL_EXEC_PREFIX]/bin/tclsh
      if {![file-isexec $with_tclsh2]} {
        hwaci-warn "Cannot find a usable tclsh (tried: $with_tclsh $with_tclsh2)"
      } else {
        set with_tclsh $with_tclsh2
      }
    }
  }
  define TCLSH_CMD $with_tclsh

  if {$use_tcl} {
    # Set up the TCLLIBDIR and TCLLIB_RPATH
    set tcllibdir [get-env TCLLIBDIR ""]
    if {"" eq $tcllibdir} {
      if {[catch {exec echo "puts stdout \$auto_path" | "$with_tclsh"} result] == 0} {
        foreach i $result {
          if {[file isdir $i]} {
            set tcllibdir $i
            break
          }
        }
      } else {
        hwaci-warn "Cannot determine TCLLIBDIR"
      }
    }
    set tclrpath ""
    if {"" ne $tcllibdir} {
      set tcllibdir "${tcllibdir}/sqlite3"
      set rp [get-define SH_LINKRPATH]
      set tclrpath [string map [list "%s" $tcllibdir] $rp]
      # Reminder: tclConfig.sh has TCL_LD_SEARCH_FLAGS to set the
      # rpath but (A) it includes an unexpand var ref to
      # ${LIB_RUNTIME_DIR}, which must be set in the makefile and (B)
      # that flag is inherently compiler-dependent so it's not as
      # portable as tclConfig.sh assumes. We'll instead use the rpath
      # flag which autosetup determines for the current compiler.
    }
    define TCLLIBDIR $tcllibdir
    define TCLLIB_RPATH $tclrpath
    #hwaci-fatal "TCLLIB_RPATH = [get-define TCLLIB_RPATH]"
  } else {
    define TCLLIBDIR ""
    define TCLLIB_RPATH ""
  }

  if {[file exists $with_tclsh]} {
    msg-result "Using tclsh: $with_tclsh"
    define HAVE_TCL 1
  } else {
    hwaci-warn "Cannot find a usable tclsh, so cannot run tests."
  }
}; # hwaci-check-tcl

hwaci-check-tcl

########################################################################
# Check which TCL to use as a code generator. Prefer jimsh simply
# because we have it in-tree (it's part of autosetup).
#
# Building jimsh0.c with -DJIM_COMPAT changes certain behavior to be
# compatible with canonical TCL. Specifically: jim's [expr] only
# accepts one arg unless JIM_COMPAT is defined. As of 2024-10-23,
# jimsh0.c defines JIM_COMPAT automatically (prior to that it intended
# to but a typo of JIM_TCL_COMPAT made it a no-op).
define CFLAGS_JIMSH {}
msg-result "Which TCL to use for code generation... "
set cgtcl [opt-val with-tclsh jimsh]
if {"jimsh" ne $cgtcl} {
  # When --with-tclsh=X is used, use that for all TCL purposes,
  # including in-tree code generation, per developer request.
  define BTCLSH "\$(TCLSH_CMD)"
} else {
  if {[cc-check-functions realpath]} {
    define-append CFLAGS_JIMSH -DHAVE_REALPATH
    define BTCLSH "\$(JIMSH)"
  } elseif {[cc-check-functions _fullpath]} {
    # _fullpath() is a Windows API
    define-append CFLAGS_JIMSH -DHAVE__FULLPATH
    define BTCLSH "\$(JIMSH)"
  } elseif {[file exists [get-define TCLSH_CMD]]} {
    set cgtcl [get-define TCLSH_CMD]
    define BTCLSH "\$(TCLSH_CMD)"
  } else {
    # One last-ditch effort to find TCLSH_CMD: use info from
    # tclConfig.sh to try to find a tclsh
    if {"" eq [get-define TCLSH_CMD]} {
      set tpre [get-define TCL_EXEC_PREFIX]
      if {"" ne $tpre} {
        set tv [get-define TCL_VERSION]
        if {[file-isexec "${tpre}/bin/tclsh${tv}"]} {
          define TCLSH_CMD "${tpre}/bin/tclsh${tv}"
        } elseif {[file-isexec "${tpre}/bin/tclsh"]} {
          define TCLSH_CMD "${tpre}/bin/tclsh"
        }
        unset tv
      }
      unset tpre
    }
    set cgtcl [get-define TCLSH_CMD]
    if {![file exists $cgtcl]} {
      hwaci-fatal "Cannot find a tclsh to use for code generation."
    }
    define BTCLSH "\$(TCLSH_CMD)"
  }
}
msg-result "TCL for code generation: $cgtcl"
unset cgtcl
#define CFLAGS_JIMSH {-DJUST_TESTING}

# /TCL
########################################################################

########################################################################
# Thread safety?
msg-checking "Support threadsafe operation? "
hwaci-if-opt-truthy threadsafe {
  msg-result yes
  add-feature-flag -DSQLITE_THREADSAFE=1
  if {![hwaci-check-function-in-lib pthread_create pthread]
      || ![hwaci-check-function-in-lib pthread_mutexattr_init pthread]} {
    user-error "Missing required pthread bits"
  }
  define LDFLAGS_PTHREAD [get-define lib_pthread_create]
  undefine lib_pthread_create
} {
  msg-result no
  add-feature-flag -DSQLITE_THREADSAFE=0
  define LDFLAGS_PTHREAD ""
}

########################################################################
# Do we want temporary databases in memory?
#
if {1} {
  set ts [opt-val with-tempstore no]
  set tsn 1
  msg-checking "Use an in-ram database for temporary tables? "
  switch -- $ts {
    never  { set tsn 0 }
    no     { set tsn 1 }
    yes    { set tsn 2 }
    always { set tsn 3 }
    default {
      user-error "Invalid with-tempstore value \[$ts]. Use one of: never, no, yes, always"
    }
  }
  msg-result $ts
  define TEMP_STORE $tsn
  unset ts tsn
}

if {1} {
  ##########
  # Figure out what C libraries are required to compile programs
  # that use "readline()" library.
  add-shell-opt -DHAVE_READLINE=[hwaci-check-readline]
} else {
  # Older impl solely for reference while porting...
  #
  # XXX TARGET_READLINE_LIBS=""
  # XXX TARGET_READLINE_INC=""
  # XXX TARGET_HAVE_READLINE=0
  # XXX TARGET_HAVE_EDITLINE=0
  if {[opt-bool editline]} {
    set with_editline $enableval
  } else {
    set with_editline auto
  }
  if {![opt-bool readline]} {
    set with_readline $enableval
  } else {
    set with_readline auto
  }

  # XXX if test x"$with_editline" != xno; then
  # XXX sLIBS=$LIBS
  # XXX LIBS=""
  # XXX TARGET_HAVE_EDITLINE=1
  if {[hwaci-check-function-in-lib readline edit]} {
    set with_readline no
  } else {
    # XXX TARGET_HAVE_EDITLINE=0
  }
  # XXX TARGET_READLINE_LIBS=$LIBS
  # XXX LIBS=$sLIBS
  # XXX fi
  # XXX if test x"$with_readline" != xno; then
  set found "yes"

  if {[opt-val with-readline-lib] ne {}} {
    set withval [lindex [opt-val with-readline-lib] end]
    set with_readline_lib $withval
  } else {
    set with_readline_lib "auto"
  }
  # XXX if test "x$with_readline_lib" = xauto; then
  # XXX save_LIBS="$LIBS"
  # XXX LIBS=""
  if {[hwaci-check-function-in-lib tgetent readline ncurses curses termcap]} {
    # XXX term_LIBS="$LIBS"
  } else {
    # XXX term_LIBS=""
  }
  if {[hwaci-check-function-in-lib readline readline]} {
    # XXX TARGET_READLINE_LIBS="-lreadline"
  } else {
    set found "no"
  }
  # XXX TARGET_READLINE_LIBS="$TARGET_READLINE_LIBS $term_LIBS"
  # XXX LIBS="$save_LIBS"
  # XXX else
  # XXX TARGET_READLINE_LIBS="$with_readline_lib"
  # XXX fi

  if {[opt-val with-readline-inc] ne {}} {
    set withval [lindex [opt-val with-readline-inc] end]
    set with_readline_inc $withval
  } else {
    set with_readline_inc "auto"
  }
  # XXX if test "x$with_readline_inc" = xauto; then
  if {[cc-check-includes readline.h]} {
    set found "yes"
  } else {
    set found "no"
    # XXX if test "$cross_compiling" != yes; then
    # XXX for dir in /usr /usr/local /usr/local/readline /usr/contrib /mingw; do
    # XXX for subdir in include include/readline; do
    # XXX AC_CHECK_FILE $dir/$subdir/readline.h found=yes
    # XXX if test "$found" = "yes"; then
    # XXX TARGET_READLINE_INC="-I$dir/$subdir"
    # XXX break
    # XXX fi
    # XXX done
    # XXX test "$found" = "yes" && break
    # XXX done
    # XXX fi
  }
  # XXX else
  # XXX TARGET_READLINE_INC="$with_readline_inc"
  # XXX fi

  # XXX if test x"$found" = xno; then
  # XXX TARGET_READLINE_LIBS=""
  # XXX TARGET_READLINE_INC=""
  # XXX TARGET_HAVE_READLINE=0
  # XXX else
  # XXX TARGET_HAVE_READLINE=1
  # XXX fi
  # XXX fi
  if {[opt-val with-linenoise] ne {}} {
    set withval [lindex [opt-val with-linenoise] end]
    set with_linenoise $withval
  } else {
    set with_linenoise "no"
  }
  # XXX if test "x$with_linenoise" != "xno"; then
  # XXX TARGET_HAVE_READLINE=0
  # XXX TARGET_HAVE_EDITLINE=0
  # XXX TARGET_HAVE_LINENOISE=1
  # XXX TARGET_READLINE_INC="-I${with_linenoise}"
  # XXX TARGET_READLINE_LIBS="${with_linenoise}/linenoise.c"
  # XXX echo "using linenoise source code at ${with_linenoise}"
  # XXX else
  # XXX TARGET_HAVE_LINENOISE=0
  # XXX echo "not using linenoise"
  # XXX fi

  # XXX AC_SUBST TARGET_READLINE_LIBS
  # XXX AC_SUBST TARGET_READLINE_INC
  # XXX AC_SUBST TARGET_HAVE_READLINE
  # XXX AC_SUBST TARGET_HAVE_EDITLINE
  # XXX AC_SUBST TARGET_HAVE_LINENOISE
}

hwaci-if-opt-truthy load-extension {
  if {[hwaci-check-function-in-lib dlopen dl]} {
    define LDFLAGS_DLOPEN [get-define lib_dlopen]
    undefine lib_dlopen
  } else {
    user-error "dlopen() not found. Use --disable-load-extension to bypass this check."
  }
} {
  define LDFLAGS_DLOPEN ""
  add-feature-flag {-DSQLITE_OMIT_LOAD_EXTENSION=1}
  msg-result "Disabling loadable extensions."
}

hwaci-if-opt-truthy math {
  if {![hwaci-check-function-in-lib ceil m]} {
    user-error "Cannot find libm functions. Use --disable-math to bypass this."
  }
  define LDFLAGS_MATH [get-define lib_ceil]
  undefine lib_ceil
  add-feature-flag {-DSQLITE_ENABLE_MATH_FUNCTIONS}
  msg-result "Enabling math SQL functions [get-define LDFLAGS_MATH]"
} {
  define LDFLAGS_MATH ""
  msg-result "Disabling math SQL functions"
}

define cross_compiling ${cross_compiling}

########################################################################
# Emscripten SDK for building the web-based wasm components.
#
set emccsh $srcdir/tool/emcc.sh
if {![get-define HAVE_WASI_SDK] && [hwaci-check-emsdk]} {
  define EMCC_WRAPPER $emccsh
  hwaci-make-from-dot-in $emccsh
  catch {exec chmod u+x $emccsh}
} else {
  define EMCC_WRAPPER ""
  file delete -force $emccsh
}
unset emccsh

########################################################################
# Check for log(3) in libm and die with an error if it is not
# found. $why should be the feature name which requires that function
# (it's used only in error messages). defines LDFLAGS_MATH to the
# required linker flags (which may be empty even if the math APIs are
# found, depending on the OS).
proc affirm-have-math {why} {
  if {![hwaci-check-function-in-lib log m]} {
    user-error "Missing math APIs for $why"
  }
  define LDFLAGS_MATH [get-define lib_log ""]
  undefine lib_log
}

########################################################################
# Handle various SQLITE_ENABLE_... feature flags.
foreach {boolFlag featureFlag ifSetEvalThis} {
  all         {} {
    hwaci-opt-set fts4
    hwaci-opt-set fts5
    hwaci-opt-set geopoly
    hwaci-opt-set rtree
    hwaci-opt-set session
  }
  fts4         -DSQLITE_ENABLE_FTS4    {affirm-have-math fts4}
  fts5         -DSQLITE_ENABLE_FTS5    {affirm-have-math fts5}
  geopoly      -DSQLITE_ENABLE_GEOPOLY {hwaci-opt-set rtree}
  rtree        -DSQLITE_ENABLE_RTREE   {}
  session      {-DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK} {}
  update-limit -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT {}
  memsys3      {} {
    if {[opt-bool memsys5]} {
      msg-result "NOT enabling memsys3 because memsys5 is enabled."
    } else {
      add-feature-flag -DSQLITE_ENABLE_MEMSYS3
    }
  }
  memsys5      -DSQLITE_ENABLE_MEMSYS5 {}
} {
  hwaci-if-opt-truthy $boolFlag {
    add-feature-flag $featureFlag
    eval $ifSetEvalThis
    if {"all" ne $boolFlag} {
      msg-result "Enabling $boolFlag"
    }
  } {
    msg-result "Not enabling $boolFlag"
  }
}

########################################################################
# Invert the above loop's logic for some explicit SQLITE_OMIT_...
# cases. If config option $boolFlag is set, [add-feature-flag
# $featureFlag], where $featureFlag is intended to be
# -DSQLITE_OMIT_...
foreach {boolFlag featureFlag} {
  json        -DSQLITE_OMIT_JSON
} {
  if {[hwaci-opt-truthy $boolFlag]} {
    msg-result "Enabling $boolFlag"
  } else {
    add-feature-flag $featureFlag
    msg-result "Disabling $boolFlag"
  }
}

########################################################################
# Maybe extend JimTCL a bit. As of this writing (2024-09-27) it only
# needs (-DHAVE_REALPATH or -DHAVE__FULLPATH) and -DHAVE_DIRENT_H to
# be compatible with our code generators. It can, however, be slightly
# extended via easy-to-detect features, should we need them.
if {0 && "" ne [get-define CFLAGS_JIMSH]} {
  foreach jimFunc {opendir fsync isascii} {
    if {[cc-check-functions $jimFunc]} {
      define-append CFLAGS_JIMSH -DHAVE_[string toupper $jimFunc]
    }
  }
  #These are hard-coded into jimsh0.c, so we must not -D them:
  #foreach jimDef {HAVE_UNISTD_H HAVE_DIRENT_H} {
  #  if {[is-defined $jimDef]} {
  #    define-append CFLAGS_JIMSH -D$jimDef
  #  }
  #}
  define-append CFLAGS_JIMSH -DHAVE_LONG_LONG; # SQLite relies on long long, so we know it's available
}; # JimTCL

########################################################################
# Determine proper rpath-handling flags
hwaci-check-rpath

########################################################################
# Generate the output files.
#
hwaci-make-from-dot-in -touch Makefile sqlite3.pc
if {0} {
  # Requires a hand-written sqlite_cfg.h.in...
  hwaci-make-from-dot-in sqlite_cfg.h
  # vs...
} else {
  # Requires no input template...
  make-config-header sqlite_cfg.h \
    -bare {SIZEOF_* HAVE_DECL_*} \
    -none {HAVE_CFLAG_* LDFLAGS_* SH_* SQLITE_AUTOREMAKE
      TARGET_* USE_GCOV TCL_*} \
    -auto {HAVE_* PACKAGE_*} \
    -none *
  hwaci-touch sqlite_cfg.h ; # help avoid frequent unnecessary @SQLITE_AUTOREMAKE@
}
#TODO hwaci-make-from-dot-in ext/wasm/GNUmakefile

set oFF [get-define OPT_FEATURE_FLAGS]
if {"" ne $oFF} {
  define OPT_FEATURE_FLAGS [lsort -unique $oFF]
  msg-result "Library feature flags: [get-define OPT_FEATURE_FLAGS]"
}
set oFF [get-define OPT_SHELL]
if {"" ne $oFF} {
  define OPT_SHELL [lsort -unique $oFF]
  msg-result "Shell options: [get-define OPT_SHELL]"
}
unset oFF

########################################################################
# Dump config-defines.json...
# Demonstrate (mis?)handling of spaces in JSON-export array values:
# define-append OPT_FOO.list {"-DFOO=bar baz" -DBAR="baz barre"}
define OPT_FEATURE_FLAGS.list [get-define OPT_FEATURE_FLAGS]
define OPT_SHELL.list [get-define OPT_SHELL]
set dumpDefsOpt {
  -bare {SIZEOF_* HAVE_DECL_*}
  -none {HAVE_CFLAG_* LDFLAGS_* SH_* SQLITE_AUTOREMAKE TARGET_* USE_GCOV TCL_*}
  -array {*.list}
  -auto {OPT_* PACKAGE_* HAVE_*}
}
if {[opt-bool defines-json-include-lowercase]} {
  lappend dumpDefsOpt -none {lib_*} ; # remnants from hwaci-check-function-in-lib and friends
  lappend dumpDefsOpt -auto {[a-z]*}
}
lappend dumpDefsOpt -none *
hwaci-dump-defs-json $DUMP_DEFINES_JSON {*}$dumpDefsOpt
undefine OPT_FEATURE_FLAGS.list
undefine OPT_SHELL.list

########################################################################
# Some build-dev/debug-only output
hwaci-if-opt-truthy dump-defines {
  global DUMP_DEFINES_TXT
  msg-result "--dump-defines is creating $DUMP_DEFINES_TXT"
  make-config-header $DUMP_DEFINES_TXT \
    -bare {SQLITE_OS* SQLITE_DEBUG USE_*} \
    -str {BIN_* CC LD AR LDFLAG* OPT_*} \
    -auto {*}
  # achtung: ^^^^ whichever SQLITE_OS_foo flag which is set to 0 will
  # get _undefined_ here unless it's part of the -bare set.
  if {0} {
    foreach x [all-defines] {
      puts "\t$x"
    }
  }
}

msg-result "Done! Now run make."
