aboutsummaryrefslogtreecommitdiff
path: root/autosetup/teaish/core.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'autosetup/teaish/core.tcl')
-rw-r--r--autosetup/teaish/core.tcl2537
1 files changed, 2537 insertions, 0 deletions
diff --git a/autosetup/teaish/core.tcl b/autosetup/teaish/core.tcl
new file mode 100644
index 000000000..381597ec5
--- /dev/null
+++ b/autosetup/teaish/core.tcl
@@ -0,0 +1,2537 @@
+########################################################################
+# 2025 April 5
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# * May you do good and not evil.
+# * May you find forgiveness for yourself and forgive others.
+# * May you share freely, never taking more than you give.
+#
+########################################################################
+# ----- @module teaish.tcl -----
+# @section TEA-ish ((TCL Extension Architecture)-ish)
+#
+# Functions in this file with a prefix of teaish__ are
+# private/internal APIs. Those with a prefix of teaish- are
+# public APIs.
+#
+# Teaish has a hard dependency on proj.tcl, and any public API members
+# of that module are considered legal for use by teaish extensions.
+#
+# Project home page: https://fossil.wanderinghorse.net/r/teaish
+
+use proj
+
+#
+# API-internal settings and shared state.
+array set teaish__Config [proj-strip-hash-comments {
+ #
+ # Teaish's version number, not to be confused with
+ # teaish__PkgInfo(-version).
+ #
+ version 0.1-beta
+
+ # set to 1 to enable some internal debugging output
+ debug-enabled 0
+
+ #
+ # 0 = don't yet have extension's pkgindex
+ # 0x01 = found TEAISH_EXT_DIR/pkgIndex.tcl.in
+ # 0x02 = found srcdir/pkgIndex.tcl.in
+ # 0x10 = found TEAISH_EXT_DIR/pkgIndex.tcl (static file)
+ # 0x20 = static-pkgIndex.tcl pragma: behave as if 0x10
+ # 0x100 = disabled by -tm.tcl.in
+ # 0x200 = disabled by -tm.tcl
+ #
+ # Reminder: it's significant that the bottom 4 bits be
+ # cases where teaish manages ./pkgIndex.tcl.
+ #
+ pkgindex-policy 0
+
+ #
+ # The pkginit counterpart of pkgindex-policy:
+ #
+ # 0 = no pkginit
+ # 0x01 = found default X.in: generate X from X.in
+ # 0x10 = found static pkginit file X
+ # 0x02 = user-provided X.in generates ./X.
+ # 0x20 = user-provided static pkginit file X
+ #
+ # The 0x0f bits indicate that teaish is responsible for cleaning up
+ # the (generated) pkginit file.
+ #
+ pkginit-policy 0
+ #
+ # 0 = no tm.tcl
+ # 0x01 = tm.tcl.in
+ # 0x10 = static tm.tcl
+ tm-policy 0
+
+ #
+ # If 1+ then teaish__verbose will emit messages.
+ #
+ verbose 0
+
+ #
+ # Mapping of pkginfo -flags to their TEAISH_xxx define (if any).
+ # This must not be modified after initialization.
+ #
+ pkginfo-f2d {
+ -name TEAISH_NAME
+ -name.dist TEAISH_DIST_NAME
+ -name.pkg TEAISH_PKGNAME
+ -version TEAISH_VERSION
+ -libDir TEAISH_LIBDIR_NAME
+ -loadPrefix TEAISH_LOAD_PREFIX
+ -vsatisfies TEAISH_VSATISFIES
+ -pkgInit.tcl TEAISH_PKGINIT_TCL
+ -pkgInit.tcl.in TEAISH_PKGINIT_TCL_IN
+ -url TEAISH_URL
+ -tm.tcl TEAISH_TM_TCL
+ -tm.tcl.in TEAISH_TM_TCL_IN
+ -options {}
+ -pragmas {}
+ }
+
+ #
+ # Queues for use with teaish-checks-queue and teaish-checks-run.
+ #
+ queued-checks-pre {}
+ queued-checks-post {}
+
+ # Whether or not "make dist" parts are enabled. They get enabled
+ # when building from an extension's dir, disabled when building
+ # elsewhere.
+ dist-enabled 1
+ # Whether or not "make install" parts are enabled. By default
+ # they are, but we have a single use case where they're
+ # both unnecessary and unhelpful, so...
+ install-enabled 1
+
+ # By default we enable compilation of a native extension but if the
+ # extension has no native code or the user wants to take that over
+ # via teaish.make.in or provide a script-only extension, we will
+ # elide the default compilation rules if this is 0.
+ dll-enabled 1
+
+ # Files to include in the "make dist" bundle.
+ dist-files {}
+
+ # List of source files for the extension.
+ extension-src {}
+
+ # Path to the teaish.tcl file.
+ teaish.tcl {}
+
+ # Dir where teaish.tcl is found.
+ extension-dir {}
+
+ # Whether the generates TEASH_VSATISFIES_CODE should error out on a
+ # satisfies error. If 0, it uses return instead of error.
+ vsatisfies-error 1
+
+ # Whether or not to allow a "full dist" - a "make dist" build which
+ # includes both the extension and teaish. By default this is only on
+ # if the extension dir is teaish's dir.
+ dist-full-enabled 0
+}]
+set teaish__Config(core-dir) $::autosetup(libdir)/teaish
+
+#
+# Array of info managed by teaish-pkginfo-get and friends. Has the
+# same set of keys as $teaish__Config(pkginfo-f2d).
+#
+array set teaish__PkgInfo {}
+
+#
+# Runs {*}$args if $lvl is <= the current verbosity level, else it has
+# no side effects.
+#
+proc teaish__verbose {lvl args} {
+ if {$lvl <= $::teaish__Config(verbose)} {
+ {*}$args
+ }
+}
+
+#
+# @teaish-argv-has flags...
+#
+# Returns true if any arg in $::argv matches any of the given globs,
+# else returns false.
+#
+proc teaish-argv-has {args} {
+ foreach glob $args {
+ foreach arg $::argv {
+ if {[string match $glob $arg]} {
+ return 1
+ }
+ }
+ }
+ return 0
+}
+
+if {[teaish-argv-has --teaish-verbose --t-v]} {
+ # Check this early so that we can use verbose-only messages in the
+ # pre-options-parsing steps.
+ set ::teaish__Config(verbose) 1
+ #teaish__verbose 1 msg-result "--teaish-verbose activated"
+}
+
+msg-quiet use system ; # Outputs "Host System" and "Build System" lines
+if {"--help" ni $::argv} {
+ teaish__verbose 1 msg-result "TEA(ish) Version = $::teaish__Config(version)"
+ teaish__verbose 1 msg-result "Source dir = $::autosetup(srcdir)"
+ teaish__verbose 1 msg-result "Build dir = $::autosetup(builddir)"
+}
+
+#
+# @teaish-configure-core
+#
+# Main entry point for the TEA-ish configure process. auto.def's primary
+# (ideally only) job should be to call this.
+#
+proc teaish-configure-core {} {
+ proj-tweak-default-env-dirs
+
+ set ::teaish__Config(install-mode) [teaish-argv-has --teaish-install*]
+ set ::teaish__Config(create-ext-mode) \
+ [teaish-argv-has --teaish-create-extension=* --t-c-e=*]
+ set gotExt 0; # True if an extension config is found
+ if {!$::teaish__Config(create-ext-mode)
+ && !$::teaish__Config(install-mode)} {
+ # Don't look for an extension if we're in --t-c-e or --t-i mode
+ set gotExt [teaish__find_extension]
+ }
+
+ #
+ # Set up the core --flags. This needs to come before teaish.tcl is
+ # sourced so that that file can use teaish-pkginfo-set to append
+ # options.
+ #
+ options-add [proj-strip-hash-comments {
+ with-tcl:DIR
+ => {Directory containing tclConfig.sh or a directory one level up from
+ that, from which we can derive a directory containing tclConfig.sh.
+ Defaults to the $TCL_HOME environment variable.}
+
+ with-tclsh:PATH
+ => {Full pathname of tclsh to use. It is used for trying to find
+ tclConfig.sh. Warning: if its containing dir has multiple tclsh
+ versions, it may select the wrong tclConfig.sh!
+ Defaults to the $TCLSH environment variable.}
+
+ # TEA has --with-tclinclude but it appears to only be useful for
+ # building an extension against an uninstalled copy of TCL's own
+ # source tree. The policy here is that either we get that info
+ # from tclConfig.sh or we give up.
+ #
+ # with-tclinclude:DIR
+ # => {Specify the directory which contains the tcl.h. This should not
+ # normally be required, as that information comes from tclConfig.sh.}
+
+ # We _generally_ want to reduce the possibility of flag collisions with
+ # extensions, and thus use a teaish-... prefix on most flags. However,
+ # --teaish-extension-dir is frequently needed, so...
+ #
+ # As of this spontaneous moment, we'll settle on using --t-A-X to
+ # abbreviate --teaish-A...-X... flags when doing so is
+ # unambiguous...
+ ted: t-e-d:
+ teaish-extension-dir:DIR
+ => {Looks for an extension in the given directory instead of the current
+ dir.}
+
+ t-c-e:
+ teaish-create-extension:TARGET_DIRECTORY
+ => {Writes stub files for creating an extension. Will refuse to overwrite
+ existing files without --teaish-force.}
+
+ t-f
+ teaish-force
+ => {Has a context-dependent meaning (autosetup defines --force for its
+ own use).}
+
+ t-d-d
+ teaish-dump-defines
+ => {Dump all configure-defined vars to config.defines.txt}
+
+ t-v:=0
+ teaish-verbose:=0
+ => {Enable more (often extraneous) messages from the teaish core.}
+
+ t-d
+ teaish-debug=0 => {Enable teaish-specific debug output}
+
+ t-i
+ teaish-install:=auto
+ => {Installs a copy of teaish, including autosetup, to the target dir.
+ When used with --teaish-create-extension=DIR, a value of "auto"
+ (no no value) will inherit that directory.}
+
+ #TODO: --teaish-install-extension:=dir as short for
+ # --t-c-e=dir --t-i
+
+ t-e-p:
+ teaish-extension-pkginfo:pkginfo
+ => {For use with --teaish-create-extension. If used, it must be a
+ list of arguments for use with teaish-pkginfo-set, e.g.
+ --teaish-extension-pkginfo="-name Foo -version 2.3"}
+
+ t-v-c
+ teaish-vsatisfies-check=1
+ => {Disable the configure-time "vsatisfies" check on the target tclsh.}
+
+ }]; # main options.
+
+ if {$gotExt} {
+ # We found an extension. Source it...
+ set ttcl $::teaish__Config(teaish.tcl)
+ proj-assert {"" ne [teaish-pkginfo-get -name]}
+ proj-assert {[file exists $ttcl]} \
+ "Expecting to have found teaish.(tcl|config) by now"
+ if {[string match *.tcl $ttcl]} {
+ uplevel 1 {source $::teaish__Config(teaish.tcl)}
+ } else {
+ teaish-pkginfo-set {*}[proj-file-content -trim $ttcl]
+ }
+ unset ttcl
+ # Set up some default values if the extension did not set them.
+ # This must happen _after_ it's sourced but before
+ # teaish-configure is called.
+ array set f2d $::teaish__Config(pkginfo-f2d)
+ foreach {pflag key type val} {
+ - TEAISH_CFLAGS -v ""
+ - TEAISH_LDFLAGS -v ""
+ - TEAISH_MAKEFILE -v ""
+ - TEAISH_MAKEFILE_CODE -v ""
+ - TEAISH_MAKEFILE_IN -v ""
+ - TEAISH_PKGINDEX_TCL -v ""
+ - TEAISH_PKGINDEX_TCL_IN -v ""
+ - TEAISH_PKGINIT_TCL -v ""
+ - TEAISH_PKGINIT_TCL_IN -v ""
+ - TEAISH_PKGINIT_TCL_TAIL -v ""
+ - TEAISH_TEST_TCL -v ""
+ - TEAISH_TEST_TCL_IN -v ""
+
+ -version - -v 0.0.0
+ -name.pkg - -e {set ::teaish__PkgInfo(-name)}
+ -name.dist - -e {set ::teaish__PkgInfo(-name)}
+ -libDir - -e {
+ join [list \
+ $::teaish__PkgInfo(-name.pkg) \
+ $::teaish__PkgInfo(-version)] ""
+ }
+ -loadPrefix - -e {
+ string totitle $::teaish__PkgInfo(-name.pkg)
+ }
+ -vsatisfies - -v {{Tcl 8.5-}}
+ -pkgInit.tcl - -v ""
+ -pkgInit.tcl.in - -v ""
+ -url - -v ""
+ -tm.tcl - -v ""
+ -tm.tcl.in - -v ""
+ } {
+ set isPIFlag [expr {"-" ne $pflag}]
+ if {$isPIFlag} {
+ if {[info exists ::teaish__PkgInfo($pflag)]} {
+ # Was already set - skip it.
+ continue;
+ }
+ proj-assert {{-} eq $key}
+ set key $f2d($pflag)
+ }
+ proj-assert {"" ne $key}
+ set got [get-define $key "<nope>"]
+ if {"<nope>" ne $got} {
+ # Was already set - skip it.
+ continue
+ }
+ switch -exact -- $type {
+ -v {}
+ -e { set val [eval $val] }
+ default { proj-error "Invalid type flag: $type" }
+ }
+ #puts "***** defining default $pflag $key {$val} isPIFlag=$isPIFlag got=$got"
+ define $key $val
+ if {$isPIFlag} {
+ set ::teaish__PkgInfo($pflag) $val
+ }
+ }
+ unset isPIFlag pflag key type val
+ array unset f2d
+ }; # sourcing extension's teaish.tcl
+
+ if {[llength [info proc teaish-options]] > 0} {
+ # Add options defined by teaish-options, which is assumed to be
+ # imported via [teaish-get -teaish-tcl].
+ set o [teaish-options]
+ if {"" ne $o} {
+ options-add $o
+ }
+ }
+ #set opts [proj-options-combine]
+ #lappend opts teaish-debug => {x}; #testing dupe entry handling
+ if {[catch {options {}} msg xopts]} {
+ # Workaround for <https://github.com/msteveb/autosetup/issues/73>
+ # where [options] behaves oddly on _some_ TCL builds when it's
+ # called from deeper than the global scope.
+ dict incr xopts -level
+ return {*}$xopts $msg
+ }
+
+ proj-xfer-options-aliases {
+ t-c-e => teaish-create-extension
+ t-d => teaish-debug
+ t-d-d => teaish-dump-defines
+ ted => teaish-extension-dir
+ t-e-d => teaish-extension-dir
+ t-e-p => teaish-extension-pkginfo
+ t-f => teaish-force
+ t-i => teaish-install
+ t-v => teaish-verbose
+ t-v-c => teaish-vsatisfies-check
+ }
+
+ scan [opt-val teaish-verbose 0] %d ::teaish__Config(verbose)
+ set ::teaish__Config(debug-enabled) [opt-bool teaish-debug]
+
+ set exitEarly 0
+ if {[proj-opt-was-provided teaish-create-extension]} {
+ teaish__create_extension [opt-val teaish-create-extension]
+ incr exitEarly
+ }
+ if {$::teaish__Config(install-mode)} {
+ teaish__install
+ incr exitEarly
+ }
+
+ if {$exitEarly} {
+ file delete -force config.log
+ return
+ }
+ proj-assert {1==$gotExt} "Else we cannot have gotten this far"
+
+ teaish__configure_phase1
+}
+
+
+#
+# Internal config-time debugging output routine. It is not legal to
+# call this from the global scope.
+#
+proc teaish-debug {msg} {
+ if {$::teaish__Config(debug-enabled)} {
+ puts stderr [proj-bold "** DEBUG: \[[proj-scope 1]\]: $msg"]
+ }
+}
+
+#
+# Runs "phase 1" of the configuration, immediately after processing
+# --flags. This is what will import the client-defined teaish.tcl.
+#
+proc teaish__configure_phase1 {} {
+ msg-result \
+ [join [list "Configuring build of Tcl extension" \
+ [proj-bold [teaish-pkginfo-get -name] \
+ [teaish-pkginfo-get -version]] "..."]]
+
+ uplevel 1 {
+ use cc cc-db cc-shared cc-lib; # pkg-config
+ }
+ teaish__check_tcl
+ apply {{} {
+ #
+ # If --prefix or --exec-prefix are _not_ provided, use their
+ # TCL_... counterpart from tclConfig.sh. Caveat: by the time we can
+ # reach this point, autosetup's system.tcl will have already done
+ # some non-trivial amount of work with these to create various
+ # derived values from them, so we temporarily end up with a mishmash
+ # of autotools-compatibility var values. That will be straightened
+ # out in the final stage of the configure script via
+ # [proj-remap-autoconf-dir-vars].
+ #
+ foreach {flag uflag tclVar} {
+ prefix prefix TCL_PREFIX
+ exec-prefix exec_prefix TCL_EXEC_PREFIX
+ } {
+ if {![proj-opt-was-provided $flag]} {
+ if {"exec-prefix" eq $flag} {
+ # If --exec-prefix was not used, ensure that --exec-prefix
+ # derives from the --prefix we may have just redefined.
+ set v {${prefix}}
+ } else {
+ set v [get-define $tclVar "???"]
+ teaish__verbose 1 msg-result "Using \$$tclVar for --$flag=$v"
+ }
+ proj-assert {"???" ne $v} "Expecting teaish__check_tcl to have defined $tclVar"
+ #puts "*** $flag $uflag $tclVar = $v"
+ proj-opt-set $flag $v
+ define $uflag $v
+
+ # ^^^ As of here, all autotools-compatibility vars which derive
+ # from --$flag, e.g. --libdir, still derive from the default
+ # --$flag value which was active when system.tcl was
+ # included. So long as those flags are not explicitly passed to
+ # the configure script, those will be straightened out via
+ # [proj-remap-autoconf-dir-vars].
+ }
+ }
+ }}; # --[exec-]prefix defaults
+ teaish__check_common_bins
+ #
+ # Set up library file names
+ #
+ proj-file-extensions
+ teaish__define_pkginfo_derived *
+
+ teaish-checks-run -pre
+ if {[llength [info proc teaish-configure]] > 0} {
+ # teaish-configure is assumed to be imported via
+ # teaish.tcl
+ teaish-configure
+ }
+ teaish-checks-run -post
+
+ apply {{} {
+ # Set up "vsatisfies" code for pkgIndex.tcl.in,
+ # _teaish.tester.tcl.in, and for a configure-time check. We would
+ # like to put this before [teaish-checks-run -pre] but it's
+ # marginally conceivable that a client may need to dynamically
+ # calculate the vsatisfies and set it via [teaish-configure].
+ set vs [get-define TEAISH_VSATISFIES ""]
+ if {"" eq $vs} return
+ set code {}
+ set n 0
+ # Treat $vs as a list-of-lists {{Tcl 8.5-} {Foo 1.0- -3.0} ...}
+ # and generate Tcl which will run package vsatisfies tests with
+ # that info.
+ foreach pv $vs {
+ set n [llength $pv]
+ if {$n < 2} {
+ proj-error "-vsatisfies: {$pv} appears malformed. Whole list is: $vs"
+ }
+ set pkg [lindex $pv 0]
+ set vcheck {}
+ for {set i 1} {$i < $n} {incr i} {
+ lappend vcheck [lindex $pv $i]
+ }
+ if {[opt-bool teaish-vsatisfies-check]} {
+ set tclsh [get-define TCLSH_CMD]
+ set vsat "package vsatisfies \[ package provide $pkg \] $vcheck"
+ set vputs "puts \[ $vsat \]"
+ #puts "*** vputs = $vputs"
+ scan [exec echo $vputs | $tclsh] %d vvcheck
+ if {0 == $vvcheck} {
+ proj-fatal -up $tclsh "check failed:" $vsat
+ }
+ }
+ if {$::teaish__Config(vsatisfies-error)} {
+ set vunsat \
+ [list error [list Package \
+ $::teaish__PkgInfo(-name) $::teaish__PkgInfo(-version) \
+ requires $pv]]
+ } else {
+ set vunsat return
+ }
+ lappend code \
+ [string trim [subst -nocommands \
+ {if { ![package vsatisfies [package provide $pkg] $vcheck] } {\n $vunsat\n}}]]
+ }; # foreach pv
+ define TEAISH_VSATISFIES_CODE [join $code "\n"]
+ }}; # vsatisfies
+
+ if {[proj-looks-like-windows]} {
+ # Without this, linking of an extension will not work on Cygwin or
+ # Msys2.
+ msg-result "Using USE_TCL_STUBS for Unix(ish)-on-Windows environment"
+ teaish-cflags-add -DUSE_TCL_STUBS=1
+ }
+
+ #define AS_LIBDIR $::autosetup(libdir)
+ define TEAISH_TESTUTIL_TCL $::teaish__Config(core-dir)/tester.tcl
+
+ apply {{} {
+ #
+ # Ensure we have a pkgIndex.tcl and don't have a stale generated one
+ # when rebuilding for different --with-tcl=... values.
+ #
+ if {!$::teaish__Config(pkgindex-policy)} {
+ proj-error "Cannot determine which pkgIndex.tcl to use"
+ }
+ if {0x300 & $::teaish__Config(pkgindex-policy)} {
+ teaish__verbose 1 msg-result "pkgIndex disabled by -tm.tcl(.in)"
+ } else {
+ set tpi [proj-coalesce \
+ [get-define TEAISH_PKGINDEX_TCL_IN] \
+ [get-define TEAISH_PKGINDEX_TCL]]
+ proj-assert {$tpi ne ""} \
+ "TEAISH_PKGINDEX_TCL should have been set up by now"
+ teaish__verbose 1 msg-result "Using pkgIndex from $tpi"
+ if {0x0f & $::teaish__Config(pkgindex-policy)} {
+ # Don't leave stale pkgIndex.tcl laying around yet don't delete
+ # or overwrite a user-managed static pkgIndex.tcl.
+ file delete -force -- [get-define TEAISH_PKGINDEX_TCL]
+ proj-dot-ins-append [get-define TEAISH_PKGINDEX_TCL_IN]
+ } else {
+ teaish-dist-add [file tail $tpi]
+ }
+ }
+ }}; # $::teaish__Config(pkgindex-policy)
+
+ #
+ # Ensure we clean up TEAISH_PKGINIT_TCL if needed and @-process
+ # TEAISH_PKGINIT_TCL_IN if needed.
+ #
+ if {0x0f & $::teaish__Config(pkginit-policy)} {
+ file delete -force -- [get-define TEAISH_PKGINIT_TCL]
+ proj-dot-ins-append [get-define TEAISH_PKGINIT_TCL_IN]
+ }
+ if {0x0f & $::teaish__Config(tm-policy)} {
+ file delete -force -- [get-define TEAISH_TM_TCL]
+ proj-dot-ins-append [get-define TEAISH_TM_TCL_IN]
+ }
+
+ apply {{} {
+ # Queue up any remaining dot-in files
+ set dotIns [list]
+ foreach d {
+ TEAISH_TESTER_TCL_IN
+ TEAISH_TEST_TCL_IN
+ TEAISH_MAKEFILE_IN
+ } {
+ lappend dotIns [get-define $d ""]
+ }
+ lappend dotIns $::autosetup(srcdir)/Makefile.in; # must be after TEAISH_MAKEFILE_IN
+ foreach f $dotIns {
+ if {"" ne $f} {
+ proj-dot-ins-append $f
+ }
+ }
+ }}
+
+ define TEAISH_DIST_FULL \
+ [expr {
+ $::teaish__Config(dist-enabled)
+ && $::teaish__Config(dist-full-enabled)
+ }]
+
+ define TEAISH_AUTOSETUP_DIR $::teaish__Config(core-dir)
+ define TEAISH_ENABLE_DIST $::teaish__Config(dist-enabled)
+ define TEAISH_ENABLE_INSTALL $::teaish__Config(install-enabled)
+ define TEAISH_ENABLE_DLL $::teaish__Config(dll-enabled)
+ define TEAISH_TCL $::teaish__Config(teaish.tcl)
+
+ define TEAISH_DIST_FILES [join $::teaish__Config(dist-files)]
+ define TEAISH_EXT_DIR [join $::teaish__Config(extension-dir)]
+ define TEAISH_EXT_SRC [join $::teaish__Config(extension-src)]
+ proj-setup-autoreconfig TEAISH_AUTORECONFIG
+ foreach f {
+ TEAISH_CFLAGS
+ TEAISH_LDFLAGS
+ } {
+ # Ensure that any of these lists are flattened
+ define $f [join [get-define $f]]
+ }
+ proj-remap-autoconf-dir-vars
+ set tdefs [teaish__defines_to_list]
+ define TEAISH__DEFINES_MAP $tdefs; # injected into _teaish.tester.tcl
+
+ #
+ # NO [define]s after this point!
+ #
+ proj-dot-ins-process -validate
+ proj-if-opt-truthy teaish-dump-defines {
+ proj-file-write config.defines.txt $tdefs
+ }
+
+}; # teaish__configure_phase1
+
+#
+# Run checks for required binaries.
+#
+proc teaish__check_common_bins {} {
+ if {"" eq [proj-bin-define install]} {
+ proj-warn "Cannot find install binary, so 'make install' will not work."
+ define BIN_INSTALL false
+ }
+ if {"" eq [proj-bin-define zip]} {
+ proj-warn "Cannot find zip, so 'make dist.zip' will not work."
+ }
+ if {"" eq [proj-bin-define tar]} {
+ proj-warn "Cannot find tar, so 'make dist.tgz' will not work."
+ }
+}
+
+#
+# TCL...
+#
+# teaish__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:
+#
+# - TCLSH_CMD is the path to the canonical tclsh or "".
+#
+# - TCL_CONFIG_SH is the path to tclConfig.sh or "".
+#
+# - TCLLIBDIR is the dir to which the extension library gets
+# - installed.
+#
+proc teaish__check_tcl {} {
+ define TCLSH_CMD false ; # Significant is that it exits with non-0
+ define TCLLIBDIR "" ; # Installation dir for TCL extension lib
+ define TCL_CONFIG_SH ""; # full path to tclConfig.sh
+
+ # Clear out all vars which would harvest from tclConfig.sh so that
+ # the late-config validation of @VARS@ works even if --disable-tcl
+ # is used.
+ proj-tclConfig-sh-to-autosetup ""
+
+ # TODO: better document the steps this is taking.
+ set srcdir $::autosetup(srcdir)
+ msg-result "Checking for a suitable tcl... "
+ set use_tcl 1
+ set withSh [opt-val with-tclsh [proj-get-env TCLSH]]
+ set tclHome [opt-val with-tcl [proj-get-env TCL_HOME]]
+ if {[string match */lib $tclHome]} {
+ # TEA compatibility kludge: its --with-tcl wants the lib
+ # dir containing tclConfig.sh.
+ #proj-warn "Replacing --with-tcl=$tclHome for TEA compatibility"
+ regsub {/lib^} $tclHome "" tclHome
+ msg-result "NOTE: stripped /lib suffix from --with-tcl=$tclHome (a TEA-ism)"
+ }
+ if {0} {
+ # This misinteracts with the $TCL_PREFIX default: it will use the
+ # autosetup-defined --prefix default
+ if {"prefix" eq $tclHome} {
+ set tclHome [get-define prefix]
+ }
+ }
+ teaish-debug "use_tcl ${use_tcl}"
+ teaish-debug "withSh=${withSh}"
+ teaish-debug "tclHome=$tclHome"
+ if {"" eq $withSh && "" eq $tclHome} {
+ # If neither --with-tclsh nor --with-tcl are provided, try to find
+ # a workable tclsh.
+ set withSh [proj-first-bin-of tclsh9.1 tclsh9.0 tclsh8.6 tclsh]
+ teaish-debug "withSh=${withSh}"
+ }
+
+ set doConfigLookup 1 ; # set to 0 to test the tclConfig.sh-not-found cases
+ if {"" ne $withSh} {
+ # --with-tclsh was provided or found above. Validate it and use it
+ # to trump any value passed via --with-tcl=DIR.
+ if {![file-isexec $withSh]} {
+ proj-error "TCL shell $withSh is not executable"
+ } else {
+ define TCLSH_CMD $withSh
+ #msg-result "Using tclsh: $withSh"
+ }
+ if {$doConfigLookup &&
+ [catch {exec $withSh $::autosetup(libdir)/find_tclconfig.tcl} result] == 0} {
+ set tclHome $result
+ }
+ if {"" ne $tclHome && [file isdirectory $tclHome]} {
+ teaish__verbose 1 msg-result "$withSh recommends the tclConfig.sh from $tclHome"
+ } else {
+ proj-warn "$withSh is unable to recommend a tclConfig.sh"
+ set use_tcl 0
+ }
+ }
+ set cfg ""
+ set tclSubdirs {tcl9.1 tcl9.0 tcl8.6 tcl8.5 lib}
+ while {$use_tcl} {
+ if {"" ne $tclHome} {
+ # Ensure that we can find tclConfig.sh under ${tclHome}/...
+ if {$doConfigLookup} {
+ if {[file readable "${tclHome}/tclConfig.sh"]} {
+ set cfg "${tclHome}/tclConfig.sh"
+ } else {
+ foreach i $tclSubdirs {
+ if {[file readable "${tclHome}/$i/tclConfig.sh"]} {
+ set cfg "${tclHome}/$i/tclConfig.sh"
+ break
+ }
+ }
+ }
+ }
+ if {"" eq $cfg} {
+ proj-error "No tclConfig.sh found under ${tclHome}"
+ }
+ } else {
+ # If we have not yet found a tclConfig.sh file, look in $libdir
+ # which is set automatically by autosetup or via 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]} {
+ break
+ }
+ }
+ teaish__verbose 1 msg-result "Using tclConfig.sh = $cfg"
+ break
+ }; # while {$use_tcl}
+ define TCL_CONFIG_SH $cfg
+ # Export a subset of tclConfig.sh to the current TCL-space. If $cfg
+ # is an empty string, this emits empty-string entries for the
+ # various options we're interested in.
+ proj-tclConfig-sh-to-autosetup $cfg
+
+ if {"" eq $withSh && $cfg ne ""} {
+ # We have tclConfig.sh but no tclsh. Attempt to locate a tclsh
+ # based on info from tclConfig.sh.
+ set tclExecPrefix [get-define TCL_EXEC_PREFIX]
+ proj-assert {"" ne $tclExecPrefix}
+ set tryThese [list \
+ $tclExecPrefix/bin/tclsh[get-define TCL_VERSION] \
+ $tclExecPrefix/bin/tclsh ]
+ foreach trySh $tryThese {
+ if {[file-isexec $trySh]} {
+ set withSh $trySh
+ break
+ }
+ }
+ if {![file-isexec $withSh]} {
+ proj-warn "Cannot find a usable tclsh (tried: $tryThese)"
+ }
+ }
+ define TCLSH_CMD $withSh
+ if {$use_tcl} {
+ # Set up the TCLLIBDIR
+ set tcllibdir [get-env TCLLIBDIR ""]
+ set extDirName [teaish-pkginfo-get -libDir]
+ if {"" eq $tcllibdir} {
+ # Attempt to extract TCLLIBDIR from TCL's $auto_path
+ if {"" ne $withSh &&
+ [catch {exec echo "puts stdout \$auto_path" | "$withSh"} result] == 0} {
+ foreach i $result {
+ if {[file isdirectory $i]} {
+ set tcllibdir $i/$extDirName
+ break
+ }
+ }
+ } else {
+ proj-error "Cannot determine TCLLIBDIR."
+ }
+ }
+ define TCLLIBDIR $tcllibdir
+ }; # find TCLLIBDIR
+
+ set gotSh [file-isexec $withSh]
+ set tmdir ""; # first tcl::tm::list entry
+ if {$gotSh} {
+ catch {
+ set tmli [exec echo {puts [tcl::tm::list]} | $withSh]
+ # Reminder: this list contains many names of dirs which do not
+ # exist but are legitimate. If we rely only on an is-dir check,
+ # we can end up not finding any of the many candidates.
+ set firstDir ""
+ foreach d $tmli {
+ if {"" eq $firstDir && ![string match //*:* $d]} {
+ # First non-VFS entry, e.g. not //zipfs:
+ set firstDir $d
+ }
+ if {[file isdirectory $d]} {
+ set tmdir $d
+ break
+ }
+ }
+ if {"" eq $tmdir} {
+ set tmdir $firstDir
+ }
+ }; # find tcl::tm path
+ }
+ define TEAISH_TCL_TM_DIR $tmdir
+
+ # Finally, let's wrap up...
+ if {$gotSh} {
+ teaish__verbose 1 msg-result "Using tclsh = $withSh"
+ if {$cfg ne ""} {
+ define HAVE_TCL 1
+ } else {
+ proj-warn "Found tclsh but no tclConfig.sh."
+ }
+ if {"" eq $tmdir} {
+ proj-warn "Did not find tcl::tm directory."
+ }
+ }
+ show-notices
+ # If TCL is not found: if it was explicitly requested then fail
+ # fatally, else just emit a warning. If we can find the APIs needed
+ # to generate a working JimTCL then that will suffice for build-time
+ # TCL purposes (see: proc sqlite-determine-codegen-tcl).
+ if {!$gotSh} {
+ proj-error "Did not find tclsh"
+ } elseif {"" eq $cfg} {
+ proj-indented-notice -error {
+ Cannot find a usable tclConfig.sh file. Use --with-tcl=DIR to
+ specify a directory near which tclConfig.sh can be found, or
+ --with-tclsh=/path/to/tclsh to allow the tclsh binary to locate
+ its tclConfig.sh, with the caveat that a symlink to tclsh, or
+ wrapper script around it, e.g. ~/bin/tclsh ->
+ $HOME/tcl/9.0/bin/tclsh9.1, may not work because tclsh emits
+ different library paths for the former than the latter.
+ }
+ }
+ msg-result "Using Tcl [get-define TCL_VERSION] from [get-define TCL_PREFIX]."
+ teaish__tcl_platform_quirks
+}; # teaish__check_tcl
+
+#
+# Perform last-minute platform-specific tweaks to account for quirks.
+#
+proc teaish__tcl_platform_quirks {} {
+ define TEAISH_POSTINST_PREREQUIRE ""
+ switch -glob -- [get-define host] {
+ *-haiku {
+ # Haiku's default TCLLIBDIR is "all wrong": it points to a
+ # read-only virtual filesystem mount-point. We bend it back to
+ # fit under $TCL_PACKAGE_PATH here.
+ foreach {k d} {
+ vj TCL_MAJOR_VERSION
+ vn TCL_MINOR_VERSION
+ pp TCL_PACKAGE_PATH
+ ld TCLLIBDIR
+ } {
+ set $k [get-define $d]
+ }
+ if {[string match /packages/* $ld]} {
+ set old $ld
+ set tail [file tail $ld]
+ if {8 == $vj} {
+ set ld "${pp}/tcl${vj}.${vn}/${tail}"
+ } else {
+ proj-assert {9 == $vj}
+ set ld "${pp}/${tail}"
+ }
+ define TCLLIBDIR $ld
+ # [load foo.so], without a directory part, does not work via
+ # automated tests on Haiku (but works when run
+ # manually). Similarly, the post-install [package require ...]
+ # test fails, presumably for a similar reason. We work around
+ # the former in _teaish.tester.tcl.in. We work around the
+ # latter by amending the post-install check's ::auto_path (in
+ # Makefile.in). This code MUST NOT contain any single-quotes.
+ define TEAISH_POSTINST_PREREQUIRE \
+ [join [list set ::auto_path \
+ \[ linsert \$::auto_path 0 $ld \] \; \
+ ]]
+ proj-indented-notice [subst -nocommands -nobackslashes {
+ Haiku users take note: patching target installation dir to match
+ Tcl's home because Haiku's is not writable.
+
+ Original : $old
+ Substitute: $ld
+ }]
+ }
+ }
+ }
+}; # teaish__tcl_platform_quirks
+
+#
+# Searches $::argv and/or the build dir and/or the source dir for
+# teaish.tcl and friends. Fails if it cannot find teaish.tcl or if
+# there are other irreconcilable problems. If it returns 0 then it did
+# not find an extension but the --help flag was seen, in which case
+# that's not an error.
+#
+# This does not _load_ the extension, it primarily locates the files
+# which make up an extension and fills out no small amount of teaish
+# state related to that.
+#
+proc teaish__find_extension {} {
+ proj-assert {!$::teaish__Config(install-mode)}
+ teaish__verbose 1 msg-result "Looking for teaish extension..."
+
+ # Helper for the foreach loop below.
+ set checkTeaishTcl {{mustHave fid dir} {
+ set f [file join $dir $fid]
+ if {[file readable $f]} {
+ file-normalize $f
+ } elseif {$mustHave} {
+ proj-error "Missing required $dir/$fid"
+ }
+ }}
+
+ #
+ # We have to handle some flags manually because the extension must
+ # be loaded before [options] is run (so that the extension can
+ # inject its own options).
+ #
+ set dirBld $::autosetup(builddir); # dir we're configuring under
+ set dirSrc $::autosetup(srcdir); # where teaish's configure script lives
+ set extT ""; # teaish.tcl
+ set largv {}; # rewritten $::argv
+ set gotHelpArg 0; # got the --help
+ foreach arg $::argv {
+ #puts "*** arg=$arg"
+ switch -glob -- $arg {
+ --ted=* -
+ --t-e-d=* -
+ --teaish-extension-dir=* {
+ # Ensure that $extD refers to a directory and contains a
+ # teaish.tcl.
+ regexp -- {--[^=]+=(.+)} $arg - extD
+ set extD [file-normalize $extD]
+ if {![file isdirectory $extD]} {
+ proj-error "--teaish-extension-dir value is not a directory: $extD"
+ }
+ set extT [apply $checkTeaishTcl 0 teaish.config $extD]
+ if {"" eq $extT} {
+ set extT [apply $checkTeaishTcl 1 teaish.tcl $extD]
+ }
+ set ::teaish__Config(extension-dir) $extD
+ }
+ --help {
+ incr gotHelpArg
+ lappend largv $arg
+ }
+ default {
+ lappend largv $arg
+ }
+ }
+ }
+ set ::argv $largv
+
+ set dirExt $::teaish__Config(extension-dir); # dir with the extension
+ #
+ # teaish.tcl is a TCL script which implements various
+ # interfaces described by this framework.
+ #
+ # We use the first one we find in the builddir or srcdir.
+ #
+ if {"" eq $extT} {
+ set flist [list]
+ proj-assert {$dirExt eq ""}
+ lappend flist $dirBld/teaish.tcl $dirBld/teaish.config $dirSrc/teaish.tcl
+ if {![proj-first-file-found extT $flist]} {
+ if {$gotHelpArg} {
+ # Tell teaish-configure-core that the lack of extension is not
+ # an error when --help or --teaish-install is used.
+ return 0;
+ }
+ proj-indented-notice -error "
+Did not find any of: $flist
+
+If you are attempting an out-of-tree build, use
+ --teaish-extension-dir=/path/to/extension"
+ }
+ }
+ if {![file readable $extT]} {
+ proj-error "extension tcl file is not readable: $extT"
+ }
+ set ::teaish__Config(teaish.tcl) $extT
+ set dirExt [file dirname $extT]
+
+ set ::teaish__Config(extension-dir) $dirExt
+ set ::teaish__Config(blddir-is-extdir) [expr {$dirBld eq $dirExt}]
+ set ::teaish__Config(dist-enabled) $::teaish__Config(blddir-is-extdir); # may change later
+ set ::teaish__Config(dist-full-enabled) \
+ [expr {[file-normalize $::autosetup(srcdir)]
+ eq [file-normalize $::teaish__Config(extension-dir)]}]
+
+ set addDist {{file} {
+ teaish-dist-add [file tail $file]
+ }}
+ apply $addDist $extT
+
+ teaish__verbose 1 msg-result "Extension dir = [teaish-get -dir]"
+ teaish__verbose 1 msg-result "Extension config = $extT"
+
+ teaish-pkginfo-set -name [file tail [file dirname $extT]]
+
+ #
+ # teaish.make[.in] provides some of the info for the main makefile,
+ # like which source(s) to build and their build flags.
+ #
+ # We use the first one of teaish.make.in or teaish.make we find in
+ # $dirExt.
+ #
+ if {[proj-first-file-found extM \
+ [list \
+ $dirExt/teaish.make.in \
+ $dirExt/teaish.make \
+ ]]} {
+ if {[string match *.in $extM]} {
+ define TEAISH_MAKEFILE_IN $extM
+ define TEAISH_MAKEFILE [file rootname [file tail $extM]]
+ } else {
+ define TEAISH_MAKEFILE_IN ""
+ define TEAISH_MAKEFILE $extM
+ }
+ apply $addDist $extM
+ teaish__verbose 1 msg-result "Extension makefile = $extM"
+ } else {
+ define TEAISH_MAKEFILE_IN ""
+ define TEAISH_MAKEFILE ""
+ }
+
+ # Look for teaish.pkginit.tcl[.in]
+ set piPolicy 0
+ if {[proj-first-file-found extI \
+ [list \
+ $dirExt/teaish.pkginit.tcl.in \
+ $dirExt/teaish.pkginit.tcl \
+ ]]} {
+ if {[string match *.in $extI]} {
+ # Generate teaish.pkginit.tcl from $extI.
+ define TEAISH_PKGINIT_TCL_IN $extI
+ define TEAISH_PKGINIT_TCL [file rootname [file tail $extI]]
+ set piPolicy 0x01
+ } else {
+ # Assume static $extI.
+ define TEAISH_PKGINIT_TCL_IN ""
+ define TEAISH_PKGINIT_TCL $extI
+ set piPolicy 0x10
+ }
+ apply $addDist $extI
+ teaish__verbose 1 msg-result "Extension post-load init = $extI"
+ define TEAISH_PKGINIT_TCL_TAIL \
+ [file tail [get-define TEAISH_PKGINIT_TCL]]; # for use in pkgIndex.tcl.in
+ }
+ set ::teaish__Config(pkginit-policy) $piPolicy
+
+ # Look for pkgIndex.tcl[.in]...
+ set piPolicy 0
+ if {[proj-first-file-found extPI $dirExt/pkgIndex.tcl.in]} {
+ # Generate ./pkgIndex.tcl from $extPI.
+ define TEAISH_PKGINDEX_TCL_IN $extPI
+ define TEAISH_PKGINDEX_TCL [file rootname [file tail $extPI]]
+ apply $addDist $extPI
+ set piPolicy 0x01
+ } elseif {$dirExt ne $dirSrc
+ && [proj-first-file-found extPI $dirSrc/pkgIndex.tcl.in]} {
+ # Generate ./pkgIndex.tcl from $extPI.
+ define TEAISH_PKGINDEX_TCL_IN $extPI
+ define TEAISH_PKGINDEX_TCL [file rootname [file tail $extPI]]
+ set piPolicy 0x02
+ } elseif {[proj-first-file-found extPI $dirExt/pkgIndex.tcl]} {
+ # Assume $extPI's a static file and use it.
+ define TEAISH_PKGINDEX_TCL_IN ""
+ define TEAISH_PKGINDEX_TCL $extPI
+ apply $addDist $extPI
+ set piPolicy 0x10
+ }
+ # Reminder: we have to delay removal of stale TEAISH_PKGINDEX_TCL
+ # and the proj-dot-ins-append of TEAISH_PKGINDEX_TCL_IN until much
+ # later in the process.
+ set ::teaish__Config(pkgindex-policy) $piPolicy
+
+ # Look for teaish.test.tcl[.in]
+ proj-assert {"" ne $dirExt}
+ set flist [list $dirExt/teaish.test.tcl.in $dirExt/teaish.test.tcl]
+ if {[proj-first-file-found ttt $flist]} {
+ if {[string match *.in $ttt]} {
+ # Generate teaish.test.tcl from $ttt
+ set xt [file rootname [file tail $ttt]]
+ file delete -force -- $xt; # ensure no stale copy is used
+ define TEAISH_TEST_TCL $xt
+ define TEAISH_TEST_TCL_IN $ttt
+ } else {
+ define TEAISH_TEST_TCL $ttt
+ define TEAISH_TEST_TCL_IN ""
+ }
+ apply $addDist $ttt
+ } else {
+ define TEAISH_TEST_TCL ""
+ define TEAISH_TEST_TCL_IN ""
+ }
+
+ # Look for _teaish.tester.tcl[.in]
+ set flist [list $dirExt/_teaish.tester.tcl.in $dirSrc/_teaish.tester.tcl.in]
+ if {[proj-first-file-found ttt $flist]} {
+ # Generate teaish.test.tcl from $ttt
+ set xt [file rootname [file tail $ttt]]
+ file delete -force -- $xt; # ensure no stale copy is used
+ define TEAISH_TESTER_TCL $xt
+ define TEAISH_TESTER_TCL_IN $ttt
+ if {[lindex $flist 0] eq $ttt} {
+ apply $addDist $ttt
+ }
+ unset ttt xt
+ } else {
+ if {[file exists [set ttt [file join $dirSrc _teaish.tester.tcl.in]]]} {
+ set xt [file rootname [file tail $ttt]]
+ define TEAISH_TESTER_TCL $xt
+ define TEAISH_TESTER_TCL_IN $ttt
+ } else {
+ define TEAISH_TESTER_TCL ""
+ define TEAISH_TESTER_TCL_IN ""
+ }
+ }
+ unset flist
+
+ # TEAISH_OUT_OF_EXT_TREE = 1 if we're building from a dir other
+ # than the extension's home dir.
+ define TEAISH_OUT_OF_EXT_TREE \
+ [expr {[file-normalize $::autosetup(builddir)] ne \
+ [file-normalize $::teaish__Config(extension-dir)]}]
+ return 1
+}; # teaish__find_extension
+
+#
+# @teaish-cflags-add ?-p|prepend? ?-define? cflags...
+#
+# Equivalent to [proj-define-amend TEAISH_CFLAGS {*}$args].
+#
+proc teaish-cflags-add {args} {
+ proj-define-amend TEAISH_CFLAGS {*}$args
+}
+
+#
+# @teaish-define-to-cflag ?flags? defineName...|{defineName...}
+#
+# Uses [proj-define-to-cflag] to expand a list of [define] keys, each
+# one a separate argument, to CFLAGS-style -D... form then appends
+# that to the current TEAISH_CFLAGS.
+#
+# It accepts these flags from proj-define-to-cflag: -quote,
+# -zero-undef. It does _not_ support its -list flag.
+#
+# It accepts its non-flag argument(s) in 2 forms: (1) each arg is a
+# single [define] key or (2) its one arg is a list of such keys.
+#
+# TODO: document teaish's well-defined (as it were) defines for this
+# purpose. At a bare minimum:
+#
+# - TEAISH_NAME
+# - TEAISH_PKGNAME
+# - TEAISH_VERSION
+# - TEAISH_LIBDIR_NAME
+# - TEAISH_LOAD_PREFIX
+# - TEAISH_URL
+#
+proc teaish-define-to-cflag {args} {
+ set flags {}
+ while {[string match -* [lindex $args 0]]} {
+ set arg [lindex $args 0]
+ switch -exact -- $arg {
+ -quote -
+ -zero-undef {
+ lappend flags $arg
+ set args [lassign $args -]
+ }
+ default break
+ }
+ }
+ if {1 == [llength $args]} {
+ set args [list {*}[lindex $args 0]]
+ }
+ #puts "***** flags=$flags args=$args"
+ teaish-cflags-add [proj-define-to-cflag {*}$flags {*}$args]
+}
+
+#
+# @teaish-cflags-for-tea ?...CFLAGS?
+#
+# Adds several -DPACKAGE_... CFLAGS using the extension's metadata,
+# all as quoted strings. Those symbolic names are commonly used in
+# TEA-based builds, and this function is intended to simplify porting
+# of such builds. The -D... flags added are:
+#
+# -DPACKAGE_VERSION=...
+# -DPACKAGE_NAME=...
+# -DPACKAGE_URL=...
+# -DPACKAGE_STRING=...
+#
+# Any arguments are passed-on as-is to teaish-cflags-add.
+#
+proc teaish-cflags-for-tea {args} {
+ set name $::teaish__PkgInfo(-name)
+ set version $::teaish__PkgInfo(-version)
+ set pstr [join [list $name $version]]
+ teaish-cflags-add \
+ {*}$args \
+ '-DPACKAGE_VERSION="$version"' \
+ '-DPACKAGE_NAME="$name"' \
+ '-DPACKAGE_STRING="$pstr"' \
+ '-DPACKAGE_URL="[teaish-get -url]"'
+}
+
+#
+# @teaish-ldflags-add ?-p|-prepend? ?-define? ldflags...
+#
+# Equivalent to [proj-define-amend TEAISH_LDFLAGS {*}$args].
+#
+# Typically, -lXYZ flags need to be in "reverse" order, with each -lY
+# resolving symbols for -lX's to its left. This order is largely
+# historical, and not relevant on all environments, but it is
+# technically correct and still relevant on some environments.
+#
+# See: teaish-ldflags-prepend
+#
+proc teaish-ldflags-add {args} {
+ proj-define-amend TEAISH_LDFLAGS {*}$args
+}
+
+#
+# @teaish-ldflags-prepend args...
+#
+# Functionally equivalent to [teaish-ldflags-add -p {*}$args]
+#
+proc teaish-ldflags-prepend {args} {
+ teaish-ldflags-add -p {*}$args
+}
+
+#
+# @teaish-src-add ?-dist? ?-dir? src-files...
+#
+# Appends all non-empty $args to the project's list of C/C++ source or
+# (in some cases) object files.
+#
+# If passed -dist then it also passes each filename, as-is, to
+# [teaish-dist-add].
+#
+# If passed -dir then each src-file has [teaish-get -dir] prepended to
+# it before they're added to the list. As often as not, that will be
+# the desired behavior so that out-of-tree builds can find the
+# sources, but there are cases where it's not desired (e.g. when using
+# a source file from outside of the extension's dir, or when adding
+# object files (which are typically in the build tree)).
+#
+proc teaish-src-add {args} {
+ set i 0
+ proj-parse-simple-flags args flags {
+ -dist 0 {expr 1}
+ -dir 0 {expr 1}
+ }
+ if {$flags(-dist)} {
+ teaish-dist-add {*}$args
+ }
+ if {$flags(-dir)} {
+ set xargs {}
+ foreach arg $args {
+ if {"" ne $arg} {
+ lappend xargs [file join $::teaish__Config(extension-dir) $arg]
+ }
+ }
+ set args $xargs
+ }
+ lappend ::teaish__Config(extension-src) {*}$args
+}
+
+#
+# @teaish-dist-add files-or-dirs...
+#
+# Adds the given files to the list of files to include with the "make
+# dist" rules.
+#
+# This is a no-op when the current build is not in the extension's
+# directory, as dist support is disabled in out-of-tree builds.
+#
+# It is not legal to call this until [teaish-get -dir] has been
+# reliably set (via teaish__find_extension).
+#
+proc teaish-dist-add {args} {
+ if {$::teaish__Config(blddir-is-extdir)} {
+ # ^^^ reminder: we ignore $::teaish__Config(dist-enabled) here
+ # because the client might want to implement their own dist
+ # rules.
+ #proj-warn "**** args=$args"
+ lappend ::teaish__Config(dist-files) {*}$args
+ }
+}
+
+# teaish-install-add files...
+# Equivalent to [proj-define-apend TEAISH_INSTALL_FILES ...].
+#proc teaish-install-add {args} {
+# proj-define-amend TEAISH_INSTALL_FILES {*}$args
+#}
+
+#
+# @teash-make-add args...
+#
+# Appends makefile code to the TEAISH_MAKEFILE_CODE define. Each
+# arg may be any of:
+#
+# -tab: emit a literal tab
+# -nl: emit a literal newline
+# -nltab: short for -nl -tab
+# -bnl: emit a backslash-escaped end-of-line
+# -bnltab: short for -eol -tab
+#
+# Anything else is appended verbatim. This function adds no additional
+# spacing between each argument nor between subsequent invocations.
+# Generally speaking, a series of calls to this function need to
+# be sure to end the series with a newline.
+proc teaish-make-add {args} {
+ set out [get-define TEAISH_MAKEFILE_CODE ""]
+ foreach a $args {
+ switch -exact -- $a {
+ -bnl { set a " \\\n" }
+ -bnltab { set a " \\\n\t" }
+ -tab { set a "\t" }
+ -nl { set a "\n" }
+ -nltab { set a "\n\t" }
+ }
+ append out $a
+ }
+ define TEAISH_MAKEFILE_CODE $out
+}
+
+# Internal helper to generate a clean/distclean rule name
+proc teaish__cleanup_rule {{tgt clean}} {
+ set x [incr ::teaish__Config(teaish__cleanup_rule-counter-${tgt})]
+ return ${tgt}-_${x}_
+}
+
+# @teaish-make-obj objfile srcfile ?...args?
+#
+# Uses teaish-make-add to inject makefile rules for $objfile from
+# $srcfile, which is assumed to be C code which uses libtcl. Unless
+# -recipe is used (see below) it invokes the compiler using the
+# makefile-defined $(CC.tcl) which, in the default Makefile.in
+# template, includes any flags needed for building against the
+# configured Tcl.
+#
+# This always terminates the resulting code with a newline.
+#
+# Any arguments after the 2nd may be flags described below or, if no
+# -recipe is provided, flags for the compiler call.
+#
+# -recipe {...}
+# Uses the trimmed value of {...} as the recipe, prefixing it with
+# a single hard-tab character.
+#
+# -deps {...}
+# List of extra files to list as dependencies of $o. Good luck
+# escaping non-trivial cases properly.
+#
+# -clean
+# Generate cleanup rules as well.
+proc teaish-make-obj {o src args} {
+ set consume 0
+ set clean 0
+ set flag ""
+ array set flags {}
+ set xargs {}
+ foreach arg $args {
+ if {$consume} {
+ set consume 0
+ set flags($flag) $arg
+ continue
+ }
+ switch -exact -- $arg {
+ -clean {incr clean}
+ -recipe -
+ -deps {
+ set flag $arg
+ incr consume
+ }
+ default {
+ lappend xargs $arg
+ }
+ }
+ }
+ teaish-make-add \
+ "# [proj-scope 1] -> [proj-scope] $o $src" -nl \
+ "$o: $src $::teaish__Config(teaish.tcl)"
+ if {[info exists flags(-deps)]} {
+ teaish-make-add " " [join $flags(-deps)]
+ }
+ teaish-make-add -nltab
+ if {[info exists flags(-recipe)]} {
+ teaish-make-add [string trim $flags(-recipe)] -nl
+ } else {
+ teaish-make-add [join [list \$(CC.tcl) -c $src {*}$xargs]] -nl
+ }
+ if {$clean} {
+ set rule [teaish__cleanup_rule]
+ teaish-make-add \
+ "clean: $rule\n$rule:\n\trm -f \"$o\"\n"
+ }
+}
+
+#
+# @teaish-make-clean ?-r? ?-dist? ...files|{...files}
+#
+# Adds makefile rules for cleaning up the given files via the "make
+# clean" or (if -dist is used) "make distclean" makefile rules. The -r
+# flag uses "rm -fr" instead of "rm -f", so be careful with that.
+#
+# The file names are taken literally as arguments to "rm", so they may
+# be shell wildcards to be resolved at cleanup-time. To clean up whole
+# directories, pass the -r flag. Each name gets quoted in
+# double-quotes, so spaces in names should not be a problem (but
+# double-quotes in names will be).
+#
+proc teaish-make-clean {args} {
+ if {1 == [llength $args]} {
+ set args [list {*}[lindex $args 0]]
+ }
+
+ set tgt clean
+ set rmflags "-f"
+ proj-parse-simple-flags args flags {
+ -dist 0 {
+ set tgt distclean
+ }
+ -r 0 {
+ set rmflags "-fr"
+ }
+ }
+ set rule [teaish__cleanup_rule $tgt]
+ teaish-make-add "# [proj-scope 1] -> [proj-scope]: [join $args]\n"
+ teaish-make-add "${rule}:\n\trm ${rmflags}"
+ foreach a $args {
+ teaish-make-add " \"$a\""
+ }
+ teaish-make-add "\n${tgt}: ${rule}\n"
+}
+
+#
+# @teaish-make-config-header filename
+#
+# Invokes autosetup's [make-config-header] and passes it $filename and
+# a relatively generic list of options for controlling which defined
+# symbols get exported. Clients which need more control over the
+# exports can copy/paste/customize this.
+#
+# The exported file is then passed to [proj-touch] because, in
+# practice, that's sometimes necessary to avoid build dependency
+# issues.
+#
+proc teaish-make-config-header {filename} {
+ make-config-header $filename \
+ -none {HAVE_CFLAG_* LDFLAGS_* SH_* TEAISH__* TEAISH_*_CODE} \
+ -auto {SIZEOF_* HAVE_* TEAISH_* TCL_*} \
+ -none *
+ proj-touch $filename; # help avoid frequent unnecessary auto-reconfig
+}
+
+#
+# @teaish-feature-cache-set $key value
+#
+# Sets a feature-check cache entry with the given key.
+# See proj-cache-set for the key's semantics. $key should
+# normally be 0.
+#
+proc teaish-feature-cache-set {key val} {
+ proj-cache-set -key $key -level 1 $val
+}
+
+#
+# @teaish-feature-cache-check key tgtVarName
+#
+# Checks for a feature-check cache entry with the given key.
+# See proj-cache-set for the key's semantics.
+#
+# $key should also almost always be 0 but, due to a tclsh
+# incompatibility in 1 OS, it cannot have a default value unless it's
+# the second argument (but it should be the first one).
+#
+# If the feature-check cache has a matching entry then this function
+# assigns its value to tgtVar and returns 1, else it assigns tgtVar to
+# "" and returns 0.
+#
+# See proj-cache-check for $key's semantics.
+#
+proc teaish-feature-cache-check {key tgtVar} {
+ upvar $tgtVar tgt
+ proj-cache-check -key $key -level 1 tgt
+}
+
+#
+# @teaish-check-cached@ ?flags? msg script...
+#
+# A proxy for feature-test impls which handles caching of a feature
+# flag check on per-function basis, using the calling scope's name as
+# the cache key.
+#
+# It emits [msg-checking $msg]. If $msg is empty then it defaults to
+# the name of the caller's scope. The -nomsg flag suppresses the
+# message for non-cache-hit checks. At the end, it will [msg-result
+# "ok"] [msg-result "no"] unless -nostatus is used, in which case the
+# caller is responsible for emitting at least a newline when it's
+# done. The -msg-0 and -msg-1 flags can be used to change the ok/no
+# text.
+#
+# This function checks for a cache hit before running $script and
+# caching the result. If no hit is found then $script is run in the
+# calling scope and its result value is stored in the cache. This
+# routine will intercept a 'return' from $script.
+#
+# $script may be a command and its arguments, as opposed to a single
+# script block.
+#
+# Flags:
+#
+# -nostatus = do not emit "ok" or "no" at the end. This presumes
+# that either $script will emit at least one newline before
+# returning or the caller will account for it. Because of how this
+# function is typically used, -nostatus is not honored when the
+# response includes a cached result.
+#
+# -quiet = disable output from Autosetup's msg-checking and
+# msg-result for the duration of the $script check. Note that when
+# -quiet is in effect, Autosetup's user-notice can be used to queue
+# up output to appear after the check is done. Also note that
+# -quiet has no effect on _this_ function, only the $script part.
+#
+# -nomsg = do not emit $msg for initial check. Like -nostatus, this
+# flag is not honored when the response includes a cached result
+# because it would otherwise produce no output (which is confusing
+# in this context). This is useful when a check runs several other
+# verbose checks and they emit all the necessary info.
+#
+# -msg-0 and -msg-1 MSG = strings to show when the check has failed
+# resp. passed. Defaults are "no" and "ok". The 0 and 1 refer to the
+# result value from teaish-feature-cache-check.
+#
+# -key cachekey = set the cache context key. Only needs to be
+# explicit when using this function multiple times from a single
+# scope. See proj-cache-check and friends for details on the key
+# name. Its default is the name of the scope which calls this
+# function.
+#
+proc teaish-check-cached {args} {
+ proj-parse-simple-flags args flags {
+ -nostatus 0 {expr 1}
+ -quiet 0 {expr 1}
+ -key => 1
+ -nomsg 0 {expr 1}
+ -msg-0 => no
+ -msg-1 => ok
+ }
+ set args [lassign $args msg]
+ set script [join $args]
+ if {"" eq $msg} {
+ set msg [proj-scope 1]
+ }
+ if {[teaish-feature-cache-check $flags(-key) check]} {
+ #if {0 == $flags(-nomsg)} {
+ msg-checking "${msg} ... (cached) "
+ #}
+ #if {!$flags(-nostatus)} {
+ msg-result $flags(-msg-[expr {0 != ${check}}])
+ #}
+ return $check
+ } else {
+ if {0 == $flags(-nomsg)} {
+ msg-checking "${msg} ... "
+ }
+ if {$flags(-quiet)} {
+ incr ::autosetup(msg-quiet)
+ }
+ set code [catch {uplevel 1 $script} rc xopt]
+ if {$flags(-quiet)} {
+ incr ::autosetup(msg-quiet) -1
+ }
+ #puts "***** cached-check got code=$code rc=$rc"
+ if {$code in {0 2}} {
+ teaish-feature-cache-set 1 $rc
+ if {!$flags(-nostatus)} {
+ msg-result $flags(-msg-[expr {0 != ${rc}}])
+ } else {
+ #show-notices; # causes a phantom newline because we're in a
+ #msg-checking scope, so...
+ if {[info exists ::autosetup(notices)]} {
+ show-notices
+ }
+ }
+ } else {
+ #puts "**** code=$code rc=$rc xopt=$xopt"
+ teaish-feature-cache-set 1 0
+ }
+ #puts "**** code=$code rc=$rc"
+ return {*}$xopt $rc
+ }
+}
+
+#
+# Internal helper for teaish__defs_format_: returns a JSON-ish quoted
+# form of the given string-type values.
+#
+# If $asList is true then the return value is in {$value} form. If
+# $asList is false it only performs the most basic of escaping and
+# the input must not contain any control characters.
+#
+proc teaish__quote_str {asList value} {
+ if {$asList} {
+ return "{${value}}"
+ }
+ return \"[string map [list \\ \\\\ \" \\\"] $value]\"
+}
+
+#
+# Internal helper for teaish__defines_to_list. Expects to be passed
+# a name and the variadic $args which are passed to
+# teaish__defines_to_list.. If it finds a pattern match for the
+# given $name in the various $args, it returns the type flag for that
+# $name, e.g. "-str" or "-bare", else returns an empty string.
+#
+proc teaish__defs_type {name spec} {
+ foreach {type patterns} $spec {
+ foreach pattern $patterns {
+ if {[string match $pattern $name]} {
+ return $type
+ }
+ }
+ }
+ return ""
+}
+
+#
+# An internal impl detail. Requires a data type specifier, as used by
+# Autosetup's [make-config-header], and a value. Returns the formatted
+# value or the value $::teaish__Config(defs-skip) if the caller should
+# skip emitting that value.
+#
+# In addition to -str, -auto, etc., as defined by make-config-header,
+# it supports:
+#
+# -list {...} will cause non-integer values to be quoted in {...}
+# instead of quotes.
+#
+# -autolist {...} works like -auto {...} except that it falls back to
+# -list {...} type instead of -str {...} style for non-integers.
+#
+# -jsarray {...} emits the output in something which, for
+# conservative inputs, will be a valid JSON array. It can only
+# handle relatively simple values with no control characters in
+# them.
+#
+set teaish__Config(defs-skip) "-teaish__defs_format sentinel"
+proc teaish__defs_format {type value} {
+ switch -exact -- $type {
+ -bare {
+ # Just output the value unchanged
+ }
+ -none {
+ set value $::teaish__Config(defs-skip)
+ }
+ -str {
+ set value [teaish__quote_str 0 $value]
+ }
+ -auto {
+ # Automatically determine the type
+ if {![string is integer -strict $value]} {
+ set value [teaish__quote_str 0 $value]
+ }
+ }
+ -autolist {
+ if {![string is integer -strict $value]} {
+ set value [teaish__quote_str 1 $value]
+ }
+ }
+ -list {
+ set value [teaish__quote_str 1 $value]
+ }
+ -jsarray {
+ set ar {}
+ foreach v $value {
+ if {![string is integer -strict $v]} {
+ set v [teaish__quote_str 0 $v]
+ }
+ if {$::teaish__Config(defs-skip) ne $v} {
+ lappend ar $v
+ }
+ }
+ set value [concat \[ [join $ar {, }] \]]
+ }
+ "" {
+ # (Much later:) Why do we do this?
+ set value $::teaish__Config(defs-skip)
+ }
+ default {
+ proj-error \
+ "Unknown [proj-scope] -type ($type) called from" \
+ [proj-scope 1]
+ }
+ }
+ return $value
+}
+
+#
+# Returns Tcl code in the form of code which evaluates to a list of
+# configure-time DEFINEs in the form {key val key2 val...}. It may
+# misbehave for values which are not numeric or simple strings. Some
+# defines are specifically filtered out of the result, either because
+# their irrelevant to teaish or because they may be arbitrarily large
+# (e.g. makefile content).
+#
+# The $args are explained in the docs for internal-use-only
+# [teaish__defs_format]. The default mode is -autolist.
+#
+proc teaish__defines_to_list {args} {
+ set lines {}
+ lappend lines "\{"
+ set skipper $::teaish__Config(defs-skip)
+ set args [list \
+ -none {
+ TEAISH__*
+ TEAISH_*_CODE
+ AM_* AS_*
+ } \
+ {*}$args \
+ -autolist *]
+ foreach d [lsort [dict keys [all-defines]]] {
+ set type [teaish__defs_type $d $args]
+ set value [teaish__defs_format $type [get-define $d]]
+ if {$skipper ne $value} {
+ lappend lines "$d $value"
+ }
+ }
+ lappend lines "\}"
+ tailcall join $lines "\n"
+}
+
+#
+# teaish__pragma ...flags
+#
+# Offers a way to tweak how teaish's core behaves in some cases, in
+# particular those which require changing how the core looks for an
+# extension and its files.
+#
+# Accepts the following flags. Those marked with [L] are safe to use
+# during initial loading of tclish.tcl (recall that most teaish APIs
+# cannot be used until [teaish-configure] is called).
+#
+# static-pkgIndex.tcl [L]: Tells teaish that ./pkgIndex.tcl is not
+# a generated file, so it will not try to overwrite or delete
+# it. Errors out if it does not find pkgIndex.tcl in the
+# extension's dir.
+#
+# no-dist [L]: tells teaish to elide the 'make dist' recipe
+# from the generated Makefile.
+#
+# no-dll [L]: tells teaish to elide the DLL-building recipe
+# from the generated Makefile.
+#
+# no-vsatisfies-error [L]: tells teaish that failure to match the
+# -vsatisfies value should simply "return" instead of "error".
+#
+# no-tester [L]: disables automatic generation of teaish.test.tcl
+# even if a copy of _teaish.tester.tcl.in is found.
+#
+# no-full-dist [L]: changes the "make dist" rules to never include
+# a copy of teaish itself. By default it will include itself only
+# if the extension lives in the same directory as teaish.
+#
+# full-dist [L]: changes the "make dist" rules to always include
+# a copy of teaish itself.
+#
+# Emits a warning message for unknown arguments.
+#
+proc teaish__pragma {args} {
+ foreach arg $args {
+ switch -exact -- $arg {
+
+ static-pkgIndex.tcl {
+ if {$::teaish__Config(tm-policy)} {
+ proj-fatal -up "Cannot use pragma $arg together with -tm.tcl or -tm.tcl.in."
+ }
+ set tpi [file join $::teaish__Config(extension-dir) pkgIndex.tcl]
+ if {[file exists $tpi]} {
+ define TEAISH_PKGINDEX_TCL_IN ""
+ define TEAISH_PKGINDEX_TCL $tpi
+ set ::teaish__Config(pkgindex-policy) 0x20
+ } else {
+ proj-error "pragma $arg: found no package-local pkgIndex.tcl\[.in]"
+ }
+ }
+
+ no-dist {
+ set ::teaish__Config(dist-enabled) 0
+ }
+
+ no-install {
+ set ::teaish__Config(install-enabled) 0
+ }
+
+ full-dist {
+ set ::teaish__Config(dist-full-enabled) 1
+ }
+
+ no-full-dist {
+ set ::teaish__Config(dist-full-enabled) 0
+ }
+
+ no-dll {
+ set ::teaish__Config(dll-enabled) 0
+ }
+
+ no-vsatisfies-error {
+ set ::teaish__Config(vsatisfies-error) 0
+ }
+
+ no-tester {
+ define TEAISH_TESTER_TCL_IN ""
+ define TEAISH_TESTER_TCL ""
+ }
+
+ default {
+ proj-error "Unknown flag: $arg"
+ }
+ }
+ }
+}
+
+#
+# @teaish-pkginfo-set ...flags
+#
+# The way to set up the initial package state. Used like:
+#
+# teaish-pkginfo-set -name foo -version 0.1.2
+#
+# Or:
+#
+# teaish-pkginfo-set ?-vars|-subst? {-name foo -version 0.1.2}
+#
+# The latter may be easier to write for a multi-line invocation.
+#
+# For the second call form, passing the -vars flag tells it to perform
+# a [subst] of (only) variables in the {...} part from the calling
+# scope. The -subst flag will cause it to [subst] the {...} with
+# command substitution as well (but no backslash substitution). When
+# using -subst for string concatenation, e.g. with -libDir
+# foo[get-version-number], be sure to wrap the value in braces:
+# -libDir {foo[get-version-number]}.
+#
+# Each pkginfo flag corresponds to one piece of extension package
+# info. Teaish provides usable default values for all of these flags,
+# but at least the -name and -version should be set by clients.
+# e.g. the default -name is the directory name the extension lives in,
+# which may change (e.g. when building it from a "make dist" bundle).
+#
+# The flags:
+#
+# -name theName: The extension's name. It defaults to the name of the
+# directory containing the extension. (In TEA this would be the
+# PACKAGE_NAME, not to be confused with...)
+#
+# -name.pkg pkg-provide-name: The extension's name for purposes of
+# Tcl_PkgProvide(), [package require], and friends. It defaults to
+# the `-name`, and is normally the same, but some projects (like
+# SQLite) have a different name here than they do in their
+# historical TEA PACKAGE_NAME.
+#
+# -version version: The extension's package version. Defaults to
+# 0.0.0.
+#
+# -libDir dirName: The base name of the directory into which this
+# extension should be installed. It defaults to a concatenation of
+# `-name.pkg` and `-version`.
+#
+# -loadPrefix prefix: For use as the second argument passed to
+# Tcl's `load` command in the package-loading process. It defaults
+# to title-cased `-name.pkg` because Tcl's `load` plugin system
+# expects it in that form.
+#
+# -options {...}: If provided, it must be a list compatible with
+# Autosetup's `options-add` function. These can also be set up via
+# `teaish-options`.
+#
+# -vsatisfies {{...} ...}: Expects a list-of-lists of conditions
+# for Tcl's `package vsatisfies` command: each list entry is a
+# sub-list of `{PkgName Condition...}`. Teaish inserts those
+# checks via its default pkgIndex.tcl.in and _teaish.tester.tcl.in
+# templates to verify that the system's package dependencies meet
+# these requirements. The default value is `{{Tcl 8.5-}}` (recall
+# that it's a list-of-lists), as 8.5 is the minimum Tcl version
+# teaish will run on, but some extensions may require newer
+# versions or dependencies on other packages. As a special case,
+# if `-vsatisfies` is given a single token, e.g. `8.6-`, then it
+# is transformed into `{Tcl $thatToken}`, i.e. it checks the Tcl
+# version which the package is being run with. If given multiple
+# lists, each `package provides` check is run in the given
+# order. Failure to meet a `vsatisfies` condition triggers an
+# error.
+#
+# -url {...}: an optional URL for the extension.
+#
+# -pragmas {...} A list of infrequently-needed lower-level
+# directives which can influence teaish, including:
+#
+# static-pkgIndex.tcl: tells teaish that the client manages their
+# own pkgIndex.tcl, so that teaish won't try to overwrite it
+# using a template.
+#
+# no-dist: tells teaish to elide the "make dist" recipe from the
+# makefile so that the client can implement it.
+#
+# no-dll: tells teaish to elide the makefile rules which build
+# the DLL, as well as any templated test script and pkgIndex.tcl
+# references to them. The intent here is to (A) support
+# client-defined build rules for the DLL and (B) eventually
+# support script-only extensions.
+#
+# Unsupported flags or pragmas will trigger an error.
+#
+# Potential pothole: setting certain state, e.g. -version, after the
+# initial call requires recalculating of some [define]s. Any such
+# changes should be made as early as possible in teaish-configure so
+# that any later use of those [define]s gets recorded properly (not
+# with the old value). This is particularly relevant when it is not
+# possible to determine the -version or -name until teaish-configure
+# has been called, and it's updated dynamically from
+# teaish-configure. Notably:
+#
+# - If -version or -name are updated, -libDir will almost certainly
+# need to be explicitly set along with them.
+#
+# - If -name is updated, -loadPrefix probably needs to be as well.
+#
+proc teaish-pkginfo-set {args} {
+ set doVars 0
+ set doCommands 0
+ set xargs $args
+ set recalc {}
+ foreach arg $args {
+ switch -exact -- $arg {
+ -vars {
+ incr doVars
+ set xargs [lassign $xargs -]
+ }
+ -subst {
+ incr doVars
+ incr doCommands
+ set xargs [lassign $xargs -]
+ }
+ default {
+ break
+ }
+ }
+ }
+ set args $xargs
+ unset xargs
+ if {1 == [llength $args] && [llength [lindex $args 0]] > 1} {
+ # Transform a single {...} arg into the canonical call form
+ set a [list {*}[lindex $args 0]]
+ if {$doVars || $doCommands} {
+ set sflags -nobackslashes
+ if {!$doCommands} {
+ lappend sflags -nocommands
+ }
+ set a [uplevel 1 [list subst {*}$sflags $a]]
+ }
+ set args $a
+ }
+ set sentinel "<nope>"
+ set flagDefs [list]
+ foreach {f d} $::teaish__Config(pkginfo-f2d) {
+ lappend flagDefs $f => $sentinel
+ }
+ proj-parse-simple-flags args flags $flagDefs
+ if {[llength $args]} {
+ proj-error -up "Too many (or unknown) arguments to [proj-scope]: $args"
+ }
+ foreach {f d} $::teaish__Config(pkginfo-f2d) {
+ if {$sentinel eq [set v $flags($f)]} continue
+ switch -exact -- $f {
+
+ -options {
+ proj-assert {"" eq $d}
+ options-add $v
+ }
+
+ -pragmas {
+ teaish__pragma {*}$v
+ }
+
+ -vsatisfies {
+ if {1 == [llength $v] && 1 == [llength [lindex $v 0]]} {
+ # Transform X to {Tcl $X}
+ set v [list [join [list Tcl $v]]]
+ }
+ define $d $v
+ }
+
+ -pkgInit.tcl -
+ -pkgInit.tcl.in {
+ if {0x22 & $::teaish__Config(pkginit-policy)} {
+ proj-fatal "Cannot use -pkgInit.tcl(.in) more than once."
+ }
+ set x [file join $::teaish__Config(extension-dir) $v]
+ set tTail [file tail $v]
+ if {"-pkgInit.tcl.in" eq $f} {
+ # Generate pkginit file X from X.in
+ set pI 0x02
+ set tIn $x
+ set tOut [file rootname $tTail]
+ set other -pkgInit.tcl
+ } else {
+ # Static pkginit file X
+ set pI 0x20
+ set tIn ""
+ set tOut $x
+ set other -pkgInit.tcl.in
+ }
+ set ::teaish__Config(pkginit-policy) $pI
+ set ::teaish__PkgInfo($other) {}
+ define TEAISH_PKGINIT_TCL_IN $tIn
+ define TEAISH_PKGINIT_TCL $tOut
+ define TEAISH_PKGINIT_TCL_TAIL $tTail
+ teaish-dist-add $v
+ set v $x
+ }
+
+ -tm.tcl -
+ -tm.tcl.in {
+ if {0x30 & $::teaish__Config(pkgindex-policy)} {
+ proj-fatal "Cannot use $f together with a pkgIndex.tcl."
+ } elseif {$::teaish__Config(tm-policy)} {
+ proj-fatal "Cannot use -tm.tcl(.in) more than once."
+ }
+ set x [file join $::teaish__Config(extension-dir) $v]
+ if {"-tm.tcl.in" eq $f} {
+ # Generate tm file X from X.in
+ set pT 0x02
+ set pI 0x100
+ set tIn $x
+ set tOut [file rootname [file tail $v]]
+ set other -tm.tcl
+ } else {
+ # Static tm file X
+ set pT 0x20
+ set pI 0x200
+ set tIn ""
+ set tOut $x
+ set other -tm.tcl.in
+ }
+ set ::teaish__Config(pkgindex-policy) $pI
+ set ::teaish__Config(tm-policy) $pT
+ set ::teaish__PkgInfo($other) {}
+ define TEAISH_TM_TCL_IN $tIn
+ define TEAISH_TM_TCL $tOut
+ define TEAISH_PKGINDEX_TCL ""
+ define TEAISH_PKGINDEX_TCL_IN ""
+ define TEAISH_PKGINDEX_TCL_TAIL ""
+ teaish-dist-add $v
+ teaish__pragma no-dll
+ set v $x
+ }
+
+ default {
+ proj-assert {"" ne $d}
+ define $d $v
+ }
+ }
+ set ::teaish__PkgInfo($f) $v
+ if {$f in {-name -version -libDir -loadPrefix}} {
+ lappend recalc $f
+ }
+ }
+ if {"" ne $recalc} {
+ teaish__define_pkginfo_derived $recalc
+ }
+}
+
+#
+# @teaish-pkginfo-get ?arg?
+#
+# If passed no arguments, it returns the extension config info in the
+# same form accepted by teaish-pkginfo-set.
+#
+# If passed one -flagname arg then it returns the value of that config
+# option.
+#
+# Else it treats arg as the name of caller-scoped variable to
+# which this function assigns an array containing the configuration
+# state of this extension, in the same structure accepted by
+# teaish-pkginfo-set. In this case it returns an empty string.
+#
+proc teaish-pkginfo-get {args} {
+ set cases {}
+ set argc [llength $args]
+ set rv {}
+ switch -exact $argc {
+ 0 {
+ # Return a list of (-flag value) pairs
+ lappend cases default {{
+ if {[info exists ::teaish__PkgInfo($flag)]} {
+ lappend rv $flag $::teaish__PkgInfo($flag)
+ } else {
+ lappend rv $flag [get-define $defName]
+ }
+ }}
+ }
+
+ 1 {
+ set arg $args
+ if {[string match -* $arg]} {
+ # Return the corresponding -flag's value
+ lappend cases $arg {{
+ if {[info exists ::teaish__PkgInfo($flag)]} {
+ return $::teaish__PkgInfo($flag)
+ } else {
+ return [get-define $defName]
+ }
+ }}
+ } else {
+ # Populate target with an array of (-flag value).
+ upvar $arg tgt
+ array set tgt {}
+ lappend cases default {{
+ if {[info exists ::teaish__PkgInfo($flag)]} {
+ set tgt($flag) $::teaish__PkgInfo($flag)
+ } else {
+ set tgt($flag) [get-define $defName]
+ }
+ }}
+ }
+ }
+
+ default {
+ proj-error "invalid arg count from [proj-scope 1]"
+ }
+ }
+
+ foreach {flag defName} $::teaish__Config(pkginfo-f2d) {
+ switch -exact -- $flag [join $cases]
+ }
+ if {0 == $argc} { return $rv }
+}
+
+# (Re)set some defines based on pkginfo state. $flags is the list of
+# pkginfo -flags which triggered this, or "*" for the initial call.
+proc teaish__define_pkginfo_derived {flags} {
+ set all [expr {{*} in $flags}]
+ if {$all || "-version" in $flags || "-name" in $flags} {
+ set name $::teaish__PkgInfo(-name) ; # _not_ -name.pkg
+ if {[info exists ::teaish__PkgInfo(-version)]} {
+ set pkgver $::teaish__PkgInfo(-version)
+ set libname "lib"
+ if {[string match *-cygwin [get-define host]]} {
+ set libname cyg
+ }
+ define TEAISH_DLL8_BASENAME $libname$name$pkgver
+ define TEAISH_DLL9_BASENAME ${libname}tcl9$name$pkgver
+ set ext [get-define TARGET_DLLEXT]
+ define TEAISH_DLL8 [get-define TEAISH_DLL8_BASENAME]$ext
+ define TEAISH_DLL9 [get-define TEAISH_DLL9_BASENAME]$ext
+ }
+ }
+ if {$all || "-libDir" in $flags} {
+ if {[info exists ::teaish__PkgInfo(-libDir)]} {
+ define TCLLIBDIR \
+ [file dirname [get-define TCLLIBDIR]]/$::teaish__PkgInfo(-libDir)
+ }
+ }
+}
+
+#
+# @teaish-checks-queue -pre|-post args...
+#
+# Queues one or more arbitrary "feature test" functions to be run when
+# teaish-checks-run is called. $flag must be one of -pre or -post to
+# specify whether the tests should be run before or after
+# teaish-configure is run. Each additional arg is the name of a
+# feature-test proc.
+#
+proc teaish-checks-queue {flag args} {
+ if {$flag ni {-pre -post}} {
+ proj-error "illegal flag: $flag"
+ }
+ lappend ::teaish__Config(queued-checks${flag}) {*}$args
+}
+
+#
+# @teaish-checks-run -pre|-post
+#
+# Runs all feature checks queued using teaish-checks-queue
+# then cleares the queue.
+#
+proc teaish-checks-run {flag} {
+ if {$flag ni {-pre -post}} {
+ proj-error "illegal flag: $flag"
+ }
+ #puts "*** running $flag: $::teaish__Config(queued-checks${flag})"
+ set foo 0
+ foreach f $::teaish__Config(queued-checks${flag}) {
+ if {![teaish-feature-cache-check $f foo]} {
+ set v [$f]
+ teaish-feature-cache-set $f $v
+ }
+ }
+ set ::teaish__Config(queued-checks${flag}) {}
+}
+
+#
+# A general-purpose getter for various teaish state. Requires one
+# flag, which determines its result value. Flags marked with [L] below
+# are safe for using at load-time, before teaish-pkginfo-set is called
+#
+# -dir [L]: returns the extension's directory, which may differ from
+# the teaish core dir or the build dir.
+#
+# -teaish-home [L]: the "home" dir of teaish itself, which may
+# differ from the extension dir or build dir.
+#
+# -build-dir [L]: the build directory (typically the current working
+# -dir).
+#
+# Any of the teaish-pkginfo-get/get flags: returns the same as
+# teaish-pkginfo-get. Not safe for use until teaish-pkginfo-set has
+# been called.
+#
+# Triggers an error if passed an unknown flag.
+#
+proc teaish-get {flag} {
+ #-teaish.tcl {return $::teaish__Config(teaish.tcl)}
+ switch -exact -- $flag {
+ -dir {
+ return $::teaish__Config(extension-dir)
+ }
+ -teaish-home {
+ return $::autosetup(srcdir)
+ }
+ -build-dir {
+ return $::autosetup(builddir)
+ }
+ default {
+ if {[info exists ::teaish__PkgInfo($flag)]} {
+ return $::teaish__PkgInfo($flag)
+ }
+ }
+ }
+ proj-error "Unhandled flag: $flag"
+}
+
+#
+# Handles --teaish-create-extension=TARGET-DIR
+#
+proc teaish__create_extension {dir} {
+ set force [opt-bool teaish-force]
+ if {"" eq $dir} {
+ proj-error "--teaish-create-extension=X requires a directory name."
+ }
+ file mkdir $dir/generic
+ set cwd [pwd]
+ #set dir [file-normalize [file join $cwd $dir]]
+ teaish__verbose 1 msg-result "Created dir $dir"
+ cd $dir
+ if {!$force} {
+ # Ensure that we don't blindly overwrite anything
+ foreach f {
+ generic/teaish.c
+ teaish.tcl
+ teaish.make.in
+ teaish.test.tcl
+ } {
+ if {[file exists $f]} {
+ error "Cowardly refusing to overwrite $dir/$f. Use --teaish-force to overwrite."
+ }
+ }
+ }
+
+ set name [file tail $dir]
+ set pkgName $name
+ set version 0.0.1
+ set loadPrefix [string totitle $pkgName]
+ set content {teaish-pkginfo-set }
+ #puts "0 content=$content"
+ if {[opt-str teaish-extension-pkginfo epi]} {
+ set epi [string trim $epi]
+ if {[string match "*\n*" $epi]} {
+ set epi "{$epi}"
+ } elseif {![string match "{*}" $epi]} {
+ append content "\{" $epi "\}"
+ } else {
+ append content $epi
+ }
+ #puts "2 content=$content\nepi=$epi"
+ } else {
+ append content [subst -nocommands -nobackslashes {{
+ -name ${name}
+ -name.pkg ${pkgName}
+ -name.dist ${pkgName}
+ -version ${version}
+ -loadPrefix $loadPrefix
+ -libDir ${name}${version}
+ -vsatisfies {{Tcl 8.5-}}
+ -url {}
+ -options {}
+ -pragmas {full-dist}
+ }}]
+ #puts "3 content=$content"
+ }
+ #puts "1 content=$content"
+ append content "\n" {
+#proc teaish-options {} {
+ # Return a list and/or use \[options-add\] to add new
+ # configure flags. This is called before teaish's
+ # bootstrapping is finished, so only teaish-*
+ # APIs which are explicitly noted as being safe
+ # early on may be used here. Any autosetup-related
+ # APIs may be used here.
+ #
+ # Return an empty string if there are no options to
+ # add or if they are added using \[options-add\].
+ #
+ # If there are no options to add, this proc need
+ # not be defined.
+#}
+
+# Called by teaish once bootstrapping is complete.
+# This function is responsible for the client-specific
+# parts of the configuration process.
+proc teaish-configure {} {
+ teaish-src-add -dir -dist generic/teaish.c
+ teaish-define-to-cflag -quote TEAISH_PKGNAME TEAISH_VERSION
+
+ # TODO: your code goes here..
+}
+}; # $content
+ proj-file-write teaish.tcl $content
+ teaish__verbose 1 msg-result "Created teaish.tcl"
+
+ set content "# Teaish test script.
+# When this tcl script is invoked via 'make test' it will have loaded
+# the package, run any teaish.pkginit.tcl code, and loaded
+# autosetup/teaish/tester.tcl.
+"
+ proj-file-write teaish.test.tcl $content
+ teaish__verbose 1 msg-result "Created teaish.test.tcl"
+
+ set content [subst -nocommands -nobackslashes {
+#include <tcl.h>
+static int
+${loadPrefix}_Cmd(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("this is the ${name} extension", -1));
+ return TCL_OK;
+}
+
+extern int DLLEXPORT ${loadPrefix}_Init(Tcl_Interp *interp){
+ if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
+ return TCL_ERROR;
+ }
+ if (Tcl_PkgProvide(interp, TEAISH_PKGNAME, TEAISH_VERSION) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+ Tcl_CreateObjCommand(interp, TEAISH_PKGNAME, ${loadPrefix}_Cmd, NULL, NULL);
+ return TCL_OK;
+}
+}]
+ proj-file-write generic/teaish.c $content
+ teaish__verbose 1 msg-result "Created generic/teaish.c"
+
+ set content "# teaish makefile for the ${name} extension
+# tx.src = \$(tx.dir)/generic/teaish.c
+# tx.LDFLAGS =
+# tx.CFLAGS =
+"
+ proj-file-write teaish.make.in $content
+ teaish__verbose 1 msg-result "Created teaish.make.in"
+
+ msg-result "Created new extension \[$dir\]."
+
+ cd $cwd
+ set ::teaish__Config(install-ext-dir) $dir
+}
+
+#
+# Internal helper for teaish__install
+#
+proc teaish__install_file {f destDir force} {
+ set dest $destDir/[file tail $f]
+ if {[file isdirectory $f]} {
+ file mkdir $dest
+ } elseif {!$force && [file exists $dest]} {
+ array set st1 [file stat $f]
+ array set st2 [file stat $dest]
+ if {($st1(mtime) == $st2(mtime))
+ && ($st1(size) == $st2(size))} {
+ if {[file tail $f] in {
+ pkgIndex.tcl.in
+ _teaish.tester.tcl.in
+ }} {
+ # Assume they're the same. In the scope of the "make dist"
+ # rules, this happens legitimately when an extension with a
+ # copy of teaish installed in the same dir assumes that the
+ # pkgIndex.tcl.in and _teaish.tester.tcl.in belong to the
+ # extension, whereas teaish believes they belong to teaish.
+ # So we end up with dupes of those.
+ return
+ }
+ }
+ proj-error -up "Cowardly refusing to overwrite \[$dest\]." \
+ "Use --teaish-force to enable overwriting."
+ } else {
+ # file copy -force $f $destDir; # loses +x bit
+ #
+ # JimTcl doesn't have [file attribute], so we can't use that here
+ # (in the context of an autosetup configure script).
+ exec cp -p $f $dest
+ }
+}
+
+#
+# Installs a copy of teaish, with autosetup, to $dDest, which defaults
+# to the --teaish-install=X or --teash-create-extension=X dir. Won't
+# overwrite files unless --teaish-force is used.
+#
+proc teaish__install {{dDest ""}} {
+ if {$dDest in {auto ""}} {
+ set dDest [opt-val teaish-install]
+ if {$dDest in {auto ""}} {
+ if {[info exists ::teaish__Config(install-ext-dir)]} {
+ set dDest $::teaish__Config(install-ext-dir)
+ }
+ }
+ }
+ set force [opt-bool teaish-force]
+ if {$dDest in {auto ""}} {
+ proj-error "Cannot determine installation directory."
+ } elseif {!$force && [file exists $dDest/auto.def]} {
+ proj-error \
+ "Target dir looks like it already contains teaish and/or autosetup: $dDest" \
+ "\nUse --teaish-force to overwrite it."
+ }
+
+ set dSrc $::autosetup(srcdir)
+ set dAS $::autosetup(libdir)
+ set dAST $::teaish__Config(core-dir)
+ set dASTF $dAST/feature
+ teaish__verbose 1 msg-result "Installing teaish to \[$dDest\]..."
+ if {$::teaish__Config(verbose)>1} {
+ msg-result "dSrc = $dSrc"
+ msg-result "dAS = $dAS"
+ msg-result "dAST = $dAST"
+ msg-result "dASTF = $dASTF"
+ msg-result "dDest = $dDest"
+ }
+
+ # Dest subdirs...
+ set ddAS $dDest/autosetup
+ set ddAST $ddAS/teaish
+ set ddASTF $ddAST/feature
+ foreach {srcDir destDir} [list \
+ $dAS $ddAS \
+ $dAST $ddAST \
+ $dASTF $ddASTF \
+ ] {
+ teaish__verbose 1 msg-result "Copying files to $destDir..."
+ file mkdir $destDir
+ foreach f [glob -directory $srcDir *] {
+ if {[string match {*~} $f] || [string match "#*#" [file tail $f]]} {
+ # Editor-generated backups and emacs lock files
+ continue
+ }
+ teaish__verbose 2 msg-result "\t$f"
+ teaish__install_file $f $destDir $force
+ }
+ }
+ teaish__verbose 1 msg-result "Copying files to $dDest..."
+ foreach f {
+ auto.def configure Makefile.in pkgIndex.tcl.in
+ _teaish.tester.tcl.in
+ } {
+ teaish__verbose 2 msg-result "\t$f"
+ teaish__install_file $dSrc/$f $dDest $force
+ }
+ set ::teaish__Config(install-self-dir) $dDest
+ msg-result "Teaish $::teaish__Config(version) installed in \[$dDest\]."
+}