aboutsummaryrefslogtreecommitdiff
path: root/autosetup
diff options
context:
space:
mode:
Diffstat (limited to 'autosetup')
-rw-r--r--autosetup/proj.tcl135
-rw-r--r--autosetup/teaish/README.txt4
-rw-r--r--autosetup/teaish/core.tcl2537
-rw-r--r--autosetup/teaish/feature.tcl214
-rw-r--r--autosetup/teaish/tester.tcl228
5 files changed, 3063 insertions, 55 deletions
diff --git a/autosetup/proj.tcl b/autosetup/proj.tcl
index 4691cfe36..133556706 100644
--- a/autosetup/proj.tcl
+++ b/autosetup/proj.tcl
@@ -60,8 +60,8 @@
# $proj__Config is an internal-use-only array for storing whatever generic
# internal stuff we need stored.
#
-array set proj__Config {
- self-tests 0
+array set ::proj__Config {
+ self-tests 1
}
@@ -74,8 +74,8 @@ array set proj__Config {
#
# See: proj-dot-ins-append and proj-dot-ins-process
#
-set proj__Config(dot-in-files) [list]
-set proj__Config(isatty) [isatty? stdout]
+set ::proj__Config(dot-in-files) [list]
+set ::proj__Config(isatty) [isatty? stdout]
#
# @proj-warn msg
@@ -88,6 +88,25 @@ proc proj-warn {args} {
puts stderr [join [list "WARNING: \[[proj-scope 1]\]: " {*}$args] " "]
}
+
+# Internal impl of [proj-fatal] and [proj-error]. It must be called
+# using tailcall.
+proc proj__faterr {failMode argv} {
+ show-notices
+ set lvl 1
+ while {"-up" eq [lindex $argv 0]} {
+ set argv [lassign $argv -]
+ incr lvl
+ }
+ if {$failMode} {
+ puts stderr [join [list "FATAL: \[[proj-scope $lvl]]: " {*}$argv]]
+ exit 1
+ } else {
+ error [join [list "\[[proj-scope $lvl]]:" {*}$argv]]
+ }
+}
+
+
#
# @proj-fatal ?-up...? msg...
#
@@ -99,31 +118,19 @@ proc proj-warn {args} {
# additional level.
#
proc proj-fatal {args} {
- show-notices
- set lvl 1
- while {"-up" eq [lindex $args 0]} {
- set args [lassign $args -]
- incr lvl
- }
- puts stderr [join [list "FATAL: \[[proj-scope $lvl]]: " {*}$args]]
- exit 1
+ tailcall proj__faterr 1 $args
}
#
# @proj-error ?-up...? msg...
#
-# Works like prop-fatal but uses [error] intead of [exit].
+# Works like proj-fatal but uses [error] intead of [exit].
#
proc proj-error {args} {
- show-notices
- set lvl 1
- while {"-up" eq [lindex $args 0]} {
- set args [lassign $args -]
- incr lvl
- }
- error [join [list "\[[proj-scope $lvl]]:" {*}$args]]
+ tailcall proj__faterr 0 $args
}
+set ::proj__Config(verbose-assert) [get-env proj-assert-verbose 0]
#
# @proj-assert script ?message?
#
@@ -133,7 +140,7 @@ proc proj-error {args} {
# used instead.
#
proc proj-assert {script {msg ""}} {
- if {1 == [get-env proj-assert 0]} {
+ if {1 eq $::proj__Config(verbose-assert)} {
msg-result [proj-bold "asserting: $script"]
}
if {![uplevel 1 [list expr $script]]} {
@@ -162,7 +169,9 @@ proc proj-bold {args} {
# @proj-indented-notice ?-error? ?-notice? msg
#
# Takes a multi-line message and emits it with consistent indentation.
-# It does not perform any line-wrapping of its own.
+# It does not perform any line-wrapping of its own. Which output
+# routine it uses depends on its flags, defaulting to msg-result.
+# For -error and -notice it uses user-notice.
#
# If the -notice flag it used then it emits using [user-notice], which
# means its rendering will (A) go to stderr and (B) be delayed until
@@ -176,7 +185,7 @@ proc proj-bold {args} {
#
proc proj-indented-notice {args} {
set fErr ""
- set outFunc "puts"
+ set outFunc "msg-result"
while {[llength $args] > 1} {
switch -exact -- [lindex $args 0] {
-error {
@@ -632,7 +641,7 @@ proc proj-no-check-module-loader {} {
}
#
-# @proj-file-conent ?-trim? filename
+# @proj-file-content ?-trim? filename
#
# Opens the given file, reads all of its content, and returns it. If
# the first arg is -trim, the contents of the file named by the second
@@ -701,10 +710,10 @@ proc proj-file-write {args} {
# argument it is assumed to be the name of an autosetup boolean config
# which controls whether to run/skip this check.
#
-# Returns 1 if supported, else 0. Defines MAKE_COMPILATION_DB to "yes"
-# if supported, "no" if not. The use of MAKE_COMPILATION_DB is
-# deprecated/discouraged. It also sets HAVE_COMPILE_COMMANDS to 0 or
-# 1, and that's the preferred usage.
+# Returns 1 if supported, else 0, and defines HAVE_COMPILE_COMMANDS to
+# that value. Defines MAKE_COMPILATION_DB to "yes" if supported, "no"
+# if not. The use of MAKE_COMPILATION_DB is deprecated/discouraged:
+# HAVE_COMPILE_COMMANDS is preferred.
#
# ACHTUNG: this test has a long history of false positive results
# because of compilers reacting differently to the -MJ flag.
@@ -713,6 +722,7 @@ proc proj-check-compile-commands {{configFlag {}}} {
msg-checking "compile_commands.json support... "
if {"" ne $configFlag && ![proj-opt-truthy $configFlag]} {
msg-result "explicitly disabled"
+ define HAVE_COMPILE_COMMANDS 0
define MAKE_COMPILATION_DB no
return 0
} else {
@@ -787,7 +797,12 @@ proc proj-make-from-dot-in {args} {
catch { exec chmod u+w $fOut }
}
#puts "making template: $fIn ==> $fOut"
- make-template $fIn $fOut
+ #define-push {top_srcdir} {
+ #puts "--- $fIn $fOut top_srcdir=[get-define top_srcdir]"
+ make-template $fIn $fOut
+ #puts "--- $fIn $fOut top_srcdir=[get-define top_srcdir]"
+ # make-template modifies top_srcdir
+ #}
if {$touch} {
proj-touch $fOut
}
@@ -1220,7 +1235,7 @@ proc proj-quote-str_ {value} {
# the formatted value or the value $::proj__Config(defs-skip) if the caller
# should skip emitting that value.
#
-set proj__Config(defs-skip) "-proj-defs-format_ sentinel"
+set ::proj__Config(defs-skip) "-proj-defs-format_ sentinel"
proc proj-defs-format_ {type value} {
switch -exact -- $type {
-bare {
@@ -1259,6 +1274,8 @@ proc proj-defs-format_ {type value} {
}
#
+# @proj-dump-defs-json outfile ...flags
+#
# This function works almost identically to autosetup's
# make-config-header but emits its output in JSON form. It is not a
# fully-functional JSON emitter, and will emit broken JSON for
@@ -1954,10 +1971,12 @@ if {0} {
array set proj__Cache {}
#
-# @proj-cache-key ?addLevel? arg
+# @proj-cache-key arg {addLevel 0}
#
# Helper to generate cache keys for [proj-cache-*].
#
+# $addLevel should almost always be 0.
+#
# Returns a cache key for the given argument:
#
# integer: relative call stack levels to get the scope name of for
@@ -1965,12 +1984,9 @@ array set proj__Cache {}
# then used to generate the key. i.e. the default of 0 uses the
# calling scope's name as the key.
#
-# "-": same as 0
-#
# Anything else: returned as-is
#
-proc proj-cache-key {{addLevel 0} arg} {
- if {"-" eq $arg} {set arg 0}
+proc proj-cache-key {arg {addLevel 0}} {
if {[string is integer -strict $arg]} {
return [proj-scope [expr {$arg + $addLevel + 1}]]
}
@@ -1978,14 +1994,19 @@ proc proj-cache-key {{addLevel 0} arg} {
}
#
-# @proj-cache-set ?key? ?addLevel? value
+# @proj-cache-set ?-key KEY? ?-level 0? value
#
# Sets a feature-check cache entry with the given key.
#
-# See proj-cache-key for $key's and $addLevel's semantics, noting that
-# this function adds one to $addLevel for purposes of that call.
-proc proj-cache-set {{key 0} {addLevel 0} val} {
- set key [proj-cache-key [expr {1 + $addLevel}] $key]
+# See proj-cache-key for -key's and -level's semantics, noting that
+# this function adds one to -level for purposes of that call.
+proc proj-cache-set {args} {
+ proj-parse-simple-flags args flags {
+ -key => 0
+ -level => 0
+ }
+ lassign $args val
+ set key [proj-cache-key $flags(-key) [expr {1 + $flags(-level)}]]
#puts "** fcheck set $key = $val"
set ::proj__Cache($key) $val
}
@@ -1995,7 +2016,7 @@ proc proj-cache-set {{key 0} {addLevel 0} val} {
#
# Removes an entry from the proj-cache.
proc proj-cache-remove {{key 0} {addLevel 0}} {
- set key [proj-cache-key [expr {1 + $addLevel}] $key]
+ set key [proj-cache-key $key [expr {1 + $addLevel}]]
set rv ""
if {[info exists ::proj__Cache($key)]} {
set rv $::proj__Cache($key)
@@ -2005,7 +2026,7 @@ proc proj-cache-remove {{key 0} {addLevel 0}} {
}
#
-# @proj-cache-check ?$key? ?addLevel? tgtVarName
+# @proj-cache-check ?-key KEY? ?-level LEVEL? tgtVarName
#
# Checks for a feature-check cache entry with the given key.
#
@@ -2015,10 +2036,15 @@ proc proj-cache-remove {{key 0} {addLevel 0}} {
#
# See proj-cache-key for $key's and $addLevel's semantics, noting that
# this function adds one to $addLevel for purposes of that call.
-proc proj-cache-check {{key 0} {addLevel 0} tgtVar} {
+proc proj-cache-check {args} {
+ proj-parse-simple-flags args flags {
+ -key => 0
+ -level => 0
+ }
+ lassign $args tgtVar
upvar $tgtVar tgt
set rc 0
- set key [proj-cache-key [expr {1 + $addLevel}] $key]
+ set key [proj-cache-key $flags(-key) [expr {1 + $flags(-level)}]]
#puts "** fcheck get key=$key"
if {[info exists ::proj__Cache($key)]} {
set tgt $::proj__Cache($key)
@@ -2046,8 +2072,6 @@ proc proj-coalesce {args} {
#
# @proj-parse-simple-flags ...
#
-# An experiment. Do not use.
-#
# A helper to parse flags from proc argument lists.
#
# Expects a list of arguments to parse, an array name to store any
@@ -2097,19 +2121,20 @@ proc proj-coalesce {args} {
#
# Example:
#
-# set args [list -foo -bar {blah} 8 9 10]
-# set args [proj-parse-simple-flags args flags {
+# set args [list -foo -bar {blah} 8 9 10 -theEnd]
+# proj-parse-simple-flags args flags {
# -foo 0 {expr 1}
# -bar => 0
# -no-baz 2 {return 0}
# }
#
# After that $flags would contain {-foo 1 -bar {blah} -no-baz 2}
-# and $args would be {8 9 10}.
+# and $args would be {8 9 10 -theEnd}.
#
# Potential TODOs: consider using lappend instead of set so that any
# given flag can be used more than once. Or add a syntax to indicate
-# that.
+# that multiples are allowed. Also consider searching the whole
+# argv list, rather than stopping at the first non-flag
#
proc proj-parse-simple-flags {argvName tgtArrayName prototype} {
upvar $argvName argv
@@ -2187,16 +2212,16 @@ proc proj-parse-simple-flags {argvName tgtArrayName prototype} {
if {$::proj__Config(self-tests)} {
apply {{} {
- proj-warn "Test code for proj-cache"
- proj-assert {![proj-cache-check here check]}
+ #proj-warn "Test code for proj-cache"
+ proj-assert {![proj-cache-check -key here check]}
proj-assert {"here" eq [proj-cache-key here]}
proj-assert {"" eq $check}
- proj-cache-set here thevalue
- proj-assert {[proj-cache-check here check]}
+ proj-cache-set -key here thevalue
+ proj-assert {[proj-cache-check -key here check]}
proj-assert {"thevalue" eq $check}
proj-assert {![proj-cache-check check]}
- #puts "*** key = ([proj-cache-key -])"
+ #puts "*** key = ([proj-cache-key 0])"
proj-assert {"" eq $check}
proj-cache-set abc
proj-assert {[proj-cache-check check]}
diff --git a/autosetup/teaish/README.txt b/autosetup/teaish/README.txt
new file mode 100644
index 000000000..e11519b04
--- /dev/null
+++ b/autosetup/teaish/README.txt
@@ -0,0 +1,4 @@
+The *.tcl files in this directory are part of the SQLite's "autoconf"
+bundle which are specific to the TEA(-ish) build. During the tarball
+generation process, they are copied into <TOP>/autoconf/autosetup/teaish
+(which itself is created as part of that process).
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\]."
+}
diff --git a/autosetup/teaish/feature.tcl b/autosetup/teaish/feature.tcl
new file mode 100644
index 000000000..6c927d1a7
--- /dev/null
+++ b/autosetup/teaish/feature.tcl
@@ -0,0 +1,214 @@
+########################################################################
+# 2025 April 7
+#
+# 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 feature-tests.tcl -----
+# @section TEA-ish collection of feature tests.
+#
+# Functions in this file with a prefix of teaish__ are
+# private/internal APIs. Those with a prefix of teaish- are
+# public APIs.
+
+
+# @teaish-check-libz
+#
+# Checks for zlib.h and the function deflate in libz. If found,
+# prepends -lz to the extension's ldflags and returns 1, else returns
+# 0. It also defines LDFLAGS_LIBZ to the libs flag.
+#
+proc teaish-check-libz {} {
+ teaish-check-cached "Checking for libz" {
+ set rc 0
+ if {[msg-quiet cc-check-includes zlib.h] && [msg-quiet proj-check-function-in-lib deflate z]} {
+ teaish-ldflags-prepend [define LDFLAGS_LIBZ [get-define lib_deflate]]
+ undefine lib_deflate
+ incr rc
+ }
+ expr $rc
+ }
+}
+
+# @teaish-check-librt ?funclist?
+#
+# Checks whether -lrt is needed for any of the given functions. If
+# so, appends -lrt via [teaish-ldflags-prepend] and returns 1, else
+# returns 0. It also defines LDFLAGS_LIBRT to the libs flag or an
+# empty string.
+#
+# Some systems (ex: SunOS) require -lrt in order to use nanosleep.
+#
+proc teaish-check-librt {{funclist {fdatasync nanosleep}}} {
+ teaish-check-cached -nostatus "Checking whether ($funclist) need librt" {
+ define LDFLAGS_LIBRT ""
+ foreach func $funclist {
+ if {[msg-quiet proj-check-function-in-lib $func rt]} {
+ set ldrt [get-define lib_${func}]
+ undefine lib_${func}
+ if {"" ne $ldrt} {
+ teaish-ldflags-prepend -r [define LDFLAGS_LIBRT $ldrt]
+ msg-result $ldrt
+ return 1
+ } else {
+ msg-result "no lib needed"
+ return 1
+ }
+ }
+ }
+ msg-result "not found"
+ return 0
+ }
+}
+
+# @teaish-check-stdint
+#
+# A thin proxy for [cc-with] which checks for <stdint.h> and the
+# various fixed-size int types it declares. It defines HAVE_STDINT_T
+# to 0 or 1 and (if it's 1) defines HAVE_XYZ_T for each XYZ int type
+# to 0 or 1, depending on whether its available.
+proc teaish-check-stdint {} {
+ teaish-check-cached "Checking for stdint.h" {
+ msg-quiet cc-with {-includes stdint.h} \
+ {cc-check-types int8_t int16_t int32_t int64_t intptr_t \
+ uint8_t uint16_t uint32_t uint64_t uintptr_t}
+ }
+}
+
+# @teaish-is-mingw
+#
+# Returns 1 if building for mingw, else 0.
+proc teaish-is-mingw {} {
+ return [expr {
+ [string match *mingw* [get-define host]] &&
+ ![file exists /dev/null]
+ }]
+}
+
+# @teaish-check-libdl
+#
+# Checks for whether dlopen() can be found and whether it requires
+# -ldl for linking. If found, returns 1, defines LDFLAGS_DLOPEN to the
+# linker flags (if any), and passes those flags to
+# teaish-ldflags-prepend. It unconditionally defines HAVE_DLOPEN to 0
+# or 1 (the its return result value).
+proc teaish-check-dlopen {} {
+ teaish-check-cached -nostatus "Checking for dlopen()" {
+ set rc 0
+ set lfl ""
+ if {[cc-with {-includes dlfcn.h} {
+ cctest -link 1 -declare "extern char* dlerror(void);" -code "dlerror();"}]} {
+ msg-result "-ldl not needed"
+ incr rc
+ } elseif {[cc-check-includes dlfcn.h]} {
+ incr rc
+ if {[cc-check-function-in-lib dlopen dl]} {
+ set lfl [get-define lib_dlopen]
+ undefine lib_dlopen
+ msg-result " dlopen() needs $lfl"
+ } else {
+ msg-result " - dlopen() not found in libdl. Assuming dlopen() is built-in."
+ }
+ } else {
+ msg-result "not found"
+ }
+ teaish-ldflags-prepend [define LDFLAGS_DLOPEN $lfl]
+ define HAVE_DLOPEN $rc
+ }
+}
+
+#
+# @teaish-check-libmath
+#
+# Handles the --enable-math flag. Returns 1 if found, else 0.
+# If found, it prepends -lm (if needed) to the linker flags.
+proc teaish-check-libmath {} {
+ teaish-check-cached "Checking for libc math library" {
+ set lfl ""
+ set rc 0
+ if {[msg-quiet proj-check-function-in-lib ceil m]} {
+ incr rc
+ set lfl [get-define lib_ceil]
+ undefine lib_ceil
+ teaish-ldflags-prepend $lfl
+ msg-checking "$lfl "
+ }
+ define LDFLAGS_LIBMATH $lfl
+ expr $rc
+ }
+}
+
+# @teaish-import-features ?-flags? feature-names...
+#
+# For each $name in feature-names... it invokes:
+#
+# use teaish/feature/$name
+#
+# to load TEAISH_AUTOSETUP_DIR/feature/$name.tcl
+#
+# By default, if a proc named teaish-check-${name}-options is defined
+# after sourcing a file, it is called and its result is passed to
+# proj-append-options. This can be suppressed with the -no-options
+# flag.
+#
+# Flags:
+#
+# -no-options: disables the automatic running of
+# teaish-check-NAME-options,
+#
+# -run: if the function teaish-check-NAME exists after importing
+# then it is called. This flag must not be used when calling this
+# function from teaish-options. This trumps both -pre and -post.
+#
+# -pre: if the function teaish-check-NAME exists after importing
+# then it is passed to [teaish-checks-queue -pre].
+#
+# -post: works like -pre but instead uses[teaish-checks-queue -post].
+proc teaish-import-features {args} {
+ set pk ""
+ set doOpt 1
+ proj-parse-simple-flags args flags {
+ -no-options 0 {set doOpt 0}
+ -run 0 {expr 1}
+ -pre 0 {set pk -pre}
+ -post 0 {set pk -post}
+ }
+ #
+ # TODO: never import the same module more than once. The "use"
+ # command is smart enough to not do that but we would need to
+ # remember whether or not any teaish-check-${arg}* procs have been
+ # called before, and skip them.
+ #
+ if {$flags(-run) && "" ne $pk} {
+ proj-error "Cannot use both -run and $pk" \
+ " (called from [proj-scope 1])"
+ }
+
+ foreach arg $args {
+ uplevel "use teaish/feature/$arg"
+ if {$doOpt} {
+ set n "teaish-check-${arg}-options"
+ if {[llength [info proc $n]] > 0} {
+ if {"" ne [set x [$n]]} {
+ options-add $x
+ }
+ }
+ }
+ if {$flags(-run)} {
+ set n "teaish-check-${arg}"
+ if {[llength [info proc $n]] > 0} {
+ uplevel 1 $n
+ }
+ } elseif {"" ne $pk} {
+ set n "teaish-check-${arg}"
+ if {[llength [info proc $n]] > 0} {
+ teaish-checks-queue {*}$pk $n
+ }
+ }
+ }
+}
diff --git a/autosetup/teaish/tester.tcl b/autosetup/teaish/tester.tcl
new file mode 100644
index 000000000..d8b5f7a0e
--- /dev/null
+++ b/autosetup/teaish/tester.tcl
@@ -0,0 +1,228 @@
+########################################################################
+# 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.
+#
+########################################################################
+#
+# Helper routines for running tests on teaish extensions
+#
+########################################################################
+# ----- @module teaish/tester.tcl -----
+#
+# @section TEA-ish Testing APIs.
+#
+# Though these are part of the autosup dir hierarchy, they are not
+# intended to be run from autosetup code. Rather, they're for use
+# with/via teaish.tester.tcl and target canonical Tcl only, not JimTcl
+# (which the autosetup pieces do target).
+
+#
+# @test-current-scope ?lvl?
+#
+# Returns the name of the _calling_ proc from ($lvl + 1) levels up the
+# call stack (where the caller's level will be 1 up from _this_
+# call). If $lvl would resolve to global scope "global scope" is
+# returned and if it would be negative then a string indicating such
+# is returned (as opposed to throwing an error).
+#
+proc test-current-scope {{lvl 0}} {
+ #uplevel [expr {$lvl + 1}] {lindex [info level 0] 0}
+ set ilvl [info level]
+ set offset [expr {$ilvl - $lvl - 1}]
+ if { $offset < 0} {
+ return "invalid scope ($offset)"
+ } elseif { $offset == 0} {
+ return "global scope"
+ } else {
+ return [lindex [info level $offset] 0]
+ }
+}
+
+# @test-msg
+#
+# Emits all arugments to stdout.
+#
+proc test-msg {args} {
+ puts "$args"
+}
+
+# @test-warn
+#
+# Emits all arugments to stderr.
+#
+proc test-warn {args} {
+ puts stderr "WARNING: $args"
+}
+
+#
+# @test-error msg
+#
+# Triggers a test-failed error with a string describing the calling
+# scope and the provided message.
+#
+proc test-fail {args} {
+ #puts stderr "ERROR: \[[test-current-scope 1]]: $msg"
+ #exit 1
+ error "FAIL: \[[test-current-scope 1]]: $args"
+}
+
+array set ::test__Counters {}
+array set ::test__Config {
+ verbose-assert 0 verbose-affirm 0
+}
+
+# Internal impl for affirm and assert.
+#
+# $args = ?-v? script {msg-on-fail ""}
+proc test__affert {failMode args} {
+ if {$failMode} {
+ set what assert
+ } else {
+ set what affirm
+ }
+ set verbose $::test__Config(verbose-$what)
+ if {"-v" eq [lindex $args 0]} {
+ lassign $args - script msg
+ if {1 == [llength $args]} {
+ # If -v is the only arg, toggle default verbose mode
+ set ::test__Config(verbose-$what) [expr {!$::test__Config(verbose-$what)}]
+ return
+ }
+ incr verbose
+ } else {
+ lassign $args script msg
+ }
+ incr ::test__Counters($what)
+ if {![uplevel 1 [concat expr [list $script]]]} {
+ if {"" eq $msg} {
+ set msg $script
+ }
+ set txt [join [list $what # $::test__Counters($what) "failed:" $msg]]
+ if {$failMode} {
+ puts stderr $txt
+ exit 1
+ } else {
+ error $txt
+ }
+ } elseif {$verbose} {
+ puts stderr [join [list $what # $::test__Counters($what) "passed:" $script]]
+ }
+}
+
+#
+# @affirm ?-v? script ?msg?
+#
+# Works like a conventional assert method does, but reports failures
+# using [error] instead of [exit]. If -v is used, it reports passing
+# assertions to stderr. $script is evaluated in the caller's scope as
+# an argument to [expr].
+#
+proc affirm {args} {
+ tailcall test__affert 0 {*}$args
+}
+
+#
+# @assert ?-v? script ?msg?
+#
+# Works like [affirm] but exits on error.
+#
+proc assert {args} {
+ tailcall test__affert 1 {*}$args
+}
+
+#
+# @test-assert testId script ?msg?
+#
+# Works like [assert] but emits $testId to stdout first.
+#
+proc test-assert {testId script {msg ""}} {
+ puts "test $testId"
+ tailcall test__affert 1 $script $msg
+}
+
+#
+# @test-expect testId script result
+#
+# Runs $script in the calling scope and compares its result to
+# $result, minus any leading or trailing whitespace. If they differ,
+# it triggers an [assert].
+#
+proc test-expect {testId script result} {
+ puts "test $testId"
+ set x [string trim [uplevel 1 $script]]
+ set result [string trim $result]
+ tailcall test__affert 0 [list $x eq $result] \
+ "\nEXPECTED: <<$result>>\nGOT: <<$x>>"
+}
+
+#
+# @test-catch cmd ?...args?
+#
+# Runs [cmd ...args], repressing any exception except to possibly log
+# the failure. Returns 1 if it caught anything, 0 if it didn't.
+#
+proc test-catch {cmd args} {
+ if {[catch {
+ $cmd {*}$args
+ } rc xopts]} {
+ puts "[test-current-scope] ignoring failure of: $cmd [lindex $args 0]: $rc"
+ return 1
+ }
+ return 0
+}
+
+if {![array exists ::teaish__BuildFlags]} {
+ array set ::teaish__BuildFlags {}
+}
+
+#
+# @teaish-build-flag3 flag tgtVar ?dflt?
+#
+# If the current build has the configure-time flag named $flag set
+# then tgtVar is assigned its value and 1 is returned, else tgtVal is
+# assigned $dflt and 0 is returned.
+#
+# Caveat #1: only valid when called in the context of teaish's default
+# "make test" recipe, e.g. from teaish.test.tcl. It is not valid from
+# a teaish.tcl configure script because (A) the state it relies on
+# doesn't fully exist at that point and (B) that level of the API has
+# more direct access to the build state. This function requires that
+# an external script have populated its internal state, which is
+# normally handled via teaish.tester.tcl.in.
+#
+# Caveat #2: defines in the style of HAVE_FEATURENAME with a value of
+# 0 are, by long-standing configure script conventions, treated as
+# _undefined_ here.
+#
+proc teaish-build-flag3 {flag tgtVar {dflt ""}} {
+ upvar $tgtVar tgt
+ if {[info exists ::teaish__BuildFlags($flag)]} {
+ set tgt $::teaish__BuildFlags($flag)
+ return 1;
+ } elseif {0==[array size ::teaish__BuildFlags]} {
+ test-warn \
+ "\[[test-current-scope]] was called from " \
+ "[test-current-scope 1] without the build flags imported."
+ }
+ set tgt $dflt
+ return 0
+}
+
+#
+# @teaish-build-flag flag ?dflt?
+#
+# Convenience form of teaish-build-flag3 which returns the
+# configure-time-defined value of $flag or "" if it's not defined (or
+# if it's an empty string).
+#
+proc teaish-build-flag {flag {dflt ""}} {
+ set tgt ""
+ teaish-build-flag3 $flag tgt $dflt
+ return $tgt
+}