# vim:se syn=tcl:
#

define JIM_VERSION 82

options-defaults {
    silent-rules 1
}

# Note: modules which support options *must* be included before 'options'
use cc cc-shared cc-db cc-lib pkg-config util
use local

# CFLAGS, CXXFLAGS only come from the user
define CFLAGS [get-env CFLAGS ""]
define CXXFLAGS [get-env CFLAGS ""]

options {
    utf8=1          => "Disable support for utf8-encoded strings"
    lineedit=1      => "Disable line editing"
    references=1    => "Disable support for references"
    math=1          => "Disable math functions"
    ssl=1           => "Disable ssl/tls support in the aio extension"
    ipv6=1          => "Disable ipv6 support in the aio extension"
    maintainer      => {Enable the [debug] command and JimPanic}
    minimal         => "Disable some optional features: ipv6, ssl, math, utf8 and some extensions. Also see --without-ext=default"
	# Note that full is now the default
    full
    allextmod       => "Enable all non-default extensions as modules if prerequisites are found"
    compat          => "Enable some backward compatibility behaviour"
    extinfo         => "Show information about available extensions"
    with-jim-shared shared => "Build a shared library instead of a static library"
    jim-regexp=1    => "Prefer POSIX regex if over the the built-in (Tcl-compatible) regex"
    docs=1          => "Don't build or install the documentation"
    docdir:path     => "Path to install docs (if built)"
    random-hash	    => "Randomise hash tables. more secure but hash table results are not predicable"
    coverage	    => "Build with code coverage support"
    with-jim-ext: {with-ext:"ext1,ext2,..."} => {
        Specify additional Jim extensions to include.
        Use --extinfo to show information about available extensions.
    }
    with-out-jim-ext: {without-ext:"default|ext1,ext2,..."} => {
        Specify Jim extensions to exclude.
        If 'default' is given, the default extensions will not be added.
    }
    with-jim-extmod: {with-mod:"ext1,ext2,..."} => {
        Specify Jim extensions to build as separate modules (either C or Tcl).
        Note that not all extensions can be built as loadable modules.
    }
    # To help out openocd with automake
    install-jim=1
}

# Attributes and help for each supportted extensions
# tcl=Pure Tcl extension
# static=Can't be built as a module
# off=Off unless explicitly enabled or required by an enabled extension
# optional=Off by default, but selected by --full
# cpp=Is a C++ extension
# always=Always include, even if minimal
global extdb

foreach {mod attrs help} {
    aio       { static }            {File and socket (networking) I/O}
    array     {}                    {Tcl-compatible array command}
    binary    { tcl optional }      {Tcl-compatible binary command}
    clock     {}                    {Tcl-compatible clock command}
    ensemble  { optional tcl }      {Ensemble command}
    eventloop { static }            {after, vwait, update}
    exec      { static }            {Tcl-compatible exec command}
    file      {}                    {Tcl-compatible file command}
    glob      { tcl }               {Tcl-compatible glob command}
    history   {}                    {Tcl access to interactive history}
    interp    {}                    {Support for child interpreters}
    json      { optional }          {JSON decoder}
    jsonencode { tcl off }          {JSON encoder}
    load      { static }            {Load binary extensions at runtime with load or package}
    mk        { cpp off }           {Interface to metakit}
    namespace { static }            {Tcl compatible namespace support}
    nshelper  { tcl off }           {}
    oo        { tcl }               {Object Oriented class support}
    pack      {}                    {Low level binary pack and unpack}
    package   { static always }     {Package management with the package command}
    posix     {}                    {Posix APIs including os.fork, os.uptime}
    readdir   {}                    {Read the contents of a directory (used by glob)}
    readline  { off }               {Interface to libreadline}
    redis     { off }               {Client interface to redis database}
    regexp    {}                    {Tcl-compatible regexp, regsub commands}
    rlprompt  { tcl off }           {readline-based REPL}
    sdl       { off }               {SDL graphics interface}
    signal    { static }            {Signal handling}
    sqlite3   { off }               {Interface to sqlite3 database}
    stdlib    { tcl static always } {Built-in commands including lambda, stacktrace and some dict subcommands}
    syslog    {}                    {System logging with syslog}
    tclcompat { tcl static }        {Tcl compatible read, gets, puts, parray, case, ...}
    tclprefix { optional }          {Support for the tcl::prefix command}
    tree      { tcl }               {OO tree structure, similar to tcllib ::struct::tree}
    win32     { off }               {Interface to win32}
    zlib      { optional }          {zlib compression interface}
} {
    dict set extdb attrs $mod $attrs
    dict set extdb help $mod $help
}

