1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00
Files
sqlite/autosetup/teaish/core.tcl

2561 lines
80 KiB
Tcl

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