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