if {[opt-bool full]} {
	user-notice "Note that --full is now the default, and need not be specified (see --minimal)"
}

if {[opt-bool extinfo]} {
    use text-formatting
    use help
    use_pager
    nl
    p {
        Jim Tcl is very modular and many extensions can be selectively
        enabled (--with-ext) or disabled (--without-ext).
        Many extensions may be statically compiled into Jim Tcl or built as loadable modules (--with-mod).
        This includes both C extensions and Tcl extensions.
    }

    # collect extension info
    set attrs [dict get $extdb attrs]
    set info {}
    foreach mod [dict keys $attrs] {
        set help [dict get $extdb help $mod]
        if {$help ne ""} {
            if {"off" in [dict get $attrs $mod]} {
                set a off
            } elseif {"optional" in [dict get $attrs $mod]} {
                set a optional
            } else {
                set a default
            }
            dict set info $mod [list $a $help]
        }
    }

    proc showmods {heading info type} {
        p $heading
        foreach mod [dict keys $info] {
            lassign [dict get $info $mod] a help
            if {$a eq $type} {
                puts "[format %10s $mod] - $help"
            }
        }
    }
    showmods "These extensions are enabled by default:" $info default
    nl
    showmods "These are enabled by default, but disabled by --minimal:" $info optional
    nl
    showmods {
        These are disabled unless explicitly enabled or --allextmod is selected and
        the prerequisites are met:
    } $info off
    exit 0
}

# Additional information about certain extensions
# dep=list of extensions which are required for this extension
# check=[expr] expression to evaluate to determine if the extension can be used
# libdep=list of 'define' symbols for dependent libraries
# pkg-config=name1 ?args?, name2* ?args? | name3 ?args?
#   Any set of packages from the alternates is acceptable (e.g. name1 and name2, or name3)
#   If the pkgname has a * appended, it is optional (so name1 without name2 is OK)
#   The optional args are pkg-config specifications (e.g. name1 >= 1.3.4)
dict set extdb info {
    binary   { dep pack }
    exec     { check {(([have-feature vfork] || [have-feature fork]) && [have-feature waitpid]) || [have-feature system]} }
    glob     { dep readdir }
    load     { check {[have-feature dlopen-compat] || [cc-check-function-in-lib dlopen dl]} libdep lib_dlopen }
    mk       { check {[check-metakit]} libdep lib_mk }
    namespace { dep nshelper }
    json     { dep jsonencode extrasrcs jsmn/jsmn.c }
    posix    { check {[have-feature waitpid]} }
    readdir  { check {[have-feature opendir]} }
    readline { pkg-config readline check {[cc-check-function-in-lib readline readline]} libdep lib_readline}
    rlprompt { dep readline }
    tree     { dep oo }
    sdl      { pkg-config {SDL2_gfx, SDL2_ttf* | SDL_gfx} check false }
    signal   { check {[have-feature sigaction]} }
    sqlite3  { pkg-config sqlite3 check {[cc-check-function-in-lib sqlite3_prepare_v2 sqlite3]} libdep lib_sqlite3_prepare_v2 }
    redis    { pkg-config hiredis check {[cc-check-function-in-lib redisConnect hiredis]} libdep lib_redisConnect }
    zlib     { pkg-config zlib check {[cc-check-function-in-lib deflate z]} libdep lib_deflate }
    syslog   { check {[have-feature syslog]} }
    tree     { dep oo }
    win32    { check {[have-feature windows]} }
}


set warnings {}

# Save the user-specified LIBS
# We add detected libs to LDLIBS explicitly
set LIBS [get-define LIBS]

# In case -Werror is passed in CFLAGS, set -Wno-error when doing checks
# to prevent warnings from becoming errors
if {"-Werror" in [get-define CFLAGS] && [cctest -cflags -Wno-error]} {
    cc-with {-cflags -Wno-error}
}

# Very old compilers might not have restrict
cc-check-some-feature restrict {
    cctest -declare {extern void restrict_test(const char * restrict param);}
}
cc-check-types "long long"
cc-check-sizeof int

