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