Home | History | Annotate | Line # | Download | only in teaish
      1 ########################################################################
      2 # 2025 April 5
      3 #
      4 # The author disclaims copyright to this source code.  In place of
      5 # a legal notice, here is a blessing:
      6 #
      7 #  * May you do good and not evil.
      8 #  * May you find forgiveness for yourself and forgive others.
      9 #  * May you share freely, never taking more than you give.
     10 #
     11 ########################################################################
     12 # ----- @module teaish.tcl -----
     13 # @section TEA-ish ((TCL Extension Architecture)-ish)
     14 #
     15 # Functions in this file with a prefix of teaish__ are
     16 # private/internal APIs. Those with a prefix of teaish- are
     17 # public APIs.
     18 #
     19 # Teaish has a hard dependency on proj.tcl, and any public API members
     20 # of that module are considered legal for use by teaish extensions.
     21 #
     22 # Project home page: https://fossil.wanderinghorse.net/r/teaish
     23 
     24 use proj
     25 
     26 #
     27 # API-internal settings and shared state.
     28 array set teaish__Config [proj-strip-hash-comments {
     29   #
     30   # Teaish's version number, not to be confused with
     31   # teaish__PkgInfo(-version).
     32   #
     33   version 0.1-beta
     34 
     35   # set to 1 to enable some internal debugging output
     36   debug-enabled 0
     37 
     38   #
     39   # 0     = don't yet have extension's pkgindex
     40   # 0x01  = found TEAISH_EXT_DIR/pkgIndex.tcl.in
     41   # 0x02  = found srcdir/pkgIndex.tcl.in
     42   # 0x10  = found TEAISH_EXT_DIR/pkgIndex.tcl (static file)
     43   # 0x20  = static-pkgIndex.tcl pragma: behave as if 0x10
     44   # 0x100 = disabled by -tm.tcl.in
     45   # 0x200 = disabled by -tm.tcl
     46   #
     47   # Reminder: it's significant that the bottom 4 bits be
     48   # cases where teaish manages ./pkgIndex.tcl.
     49   #
     50   pkgindex-policy 0
     51 
     52   #
     53   # The pkginit counterpart of pkgindex-policy:
     54   #
     55   # 0    = no pkginit
     56   # 0x01 = found default X.in: generate X from X.in
     57   # 0x10 = found static pkginit file X
     58   # 0x02 = user-provided X.in generates ./X.
     59   # 0x20 = user-provided static pkginit file X
     60   #
     61   # The 0x0f bits indicate that teaish is responsible for cleaning up
     62   # the (generated) pkginit file.
     63   #
     64   pkginit-policy 0
     65   #
     66   # 0    = no tm.tcl
     67   # 0x01 = tm.tcl.in
     68   # 0x10 = static tm.tcl
     69   tm-policy 0
     70 
     71   #
     72   # If 1+ then teaish__verbose will emit messages.
     73   #
     74   verbose 0
     75 
     76   #
     77   # Mapping of pkginfo -flags to their TEAISH_xxx define (if any).
     78   # This must not be modified after initialization.
     79   #
     80   pkginfo-f2d {
     81     -name            TEAISH_NAME
     82     -name.dist       TEAISH_DIST_NAME
     83     -name.pkg        TEAISH_PKGNAME
     84     -version         TEAISH_VERSION
     85     -libDir          TEAISH_LIBDIR_NAME
     86     -loadPrefix      TEAISH_LOAD_PREFIX
     87     -vsatisfies      TEAISH_VSATISFIES
     88     -pkgInit.tcl     TEAISH_PKGINIT_TCL
     89     -pkgInit.tcl.in  TEAISH_PKGINIT_TCL_IN
     90     -url             TEAISH_URL
     91     -tm.tcl          TEAISH_TM_TCL
     92     -tm.tcl.in       TEAISH_TM_TCL_IN
     93     -options         {}
     94     -pragmas         {}
     95     -src             {}
     96   }
     97 
     98   #
     99   # Queues for use with teaish-checks-queue and teaish-checks-run.
    100   #
    101   queued-checks-pre {}
    102   queued-checks-post {}
    103 
    104   # Whether or not "make dist" parts are enabled. They get enabled
    105   # when building from an extension's dir, disabled when building
    106   # elsewhere.
    107   dist-enabled 1
    108   # Whether or not "make install" parts are enabled. By default
    109   # they are, but we have a single use case where they're
    110   # both unnecessary and unhelpful, so...
    111   install-enabled 1
    112 
    113   # By default we enable compilation of a native extension but if the
    114   # extension has no native code or the user wants to take that over
    115   # via teaish.make.in or provide a script-only extension, we will
    116   # elide the default compilation rules if this is 0.
    117   dll-enabled 1
    118 
    119   # Files to include in the "make dist" bundle.
    120   dist-files {}
    121 
    122   # List of source files for the extension.
    123   extension-src {}
    124 
    125   # Path to the teaish.tcl file.
    126   teaish.tcl {}
    127 
    128   # Dir where teaish.tcl is found.
    129   extension-dir {}
    130 
    131   # Whether the generates TEASH_VSATISFIES_CODE should error out on a
    132   # satisfies error. If 0, it uses return instead of error.
    133   vsatisfies-error 1
    134 
    135   # Whether or not to allow a "full dist" - a "make dist" build which
    136   # includes both the extension and teaish. By default this is only on
    137   # if the extension dir is teaish's dir.
    138   dist-full-enabled 0
    139 }]
    140 set teaish__Config(core-dir) $::autosetup(libdir)/teaish
    141 
    142 #
    143 # Array of info managed by teaish-pkginfo-get and friends.  Has the
    144 # same set of keys as $teaish__Config(pkginfo-f2d).
    145 #
    146 array set teaish__PkgInfo {}
    147 
    148 #
    149 # Runs {*}$args if $lvl is <= the current verbosity level, else it has
    150 # no side effects.
    151 #
    152 proc teaish__verbose {lvl args} {
    153   if {$lvl <= $::teaish__Config(verbose)} {
    154     {*}$args
    155   }
    156 }
    157 
    158 #
    159 # @teaish-argv-has flags...
    160 #
    161 # Returns true if any arg in $::argv matches any of the given globs,
    162 # else returns false.
    163 #
    164 proc teaish-argv-has {args} {
    165   foreach glob $args {
    166     foreach arg $::argv {
    167       if {[string match $glob $arg]} {
    168         return 1
    169       }
    170     }
    171   }
    172   return 0
    173 }
    174 
    175 if {[teaish-argv-has --teaish-verbose --t-v]} {
    176   # Check this early so that we can use verbose-only messages in the
    177   # pre-options-parsing steps.
    178   set ::teaish__Config(verbose) 1
    179   #teaish__verbose 1 msg-result "--teaish-verbose activated"
    180 }
    181 
    182 msg-quiet use system ; # Outputs "Host System" and "Build System" lines
    183 if {"--help" ni $::argv} {
    184   teaish__verbose 1 msg-result "TEA(ish) Version = $::teaish__Config(version)"
    185   teaish__verbose 1 msg-result "Source dir       = $::autosetup(srcdir)"
    186   teaish__verbose 1 msg-result "Build dir        = $::autosetup(builddir)"
    187 }
    188 
    189 #
    190 # @teaish-configure-core
    191 #
    192 # Main entry point for the TEA-ish configure process. auto.def's primary
    193 # (ideally only) job should be to call this.
    194 #
    195 proc teaish-configure-core {} {
    196   proj-tweak-default-env-dirs
    197 
    198   set ::teaish__Config(install-mode) [teaish-argv-has --teaish-install*]
    199   set ::teaish__Config(create-ext-mode) \
    200     [teaish-argv-has --teaish-create-extension=* --t-c-e=*]
    201   set gotExt 0; # True if an extension config is found
    202   if {!$::teaish__Config(create-ext-mode)
    203       && !$::teaish__Config(install-mode)} {
    204     # Don't look for an extension if we're in --t-c-e or --t-i mode
    205     set gotExt [teaish__find_extension]
    206   }
    207 
    208   #
    209   # Set up the core --flags. This needs to come before teaish.tcl is
    210   # sourced so that that file can use teaish-pkginfo-set to append
    211   # options.
    212   #
    213   options-add [proj-strip-hash-comments {
    214     with-tcl:DIR
    215       => {Directory containing tclConfig.sh or a directory one level up from
    216           that, from which we can derive a directory containing tclConfig.sh.
    217           Defaults to the $TCL_HOME environment variable.}
    218 
    219     with-tclsh:PATH
    220       => {Full pathname of tclsh to use.  It is used for trying to find
    221           tclConfig.sh.  Warning: if its containing dir has multiple tclsh
    222           versions, it may select the wrong tclConfig.sh!
    223         Defaults to the $TCLSH environment variable.}
    224 
    225     tcl-stubs=0 => {Enable use of Tcl stubs library.}
    226 
    227     # TEA has --with-tclinclude but it appears to only be useful for
    228     # building an extension against an uninstalled copy of TCL's own
    229     # source tree. The policy here is that either we get that info
    230     # from tclConfig.sh or we give up.
    231     #
    232     # with-tclinclude:DIR
    233     #   => {Specify the directory which contains the tcl.h. This should not
    234     #       normally be required, as that information comes from tclConfig.sh.}
    235 
    236     # We _generally_ want to reduce the possibility of flag collisions with
    237     # extensions, and thus use a teaish-... prefix on most flags. However,
    238     # --teaish-extension-dir is frequently needed, so...
    239     #
    240     # As of this spontaneous moment, we'll settle on using --t-A-X to
    241     # abbreviate --teaish-A...-X... flags when doing so is
    242     # unambiguous...
    243     ted: t-e-d:
    244     teaish-extension-dir:DIR
    245       => {Looks for an extension in the given directory instead of the current
    246           dir.}
    247 
    248     t-c-e:
    249     teaish-create-extension:TARGET_DIRECTORY
    250       => {Writes stub files for creating an extension. Will refuse to overwrite
    251           existing files without --teaish-force.}
    252 
    253     t-f
    254     teaish-force
    255       => {Has a context-dependent meaning (autosetup defines --force for its
    256           own use).}
    257 
    258     t-d-d
    259     teaish-dump-defines
    260       => {Dump all configure-defined vars to config.defines.txt}
    261 
    262     t-v:=0
    263     teaish-verbose:=0
    264       => {Enable more (often extraneous) messages from the teaish core.}
    265 
    266     t-d
    267     teaish-debug=0 => {Enable teaish-specific debug output}
    268 
    269     t-i
    270     teaish-install:=auto
    271       => {Installs a copy of teaish, including autosetup, to the target dir.
    272           When used with --teaish-create-extension=DIR, a value of "auto"
    273           (no no value) will inherit that directory.}
    274 
    275     #TODO: --teaish-install-extension:=dir as short for
    276     # --t-c-e=dir --t-i
    277 
    278     t-e-p:
    279     teaish-extension-pkginfo:pkginfo
    280       => {For use with --teaish-create-extension. If used, it must be a
    281           list of arguments for use with teaish-pkginfo-set, e.g.
    282           --teaish-extension-pkginfo="-name Foo -version 2.3"}
    283 
    284     t-v-c
    285     teaish-vsatisfies-check=1
    286       => {Disable the configure-time "vsatisfies" check on the target tclsh.}
    287 
    288   }]; # main options.
    289 
    290   if {$gotExt} {
    291     # We found an extension. Source it...
    292     set ttcl $::teaish__Config(teaish.tcl)
    293     proj-assert {"" ne [teaish-pkginfo-get -name]}
    294     proj-assert {[file exists $ttcl]} \
    295       "Expecting to have found teaish.(tcl|config) by now"
    296     if {[string match *.tcl $ttcl]} {
    297       uplevel 1 {source $::teaish__Config(teaish.tcl)}
    298     } else {
    299       teaish-pkginfo-set {*}[proj-file-content -trim $ttcl]
    300     }
    301     unset ttcl
    302     # Set up some default values if the extension did not set them.
    303     # This must happen _after_ it's sourced but before
    304     # teaish-configure is called.
    305     array set f2d $::teaish__Config(pkginfo-f2d)
    306     foreach {pflag key type val} {
    307       - TEAISH_CFLAGS            -v ""
    308       - TEAISH_LDFLAGS           -v ""
    309       - TEAISH_MAKEFILE          -v ""
    310       - TEAISH_MAKEFILE_CODE     -v ""
    311       - TEAISH_MAKEFILE_IN       -v ""
    312       - TEAISH_PKGINDEX_TCL      -v ""
    313       - TEAISH_PKGINDEX_TCL_IN   -v ""
    314       - TEAISH_PKGINIT_TCL       -v ""
    315       - TEAISH_PKGINIT_TCL_IN    -v ""
    316       - TEAISH_PKGINIT_TCL_TAIL  -v ""
    317       - TEAISH_TEST_TCL          -v ""
    318       - TEAISH_TEST_TCL_IN       -v ""
    319 
    320       -version          -       -v 0.0.0
    321       -name.pkg         -       -e {set ::teaish__PkgInfo(-name)}
    322       -name.dist        -       -e {set ::teaish__PkgInfo(-name)}
    323       -libDir           -       -e {
    324         join [list \
    325                 $::teaish__PkgInfo(-name.pkg) \
    326                 $::teaish__PkgInfo(-version)] ""
    327       }
    328       -loadPrefix       -       -e {
    329         string totitle $::teaish__PkgInfo(-name.pkg)
    330       }
    331       -vsatisfies       -       -v {{Tcl 8.5-}}
    332       -pkgInit.tcl      -       -v ""
    333       -pkgInit.tcl.in   -       -v ""
    334       -url              -       -v ""
    335       -tm.tcl           -       -v ""
    336       -tm.tcl.in        -       -v ""
    337       -src              -       -v ""
    338     } {
    339       #proj-assert 0 {Just testing}
    340       set isPIFlag [expr {"-" ne $pflag}]
    341       if {$isPIFlag} {
    342         if {[info exists ::teaish__PkgInfo($pflag)]} {
    343           # Was already set - skip it.
    344           continue;
    345         }
    346         proj-assert {{-} eq $key};# "Unexpected pflag=$pflag key=$key type=$type val=$val"
    347         set key $f2d($pflag)
    348       }
    349       if {"" ne $key} {
    350         if {"<nope>" ne [get-define $key "<nope>"]} {
    351           # Was already set - skip it.
    352           continue
    353         }
    354       }
    355       switch -exact -- $type {
    356         -v {}
    357         -e { set val [eval $val] }
    358         default { proj-error "Invalid type flag: $type" }
    359       }
    360       #puts "***** defining default $pflag $key {$val} isPIFlag=$isPIFlag"
    361       if {$key ne ""} {
    362         define $key $val
    363       }
    364       if {$isPIFlag} {
    365         set ::teaish__PkgInfo($pflag) $val
    366       }
    367     }
    368     unset isPIFlag pflag key type val
    369     array unset f2d
    370   }; # sourcing extension's teaish.tcl
    371 
    372   if {[llength [info proc teaish-options]] > 0} {
    373     # Add options defined by teaish-options, which is assumed to be
    374     # imported via [teaish-get -teaish-tcl].
    375     set o [teaish-options]
    376     if {"" ne $o} {
    377       options-add $o
    378     }
    379   }
    380   #set opts [proj-options-combine]
    381   #lappend opts teaish-debug => {x}; #testing dupe entry handling
    382   if {[catch {options {}} msg xopts]} {
    383     # Workaround for <https://github.com/msteveb/autosetup/issues/73>
    384     # where [options] behaves oddly on _some_ TCL builds when it's
    385     # called from deeper than the global scope.
    386     dict incr xopts -level
    387     return {*}$xopts $msg
    388   }
    389 
    390   proj-xfer-options-aliases {
    391     t-c-e  => teaish-create-extension
    392     t-d    => teaish-debug
    393     t-d-d  => teaish-dump-defines
    394     ted    => teaish-extension-dir
    395     t-e-d  => teaish-extension-dir
    396     t-e-p  => teaish-extension-pkginfo
    397     t-f    => teaish-force
    398     t-i    => teaish-install
    399     t-v    => teaish-verbose
    400     t-v-c  => teaish-vsatisfies-check
    401   }
    402 
    403   scan [opt-val teaish-verbose 0] %d ::teaish__Config(verbose)
    404   set ::teaish__Config(debug-enabled) [opt-bool teaish-debug]
    405 
    406   set exitEarly 0
    407   if {[proj-opt-was-provided teaish-create-extension]} {
    408     teaish__create_extension [opt-val teaish-create-extension]
    409     incr exitEarly
    410   }
    411   if {$::teaish__Config(install-mode)} {
    412     teaish__install
    413     incr exitEarly
    414   }
    415 
    416   if {$exitEarly} {
    417     file delete -force config.log
    418     return
    419   }
    420   proj-assert {1==$gotExt} "Else we cannot have gotten this far"
    421 
    422   teaish__configure_phase1
    423 }
    424 
    425 
    426 #
    427 # Internal config-time debugging output routine. It is not legal to
    428 # call this from the global scope.
    429 #
    430 proc teaish-debug {msg} {
    431   if {$::teaish__Config(debug-enabled)} {
    432     puts stderr [proj-bold "** DEBUG: \[[proj-scope 1]\]: $msg"]
    433   }
    434 }
    435 
    436 #
    437 # Runs "phase 1" of the configuration, immediately after processing
    438 # --flags. This is what will import the client-defined teaish.tcl.
    439 #
    440 proc teaish__configure_phase1 {} {
    441   msg-result \
    442     [join [list "Configuring build of Tcl extension" \
    443        [proj-bold [teaish-pkginfo-get -name] \
    444           [teaish-pkginfo-get -version]] "..."]]
    445 
    446   uplevel 1 {
    447     use cc cc-db cc-shared cc-lib; # pkg-config
    448   }
    449   teaish__check_tcl
    450   apply {{} {
    451     #
    452     # If --prefix or --exec-prefix are _not_ provided, use their
    453     # TCL_... counterpart from tclConfig.sh.  Caveat: by the time we can
    454     # reach this point, autosetup's system.tcl will have already done
    455     # some non-trivial amount of work with these to create various
    456     # derived values from them, so we temporarily end up with a mishmash
    457     # of autotools-compatibility var values. That will be straightened
    458     # out in the final stage of the configure script via
    459     # [proj-remap-autoconf-dir-vars].
    460     #
    461     foreach {flag uflag tclVar} {
    462       prefix      prefix      TCL_PREFIX
    463       exec-prefix exec_prefix TCL_EXEC_PREFIX
    464     } {
    465       if {![proj-opt-was-provided $flag]} {
    466         if {"exec-prefix" eq $flag} {
    467           # If --exec-prefix was not used, ensure that --exec-prefix
    468           # derives from the --prefix we may have just redefined.
    469           set v {${prefix}}
    470         } else {
    471           set v [get-define $tclVar "???"]
    472           teaish__verbose 1 msg-result "Using \$$tclVar for --$flag=$v"
    473         }
    474         proj-assert {"???" ne $v} "Expecting teaish__check_tcl to have defined $tclVar"
    475         #puts "*** $flag $uflag $tclVar = $v"
    476         proj-opt-set $flag $v
    477         define $uflag $v
    478 
    479         # ^^^ As of here, all autotools-compatibility vars which derive
    480         # from --$flag, e.g. --libdir, still derive from the default
    481         # --$flag value which was active when system.tcl was
    482         # included. So long as those flags are not explicitly passed to
    483         # the configure script, those will be straightened out via
    484         # [proj-remap-autoconf-dir-vars].
    485       }
    486     }
    487   }}; # --[exec-]prefix defaults
    488   teaish__check_common_bins
    489   #
    490   # Set up library file names
    491   #
    492   proj-file-extensions
    493   teaish__define_pkginfo_derived *
    494 
    495   teaish-checks-run -pre
    496   if {[llength [info proc teaish-configure]] > 0} {
    497     # teaish-configure is assumed to be imported via
    498     # teaish.tcl
    499     teaish-configure
    500   }
    501   teaish-checks-run -post
    502 
    503   define TEAISH_USE_STUBS [opt-bool tcl-stubs]
    504 
    505   apply {{} {
    506     # Set up "vsatisfies" code for pkgIndex.tcl.in,
    507     # _teaish.tester.tcl.in, and for a configure-time check.  We would
    508     # like to put this before [teaish-checks-run -pre] but it's
    509     # marginally conceivable that a client may need to dynamically
    510     # calculate the vsatisfies and set it via [teaish-configure].
    511     set vs [get-define TEAISH_VSATISFIES ""]
    512     if {"" eq $vs} return
    513     set code {}
    514     set n 0
    515     # Treat $vs as a list-of-lists {{Tcl 8.5-} {Foo 1.0- -3.0} ...}
    516     # and generate Tcl which will run package vsatisfies tests with
    517     # that info.
    518     foreach pv $vs {
    519       set n [llength $pv]
    520       if {$n < 2} {
    521         proj-error "-vsatisfies: {$pv} appears malformed. Whole list is: $vs"
    522       }
    523       set pkg [lindex $pv 0]
    524       set vcheck {}
    525       for {set i 1} {$i < $n} {incr i} {
    526         lappend vcheck [lindex $pv $i]
    527       }
    528       if {[opt-bool teaish-vsatisfies-check]} {
    529         set tclsh [get-define TCLSH_CMD]
    530         set vsat "package vsatisfies \[ package provide $pkg \] $vcheck"
    531         set vputs "puts \[ $vsat \]"
    532         #puts "*** vputs = $vputs"
    533         scan [exec echo $vputs | $tclsh] %d vvcheck
    534         if {![info exists vvcheck] || 0 == $vvcheck} {
    535           proj-fatal -up $tclsh "check failed:" $vsat
    536         }
    537       }
    538       if {$::teaish__Config(vsatisfies-error)} {
    539         set vunsat \
    540           [list error [list Package \
    541                          $::teaish__PkgInfo(-name) $::teaish__PkgInfo(-version) \
    542                          requires $pv]]
    543       } else {
    544         set vunsat return
    545       }
    546       lappend code \
    547         [string trim [subst -nocommands \
    548           {if { ![package vsatisfies [package provide $pkg] $vcheck] } {\n  $vunsat\n}}]]
    549     }; # foreach pv
    550     define TEAISH_VSATISFIES_CODE [join $code "\n"]
    551   }}; # vsatisfies
    552 
    553   if {[proj-looks-like-windows]} {
    554     # Without this, linking of an extension will not work on Cygwin or
    555     # Msys2.
    556     msg-result "Using USE_TCL_STUBS for Unix(ish)-on-Windows environment"
    557     teaish-cflags-add -DUSE_TCL_STUBS=1
    558   }
    559 
    560   #define AS_LIBDIR $::autosetup(libdir)
    561   define TEAISH_TESTUTIL_TCL $::teaish__Config(core-dir)/tester.tcl
    562 
    563   apply {{} {
    564     #
    565     # Ensure we have a pkgIndex.tcl and don't have a stale generated one
    566     # when rebuilding for different --with-tcl=... values.
    567     #
    568     if {!$::teaish__Config(pkgindex-policy)} {
    569       proj-error "Cannot determine which pkgIndex.tcl to use"
    570     }
    571     if {0x300 & $::teaish__Config(pkgindex-policy)} {
    572       teaish__verbose 1 msg-result "pkgIndex disabled by -tm.tcl(.in)"
    573     } else {
    574       set tpi [proj-coalesce \
    575                  [get-define TEAISH_PKGINDEX_TCL_IN] \
    576                  [get-define TEAISH_PKGINDEX_TCL]]
    577       proj-assert {$tpi ne ""} \
    578         "TEAISH_PKGINDEX_TCL should have been set up by now"
    579       teaish__verbose 1 msg-result "Using pkgIndex from $tpi"
    580       if {0x0f & $::teaish__Config(pkgindex-policy)} {
    581         # Don't leave stale pkgIndex.tcl laying around yet don't delete
    582         # or overwrite a user-managed static pkgIndex.tcl.
    583         file delete -force -- [get-define TEAISH_PKGINDEX_TCL]
    584         proj-dot-ins-append [get-define TEAISH_PKGINDEX_TCL_IN]
    585       } else {
    586         teaish-dist-add [file tail $tpi]
    587       }
    588     }
    589   }}; # $::teaish__Config(pkgindex-policy)
    590 
    591   #
    592   # Ensure we clean up TEAISH_PKGINIT_TCL if needed and @-process
    593   # TEAISH_PKGINIT_TCL_IN if needed.
    594   #
    595   if {0x0f & $::teaish__Config(pkginit-policy)} {
    596     file delete -force -- [get-define TEAISH_PKGINIT_TCL]
    597     proj-dot-ins-append [get-define TEAISH_PKGINIT_TCL_IN] \
    598       [get-define TEAISH_PKGINIT_TCL]
    599   }
    600   if {0x0f & $::teaish__Config(tm-policy)} {
    601     file delete -force -- [get-define TEAISH_TM_TCL]
    602     proj-dot-ins-append [get-define TEAISH_TM_TCL_IN]
    603   }
    604 
    605   apply {{} {
    606     # Queue up any remaining dot-in files
    607     set dotIns [list]
    608     foreach {dIn => dOut} {
    609       TEAISH_TESTER_TCL_IN => TEAISH_TESTER_TCL
    610       TEAISH_TEST_TCL_IN   => TEAISH_TEST_TCL
    611       TEAISH_MAKEFILE_IN   => TEAISH_MAKEFILE
    612     } {
    613       lappend dotIns [get-define $dIn ""] [get-define $dOut ""]
    614     }
    615     lappend dotIns $::autosetup(srcdir)/Makefile.in Makefile; # must be after TEAISH_MAKEFILE_IN.
    616     # Much later: probably because of timestamps for deps purposes :-?
    617     #puts "dotIns=$dotIns"
    618     foreach {i o} $dotIns {
    619       if {"" ne $i && "" ne $o} {
    620         #puts " pre-dot-ins-append:  \[$i\] -> \[$o\]"
    621         proj-dot-ins-append $i $o
    622       }
    623     }
    624   }}
    625 
    626   define TEAISH_DIST_FULL \
    627     [expr {
    628            $::teaish__Config(dist-enabled)
    629            && $::teaish__Config(dist-full-enabled)
    630          }]
    631 
    632   define TEAISH_AUTOSETUP_DIR  $::teaish__Config(core-dir)
    633   define TEAISH_ENABLE_DIST    $::teaish__Config(dist-enabled)
    634   define TEAISH_ENABLE_INSTALL $::teaish__Config(install-enabled)
    635   define TEAISH_ENABLE_DLL     $::teaish__Config(dll-enabled)
    636   define TEAISH_TCL            $::teaish__Config(teaish.tcl)
    637 
    638   define TEAISH_DIST_FILES     [join $::teaish__Config(dist-files)]
    639   define TEAISH_EXT_DIR        [join $::teaish__Config(extension-dir)]
    640   define TEAISH_EXT_SRC        [join $::teaish__Config(extension-src)]
    641   proj-setup-autoreconfig TEAISH_AUTORECONFIG
    642   foreach f {
    643     TEAISH_CFLAGS
    644     TEAISH_LDFLAGS
    645   } {
    646     # Ensure that any of these lists are flattened
    647     define $f [join [get-define $f]]
    648   }
    649   proj-remap-autoconf-dir-vars
    650   set tdefs [teaish__defines_to_list]
    651   define TEAISH__DEFINES_MAP $tdefs; # injected into _teaish.tester.tcl
    652 
    653   #
    654   # NO [define]s after this point!
    655   #
    656   proj-if-opt-truthy teaish-dump-defines {
    657     proj-file-write config.defines.txt $tdefs
    658   }
    659   proj-dot-ins-process -validate
    660 
    661 }; # teaish__configure_phase1
    662 
    663 #
    664 # Run checks for required binaries.
    665 #
    666 proc teaish__check_common_bins {} {
    667   if {"" eq [proj-bin-define install]} {
    668     proj-warn "Cannot find install binary, so 'make install' will not work."
    669     define BIN_INSTALL false
    670   }
    671   if {"" eq [proj-bin-define zip]} {
    672     proj-warn "Cannot find zip, so 'make dist.zip' will not work."
    673   }
    674   if {"" eq [proj-bin-define tar]} {
    675     proj-warn "Cannot find tar, so 'make dist.tgz' will not work."
    676   }
    677 }
    678 
    679 #
    680 # TCL...
    681 #
    682 # teaish__check_tcl performs most of the --with-tcl and --with-tclsh
    683 # handling. Some related bits and pieces are performed before and
    684 # after that function is called.
    685 #
    686 # Important [define]'d vars:
    687 #
    688 #  - TCLSH_CMD is the path to the canonical tclsh or "".
    689 #
    690 #  - TCL_CONFIG_SH is the path to tclConfig.sh or "".
    691 #
    692 #  - TCLLIBDIR is the dir to which the extension library gets
    693 #  - installed.
    694 #
    695 proc teaish__check_tcl {} {
    696   define TCLSH_CMD false ; # Significant is that it exits with non-0
    697   define TCLLIBDIR ""    ; # Installation dir for TCL extension lib
    698   define TCL_CONFIG_SH ""; # full path to tclConfig.sh
    699 
    700   # Clear out all vars which would harvest from tclConfig.sh so that
    701   # the late-config validation of @VARS@ works even if --disable-tcl
    702   # is used.
    703   proj-tclConfig-sh-to-autosetup ""
    704 
    705   # TODO: better document the steps this is taking.
    706   set srcdir $::autosetup(srcdir)
    707   msg-result "Checking for a suitable tcl... "
    708   set use_tcl 1
    709   set withSh [opt-val with-tclsh [proj-get-env TCLSH]]
    710   set tclHome [opt-val with-tcl [proj-get-env TCL_HOME]]
    711   if {[string match */lib $tclHome]} {
    712     # TEA compatibility kludge: its --with-tcl wants the lib
    713     # dir containing tclConfig.sh.
    714     #proj-warn "Replacing --with-tcl=$tclHome for TEA compatibility"
    715     regsub {/lib^} $tclHome "" tclHome
    716     msg-result "NOTE: stripped /lib suffix from --with-tcl=$tclHome (a TEA-ism)"
    717   }
    718   if {0} {
    719     # This misinteracts with the $TCL_PREFIX default: it will use the
    720     # autosetup-defined --prefix default
    721     if {"prefix" eq $tclHome} {
    722       set tclHome [get-define prefix]
    723     }
    724   }
    725   teaish-debug "use_tcl ${use_tcl}"
    726   teaish-debug "withSh=${withSh}"
    727   teaish-debug "tclHome=$tclHome"
    728   if {"" eq $withSh && "" eq $tclHome} {
    729     # If neither --with-tclsh nor --with-tcl are provided, try to find
    730     # a workable tclsh.
    731     set withSh [proj-first-bin-of tclsh9.1 tclsh9.0 tclsh8.6 tclsh]
    732     teaish-debug "withSh=${withSh}"
    733   }
    734 
    735   set doConfigLookup 1 ; # set to 0 to test the tclConfig.sh-not-found cases
    736   if {"" ne $withSh} {
    737     # --with-tclsh was provided or found above. Validate it and use it
    738     # to trump any value passed via --with-tcl=DIR.
    739     if {![file-isexec $withSh]} {
    740       proj-error "TCL shell $withSh is not executable"
    741     } else {
    742       define TCLSH_CMD $withSh
    743       #msg-result "Using tclsh: $withSh"
    744     }
    745     if {$doConfigLookup &&
    746         [catch {exec $withSh $::autosetup(libdir)/find_tclconfig.tcl} result] == 0} {
    747       set tclHome $result
    748     }
    749     if {"" ne $tclHome && [file isdirectory $tclHome]} {
    750       teaish__verbose 1 msg-result "$withSh recommends the tclConfig.sh from $tclHome"
    751     } else {
    752       proj-warn "$withSh is unable to recommend a tclConfig.sh"
    753       set use_tcl 0
    754     }
    755   }
    756   set cfg ""
    757   set tclSubdirs {tcl9.1 tcl9.0 tcl8.6 tcl8.5 lib}
    758   while {$use_tcl} {
    759     if {"" ne $tclHome} {
    760       # Ensure that we can find tclConfig.sh under ${tclHome}/...
    761       if {$doConfigLookup} {
    762         if {[file readable "${tclHome}/tclConfig.sh"]} {
    763           set cfg "${tclHome}/tclConfig.sh"
    764         } else {
    765           foreach i $tclSubdirs {
    766             if {[file readable "${tclHome}/$i/tclConfig.sh"]} {
    767               set cfg "${tclHome}/$i/tclConfig.sh"
    768               break
    769             }
    770           }
    771         }
    772       }
    773       if {"" eq $cfg} {
    774         proj-error "No tclConfig.sh found under ${tclHome}"
    775       }
    776     } else {
    777       # If we have not yet found a tclConfig.sh file, look in $libdir
    778       # which is set automatically by autosetup or via the --prefix
    779       # command-line option.  See
    780       # https://sqlite.org/forum/forumpost/e04e693439a22457
    781       set libdir [get-define libdir]
    782       if {[file readable "${libdir}/tclConfig.sh"]} {
    783         set cfg "${libdir}/tclConfig.sh"
    784       } else {
    785         foreach i $tclSubdirs {
    786           if {[file readable "${libdir}/$i/tclConfig.sh"]} {
    787             set cfg "${libdir}/$i/tclConfig.sh"
    788             break
    789           }
    790         }
    791       }
    792       if {![file readable $cfg]} {
    793         break
    794       }
    795     }
    796     teaish__verbose 1 msg-result "Using tclConfig.sh = $cfg"
    797     break
    798   }; # while {$use_tcl}
    799   define TCL_CONFIG_SH $cfg
    800   # Export a subset of tclConfig.sh to the current TCL-space.  If $cfg
    801   # is an empty string, this emits empty-string entries for the
    802   # various options we're interested in.
    803   proj-tclConfig-sh-to-autosetup $cfg
    804 
    805   if {"" eq $withSh && $cfg ne ""} {
    806     # We have tclConfig.sh but no tclsh. Attempt to locate a tclsh
    807     # based on info from tclConfig.sh.
    808     set tclExecPrefix [get-define TCL_EXEC_PREFIX]
    809     proj-assert {"" ne $tclExecPrefix}
    810     set tryThese [list \
    811                     $tclExecPrefix/bin/tclsh[get-define TCL_VERSION] \
    812                     $tclExecPrefix/bin/tclsh ]
    813     foreach trySh $tryThese {
    814       if {[file-isexec $trySh]} {
    815         set withSh $trySh
    816         break
    817       }
    818     }
    819     if {![file-isexec $withSh]} {
    820       proj-warn "Cannot find a usable tclsh (tried: $tryThese)"
    821     }
    822   }
    823   define TCLSH_CMD $withSh
    824   if {$use_tcl} {
    825     # Set up the TCLLIBDIR
    826     set tcllibdir [get-env TCLLIBDIR ""]
    827     set extDirName [teaish-pkginfo-get -libDir]
    828     if {"" eq $tcllibdir} {
    829       # Attempt to extract TCLLIBDIR from TCL's $auto_path
    830       if {"" ne $withSh &&
    831           [catch {exec echo "puts stdout \$auto_path" | "$withSh"} result] == 0} {
    832         foreach i $result {
    833           if {![string match //zip* $i] && [file isdirectory $i]} {
    834             # isdirectory actually passes on //zipfs:/..., but those are
    835             # useless for our purposes
    836             set tcllibdir $i/$extDirName
    837             break
    838           }
    839         }
    840       } else {
    841         proj-error "Cannot determine TCLLIBDIR."
    842       }
    843     }
    844     define TCLLIBDIR $tcllibdir
    845   }; # find TCLLIBDIR
    846 
    847   set gotSh [file-isexec $withSh]
    848   set tmdir ""; # first tcl::tm::list entry
    849   if {$gotSh} {
    850     catch {
    851       set tmli [exec echo {puts [tcl::tm::list]} | $withSh]
    852       # Reminder: this list contains many names of dirs which do not
    853       # exist but are legitimate. If we rely only on an is-dir check,
    854       # we can end up not finding any of the many candidates.
    855       set firstDir ""
    856       foreach d $tmli {
    857         if {"" eq $firstDir && ![string match //*:* $d]} {
    858           # First non-VFS entry, e.g. not //zipfs:
    859           set firstDir $d
    860         }
    861         if {[file isdirectory $d]} {
    862           set tmdir $d
    863           break
    864         }
    865       }
    866       if {"" eq $tmdir} {
    867         set tmdir $firstDir
    868       }
    869     }; # find tcl::tm path
    870   }
    871   define TEAISH_TCL_TM_DIR $tmdir
    872 
    873   # Finally, let's wrap up...
    874   if {$gotSh} {
    875     teaish__verbose 1 msg-result "Using tclsh        = $withSh"
    876     if {$cfg ne ""} {
    877       define HAVE_TCL 1
    878     } else {
    879       proj-warn "Found tclsh but no tclConfig.sh."
    880     }
    881     if {"" eq $tmdir} {
    882       proj-warn "Did not find tcl::tm directory."
    883     }
    884   }
    885   show-notices
    886   # If TCL is not found: if it was explicitly requested then fail
    887   # fatally, else just emit a warning. If we can find the APIs needed
    888   # to generate a working JimTCL then that will suffice for build-time
    889   # TCL purposes (see: proc sqlite-determine-codegen-tcl).
    890   if {!$gotSh} {
    891     proj-error "Did not find tclsh"
    892   } elseif {"" eq $cfg} {
    893     proj-indented-notice -error {
    894       Cannot find a usable tclConfig.sh file.  Use --with-tcl=DIR to
    895       specify a directory near which tclConfig.sh can be found, or
    896       --with-tclsh=/path/to/tclsh to allow the tclsh binary to locate
    897       its tclConfig.sh, with the caveat that a symlink to tclsh, or
    898       wrapper script around it, e.g. ~/bin/tclsh ->
    899       $HOME/tcl/9.0/bin/tclsh9.1, may not work because tclsh emits
    900       different library paths for the former than the latter.
    901     }
    902   }
    903   msg-result "Using Tcl [get-define TCL_VERSION] from [get-define TCL_PREFIX]."
    904   teaish__tcl_platform_quirks
    905 }; # teaish__check_tcl
    906 
    907 #
    908 # Perform last-minute platform-specific tweaks to account for quirks.
    909 #
    910 proc teaish__tcl_platform_quirks {} {
    911   define TEAISH_POSTINST_PREREQUIRE ""
    912   switch -glob -- [get-define host] {
    913     *-haiku {
    914       # Haiku's default TCLLIBDIR is "all wrong": it points to a
    915       # read-only virtual filesystem mount-point. We bend it back to
    916       # fit under $TCL_PACKAGE_PATH here.
    917       foreach {k d} {
    918         vj TCL_MAJOR_VERSION
    919         vn TCL_MINOR_VERSION
    920         pp TCL_PACKAGE_PATH
    921         ld TCLLIBDIR
    922       } {
    923         set $k [get-define $d]
    924       }
    925       if {[string match /packages/* $ld]} {
    926         set old $ld
    927         set tail [file tail $ld]
    928         if {8 == $vj} {
    929           set ld "${pp}/tcl${vj}.${vn}/${tail}"
    930         } else {
    931           proj-assert {9 == $vj}
    932           set ld "${pp}/${tail}"
    933         }
    934         define TCLLIBDIR $ld
    935         # [load foo.so], without a directory part, does not work via
    936         # automated tests on Haiku (but works when run
    937         # manually). Similarly, the post-install [package require ...]
    938         # test fails, presumably for a similar reason. We work around
    939         # the former in _teaish.tester.tcl.in. We work around the
    940         # latter by amending the post-install check's ::auto_path (in
    941         # Makefile.in). This code MUST NOT contain any single-quotes.
    942         define TEAISH_POSTINST_PREREQUIRE \
    943           [join [list set ::auto_path \
    944                    \[ linsert \$::auto_path 0 $ld \] \; \
    945                   ]]
    946         proj-indented-notice [subst -nocommands -nobackslashes {
    947           Haiku users take note: patching target installation dir to match
    948           Tcl's home because Haiku's is not writable.
    949 
    950           Original  : $old
    951           Substitute: $ld
    952         }]
    953       }
    954     }
    955   }
    956 }; # teaish__tcl_platform_quirks
    957 
    958 #
    959 # Searches $::argv and/or the build dir and/or the source dir for
    960 # teaish.tcl and friends. Fails if it cannot find teaish.tcl or if
    961 # there are other irreconcilable problems. If it returns 0 then it did
    962 # not find an extension but the --help flag was seen, in which case
    963 # that's not an error.
    964 #
    965 # This does not _load_ the extension, it primarily locates the files
    966 # which make up an extension and fills out no small amount of teaish
    967 # state related to that.
    968 #
    969 proc teaish__find_extension {} {
    970   proj-assert {!$::teaish__Config(install-mode)}
    971   teaish__verbose 1 msg-result "Looking for teaish extension..."
    972 
    973   # Helper for the foreach loop below.
    974   set checkTeaishTcl {{mustHave fid dir} {
    975     set f [file join $dir $fid]
    976     if {[file readable $f]} {
    977       file-normalize $f
    978     } elseif {$mustHave} {
    979       proj-error "Missing required $dir/$fid"
    980     }
    981   }}
    982 
    983   #
    984   # We have to handle some flags manually because the extension must
    985   # be loaded before [options] is run (so that the extension can
    986   # inject its own options).
    987   #
    988   set dirBld $::autosetup(builddir); # dir we're configuring under
    989   set dirSrc $::autosetup(srcdir);   # where teaish's configure script lives
    990   set extT ""; # teaish.tcl
    991   set largv {}; # rewritten $::argv
    992   set gotHelpArg 0; # got the --help
    993   foreach arg $::argv {
    994     #puts "*** arg=$arg"
    995     switch -glob -- $arg {
    996       --ted=* -
    997       --t-e-d=* -
    998       --teaish-extension-dir=* {
    999         # Ensure that $extD refers to a directory and contains a
   1000         # teaish.tcl.
   1001         regexp -- {--[^=]+=(.+)} $arg - extD
   1002         set extD [file-normalize $extD]
   1003         if {![file isdirectory $extD]} {
   1004           proj-error "--teaish-extension-dir value is not a directory: $extD"
   1005         }
   1006         set extT [apply $checkTeaishTcl 0 teaish.config $extD]
   1007         if {"" eq $extT} {
   1008           set extT [apply $checkTeaishTcl 1 teaish.tcl $extD]
   1009         }
   1010         set ::teaish__Config(extension-dir) $extD
   1011       }
   1012       --help {
   1013         incr gotHelpArg
   1014         lappend largv $arg
   1015       }
   1016       default {
   1017         lappend largv $arg
   1018       }
   1019     }
   1020   }
   1021   set ::argv $largv
   1022 
   1023   set dirExt $::teaish__Config(extension-dir); # dir with the extension
   1024   #
   1025   # teaish.tcl is a TCL script which implements various
   1026   # interfaces described by this framework.
   1027   #
   1028   # We use the first one we find in the builddir or srcdir.
   1029   #
   1030   if {"" eq $extT} {
   1031     set flist [list]
   1032     proj-assert {$dirExt eq ""}
   1033     lappend flist $dirBld/teaish.tcl $dirBld/teaish.config $dirSrc/teaish.tcl
   1034     if {![proj-first-file-found extT $flist]} {
   1035       if {$gotHelpArg} {
   1036         # Tell teaish-configure-core that the lack of extension is not
   1037         # an error when --help or --teaish-install is used.
   1038         return 0;
   1039       }
   1040       proj-indented-notice -error "
   1041 Did not find any of: $flist
   1042 
   1043 If you are attempting an out-of-tree build, use
   1044  --teaish-extension-dir=/path/to/extension"
   1045     }
   1046   }
   1047   if {![file readable $extT]} {
   1048     proj-error "extension tcl file is not readable: $extT"
   1049   }
   1050   set ::teaish__Config(teaish.tcl) $extT
   1051   set dirExt [file dirname $extT]
   1052 
   1053   set ::teaish__Config(extension-dir) $dirExt
   1054   set ::teaish__Config(blddir-is-extdir) [expr {$dirBld eq $dirExt}]
   1055   set ::teaish__Config(dist-enabled) $::teaish__Config(blddir-is-extdir); # may change later
   1056   set ::teaish__Config(dist-full-enabled) \
   1057     [expr {[file-normalize $::autosetup(srcdir)]
   1058            eq [file-normalize $::teaish__Config(extension-dir)]}]
   1059 
   1060   set addDist {{file} {
   1061     teaish-dist-add [file tail $file]
   1062   }}
   1063   apply $addDist $extT
   1064 
   1065   teaish__verbose 1 msg-result "Extension dir            = [teaish-get -dir]"
   1066   teaish__verbose 1 msg-result "Extension config         = $extT"
   1067 
   1068   teaish-pkginfo-set -name [file tail [file dirname $extT]]
   1069 
   1070   #
   1071   # teaish.make[.in] provides some of the info for the main makefile,
   1072   # like which source(s) to build and their build flags.
   1073   #
   1074   # We use the first one of teaish.make.in or teaish.make we find in
   1075   # $dirExt.
   1076   #
   1077   if {[proj-first-file-found extM \
   1078          [list \
   1079             $dirExt/teaish.make.in \
   1080             $dirExt/teaish.make \
   1081          ]]} {
   1082     if {[string match *.in $extM]} {
   1083       define TEAISH_MAKEFILE_IN $extM
   1084       define TEAISH_MAKEFILE _[file rootname [file tail $extM]]
   1085     } else {
   1086       define TEAISH_MAKEFILE_IN ""
   1087       define TEAISH_MAKEFILE $extM
   1088     }
   1089     apply $addDist $extM
   1090     teaish__verbose 1 msg-result "Extension makefile       = $extM"
   1091   } else {
   1092     define TEAISH_MAKEFILE_IN ""
   1093     define TEAISH_MAKEFILE ""
   1094   }
   1095 
   1096   # Look for teaish.pkginit.tcl[.in]
   1097   set piPolicy 0
   1098   if {[proj-first-file-found extI \
   1099          [list \
   1100             $dirExt/teaish.pkginit.tcl.in \
   1101             $dirExt/teaish.pkginit.tcl \
   1102            ]]} {
   1103     if {[string match *.in $extI]} {
   1104       # Generate teaish.pkginit.tcl from $extI.
   1105       define TEAISH_PKGINIT_TCL_IN $extI
   1106       define TEAISH_PKGINIT_TCL [file rootname [file tail $extI]]
   1107       set piPolicy 0x01
   1108     } else {
   1109       # Assume static $extI.
   1110       define TEAISH_PKGINIT_TCL_IN ""
   1111       define TEAISH_PKGINIT_TCL $extI
   1112       set piPolicy 0x10
   1113     }
   1114     apply $addDist $extI
   1115     teaish__verbose 1 msg-result "Extension post-load init = $extI"
   1116     define TEAISH_PKGINIT_TCL_TAIL \
   1117       [file tail [get-define TEAISH_PKGINIT_TCL]]; # for use in pkgIndex.tcl.in
   1118   }
   1119   set ::teaish__Config(pkginit-policy) $piPolicy
   1120 
   1121   # Look for pkgIndex.tcl[.in]...
   1122   set piPolicy 0
   1123   if {[proj-first-file-found extPI $dirExt/pkgIndex.tcl.in]} {
   1124     # Generate ./pkgIndex.tcl from $extPI.
   1125     define TEAISH_PKGINDEX_TCL_IN $extPI
   1126     define TEAISH_PKGINDEX_TCL [file rootname [file tail $extPI]]
   1127     apply $addDist $extPI
   1128     set piPolicy 0x01
   1129   } elseif {$dirExt ne $dirSrc
   1130             && [proj-first-file-found extPI $dirSrc/pkgIndex.tcl.in]} {
   1131     # Generate ./pkgIndex.tcl from $extPI.
   1132     define TEAISH_PKGINDEX_TCL_IN $extPI
   1133     define TEAISH_PKGINDEX_TCL [file rootname [file tail $extPI]]
   1134     set piPolicy 0x02
   1135   } elseif {[proj-first-file-found extPI $dirExt/pkgIndex.tcl]} {
   1136     # Assume $extPI's a static file and use it.
   1137     define TEAISH_PKGINDEX_TCL_IN ""
   1138     define TEAISH_PKGINDEX_TCL $extPI
   1139     apply $addDist $extPI
   1140     set piPolicy 0x10
   1141   }
   1142   # Reminder: we have to delay removal of stale TEAISH_PKGINDEX_TCL
   1143   # and the proj-dot-ins-append of TEAISH_PKGINDEX_TCL_IN until much
   1144   # later in the process.
   1145   set ::teaish__Config(pkgindex-policy) $piPolicy
   1146 
   1147   # Look for teaish.test.tcl[.in]
   1148   proj-assert {"" ne $dirExt}
   1149   set flist [list $dirExt/teaish.test.tcl.in $dirExt/teaish.test.tcl]
   1150   if {[proj-first-file-found ttt $flist]} {
   1151     if {[string match *.in $ttt]} {
   1152       # Generate _teaish.test.tcl from $ttt
   1153       set xt _[file rootname [file tail $ttt]]
   1154       file delete -force -- $xt; # ensure no stale copy is used
   1155       define TEAISH_TEST_TCL $xt
   1156       define TEAISH_TEST_TCL_IN $ttt
   1157     } else {
   1158       define TEAISH_TEST_TCL $ttt
   1159       define TEAISH_TEST_TCL_IN ""
   1160     }
   1161     apply $addDist $ttt
   1162   } else {
   1163     define TEAISH_TEST_TCL ""
   1164     define TEAISH_TEST_TCL_IN ""
   1165   }
   1166 
   1167   # Look for _teaish.tester.tcl[.in]
   1168   set flist [list $dirExt/_teaish.tester.tcl.in $dirSrc/_teaish.tester.tcl.in]
   1169   if {[proj-first-file-found ttt $flist]} {
   1170     # Generate teaish.test.tcl from $ttt
   1171     set xt [file rootname [file tail $ttt]]
   1172     file delete -force -- $xt; # ensure no stale copy is used
   1173     define TEAISH_TESTER_TCL $xt
   1174     define TEAISH_TESTER_TCL_IN $ttt
   1175     if {[lindex $flist 0] eq $ttt} {
   1176       apply $addDist $ttt
   1177     }
   1178     unset ttt xt
   1179   } else {
   1180     if {[file exists [set ttt [file join $dirSrc _teaish.tester.tcl.in]]]} {
   1181       set xt [file rootname [file tail $ttt]]
   1182       define TEAISH_TESTER_TCL $xt
   1183       define TEAISH_TESTER_TCL_IN $ttt
   1184     } else {
   1185       define TEAISH_TESTER_TCL ""
   1186       define TEAISH_TESTER_TCL_IN ""
   1187     }
   1188   }
   1189   unset flist
   1190 
   1191   # TEAISH_OUT_OF_EXT_TREE = 1 if we're building from a dir other
   1192   # than the extension's home dir.
   1193   define TEAISH_OUT_OF_EXT_TREE \
   1194     [expr {[file-normalize $::autosetup(builddir)] ne \
   1195              [file-normalize $::teaish__Config(extension-dir)]}]
   1196   return 1
   1197 }; # teaish__find_extension
   1198 
   1199 #
   1200 # @teaish-cflags-add ?-p|prepend? ?-define? cflags...
   1201 #
   1202 # Equivalent to [proj-define-amend TEAISH_CFLAGS {*}$args].
   1203 #
   1204 proc teaish-cflags-add {args} {
   1205   proj-define-amend TEAISH_CFLAGS {*}$args
   1206 }
   1207 
   1208 #
   1209 # @teaish-define-to-cflag ?flags? defineName...|{defineName...}
   1210 #
   1211 # Uses [proj-define-to-cflag] to expand a list of [define] keys, each
   1212 # one a separate argument, to CFLAGS-style -D... form then appends
   1213 # that to the current TEAISH_CFLAGS.
   1214 #
   1215 # It accepts these flags from proj-define-to-cflag: -quote,
   1216 # -zero-undef. It does _not_ support its -list flag.
   1217 #
   1218 # It accepts its non-flag argument(s) in 2 forms: (1) each arg is a
   1219 # single [define] key or (2) its one arg is a list of such keys.
   1220 #
   1221 # TODO: document teaish's well-defined (as it were) defines for this
   1222 # purpose. At a bare minimum:
   1223 #
   1224 #  - TEAISH_NAME
   1225 #  - TEAISH_PKGNAME
   1226 #  - TEAISH_VERSION
   1227 #  - TEAISH_LIBDIR_NAME
   1228 #  - TEAISH_LOAD_PREFIX
   1229 #  - TEAISH_URL
   1230 #
   1231 proc teaish-define-to-cflag {args} {
   1232   set flags {}
   1233   while {[string match -* [lindex $args 0]]} {
   1234     set arg [lindex $args 0]
   1235     switch -exact -- $arg {
   1236       -quote -
   1237       -zero-undef {
   1238         lappend flags $arg
   1239         set args [lassign $args -]
   1240       }
   1241       default break
   1242     }
   1243   }
   1244   if {1 == [llength $args]} {
   1245     set args [list {*}[lindex $args 0]]
   1246   }
   1247   #puts "***** flags=$flags args=$args"
   1248   teaish-cflags-add [proj-define-to-cflag {*}$flags {*}$args]
   1249 }
   1250 
   1251 #
   1252 # @teaish-cflags-for-tea ?...CFLAGS?
   1253 #
   1254 # Adds several -DPACKAGE_... CFLAGS using the extension's metadata,
   1255 # all as quoted strings. Those symbolic names are commonly used in
   1256 # TEA-based builds, and this function is intended to simplify porting
   1257 # of such builds. The -D... flags added are:
   1258 #
   1259 #  -DPACKAGE_VERSION=...
   1260 #  -DPACKAGE_NAME=...
   1261 #  -DPACKAGE_URL=...
   1262 #  -DPACKAGE_STRING=...
   1263 #
   1264 # Any arguments are passed-on as-is to teaish-cflags-add.
   1265 #
   1266 proc teaish-cflags-for-tea {args} {
   1267   set name $::teaish__PkgInfo(-name)
   1268   set version $::teaish__PkgInfo(-version)
   1269   set pstr [join [list $name $version]]
   1270   teaish-cflags-add \
   1271     {*}$args \
   1272     '-DPACKAGE_VERSION="$version"' \
   1273     '-DPACKAGE_NAME="$name"' \
   1274     '-DPACKAGE_STRING="$pstr"' \
   1275     '-DPACKAGE_URL="[teaish-get -url]"'
   1276 }
   1277 
   1278 #
   1279 # @teaish-ldflags-add ?-p|-prepend? ?-define? ldflags...
   1280 #
   1281 # Equivalent to [proj-define-amend TEAISH_LDFLAGS {*}$args].
   1282 #
   1283 # Typically, -lXYZ flags need to be in "reverse" order, with each -lY
   1284 # resolving symbols for -lX's to its left. This order is largely
   1285 # historical, and not relevant on all environments, but it is
   1286 # technically correct and still relevant on some environments.
   1287 #
   1288 # See: teaish-ldflags-prepend
   1289 #
   1290 proc teaish-ldflags-add {args} {
   1291   proj-define-amend TEAISH_LDFLAGS {*}$args
   1292 }
   1293 
   1294 #
   1295 # @teaish-ldflags-prepend args...
   1296 #
   1297 # Functionally equivalent to [teaish-ldflags-add -p {*}$args]
   1298 #
   1299 proc teaish-ldflags-prepend {args} {
   1300   teaish-ldflags-add -p {*}$args
   1301 }
   1302 
   1303 #
   1304 # @teaish-src-add ?-dist? ?-dir? src-files...
   1305 #
   1306 # Appends all non-empty $args to the project's list of C/C++ source or
   1307 # (in some cases) object files.
   1308 #
   1309 # If passed -dist then it also passes each filename, as-is, to
   1310 # [teaish-dist-add].
   1311 #
   1312 # If passed -dir then each src-file has [teaish-get -dir] prepended to
   1313 # it before they're added to the list. As often as not, that will be
   1314 # the desired behavior so that out-of-tree builds can find the
   1315 # sources, but there are cases where it's not desired (e.g. when using
   1316 # a source file from outside of the extension's dir, or when adding
   1317 # object files (which are typically in the build tree)).
   1318 #
   1319 proc teaish-src-add {args} {
   1320   proj-parse-simple-flags args flags {
   1321     -dist 0 {expr 1}
   1322     -dir  0 {expr 1}
   1323   }
   1324   if {$flags(-dist)} {
   1325     teaish-dist-add {*}$args
   1326   }
   1327   if {$flags(-dir)} {
   1328     set xargs {}
   1329     foreach arg $args {
   1330       if {"" ne $arg} {
   1331         lappend xargs [file join $::teaish__Config(extension-dir) $arg]
   1332       }
   1333     }
   1334     set args $xargs
   1335   }
   1336   lappend ::teaish__Config(extension-src) {*}$args
   1337 }
   1338 
   1339 #
   1340 # @teaish-dist-add files-or-dirs...
   1341 #
   1342 # Adds the given files to the list of files to include with the "make
   1343 # dist" rules.
   1344 #
   1345 # This is a no-op when the current build is not in the extension's
   1346 # directory, as dist support is disabled in out-of-tree builds.
   1347 #
   1348 # It is not legal to call this until [teaish-get -dir] has been
   1349 # reliably set (via teaish__find_extension).
   1350 #
   1351 proc teaish-dist-add {args} {
   1352   if {$::teaish__Config(blddir-is-extdir)} {
   1353     # ^^^ reminder: we ignore $::teaish__Config(dist-enabled) here
   1354     # because the client might want to implement their own dist
   1355     # rules.
   1356     #proj-warn "**** args=$args"
   1357     lappend ::teaish__Config(dist-files) {*}$args
   1358   }
   1359 }
   1360 
   1361 # teaish-install-add files...
   1362 # Equivalent to [proj-define-apend TEAISH_INSTALL_FILES ...].
   1363 #proc teaish-install-add {args} {
   1364 #  proj-define-amend TEAISH_INSTALL_FILES {*}$args
   1365 #}
   1366 
   1367 #
   1368 # @teash-make-add args...
   1369 #
   1370 # Appends makefile code to the TEAISH_MAKEFILE_CODE define. Each
   1371 # arg may be any of:
   1372 #
   1373 # -tab: emit a literal tab
   1374 # -nl: emit a literal newline
   1375 # -nltab: short for -nl -tab
   1376 # -bnl: emit a backslash-escaped end-of-line
   1377 # -bnltab: short for -eol -tab
   1378 #
   1379 # Anything else is appended verbatim. This function adds no additional
   1380 # spacing between each argument nor between subsequent invocations.
   1381 # Generally speaking, a series of calls to this function need to
   1382 # be sure to end the series with a newline.
   1383 proc teaish-make-add {args} {
   1384   set out [get-define TEAISH_MAKEFILE_CODE ""]
   1385   foreach a $args {
   1386     switch -exact -- $a {
   1387       -bnl    { set a " \\\n" }
   1388       -bnltab { set a " \\\n\t" }
   1389       -tab    { set a "\t" }
   1390       -nl     { set a "\n" }
   1391       -nltab  { set a "\n\t" }
   1392     }
   1393     append out $a
   1394   }
   1395   define TEAISH_MAKEFILE_CODE $out
   1396 }
   1397 
   1398 # Internal helper to generate a clean/distclean rule name
   1399 proc teaish__cleanup_rule {{tgt clean}} {
   1400   set x [incr ::teaish__Config(teaish__cleanup_rule-counter-${tgt})]
   1401   return ${tgt}-_${x}_
   1402 }
   1403 
   1404 # @teaish-make-obj ?flags? ?...args?
   1405 #
   1406 # Uses teaish-make-add to inject makefile rules for $objfile from
   1407 # $srcfile, which is assumed to be C code which uses libtcl. Unless
   1408 # -recipe is used (see below) it invokes the compiler using the
   1409 # makefile-defined $(CC.tcl) which, in the default Makefile.in
   1410 # template, includes any flags needed for building against the
   1411 # configured Tcl.
   1412 #
   1413 # This always terminates the resulting code with a newline.
   1414 #
   1415 # Any arguments after the 2nd may be flags described below or, if no
   1416 # -recipe is provided, flags for the compiler call.
   1417 #
   1418 #   -obj obj-filename.o
   1419 #
   1420 #   -src src-filename.c
   1421 #
   1422 #   -recipe {...}
   1423 #   Uses the trimmed value of {...} as the recipe, prefixing it with
   1424 #   a single hard-tab character.
   1425 #
   1426 #   -deps {...}
   1427 #   List of extra files to list as dependencies of $o.
   1428 #
   1429 #   -clean
   1430 #   Generate cleanup rules as well.
   1431 proc teaish-make-obj {args} {
   1432   proj-parse-simple-flags args flags {
   1433     -clean 0 {expr 1}
   1434     -recipe => {}
   1435     -deps => {}
   1436     -obj => {}
   1437     -src => {}
   1438   }
   1439   #parray flags
   1440   if {"" eq $flags(-obj)} {
   1441     set args [lassign $args flags(-obj)]
   1442     if {"" eq $flags(-obj)} {
   1443       proj-error "Missing -obj flag."
   1444     }
   1445   }
   1446   foreach f {-deps -src} {
   1447     set flags($f) [string trim [string map {\n " "} $flags($f)]]
   1448   }
   1449   foreach f {-deps -src} {
   1450     set flags($f) [string trim $flags($f)]
   1451   }
   1452   #parray flags
   1453   #puts "-- args=$args"
   1454   teaish-make-add \
   1455     "# [proj-scope 1] -> [proj-scope] $flags(-obj) $flags(-src)" -nl \
   1456     "$flags(-obj): $flags(-src) $::teaish__Config(teaish.tcl)"
   1457   if {[info exists flags(-deps)]} {
   1458     teaish-make-add " " [join $flags(-deps)]
   1459   }
   1460   teaish-make-add -nltab
   1461   if {[info exists flags(-recipe)]} {
   1462     teaish-make-add [string trim $flags(-recipe)] -nl
   1463   } else {
   1464     teaish-make-add [join [list \$(CC.tcl) -c $flags(-src) {*}$args]] -nl
   1465   }
   1466   if {$flags(-clean)} {
   1467     set rule [teaish__cleanup_rule]
   1468     teaish-make-add \
   1469       "clean: $rule\n$rule:\n\trm -f \"$flags(-obj)\"\n"
   1470   }
   1471 }
   1472 
   1473 #
   1474 # @teaish-make-clean ?-r? ?-dist? ...files|{...files}
   1475 #
   1476 # Adds makefile rules for cleaning up the given files via the "make
   1477 # clean" or (if -dist is used) "make distclean" makefile rules. The -r
   1478 # flag uses "rm -fr" instead of "rm -f", so be careful with that.
   1479 #
   1480 # The file names are taken literally as arguments to "rm", so they may
   1481 # be shell wildcards to be resolved at cleanup-time. To clean up whole
   1482 # directories, pass the -r flag. Each name gets quoted in
   1483 # double-quotes, so spaces in names should not be a problem (but
   1484 # double-quotes in names will be).
   1485 #
   1486 proc teaish-make-clean {args} {
   1487   if {1 == [llength $args]} {
   1488     set args [list {*}[lindex $args 0]]
   1489   }
   1490 
   1491   set tgt clean
   1492   set rmflags "-f"
   1493   proj-parse-simple-flags args flags {
   1494     -dist 0 {
   1495       set tgt distclean
   1496     }
   1497     -r 0 {
   1498       set rmflags "-fr"
   1499     }
   1500   }
   1501   set rule [teaish__cleanup_rule $tgt]
   1502   teaish-make-add "# [proj-scope 1] -> [proj-scope]: [join $args]\n"
   1503   teaish-make-add "${rule}:\n\trm ${rmflags}"
   1504   foreach a $args {
   1505     teaish-make-add " \"$a\""
   1506   }
   1507   teaish-make-add "\n${tgt}: ${rule}\n"
   1508 }
   1509 
   1510 #
   1511 # @teaish-make-config-header filename
   1512 #
   1513 # Invokes autosetup's [make-config-header] and passes it $filename and
   1514 # a relatively generic list of options for controlling which defined
   1515 # symbols get exported. Clients which need more control over the
   1516 # exports can copy/paste/customize this.
   1517 #
   1518 # The exported file is then passed to [proj-touch] because, in
   1519 # practice, that's sometimes necessary to avoid build dependency
   1520 # issues.
   1521 #
   1522 proc teaish-make-config-header {filename} {
   1523   make-config-header $filename \
   1524     -none {HAVE_CFLAG_* LDFLAGS_* SH_* TEAISH__* TEAISH_*_CODE} \
   1525     -auto {SIZEOF_* HAVE_* TEAISH_*  TCL_*} \
   1526     -none *
   1527   proj-touch $filename; # help avoid frequent unnecessary auto-reconfig
   1528 }
   1529 
   1530 #
   1531 # @teaish-feature-cache-set $key value
   1532 #
   1533 # Sets a feature-check cache entry with the given key.
   1534 # See proj-cache-set for the key's semantics. $key should
   1535 # normally be 0.
   1536 #
   1537 proc teaish-feature-cache-set {key val} {
   1538   proj-cache-set -key $key -level 1 $val
   1539 }
   1540 
   1541 #
   1542 # @teaish-feature-cache-check key tgtVarName
   1543 #
   1544 # Checks for a feature-check cache entry with the given key.
   1545 # See proj-cache-set for the key's semantics.
   1546 #
   1547 # $key should also almost always be 0 but, due to a tclsh
   1548 # incompatibility in 1 OS, it cannot have a default value unless it's
   1549 # the second argument (but it should be the first one).
   1550 #
   1551 # If the feature-check cache has a matching entry then this function
   1552 # assigns its value to tgtVar and returns 1, else it assigns tgtVar to
   1553 # "" and returns 0.
   1554 #
   1555 # See proj-cache-check for $key's semantics.
   1556 #
   1557 proc teaish-feature-cache-check {key tgtVar} {
   1558   upvar $tgtVar tgt
   1559   proj-cache-check -key $key -level 1 tgt
   1560 }
   1561 
   1562 #
   1563 # @teaish-check-cached@ ?flags? msg script...
   1564 #
   1565 # A proxy for feature-test impls which handles caching of a feature
   1566 # flag check on per-function basis, using the calling scope's name as
   1567 # the cache key.
   1568 #
   1569 # It emits [msg-checking $msg]. If $msg is empty then it defaults to
   1570 # the name of the caller's scope. The -nomsg flag suppresses the
   1571 # message for non-cache-hit checks. At the end, it will [msg-result
   1572 # "ok"] [msg-result "no"] unless -nostatus is used, in which case the
   1573 # caller is responsible for emitting at least a newline when it's
   1574 # done. The -msg-0 and -msg-1 flags can be used to change the ok/no
   1575 # text.
   1576 #
   1577 # This function checks for a cache hit before running $script and
   1578 # caching the result. If no hit is found then $script is run in the
   1579 # calling scope and its result value is stored in the cache. This
   1580 # routine will intercept a 'return' from $script.
   1581 #
   1582 # $script may be a command and its arguments, as opposed to a single
   1583 # script block.
   1584 #
   1585 # Flags:
   1586 #
   1587 #   -nostatus = do not emit "ok" or "no" at the end. This presumes
   1588 #    that either $script will emit at least one newline before
   1589 #    returning or the caller will account for it. Because of how this
   1590 #    function is typically used, -nostatus is not honored when the
   1591 #    response includes a cached result.
   1592 #
   1593 #   -quiet = disable output from Autosetup's msg-checking and
   1594 #    msg-result for the duration of the $script check. Note that when
   1595 #    -quiet is in effect, Autosetup's user-notice can be used to queue
   1596 #    up output to appear after the check is done. Also note that
   1597 #    -quiet has no effect on _this_ function, only the $script part.
   1598 #
   1599 #   -nomsg = do not emit $msg for initial check. Like -nostatus, this
   1600 #    flag is not honored when the response includes a cached result
   1601 #    because it would otherwise produce no output (which is confusing
   1602 #    in this context). This is useful when a check runs several other
   1603 #    verbose checks and they emit all the necessary info.
   1604 #
   1605 #   -msg-0 and -msg-1 MSG = strings to show when the check has failed
   1606 #    resp. passed. Defaults are "no" and "ok". The 0 and 1 refer to the
   1607 #    result value from teaish-feature-cache-check.
   1608 #
   1609 #   -key cachekey = set the cache context key. Only needs to be
   1610 #    explicit when using this function multiple times from a single
   1611 #    scope. See proj-cache-check and friends for details on the key
   1612 #    name. Its default is the name of the scope which calls this
   1613 #    function.
   1614 #
   1615 proc teaish-check-cached {args} {
   1616   proj-parse-simple-flags args flags {
   1617     -nostatus 0 {expr 1}
   1618     -quiet    0 {expr 1}
   1619     -key      => 1
   1620     -nomsg    0 {expr 1}
   1621     -msg-0    => no
   1622     -msg-1    => ok
   1623   }
   1624   set args [lassign $args msg]
   1625   set script [join $args]
   1626   if {"" eq $msg} {
   1627     set msg [proj-scope 1]
   1628   }
   1629   if {[teaish-feature-cache-check $flags(-key) check]} {
   1630     #if {0 == $flags(-nomsg)} {
   1631     msg-checking "${msg} ... (cached) "
   1632     #}
   1633     #if {!$flags(-nostatus)} {
   1634     msg-result $flags(-msg-[expr {0 != ${check}}])
   1635     #}
   1636     return $check
   1637   } else {
   1638     if {0 == $flags(-nomsg)} {
   1639       msg-checking "${msg} ... "
   1640     }
   1641     if {$flags(-quiet)} {
   1642       incr ::autosetup(msg-quiet)
   1643     }
   1644     set code [catch {uplevel 1 $script} rc xopt]
   1645     if {$flags(-quiet)} {
   1646       incr ::autosetup(msg-quiet) -1
   1647     }
   1648     #puts "***** cached-check got code=$code rc=$rc"
   1649     if {$code in {0 2}} {
   1650       teaish-feature-cache-set 1 $rc
   1651       if {!$flags(-nostatus)} {
   1652         msg-result $flags(-msg-[expr {0 != ${rc}}])
   1653       } else {
   1654         #show-notices; # causes a phantom newline because we're in a
   1655         #msg-checking scope, so...
   1656         if {[info exists ::autosetup(notices)]} {
   1657           show-notices
   1658         }
   1659       }
   1660     } else {
   1661       #puts "**** code=$code rc=$rc xopt=$xopt"
   1662       teaish-feature-cache-set 1 0
   1663     }
   1664     #puts "**** code=$code rc=$rc"
   1665     return {*}$xopt $rc
   1666   }
   1667 }
   1668 
   1669 #
   1670 # Internal helper for teaish__defs_format_: returns a JSON-ish quoted
   1671 # form of the given string-type values.
   1672 #
   1673 # If $asList is true then the return value is in {$value} form.  If
   1674 # $asList is false it only performs the most basic of escaping and
   1675 # the input must not contain any control characters.
   1676 #
   1677 proc teaish__quote_str {asList value} {
   1678   if {$asList} {
   1679     return "{${value}}"
   1680   }
   1681   return \"[string map [list \\ \\\\ \" \\\"] $value]\"
   1682 }
   1683 
   1684 #
   1685 # Internal helper for teaish__defines_to_list. Expects to be passed
   1686 # a name and the variadic $args which are passed to
   1687 # teaish__defines_to_list.. If it finds a pattern match for the
   1688 # given $name in the various $args, it returns the type flag for that
   1689 # $name, e.g. "-str" or "-bare", else returns an empty string.
   1690 #
   1691 proc teaish__defs_type {name spec} {
   1692   foreach {type patterns} $spec {
   1693     foreach pattern $patterns {
   1694       if {[string match $pattern $name]} {
   1695         return $type
   1696       }
   1697     }
   1698   }
   1699   return ""
   1700 }
   1701 
   1702 #
   1703 # An internal impl detail. Requires a data type specifier, as used by
   1704 # Autosetup's [make-config-header], and a value. Returns the formatted
   1705 # value or the value $::teaish__Config(defs-skip) if the caller should
   1706 # skip emitting that value.
   1707 #
   1708 # In addition to -str, -auto, etc., as defined by make-config-header,
   1709 # it supports:
   1710 #
   1711 #  -list {...} will cause non-integer values to be quoted in {...}
   1712 #  instead of quotes.
   1713 #
   1714 #  -autolist {...} works like -auto {...} except that it falls back to
   1715 #   -list {...} type instead of -str {...} style for non-integers.
   1716 #
   1717 #  -jsarray {...} emits the output in something which, for
   1718 #   conservative inputs, will be a valid JSON array. It can only
   1719 #   handle relatively simple values with no control characters in
   1720 #   them.
   1721 #
   1722 set teaish__Config(defs-skip) "-teaish__defs_format sentinel"
   1723 proc teaish__defs_format {type value} {
   1724   switch -exact -- $type {
   1725     -bare {
   1726       # Just output the value unchanged
   1727     }
   1728     -none {
   1729       set value $::teaish__Config(defs-skip)
   1730     }
   1731     -str {
   1732       set value [teaish__quote_str 0 $value]
   1733     }
   1734     -auto {
   1735       # Automatically determine the type
   1736       if {![string is integer -strict $value]} {
   1737         set value [teaish__quote_str 0 $value]
   1738       }
   1739     }
   1740     -autolist {
   1741       if {![string is integer -strict $value]} {
   1742         set value [teaish__quote_str 1 $value]
   1743       }
   1744     }
   1745     -list {
   1746       set value [teaish__quote_str 1 $value]
   1747     }
   1748     -jsarray {
   1749       set ar {}
   1750       foreach v $value {
   1751         if {![string is integer -strict $v]} {
   1752           set v [teaish__quote_str 0 $v]
   1753         }
   1754         if {$::teaish__Config(defs-skip) ne $v} {
   1755           lappend ar $v
   1756         }
   1757       }
   1758       set value [concat \[ [join $ar {, }] \]]
   1759     }
   1760     "" {
   1761       # (Much later:) Why do we do this?
   1762       set value $::teaish__Config(defs-skip)
   1763     }
   1764     default {
   1765       proj-error \
   1766         "Unknown [proj-scope] -type ($type) called from" \
   1767         [proj-scope 1]
   1768     }
   1769   }
   1770   return $value
   1771 }
   1772 
   1773 #
   1774 # Returns Tcl code in the form of code which evaluates to a list of
   1775 # configure-time DEFINEs in the form {key val key2 val...}. It may
   1776 # misbehave for values which are not numeric or simple strings.  Some
   1777 # defines are specifically filtered out of the result, either because
   1778 # their irrelevant to teaish or because they may be arbitrarily large
   1779 # (e.g. makefile content).
   1780 #
   1781 # The $args are explained in the docs for internal-use-only
   1782 # [teaish__defs_format]. The default mode is -autolist.
   1783 #
   1784 proc teaish__defines_to_list {args} {
   1785   set lines {}
   1786   lappend lines "\{"
   1787   set skipper $::teaish__Config(defs-skip)
   1788   set args [list \
   1789               -none {
   1790                 TEAISH__*
   1791                 TEAISH_*_CODE
   1792                 AM_* AS_*
   1793               } \
   1794               {*}$args \
   1795               -autolist *]
   1796   foreach d [lsort [dict keys [all-defines]]] {
   1797     set type [teaish__defs_type $d $args]
   1798     set value [teaish__defs_format $type [get-define $d]]
   1799     if {$skipper ne $value} {
   1800       lappend lines "$d $value"
   1801     }
   1802   }
   1803   lappend lines "\}"
   1804   tailcall join $lines "\n"
   1805 }
   1806 
   1807 #
   1808 # teaish__pragma ...flags
   1809 #
   1810 # Offers a way to tweak how teaish's core behaves in some cases, in
   1811 # particular those which require changing how the core looks for an
   1812 # extension and its files.
   1813 #
   1814 # Accepts the following flags. Those marked with [L] are safe to use
   1815 # during initial loading of tclish.tcl (recall that most teaish APIs
   1816 # cannot be used until [teaish-configure] is called).
   1817 #
   1818 #    static-pkgIndex.tcl [L]: Tells teaish that ./pkgIndex.tcl is not
   1819 #    a generated file, so it will not try to overwrite or delete
   1820 #    it. Errors out if it does not find pkgIndex.tcl in the
   1821 #    extension's dir.
   1822 #
   1823 #    no-dist [L]: tells teaish to elide the 'make dist' recipe
   1824 #    from the generated Makefile.
   1825 #
   1826 #    no-dll [L]: tells teaish to elide the DLL-building recipe
   1827 #    from the generated Makefile.
   1828 #
   1829 #    no-vsatisfies-error [L]: tells teaish that failure to match the
   1830 #    -vsatisfies value should simply "return" instead of "error".
   1831 #
   1832 #    no-tester [L]: disables automatic generation of teaish.test.tcl
   1833 #    even if a copy of _teaish.tester.tcl.in is found.
   1834 #
   1835 #    no-full-dist [L]: changes the "make dist" rules to never include
   1836 #    a copy of teaish itself. By default it will include itself only
   1837 #    if the extension lives in the same directory as teaish.
   1838 #
   1839 #    full-dist [L]: changes the "make dist" rules to always include
   1840 #    a copy of teaish itself.
   1841 #
   1842 # Emits a warning message for unknown arguments.
   1843 #
   1844 proc teaish__pragma {args} {
   1845   foreach arg $args {
   1846     switch -exact -- $arg {
   1847 
   1848       static-pkgIndex.tcl {
   1849         if {$::teaish__Config(tm-policy)} {
   1850           proj-fatal -up "Cannot use pragma $arg together with -tm.tcl or -tm.tcl.in."
   1851         }
   1852         set tpi [file join $::teaish__Config(extension-dir) pkgIndex.tcl]
   1853         if {[file exists $tpi]} {
   1854           define TEAISH_PKGINDEX_TCL_IN ""
   1855           define TEAISH_PKGINDEX_TCL $tpi
   1856           set ::teaish__Config(pkgindex-policy) 0x20
   1857         } else {
   1858           proj-error "pragma $arg: found no package-local pkgIndex.tcl\[.in]"
   1859         }
   1860       }
   1861 
   1862       no-dist {
   1863         set ::teaish__Config(dist-enabled) 0
   1864       }
   1865 
   1866       no-install {
   1867         set ::teaish__Config(install-enabled) 0
   1868       }
   1869 
   1870       full-dist {
   1871         set ::teaish__Config(dist-full-enabled) 1
   1872       }
   1873 
   1874       no-full-dist {
   1875         set ::teaish__Config(dist-full-enabled) 0
   1876       }
   1877 
   1878       no-dll {
   1879         set ::teaish__Config(dll-enabled) 0
   1880       }
   1881 
   1882       no-vsatisfies-error {
   1883         set ::teaish__Config(vsatisfies-error) 0
   1884       }
   1885 
   1886       no-tester {
   1887         define TEAISH_TESTER_TCL_IN ""
   1888         define TEAISH_TESTER_TCL ""
   1889       }
   1890 
   1891       default {
   1892         proj-error "Unknown flag: $arg"
   1893       }
   1894     }
   1895   }
   1896 }
   1897 
   1898 #
   1899 # @teaish-pkginfo-set ...flags
   1900 #
   1901 # The way to set up the initial package state. Used like:
   1902 #
   1903 #   teaish-pkginfo-set -name foo -version 0.1.2
   1904 #
   1905 # Or:
   1906 #
   1907 #   teaish-pkginfo-set ?-vars|-subst? {-name foo -version 0.1.2}
   1908 #
   1909 # The latter may be easier to write for a multi-line invocation.
   1910 #
   1911 # For the second call form, passing the -vars flag tells it to perform
   1912 # a [subst] of (only) variables in the {...} part from the calling
   1913 # scope. The -subst flag will cause it to [subst] the {...} with
   1914 # command substitution as well (but no backslash substitution). When
   1915 # using -subst for string concatenation, e.g.  with -libDir
   1916 # foo[get-version-number], be sure to wrap the value in braces:
   1917 # -libDir {foo[get-version-number]}.
   1918 #
   1919 # Each pkginfo flag corresponds to one piece of extension package
   1920 # info.  Teaish provides usable default values for all of these flags,
   1921 # but at least the -name and -version should be set by clients.
   1922 # e.g. the default -name is the directory name the extension lives in,
   1923 # which may change (e.g. when building it from a "make dist" bundle).
   1924 #
   1925 # The flags:
   1926 #
   1927 #    -name theName: The extension's name. It defaults to the name of the
   1928 #     directory containing the extension. (In TEA this would be the
   1929 #     PACKAGE_NAME, not to be confused with...)
   1930 #
   1931 #    -name.pkg pkg-provide-name: The extension's name for purposes of
   1932 #     Tcl_PkgProvide(), [package require], and friends. It defaults to
   1933 #     the `-name`, and is normally the same, but some projects (like
   1934 #     SQLite) have a different name here than they do in their
   1935 #     historical TEA PACKAGE_NAME.
   1936 #
   1937 #    -version version: The extension's package version. Defaults to
   1938 #     0.0.0.
   1939 #
   1940 #    -libDir dirName: The base name of the directory into which this
   1941 #     extension should be installed. It defaults to a concatenation of
   1942 #     `-name.pkg` and `-version`.
   1943 #
   1944 #    -loadPrefix prefix: For use as the second argument passed to
   1945 #     Tcl's `load` command in the package-loading process. It defaults
   1946 #     to title-cased `-name.pkg` because Tcl's `load` plugin system
   1947 #     expects it in that form.
   1948 #
   1949 #    -options {...}: If provided, it must be a list compatible with
   1950 #     Autosetup's `options-add` function. These can also be set up via
   1951 #     `teaish-options`.
   1952 #
   1953 #    -vsatisfies {{...} ...}: Expects a list-of-lists of conditions
   1954 #     for Tcl's `package vsatisfies` command: each list entry is a
   1955 #     sub-list of `{PkgName Condition...}`.  Teaish inserts those
   1956 #     checks via its default pkgIndex.tcl.in and _teaish.tester.tcl.in
   1957 #     templates to verify that the system's package dependencies meet
   1958 #     these requirements. The default value is `{{Tcl 8.5-}}` (recall
   1959 #     that it's a list-of-lists), as 8.5 is the minimum Tcl version
   1960 #     teaish will run on, but some extensions may require newer
   1961 #     versions or dependencies on other packages. As a special case,
   1962 #     if `-vsatisfies` is given a single token, e.g. `8.6-`, then it
   1963 #     is transformed into `{Tcl $thatToken}`, i.e. it checks the Tcl
   1964 #     version which the package is being run with.  If given multiple
   1965 #     lists, each `package provides` check is run in the given
   1966 #     order. Failure to meet a `vsatisfies` condition triggers an
   1967 #     error.
   1968 #
   1969 #    -url {...}: an optional URL for the extension.
   1970 #
   1971 #    -pragmas {...}  A list of infrequently-needed lower-level
   1972 #     directives which can influence teaish, including:
   1973 #
   1974 #      static-pkgIndex.tcl: tells teaish that the client manages their
   1975 #      own pkgIndex.tcl, so that teaish won't try to overwrite it
   1976 #      using a template.
   1977 #
   1978 #      no-dist: tells teaish to elide the "make dist" recipe from the
   1979 #      makefile so that the client can implement it.
   1980 #
   1981 #      no-dll: tells teaish to elide the makefile rules which build
   1982 #      the DLL, as well as any templated test script and pkgIndex.tcl
   1983 #      references to them. The intent here is to (A) support
   1984 #      client-defined build rules for the DLL and (B) eventually
   1985 #      support script-only extensions.
   1986 #
   1987 # Unsupported flags or pragmas will trigger an error.
   1988 #
   1989 # Potential pothole: setting certain state, e.g. -version, after the
   1990 # initial call requires recalculating of some [define]s. Any such
   1991 # changes should be made as early as possible in teaish-configure so
   1992 # that any later use of those [define]s gets recorded properly (not
   1993 # with the old value).  This is particularly relevant when it is not
   1994 # possible to determine the -version or -name until teaish-configure
   1995 # has been called, and it's updated dynamically from
   1996 # teaish-configure. Notably:
   1997 #
   1998 #   - If -version or -name are updated, -libDir will almost certainly
   1999 #     need to be explicitly set along with them.
   2000 #
   2001 #   - If -name is updated, -loadPrefix probably needs to be as well.
   2002 #
   2003 proc teaish-pkginfo-set {args} {
   2004   set doVars 0
   2005   set doCommands 0
   2006   set xargs $args
   2007   set recalc {}
   2008   foreach arg $args {
   2009     switch -exact -- $arg {
   2010       -vars {
   2011         incr doVars
   2012         set xargs [lassign $xargs -]
   2013       }
   2014       -subst {
   2015         incr doVars
   2016         incr doCommands
   2017         set xargs [lassign $xargs -]
   2018       }
   2019       default {
   2020         break
   2021       }
   2022     }
   2023   }
   2024   set args $xargs
   2025   unset xargs
   2026   if {1 == [llength $args] && [llength [lindex $args 0]] > 1} {
   2027     # Transform a single {...} arg into the canonical call form
   2028     set a [list {*}[lindex $args 0]]
   2029     if {$doVars || $doCommands} {
   2030       set sflags -nobackslashes
   2031       if {!$doCommands} {
   2032         lappend sflags -nocommands
   2033       }
   2034       set a [uplevel 1 [list subst {*}$sflags $a]]
   2035     }
   2036     set args $a
   2037   }
   2038   set sentinel "<nope>"
   2039   set flagDefs [list]
   2040   foreach {f d} $::teaish__Config(pkginfo-f2d) {
   2041     lappend flagDefs $f => $sentinel
   2042   }
   2043   proj-parse-simple-flags args flags $flagDefs
   2044   if {[llength $args]} {
   2045     proj-error -up "Too many (or unknown) arguments to [proj-scope]: $args"
   2046   }
   2047   foreach {f d} $::teaish__Config(pkginfo-f2d) {
   2048     if {$sentinel eq [set v $flags($f)]} continue
   2049     switch -exact -- $f {
   2050 
   2051       -options {
   2052         proj-assert {"" eq $d}
   2053         options-add $v
   2054       }
   2055 
   2056       -pragmas {
   2057         teaish__pragma {*}$v
   2058       }
   2059 
   2060       -vsatisfies {
   2061         if {1 == [llength $v] && 1 == [llength [lindex $v 0]]} {
   2062           # Transform X to {Tcl $X}
   2063           set v [list [join [list Tcl $v]]]
   2064         }
   2065         define $d $v
   2066       }
   2067 
   2068       -pkgInit.tcl -
   2069       -pkgInit.tcl.in {
   2070         if {0x22 & $::teaish__Config(pkginit-policy)} {
   2071           proj-fatal "Cannot use -pkgInit.tcl(.in) more than once."
   2072         }
   2073         set x [file join $::teaish__Config(extension-dir) $v]
   2074         set tTail [file tail $v]
   2075         if {"-pkgInit.tcl.in" eq $f} {
   2076           # Generate pkginit file X from X.in
   2077           set pI 0x02
   2078           set tIn $x
   2079           set tOut [file rootname $tTail]
   2080           set other -pkgInit.tcl
   2081         } else {
   2082           # Static pkginit file X
   2083           set pI 0x20
   2084           set tIn ""
   2085           set tOut $x
   2086           set other -pkgInit.tcl.in
   2087         }
   2088         set ::teaish__Config(pkginit-policy) $pI
   2089         set ::teaish__PkgInfo($other) {}
   2090         define TEAISH_PKGINIT_TCL_IN $tIn
   2091         define TEAISH_PKGINIT_TCL $tOut
   2092         define TEAISH_PKGINIT_TCL_TAIL $tTail
   2093         teaish-dist-add $v
   2094         set v $x
   2095       }
   2096 
   2097       -src {
   2098         set d $::teaish__Config(extension-dir)
   2099         foreach f $v {
   2100           lappend ::teaish__Config(dist-files) $f
   2101           lappend ::teaish__Config(extension-src) $d/$f
   2102           lappend ::teaish__PkgInfo(-src) $f
   2103           # ^^^ so that default-value initialization in
   2104           # teaish-configure-core recognizes that it's been set.
   2105         }
   2106       }
   2107 
   2108       -tm.tcl -
   2109       -tm.tcl.in {
   2110         if {0x30 & $::teaish__Config(pkgindex-policy)} {
   2111           proj-fatal "Cannot use $f together with a pkgIndex.tcl."
   2112         } elseif {$::teaish__Config(tm-policy)} {
   2113           proj-fatal "Cannot use -tm.tcl(.in) more than once."
   2114         }
   2115         set x [file join $::teaish__Config(extension-dir) $v]
   2116         if {"-tm.tcl.in" eq $f} {
   2117           # Generate tm file X from X.in
   2118           set pT 0x02
   2119           set pI 0x100
   2120           set tIn $x
   2121           set tOut [file rootname [file tail $v]]
   2122           set other -tm.tcl
   2123         } else {
   2124           # Static tm file X
   2125           set pT 0x20
   2126           set pI 0x200
   2127           set tIn ""
   2128           set tOut $x
   2129           set other -tm.tcl.in
   2130         }
   2131         set ::teaish__Config(pkgindex-policy) $pI
   2132         set ::teaish__Config(tm-policy) $pT
   2133         set ::teaish__PkgInfo($other) {}
   2134         define TEAISH_TM_TCL_IN $tIn
   2135         define TEAISH_TM_TCL $tOut
   2136         define TEAISH_PKGINDEX_TCL ""
   2137         define TEAISH_PKGINDEX_TCL_IN ""
   2138         define TEAISH_PKGINDEX_TCL_TAIL ""
   2139         teaish-dist-add $v
   2140         teaish__pragma no-dll
   2141         set v $x
   2142       }
   2143 
   2144       default {
   2145         proj-assert {"" ne $d}
   2146         define $d $v
   2147       }
   2148     }
   2149     set ::teaish__PkgInfo($f) $v
   2150     if {$f in {-name -version -libDir -loadPrefix}} {
   2151       lappend recalc $f
   2152     }
   2153   }
   2154   if {"" ne $recalc} {
   2155     teaish__define_pkginfo_derived $recalc
   2156   }
   2157 }
   2158 
   2159 #
   2160 # @teaish-pkginfo-get ?arg?
   2161 #
   2162 # If passed no arguments, it returns the extension config info in the
   2163 # same form accepted by teaish-pkginfo-set.
   2164 #
   2165 # If passed one -flagname arg then it returns the value of that config
   2166 # option.
   2167 #
   2168 # Else it treats arg as the name of caller-scoped variable to
   2169 # which this function assigns an array containing the configuration
   2170 # state of this extension, in the same structure accepted by
   2171 # teaish-pkginfo-set. In this case it returns an empty string.
   2172 #
   2173 proc teaish-pkginfo-get {args} {
   2174   set cases {}
   2175   set argc [llength $args]
   2176   set rv {}
   2177   switch -exact $argc {
   2178     0 {
   2179       # Return a list of (-flag value) pairs
   2180       lappend cases default {{
   2181         if {[info exists ::teaish__PkgInfo($flag)]} {
   2182           lappend rv $flag $::teaish__PkgInfo($flag)
   2183         } else {
   2184           lappend rv $flag [get-define $defName]
   2185         }
   2186       }}
   2187     }
   2188 
   2189     1 {
   2190       set arg $args
   2191       if {[string match -* $arg]} {
   2192         # Return the corresponding -flag's value
   2193         lappend cases $arg {{
   2194           if {[info exists ::teaish__PkgInfo($flag)]} {
   2195             return $::teaish__PkgInfo($flag)
   2196           } else {
   2197             return [get-define $defName]
   2198           }
   2199         }}
   2200       } else {
   2201         # Populate target with an array of (-flag value).
   2202         upvar $arg tgt
   2203         array set tgt {}
   2204         lappend cases default {{
   2205           if {[info exists ::teaish__PkgInfo($flag)]} {
   2206             set tgt($flag) $::teaish__PkgInfo($flag)
   2207           } else {
   2208             set tgt($flag) [get-define $defName]
   2209           }
   2210         }}
   2211       }
   2212     }
   2213 
   2214     default {
   2215       proj-error "invalid arg count from [proj-scope 1]"
   2216     }
   2217   }
   2218 
   2219   foreach {flag defName} $::teaish__Config(pkginfo-f2d) {
   2220     switch -exact -- $flag [join $cases]
   2221   }
   2222   if {0 == $argc} { return $rv }
   2223 }
   2224 
   2225 # (Re)set some defines based on pkginfo state. $flags is the list of
   2226 # pkginfo -flags which triggered this, or "*" for the initial call.
   2227 proc teaish__define_pkginfo_derived {flags} {
   2228   set all [expr {{*} in $flags}]
   2229   if {$all || "-version" in $flags || "-name" in $flags} {
   2230     set name $::teaish__PkgInfo(-name) ; # _not_ -name.pkg
   2231     if {[info exists ::teaish__PkgInfo(-version)]} {
   2232       set pkgver $::teaish__PkgInfo(-version)
   2233       set libname "lib"
   2234       if {[string match *-cygwin [get-define host]]} {
   2235         set libname cyg
   2236       }
   2237       define TEAISH_DLL8_BASENAME $libname$name$pkgver
   2238       define TEAISH_DLL9_BASENAME ${libname}tcl9$name$pkgver
   2239       set ext [get-define TARGET_DLLEXT]
   2240       define TEAISH_DLL8 [get-define TEAISH_DLL8_BASENAME]$ext
   2241       define TEAISH_DLL9 [get-define TEAISH_DLL9_BASENAME]$ext
   2242     }
   2243   }
   2244   if {$all || "-libDir" in $flags} {
   2245     if {[info exists ::teaish__PkgInfo(-libDir)]} {
   2246       define TCLLIBDIR \
   2247         [file dirname [get-define TCLLIBDIR]]/$::teaish__PkgInfo(-libDir)
   2248     }
   2249   }
   2250 }
   2251 
   2252 #
   2253 # @teaish-checks-queue -pre|-post args...
   2254 #
   2255 # Queues one or more arbitrary "feature test" functions to be run when
   2256 # teaish-checks-run is called. $flag must be one of -pre or -post to
   2257 # specify whether the tests should be run before or after
   2258 # teaish-configure is run. Each additional arg is the name of a
   2259 # feature-test proc.
   2260 #
   2261 proc teaish-checks-queue {flag args} {
   2262   if {$flag ni {-pre -post}} {
   2263     proj-error "illegal flag: $flag"
   2264   }
   2265   lappend ::teaish__Config(queued-checks${flag}) {*}$args
   2266 }
   2267 
   2268 #
   2269 # @teaish-checks-run -pre|-post
   2270 #
   2271 # Runs all feature checks queued using teaish-checks-queue
   2272 # then cleares the queue.
   2273 #
   2274 proc teaish-checks-run {flag} {
   2275   if {$flag ni {-pre -post}} {
   2276     proj-error "illegal flag: $flag"
   2277   }
   2278   #puts "*** running $flag: $::teaish__Config(queued-checks${flag})"
   2279   set foo 0
   2280   foreach f $::teaish__Config(queued-checks${flag}) {
   2281     if {![teaish-feature-cache-check $f foo]} {
   2282       set v [$f]
   2283       teaish-feature-cache-set $f $v
   2284     }
   2285   }
   2286   set ::teaish__Config(queued-checks${flag}) {}
   2287 }
   2288 
   2289 #
   2290 # A general-purpose getter for various teaish state. Requires one
   2291 # flag, which determines its result value. Flags marked with [L] below
   2292 # are safe for using at load-time, before teaish-pkginfo-set is called
   2293 #
   2294 #   -dir [L]: returns the extension's directory, which may differ from
   2295 #    the teaish core dir or the build dir.
   2296 #
   2297 #   -teaish-home [L]: the "home" dir of teaish itself, which may
   2298 #   differ from the extension dir or build dir.
   2299 #
   2300 #   -build-dir [L]: the build directory (typically the current working
   2301 #   -dir).
   2302 #
   2303 #   Any of the teaish-pkginfo-get/get flags: returns the same as
   2304 #   teaish-pkginfo-get. Not safe for use until teaish-pkginfo-set has
   2305 #   been called.
   2306 #
   2307 # Triggers an error if passed an unknown flag.
   2308 #
   2309 proc teaish-get {flag} {
   2310   #-teaish.tcl {return $::teaish__Config(teaish.tcl)}
   2311   switch -exact -- $flag {
   2312     -dir {
   2313       return $::teaish__Config(extension-dir)
   2314     }
   2315     -teaish-home {
   2316       return $::autosetup(srcdir)
   2317     }
   2318     -build-dir {
   2319       return $::autosetup(builddir)
   2320     }
   2321     default {
   2322       if {[info exists ::teaish__PkgInfo($flag)]} {
   2323         return $::teaish__PkgInfo($flag)
   2324       }
   2325     }
   2326   }
   2327   proj-error "Unhandled flag: $flag"
   2328 }
   2329 
   2330 #
   2331 # Handles --teaish-create-extension=TARGET-DIR
   2332 #
   2333 proc teaish__create_extension {dir} {
   2334   set force [opt-bool teaish-force]
   2335   if {"" eq $dir} {
   2336     proj-error "--teaish-create-extension=X requires a directory name."
   2337   }
   2338   file mkdir $dir/generic
   2339   set cwd [pwd]
   2340   #set dir [file-normalize [file join $cwd $dir]]
   2341   teaish__verbose 1 msg-result "Created dir $dir"
   2342   cd $dir
   2343   if {!$force} {
   2344     # Ensure that we don't blindly overwrite anything
   2345     foreach f {
   2346       generic/teaish.c
   2347       teaish.tcl
   2348       teaish.make.in
   2349       teaish.test.tcl
   2350     } {
   2351       if {[file exists $f]} {
   2352         error "Cowardly refusing to overwrite $dir/$f. Use --teaish-force to overwrite."
   2353       }
   2354     }
   2355   }
   2356 
   2357   set name [file tail $dir]
   2358   set pkgName $name
   2359   set version 0.0.1
   2360   set loadPrefix [string totitle $pkgName]
   2361   set content {teaish-pkginfo-set }
   2362   #puts "0 content=$content"
   2363   if {[opt-str teaish-extension-pkginfo epi]} {
   2364     set epi [string trim $epi]
   2365     if {[string match "*\n*" $epi]} {
   2366       set epi "{$epi}"
   2367     } elseif {![string match "{*}" $epi]} {
   2368       append content "\{" $epi "\}"
   2369     } else {
   2370       append content $epi
   2371     }
   2372     #puts "2 content=$content\nepi=$epi"
   2373   } else {
   2374     append content [subst -nocommands -nobackslashes {{
   2375       -name ${name}
   2376       -name.pkg ${pkgName}
   2377       -name.dist ${pkgName}
   2378       -version ${version}
   2379       -loadPrefix $loadPrefix
   2380       -libDir ${name}${version}
   2381       -vsatisfies {{Tcl 8.5-}}
   2382       -url {}
   2383       -options {}
   2384       -pragmas {full-dist}
   2385     }}]
   2386     #puts "3 content=$content"
   2387   }
   2388   #puts "1 content=$content"
   2389   append content "\n" {
   2390 #proc teaish-options {} {
   2391   # Return a list and/or use \[options-add\] to add new
   2392   # configure flags. This is called before teaish's
   2393   # bootstrapping is finished, so only teaish-*
   2394   # APIs which are explicitly noted as being safe
   2395   # early on may be used here. Any autosetup-related
   2396   # APIs may be used here.
   2397   #
   2398   # Return an empty string if there are no options to
   2399   # add or if they are added using \[options-add\].
   2400   #
   2401   # If there are no options to add, this proc need
   2402   # not be defined.
   2403 #}
   2404 
   2405 # Called by teaish once bootstrapping is complete.
   2406 # This function is responsible for the client-specific
   2407 # parts of the configuration process.
   2408 proc teaish-configure {} {
   2409   teaish-src-add -dir -dist generic/teaish.c
   2410   teaish-define-to-cflag -quote TEAISH_PKGNAME TEAISH_VERSION
   2411 
   2412   # TODO: your code goes here..
   2413 }
   2414 }; # $content
   2415   proj-file-write teaish.tcl $content
   2416   teaish__verbose 1 msg-result "Created teaish.tcl"
   2417 
   2418   set content "# Teaish test script.
   2419 # When this tcl script is invoked via 'make test' it will have loaded
   2420 # the package, run any teaish.pkginit.tcl code, and loaded
   2421 # autosetup/teaish/tester.tcl.
   2422 "
   2423   proj-file-write teaish.test.tcl $content
   2424   teaish__verbose 1 msg-result "Created teaish.test.tcl"
   2425 
   2426   set content [subst -nocommands -nobackslashes {
   2427 #include <tcl.h>
   2428 static int
   2429 ${loadPrefix}_Cmd(ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]){
   2430   Tcl_SetObjResult(interp, Tcl_NewStringObj("this is the ${name} extension", -1));
   2431   return TCL_OK;
   2432 }
   2433 
   2434 extern int DLLEXPORT ${loadPrefix}_Init(Tcl_Interp *interp){
   2435   if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
   2436     return TCL_ERROR;
   2437   }
   2438   if (Tcl_PkgProvide(interp, TEAISH_PKGNAME, TEAISH_VERSION) == TCL_ERROR) {
   2439     return TCL_ERROR;
   2440   }
   2441   Tcl_CreateObjCommand(interp, TEAISH_PKGNAME, ${loadPrefix}_Cmd, NULL, NULL);
   2442   return TCL_OK;
   2443 }
   2444 }]
   2445   proj-file-write generic/teaish.c $content
   2446   teaish__verbose 1 msg-result "Created generic/teaish.c"
   2447 
   2448   set content "# teaish makefile for the ${name} extension
   2449 # tx.src      = \$(tx.dir)/generic/teaish.c
   2450 # tx.LDFLAGS  =
   2451 # tx.CFLAGS   =
   2452 "
   2453   proj-file-write teaish.make.in $content
   2454   teaish__verbose 1 msg-result "Created teaish.make.in"
   2455 
   2456   msg-result "Created new extension \[$dir\]."
   2457 
   2458   cd $cwd
   2459   set ::teaish__Config(install-ext-dir) $dir
   2460 }
   2461 
   2462 #
   2463 # Internal helper for teaish__install
   2464 #
   2465 proc teaish__install_file {f destDir force} {
   2466   set dest $destDir/[file tail $f]
   2467   if {[file isdirectory $f]} {
   2468     file mkdir $dest
   2469   } elseif {!$force && [file exists $dest]} {
   2470     array set st1 [file stat $f]
   2471     array set st2 [file stat $dest]
   2472     if {($st1(mtime) == $st2(mtime))
   2473         && ($st1(size) == $st2(size))} {
   2474       if {[file tail $f] in {
   2475         pkgIndex.tcl.in
   2476         _teaish.tester.tcl.in
   2477       }} {
   2478         # Assume they're the same. In the scope of the "make dist"
   2479         # rules, this happens legitimately when an extension with a
   2480         # copy of teaish installed in the same dir assumes that the
   2481         # pkgIndex.tcl.in and _teaish.tester.tcl.in belong to the
   2482         # extension, whereas teaish believes they belong to teaish.
   2483         # So we end up with dupes of those.
   2484         return
   2485       }
   2486     }
   2487     proj-error -up "Cowardly refusing to overwrite \[$dest\]." \
   2488       "Use --teaish-force to enable overwriting."
   2489   } else {
   2490     # file copy -force $f $destDir; # loses +x bit
   2491     #
   2492     # JimTcl doesn't have [file attribute], so we can't use that here
   2493     # (in the context of an autosetup configure script).
   2494     exec cp -p $f $dest
   2495   }
   2496 }
   2497 
   2498 #
   2499 # Installs a copy of teaish, with autosetup, to $dDest, which defaults
   2500 # to the --teaish-install=X or --teash-create-extension=X dir. Won't
   2501 # overwrite files unless --teaish-force is used.
   2502 #
   2503 proc teaish__install {{dDest ""}} {
   2504   if {$dDest in {auto ""}} {
   2505     set dDest [opt-val teaish-install]
   2506     if {$dDest in {auto ""}} {
   2507       if {[info exists ::teaish__Config(install-ext-dir)]} {
   2508         set dDest $::teaish__Config(install-ext-dir)
   2509       }
   2510     }
   2511   }
   2512   set force [opt-bool teaish-force]
   2513   if {$dDest in {auto ""}} {
   2514     proj-error "Cannot determine installation directory."
   2515   } elseif {!$force && [file exists $dDest/auto.def]} {
   2516     proj-error \
   2517       "Target dir looks like it already contains teaish and/or autosetup: $dDest" \
   2518       "\nUse --teaish-force to overwrite it."
   2519   }
   2520 
   2521   set dSrc $::autosetup(srcdir)
   2522   set dAS $::autosetup(libdir)
   2523   set dAST $::teaish__Config(core-dir)
   2524   set dASTF $dAST/feature
   2525   teaish__verbose 1 msg-result "Installing teaish to \[$dDest\]..."
   2526   if {$::teaish__Config(verbose)>1} {
   2527     msg-result "dSrc  = $dSrc"
   2528     msg-result "dAS   = $dAS"
   2529     msg-result "dAST  = $dAST"
   2530     msg-result "dASTF = $dASTF"
   2531     msg-result "dDest = $dDest"
   2532   }
   2533 
   2534   # Dest subdirs...
   2535   set ddAS   $dDest/autosetup
   2536   set ddAST  $ddAS/teaish
   2537   set ddASTF $ddAST/feature
   2538   foreach {srcDir destDir} [list \
   2539                               $dAS   $ddAS \
   2540                               $dAST  $ddAST \
   2541                               $dASTF $ddASTF \
   2542                              ] {
   2543     teaish__verbose 1 msg-result "Copying files to $destDir..."
   2544     file mkdir $destDir
   2545     foreach f  [glob -nocomplain -directory $srcDir *] {
   2546       if {[string match {*~} $f] || [string match "#*#" [file tail $f]]} {
   2547         # Editor-generated backups and emacs lock files
   2548         continue
   2549       }
   2550       teaish__verbose 2 msg-result "\t$f"
   2551       teaish__install_file $f $destDir $force
   2552     }
   2553   }
   2554   teaish__verbose 1 msg-result "Copying files to $dDest..."
   2555   foreach f {
   2556     auto.def configure Makefile.in pkgIndex.tcl.in
   2557     _teaish.tester.tcl.in
   2558   } {
   2559     teaish__verbose 2 msg-result "\t$f"
   2560     teaish__install_file $dSrc/$f $dDest $force
   2561   }
   2562   set ::teaish__Config(install-self-dir) $dDest
   2563   msg-result "Teaish $::teaish__Config(version) installed in \[$dDest\]."
   2564 }
   2565