# Default optimisation
define-append AS_CPPFLAGS -O2

# check, but don't add to -cflags
cc-with {} {
	cc-check-flags -fno-unwind-tables -fno-asynchronous-unwind-tables
}
if {[opt-bool coverage]} {
    if {[cctest -link 1 -cflags --coverage]} {
        # When using coverage, disable ccache and compiler optimisation
        define CCACHE ""
        define-append AS_CFLAGS --coverage -O0
        define-append LDFLAGS --coverage
        define COVERAGE 1
        if {[cc-check-progs gcovr]} {
            define COVERAGE_TOOL gcovr
        } elseif {[cc-check-progs lcov] && [cc-check-progs genhtml]} {
            define COVERAGE_TOOL lcov
        } else {
            define COVERAGE_TOOL gcov
            lappend warnings "Note: Neither lcov nor gcovr is available, falling back to gcov"
        }
    } else {
        lappend warnings "Warning: --coverage specified, but compiler does not support --coverage"
    }
}

cc-check-includes time.h sys/time.h sys/socket.h netinet/in.h arpa/inet.h netdb.h
cc-check-includes util.h pty.h sys/un.h dlfcn.h unistd.h dirent.h crt_externs.h execinfo.h

# Check sizeof time_t so we can warn on non-Y2038 compliance
cc-with {-includes time.h} {
    cc-check-sizeof time_t
}

define LDLIBS ""

# Haiku needs -lnetwork, Solaris needs -lnsl
if {[cc-check-function-in-lib inet_ntop {nsl network}]} {
    # This does nothing if no libs are needed
    cc-with [list -libs [get-define lib_inet_ntop]]
    define-append LDLIBS [get-define lib_inet_ntop]
}
# Solaris needs -lsocket, Windows needs -lwsock32
if {[cc-check-function-in-lib socket socket]} {
    define-append LDLIBS [get-define lib_socket]
}

cc-check-functions ualarm lstat fork system select execvpe
cc-check-functions geteuid mkstemp isatty
cc-check-functions regcomp waitpid sigaction sys_signame sys_siglist isascii
cc-check-functions syslog opendir readlink sleep usleep pipe getaddrinfo utimes
cc-check-functions shutdown socketpair link symlink fsync dup umask
cc-check-functions localtime gmtime strptime
if {![cc-check-functions realpath]} {
	cc-check-functions _fullpath
}

cc-with {-includes math.h} {
	cc-check-decls isinf isnan
}

if {[have-feature fork]} {
	# Only use vfork if not deprecated
	cc-with {-nooutput 1} {
		cc-check-functions vfork
	}
}

if {[cc-check-function-in-lib backtrace execinfo]} {
    define-append LDLIBS [get-define lib_backtrace]
}
if {[cc-check-function-in-lib openpty util]} {
    define-append LDLIBS [get-define lib_openpty]
}

if {[cc-check-functions sysinfo]} {
    cc-with {-includes sys/sysinfo.h} {
        cc-check-members "struct sysinfo.uptime"
    }
}

cc-with {-includes {sys/types.h sys/stat.h}} {
    cc-check-members "struct stat.st_mtimespec"
    cc-check-members "struct stat.st_mtim"
}

cc-with {-includes fcntl.h} {
    cc-check-types "struct flock"
}

cc-check-lfs
cc-check-functions fseeko ftello

define TCL_LIBRARY [get-define libdir]/jim

lassign [split [get-define host] -] host_cpu host_vendor host_os
# Scrub revision from the host_os
regsub -all {[0-9.]} $host_os {} host_os

switch -glob -- $host_os {
    mingw* {
        # We provide our own implementation of dlopen for mingw32
        define-feature dlopen-compat
        define-feature winconsole
        define TCL_PLATFORM_OS $host_os
        define TCL_PLATFORM_PLATFORM windows
        define TCL_PLATFORM_PATH_SEPARATOR {;}
        # Target WinXP or later. Should this be configurable?
        define-append AS_CFLAGS -D_WIN32_WINNT=0x501 -Wno-deprecated-declarations
    }
    default {
        # Note that cygwin is considered a unix platform
        define TCL_PLATFORM_OS $host_os
        define TCL_PLATFORM_PLATFORM unix
        define TCL_PLATFORM_PATH_SEPARATOR :
    }
}

# Find some tools
cc-check-tools ar ranlib strip
define tclsh [quote-if-needed [info nameofexecutable]]

# We only support silent-rules for GNU Make
define NO_SILENT_RULES
if {[get-define AM_SILENT_RULES 0]} {
    if {[cc-check-progs [get-define MAKE make]]} {
        # Are we using GNU make?
        catch {exec [get-define MAKE] --version} makeversion
        if {[string match "GNU Make*" $makeversion]} {
            define NO_SILENT_RULES 0
        }
    }
}

if {[opt-bool docs]} {
    if {[cc-check-progs asciidoc sed]} {
        define INSTALL_DOCS docs
        define HAVE_ASCIIDOC
    } else {
        define INSTALL_DOCS shipped
    }
} else {
    define INSTALL_DOCS nodocs
}

if {![cc-check-functions _NSGetEnviron]} {
    msg-checking "Checking environ declared in unistd.h..."
    if {[cctest -cflags {-D_GNU_SOURCE -D_POSIX_SOURCE} -includes unistd.h -code {char **ep = environ;}]} {
        define NO_ENVIRON_EXTERN
        msg-result "yes"
    } else {
        msg-result "no"
    }
}

# Windows has a mkdir with no permission arg
cc-check-includes sys/types.h sys/stat.h
msg-checking "Checking for mkdir with one arg..."
if {[cctest -includes {sys/types.h sys/stat.h} -code {mkdir("/dummy");}]} {
    define HAVE_MKDIR_ONE_ARG
    msg-result yes
} else {
    msg-result no
}

cc-with {-includes sys/stat.h} {
    foreach i {S_IXUSR S_IRWXG S_IRWXO} {
        if {![cc-check-decls $i]} {
            define $i 0
        }
    }
}

set extra_objs {}
set jimregexp 0

# Like opt-bool except defaults to on normally and off if --minimal
# In either case if explicitly enable or disabled, that takes precedence
proc opt-bool-unless-minimal {opt} {
    if {[opt-bool minimal]} {
		set default 0
	} else {
		set default 1
	}
	# If not explicitly enabled/disabled, use the default based on --minimal
	set val [opt-bool -nodefault $opt]
	if {$val < 0} {
		set val $default
	}
	return $val
}

if {[opt-bool-unless-minimal utf8]} {
    msg-result "Enabling UTF-8"
    define JIM_UTF8
    define-append AS_CFLAGS -DUSE_UTF8
    define PARSE_UNIDATA_FLAGS ""
    incr jimregexp
} else {
    define JIM_UTF8 0
}
if {[opt-bool maintainer]} {
    msg-result "Enabling maintainer settings"
    define JIM_MAINTAINER
}
if {[opt-bool-unless-minimal math]} {
    msg-result "Enabling math functions"
    define JIM_MATH_FUNCTIONS
    cc-check-function-in-lib sin m
    define-append LDLIBS [get-define lib_sin]
}
if {[opt-bool-unless-minimal ipv6]} {
    msg-result "Enabling IPv6"
    define JIM_IPV6
}
define-append PKG_CONFIG_REQUIRES ""
if {[opt-bool-unless-minimal ssl]} {
    if {[pkg-config-init 0]} {
        foreach pkg {openssl libssl} {
            if {[pkg-config $pkg]} {
                define JIM_SSL
                define-append LDLIBS [pkg-config-get $pkg LIBS]
                define-append LDFLAGS [pkg-config-get $pkg LDFLAGS]
                define-append AS_CFLAGS [pkg-config-get $pkg CFLAGS]
                msg-result "Enabling SSL ($pkg)"
                define-append PKG_CONFIG_REQUIRES $pkg
                break
            }
        }
    }
    if {![is-defined JIM_SSL]} {
        if {[cc-check-includes openssl/ssl.h] && [cc-check-function-in-lib ERR_error_string crypto] && [cc-check-function-in-lib TLSv1_2_method ssl]} {
            msg-result "Enabling SSL"
            define JIM_SSL
            define-append LDLIBS [get-define lib_ERR_error_string] [get-define lib_TLSv1_2_method]
        } elseif {[opt-bool ssl]} {
            user-error "SSL support requires OpenSSL"
        }
    }
    # Later versions deprecate TLSv1_2_method, but older versions don't have TLS_method
    if {![cc-check-function-in-lib TLS_method ssl]} {
        define-append AS_CFLAGS -DUSE_TLSv1_2_method
    }
}
if {[opt-bool-unless-minimal lineedit]} {
    if {([cc-check-includes termios.h] && [have-feature isatty]) || [have-feature winconsole]} {
        msg-result "Enabling line editing"
        define USE_LINENOISE
        define-append PARSE_UNIDATA_FLAGS -width
        lappend extra_objs linenoise.o
        if {[cc-check-inline] && [is-defined inline]} {
            define-append AS_CFLAGS -Dinline=[get-define inline]
        }
    }
}
if {[opt-bool references]} {
    msg-result "Enabling references"
    define JIM_REFERENCES
}
if {[opt-bool compat]} {
    msg-result "Enabling compatibility mode"
    define JIM_COMPAT
}
if {[opt-bool shared with-jim-shared]} {
    msg-result "Building shared library"
} else {
    msg-result "Building static library"
    define JIM_STATICLIB
}
define VERSION [format %.2f [expr {[get-define JIM_VERSION] / 100.0}]]
define LIBSOEXT [format [get-define SH_SOEXTVER] [get-define VERSION]]
if {[get-define libdir] ni {/lib /usr/lib /usr/lib64}} {
    define SH_LINKRPATH_FLAGS [format [get-define SH_LINKRPATH] [get-define libdir]]
} else {
    define SH_LINKRPATH_FLAGS ""
}
define JIM_INSTALL [opt-bool install-jim]
define JIM_DOCS [opt-bool docs]
define JIM_RANDOMISE_HASH [opt-bool random-hash]
define docdir [opt-str docdir o {${prefix}/docs/jim}]

# autosetup cc-check-function-in-library can't handle C++ libraries
proc check-metakit {} {
    set found 0
    msg-checking "Checking for Metakit..."
    cc-with {-lang c++} {
        if {[cctest -includes mk4.h -libs -lmk4 -code {c4_Storage dummy();}]} {
            msg-result ok
            define lib_mk -lmk4
            incr found
        } else {
            msg-result "not found"
        }
    }
    return $found
}

# Takes a list of module names, separated by spaces or commas
# and returns them as a single list
proc join-ext-names {list} {
    set result {}
    # Replace comma with space in each list then concatenate the result
    foreach l $list {
        lappend result {*}[string map {, " "} $l]
    }
    return $result
}

# Set up the withinfo array based on what the user selected
global withinfo
set withinfo(without) [join-ext-names [opt-val {without-ext with-out-jim-ext}]]
set withinfo(ext) [join-ext-names [opt-val {with-ext with-jim-ext}]]
set withinfo(mod) [join-ext-names [opt-val {with-mod with-jim-extmod}]]
set withinfo(nodefault) 0
set withinfo(optional) 1
if {[opt-bool minimal]} {
	set withinfo(optional) 0
}
if {$withinfo(without) eq "default"} {
    set withinfo(without) {}
    set withinfo(nodefault) 1
}

# Now go check everything - see autosetup/local.tcl
array set extinfo [check-extensions [opt-bool allextmod]]

set buildjimext 1

# Now special checks
if {[have-feature windows]} {
    lappend extra_objs jim-win32compat.o

    if {[get-define JIM_STATICLIB]} {
        if {[llength $extinfo(module-c)]} {
            user-error "cygwin/mingw require --shared for dynamic modules"
        } else {
            user-notice "Building static library, so build-jim-ext will not work on cygwin/mingw"
            set buildjimext 0
        }
    }
} else {
    # We don't trust any version of clock_gettime on windows
    cc-check-functions clock_gettime
}
if {[have-feature termios.h]} {
    lappend extra_objs jim-tty.o
}

if {[ext-get-status regexp] in {y m}} {
    if {![have-feature regcomp]} {
        # No regcomp means we need to use the built-in version
        incr jimregexp
    }
}

if {$jimregexp || [opt-bool jim-regexp]} {
    msg-result "Using built-in regexp"
    define JIM_REGEXP

    # If the built-in regexp overrides the system regcomp, etc.
    # jim must be built shared so that the correct symbols are found
    if {[ext-get-status regexp] eq "m" && [get-define JIM_STATICLIB] && [have-feature regcomp]} {
        user-error "Must use --shared with regexp module and built-in regexp"
    }
}

foreach mod $extinfo(static-c) {
    if {[dict exists $extdb info $mod extrasrcs]} {
        foreach src [dict get $extdb info $mod extrasrcs] {
            # In case we are building out-of-tree and $src is in a subdir
            file mkdir [file dirname $src]
            lappend extra_objs {*}[file rootname $src].o
        }
    }
}

# poor-man's signals
if {"signal" ni $extinfo(static-c)} {
    lappend extra_objs jim-nosignal.o
}

if {[ext-get-status load] eq "n"} {
    # If we don't have load, no need to support shared objects
    define SH_LINKFLAGS ""
}

# Are we cross compiling?
if {[get-define host] ne [get-define build]} {
	define cross_compiling 1
} else {
	define cross_compiling 0
}

msg-result "Jim static extensions: [lsort [concat $extinfo(static-tcl) $extinfo(static-c)]]"
if {[llength $extinfo(module-tcl)]} {
    msg-result "Jim Tcl extensions: [lsort $extinfo(module-tcl)]"
}
if {[llength $extinfo(module-c)]} {
    msg-result "Jim dynamic extensions: [lsort $extinfo(module-c)]"
}

define STATIC_EXTS [concat $extinfo(static-c) $extinfo(static-tcl)]
define C_EXT_OBJS [prefix jim- [suffix .o $extinfo(static-c)]]
define TCL_EXT_OBJS [suffix .o $extinfo(static-tcl)]
define C_EXT_SHOBJS [suffix .so $extinfo(module-c)]
define TCL_EXTS [suffix .tcl $extinfo(module-tcl)]
define EXTRA_OBJS $extra_objs

# Restore the user-specified LIBS
define LIBS $LIBS

# Now generate the Makefile rules to build the external C shared objects
# It is easier to do this here rather than listing them out explicitly in Makefile.in
set lines {}
foreach mod $extinfo(module-c) {
    set objs {}
    set libs [get-define LDLIBS_$mod]
    set srcs jim-$mod.c
    if {[dict exists $extdb info $mod extrasrcs]} {
        lappend srcs {*}[dict get $extdb info $mod extrasrcs]
    }
    lappend lines "$mod.so: $srcs \$(LIBJIM)"
    foreach src $srcs {
        set obj [file rootname $src].o
        lappend objs $obj
        lappend lines "\t\$(ECHO)\t\"\tCC\t$obj\""
        lappend lines "\t\$(Q)\$(CC) \$(AS_CFLAGS) \$(AS_CPPFLAGS) \$(CFLAGS) \$(CPPFLAGS) \$(SHOBJ_CFLAGS) -c -o $obj \$(srcdir)/$src"
    }
    lappend lines "\t\$(ECHO)\t\"\tLDSO\t\$@\""
    lappend lines "\t\$(Q)\$(CC) \$(AS_CFLAGS) \$(CFLAGS) \$(LDFLAGS) \$(SHOBJ_LDFLAGS) -o \$@ $objs \$(SH_LIBJIM) $libs"
    lappend lines ""
}
define BUILD_SHOBJS [join $lines \n]

make-config-header jim-config.h -auto {HAVE_LONG_LONG* JIM_UTF8 SIZEOF_INT} -bare JIM_VERSION -none *
make-config-header jimautoconf.h -auto {jim_ext_* TCL_PLATFORM_* TCL_LIBRARY USE_* JIM_* _FILE_OFFSET*} -bare {S_I*}
make-template Makefile.in
make-template tests/Makefile.in
make-template examples.api/Makefile.in
if {$buildjimext} {
    make-template build-jim-ext.in
    catch {exec chmod +x build-jim-ext}
}
make-template jimtcl.pc.in


if {[get-define SIZEOF_TIME_T] <= 4} {
    set note ""
    if {[have-feature windows]} {
        set note ", consider CFLAGS=-D__MINGW_USE_VC2005_COMPAT on mingw32"
    }
    lappend warnings "Warning: sizeof(time_t) is [get-define SIZEOF_TIME_T] -- not Y2038 compliant$note"
}

# Output any warnings at the end to make them easier to see
foreach warning $warnings {
    user-notice $warning
}
