Home | History | Annotate | Line # | Download | only in autosetup
      1 ########################################################################
      2 # 2024 September 25
      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 #
     13 # ----- @module proj.tcl -----
     14 # @section Project-agnostic Helper APIs
     15 #
     16 
     17 #
     18 # Routines for Steve Bennett's autosetup which are common to trees
     19 # managed in and around the umbrella of the SQLite project.
     20 #
     21 # The intent is that these routines be relatively generic, independent
     22 # of a given project.
     23 #
     24 # For practical purposes, the copy of this file hosted in the SQLite
     25 # project is the "canonical" one:
     26 #
     27 # https://sqlite.org/src/file/autosetup/proj.tcl
     28 #
     29 # This file was initially derived from one used in the libfossil
     30 # project, authored by the same person who ported it here, and this is
     31 # noted here only as an indication that there are no licensing issues
     32 # despite this code having a handful of near-twins running around a
     33 # handful of third-party source trees.
     34 #
     35 # Design notes:
     36 #
     37 # - Symbols with _ separators are intended for internal use within
     38 #   this file, and are not part of the API which auto.def files should
     39 #   rely on. Symbols with - separators are public APIs.
     40 #
     41 # - By and large, autosetup prefers to update global state with the
     42 #   results of feature checks, e.g. whether the compiler supports flag
     43 #   --X.  In this developer's opinion that (A) causes more confusion
     44 #   than it solves[^1] and (B) adds an unnecessary layer of "voodoo"
     45 #   between the autosetup user and its internals. This module, in
     46 #   contrast, instead injects the results of its own tests into
     47 #   well-defined variables and leaves the integration of those values
     48 #   to the caller's discretion.
     49 #
     50 # [1]: As an example: testing for the -rpath flag, using
     51 # cc-check-flags, can break later checks which use
     52 # [cc-check-function-in-lib ...] because the resulting -rpath flag
     53 # implicitly becomes part of those tests. In the case of an rpath
     54 # test, downstream tests may not like the $prefix/lib path added by
     55 # the rpath test. To avoid such problems, we avoid (intentionally)
     56 # updating global state via feature tests.
     57 #
     58 
     59 #
     60 # $proj__Config is an internal-use-only array for storing whatever generic
     61 # internal stuff we need stored.
     62 #
     63 array set ::proj__Config [subst {
     64   self-tests [get-env proj.self-tests 0]
     65   verbose-assert [get-env proj.assert-verbose 0]
     66   isatty [isatty? stdout]
     67 }]
     68 
     69 #
     70 # List of dot-in files to filter in the final stages of
     71 # configuration. Some configuration steps may append to this.  Each
     72 # one in this list which exists will trigger the generation of a
     73 # file with that same name, minus the ".in", in the build directory
     74 # (which differ from the source dir in out-of-tree builds).
     75 #
     76 # See: proj-dot-ins-append and proj-dot-ins-process
     77 #
     78 set ::proj__Config(dot-in-files) [list]
     79 
     80 #
     81 # @proj-warn msg
     82 #
     83 # Emits a warning message to stderr. All args are appended with a
     84 # space between each.
     85 #
     86 proc proj-warn {args} {
     87   show-notices
     88   puts stderr [join [list "WARNING:" \[ [proj-scope 1] \]: {*}$args] " "]
     89 }
     90 
     91 
     92 #
     93 # Internal impl of [proj-fatal] and [proj-error]. It must be called
     94 # using tailcall.
     95 #
     96 proc proj__faterr {failMode args} {
     97   show-notices
     98   set lvl 1
     99   while {"-up" eq [lindex $args 0]} {
    100     set args [lassign $args -]
    101     incr lvl
    102   }
    103   if {$failMode} {
    104     puts stderr [join [list "FATAL:" \[ [proj-scope $lvl] \]: {*}$args]]
    105     exit 1
    106   } else {
    107     error [join [list in \[ [proj-scope $lvl] \]: {*}$args]]
    108   }
    109 }
    110 
    111 #
    112 # @proj-fatal ?-up...? msg...
    113 #
    114 # Emits an error message to stderr and exits with non-0. All args are
    115 # appended with a space between each.
    116 #
    117 # The calling scope's name is used in the error message. To instead
    118 # use the name of a call higher up in the stack, use -up once for each
    119 # additional level.
    120 #
    121 proc proj-fatal {args} {
    122   tailcall proj__faterr 1 {*}$args
    123 }
    124 
    125 #
    126 # @proj-error ?-up...? msg...
    127 #
    128 # Works like proj-fatal but uses [error] intead of [exit].
    129 #
    130 proc proj-error {args} {
    131   tailcall proj__faterr 0 {*}$args
    132 }
    133 
    134 #
    135 # @proj-assert script ?message?
    136 #
    137 # Kind of like a C assert: if uplevel of [list expr $script] is false,
    138 # a fatal error is triggered. The error message, by default, includes
    139 # the body of the failed assertion, but if $msg is set then that is
    140 # used instead.
    141 #
    142 proc proj-assert {script {msg ""}} {
    143   if {1 eq $::proj__Config(verbose-assert)} {
    144     msg-result [proj-bold "asserting: $script"]
    145   }
    146   if {![uplevel 1 [list expr $script]]} {
    147     if {"" eq $msg} {
    148       set msg $script
    149     }
    150     tailcall proj__faterr 1 "Assertion failed:" $msg
    151   }
    152 }
    153 
    154 #
    155 # @proj-bold str
    156 #
    157 # If this function believes that the current console might support
    158 # ANSI escape sequences then this returns $str wrapped in a sequence
    159 # to bold that text, else it returns $str as-is.
    160 #
    161 proc proj-bold {args} {
    162   if {$::autosetup(iswin) || !$::proj__Config(isatty)} {
    163     return [join $args]
    164   }
    165   return "\033\[1m${args}\033\[0m"
    166 }
    167 
    168 #
    169 # @proj-indented-notice ?-error? ?-notice? msg
    170 #
    171 # Takes a multi-line message and emits it with consistent indentation.
    172 # It does not perform any line-wrapping of its own. Which output
    173 # routine it uses depends on its flags, defaulting to msg-result.
    174 # For -error and -notice it uses user-notice.
    175 #
    176 # If the -notice flag it used then it emits using [user-notice], which
    177 # means its rendering will (A) go to stderr and (B) be delayed until
    178 # the next time autosetup goes to output a message.
    179 #
    180 # If the -error flag is provided then it renders the message
    181 # immediately to stderr and then exits.
    182 #
    183 # If neither -notice nor -error are used, the message will be sent to
    184 # stdout without delay.
    185 #
    186 proc proj-indented-notice {args} {
    187   set fErr ""
    188   set outFunc "msg-result"
    189   while {[llength $args] > 1} {
    190     switch -exact -- [lindex $args 0] {
    191       -error  {
    192         set args [lassign $args fErr]
    193         set outFunc "user-notice"
    194       }
    195       -notice {
    196         set args [lassign $args -]
    197         set outFunc "user-notice"
    198       }
    199       default {
    200         break
    201       }
    202     }
    203   }
    204   set lines [split [join $args] \n]
    205   foreach line $lines {
    206     set line [string trimleft $line]
    207     if {"" eq $line} {
    208       $outFunc $line
    209     } else {
    210       $outFunc "    $line"
    211     }
    212   }
    213   if {"" ne $fErr} {
    214     show-notices
    215     exit 1
    216   }
    217 }
    218 
    219 #
    220 # @proj-is-cross-compiling
    221 #
    222 # Returns 1 if cross-compiling, else 0.
    223 #
    224 proc proj-is-cross-compiling {} {
    225   expr {[get-define host] ne [get-define build]}
    226 }
    227 
    228 #
    229 # @proj-strip-hash-comments value
    230 #
    231 # Expects to receive string input, which it splits on newlines, strips
    232 # out any lines which begin with any number of whitespace followed by
    233 # a '#', and returns a value containing the [append]ed results of each
    234 # remaining line with a \n between each. It does not strip out
    235 # comments which appear after the first non-whitespace character.
    236 #
    237 proc proj-strip-hash-comments {val} {
    238   set x {}
    239   foreach line [split $val \n] {
    240     if {![string match "#*" [string trimleft $line]]} {
    241       append x $line \n
    242     }
    243   }
    244   return $x
    245 }
    246 
    247 #
    248 # @proj-cflags-without-werror
    249 #
    250 # Fetches [define $var], strips out any -Werror entries, and returns
    251 # the new value. This is intended for temporarily stripping -Werror
    252 # from CFLAGS or CPPFLAGS within the scope of a [define-push] block.
    253 #
    254 proc proj-cflags-without-werror {{var CFLAGS}} {
    255   set rv {}
    256   foreach f [get-define $var ""] {
    257     switch -exact -- $f {
    258       -Werror {}
    259       default { lappend rv $f }
    260     }
    261   }
    262   join $rv " "
    263 }
    264 
    265 #
    266 # @proj-check-function-in-lib
    267 #
    268 # A proxy for cc-check-function-in-lib with the following differences:
    269 #
    270 # - Does not make any global changes to the LIBS define.
    271 #
    272 # - Strips out the -Werror flag from CFLAGS before running the test,
    273 #   as these feature tests will often fail if -Werror is used.
    274 #
    275 # Returns the result of cc-check-function-in-lib (i.e. true or false).
    276 # The resulting linker flags are stored in the [define] named
    277 # lib_${function}.
    278 #
    279 proc proj-check-function-in-lib {function libs {otherlibs {}}} {
    280   set found 0
    281   define-push {LIBS CFLAGS} {
    282     #puts "CFLAGS before=[get-define CFLAGS]"
    283     define CFLAGS [proj-cflags-without-werror]
    284     #puts "CFLAGS after =[get-define CFLAGS]"
    285     set found [cc-check-function-in-lib $function $libs $otherlibs]
    286   }
    287   return $found
    288 }
    289 
    290 #
    291 # @proj-search-for-header-dir ?-dirs LIST? ?-subdirs LIST? header
    292 #
    293 # Searches for $header in a combination of dirs and subdirs, specified
    294 # by the -dirs {LIST} and -subdirs {LIST} flags (each of which have
    295 # sane defaults). Returns either the first matching dir or an empty
    296 # string.  The return value does not contain the filename part.
    297 #
    298 proc proj-search-for-header-dir {header args} {
    299   set subdirs {include}
    300   set dirs {/usr /usr/local /mingw}
    301 # Debatable:
    302 #  if {![proj-is-cross-compiling]} {
    303 #    lappend dirs [get-define prefix]
    304 #  }
    305   while {[llength $args]} {
    306     switch -exact -- [lindex $args 0] {
    307       -dirs     { set args [lassign $args - dirs] }
    308       -subdirs  { set args [lassign $args - subdirs] }
    309       default   {
    310         proj-error "Unhandled argument: $args"
    311       }
    312     }
    313   }
    314   foreach dir $dirs {
    315     foreach sub $subdirs {
    316       if {[file exists $dir/$sub/$header]} {
    317         return "$dir/$sub"
    318       }
    319     }
    320   }
    321   return ""
    322 }
    323 
    324 #
    325 # @proj-find-executable-path ?-v? binaryName
    326 #
    327 # Works similarly to autosetup's [find-executable-path $binName] but:
    328 #
    329 # - If the first arg is -v, it's verbose about searching, else it's quiet.
    330 #
    331 # Returns the full path to the result or an empty string.
    332 #
    333 proc proj-find-executable-path {args} {
    334   set binName $args
    335   set verbose 0
    336   if {[lindex $args 0] eq "-v"} {
    337     set verbose 1
    338     set args [lassign $args - binName]
    339     msg-checking "Looking for $binName ... "
    340   }
    341   set check [find-executable-path $binName]
    342   if {$verbose} {
    343     if {"" eq $check} {
    344       msg-result "not found"
    345     } else {
    346       msg-result $check
    347     }
    348   }
    349   return $check
    350 }
    351 
    352 #
    353 # @proj-bin-define binName ?defName?
    354 #
    355 # Uses [proj-find-executable-path $binName] to (verbosely) search for
    356 # a binary, sets a define (see below) to the result, and returns the
    357 # result (an empty string if not found).
    358 #
    359 # The define'd name is: If $defName is not empty, it is used as-is. If
    360 # $defName is empty then "BIN_X" is used, where X is the upper-case
    361 # form of $binName with any '-' characters replaced with '_'.
    362 #
    363 proc proj-bin-define {binName {defName {}}} {
    364   set check [proj-find-executable-path -v $binName]
    365   if {"" eq $defName} {
    366     set defName "BIN_[string toupper [string map {- _} $binName]]"
    367   }
    368   define $defName $check
    369   return $check
    370 }
    371 
    372 #
    373 # @proj-first-bin-of bin...
    374 #
    375 # Looks for the first binary found of the names passed to this
    376 # function.  If a match is found, the full path to that binary is
    377 # returned, else "" is returned.
    378 #
    379 # Despite using cc-path-progs to do the search, this function clears
    380 # any define'd name that function stores for the result (because the
    381 # caller has no sensible way of knowing which [define] name it has
    382 # unless they pass only a single argument).
    383 #
    384 proc proj-first-bin-of {args} {
    385   set rc ""
    386   foreach b $args {
    387     set u [string toupper $b]
    388     # Note that cc-path-progs defines $u to "false" if it finds no
    389     # match.
    390     if {[cc-path-progs $b]} {
    391       set rc [get-define $u]
    392     }
    393     undefine $u
    394     if {"" ne $rc} break
    395   }
    396   return $rc
    397 }
    398 
    399 #
    400 # @proj-opt-was-provided key
    401 #
    402 # Returns 1 if the user specifically provided the given configure flag
    403 # or if it was specifically set using proj-opt-set, else 0. This can
    404 # be used to distinguish between options which have a default value
    405 # and those which were explicitly provided by the user, even if the
    406 # latter is done in a way which uses the default value.
    407 #
    408 # For example, with a configure flag defined like:
    409 #
    410 #   { foo-bar:=baz => {its help text} }
    411 #
    412 # This function will, when passed foo-bar, return 1 only if the user
    413 # passes --foo-bar to configure, even if that invocation would resolve
    414 # to the default value of baz. If the user does not explicitly pass in
    415 # --foo-bar (with or without a value) then this returns 0.
    416 #
    417 # Calling [proj-opt-set] is, for purposes of the above, equivalent to
    418 # explicitly passing in the flag.
    419 #
    420 # Note: unlike most functions which deal with configure --flags, this
    421 # one does not validate that $key refers to a pre-defined flag. i.e.
    422 # it accepts arbitrary keys, even those not defined via an [options]
    423 # call. [proj-opt-set] manipulates the internal list of flags, such
    424 # that new options set via that function will cause this function to
    425 # return true. (That's an unintended and unavoidable side-effect, not
    426 # specifically a feature which should be made use of.)
    427 #
    428 proc proj-opt-was-provided {key} {
    429   dict exists $::autosetup(optset) $key
    430 }
    431 
    432 #
    433 # @proj-opt-set flag ?val?
    434 #
    435 # Force-set autosetup option $flag to $val. The value can be fetched
    436 # later with [opt-val], [opt-bool], and friends.
    437 #
    438 # Returns $val.
    439 #
    440 proc proj-opt-set {flag {val 1}} {
    441   if {$flag ni $::autosetup(options)} {
    442     # We have to add this to autosetup(options) or else future calls
    443     # to [opt-bool $flag] will fail validation of $flag.
    444     lappend ::autosetup(options) $flag
    445   }
    446   dict set ::autosetup(optset) $flag $val
    447   return $val
    448 }
    449 
    450 #
    451 # @proj-opt-exists flag
    452 #
    453 # Returns 1 if the given flag has been defined as a legal configure
    454 # option, else returns 0. Options set via proj-opt-set "exist" for
    455 # this purpose even if they were not defined via autosetup's
    456 # [options] function.
    457 #
    458 proc proj-opt-exists {flag} {
    459   expr {$flag in $::autosetup(options)};
    460 }
    461 
    462 #
    463 # @proj-val-truthy val
    464 #
    465 # Returns 1 if $val appears to be a truthy value, else returns
    466 # 0. Truthy values are any of {1 on true yes enabled}
    467 #
    468 proc proj-val-truthy {val} {
    469   expr {$val in {1 on true yes enabled}}
    470 }
    471 
    472 #
    473 # @proj-opt-truthy flag
    474 #
    475 # Returns 1 if [opt-val $flag] appears to be a truthy value or
    476 # [opt-bool $flag] is true. See proj-val-truthy.
    477 #
    478 proc proj-opt-truthy {flag} {
    479   if {[proj-val-truthy [opt-val $flag]]} { return 1 }
    480   set rc 0
    481   catch {
    482     # opt-bool will throw if $flag is not a known boolean flag
    483     set rc [opt-bool $flag]
    484   }
    485   return $rc
    486 }
    487 
    488 #
    489 # @proj-if-opt-truthy boolFlag thenScript ?elseScript?
    490 #
    491 # If [proj-opt-truthy $flag] is true, eval $then, else eval $else.
    492 #
    493 proc proj-if-opt-truthy {boolFlag thenScript {elseScript {}}} {
    494   if {[proj-opt-truthy $boolFlag]} {
    495     uplevel 1 $thenScript
    496   } else {
    497     uplevel 1 $elseScript
    498   }
    499 }
    500 
    501 #
    502 # @proj-define-for-opt flag def ?msg? ?iftrue? ?iffalse?
    503 #
    504 # If [proj-opt-truthy $flag] then [define $def $iftrue] else [define
    505 # $def $iffalse]. If $msg is not empty, output [msg-checking $msg] and
    506 # a [msg-results ...] which corresponds to the result. Returns 1 if
    507 # the opt-truthy check passes, else 0.
    508 #
    509 proc proj-define-for-opt {flag def {msg ""} {iftrue 1} {iffalse 0}} {
    510   if {"" ne $msg} {
    511     msg-checking "$msg "
    512   }
    513   set rcMsg ""
    514   set rc 0
    515   if {[proj-opt-truthy $flag]} {
    516     define $def $iftrue
    517     set rc 1
    518   } else {
    519     define $def $iffalse
    520   }
    521   switch -- [proj-val-truthy [get-define $def]] {
    522     0 { set rcMsg no }
    523     1 { set rcMsg yes }
    524   }
    525   if {"" ne $msg} {
    526     msg-result $rcMsg
    527   }
    528   return $rc
    529 }
    530 
    531 #
    532 # @proj-opt-define-bool ?-v? optName defName ?descr?
    533 #
    534 # Checks [proj-opt-truthy $optName] and calls [define $defName X]
    535 # where X is 0 for false and 1 for true. $descr is an optional
    536 # [msg-checking] argument which defaults to $defName. Returns X.
    537 #
    538 # If args[0] is -v then the boolean semantics are inverted: if
    539 # the option is set, it gets define'd to 0, else 1. Returns the
    540 # define'd value.
    541 #
    542 proc proj-opt-define-bool {args} {
    543   set invert 0
    544   if {[lindex $args 0] eq "-v"} {
    545     incr invert
    546     lassign $args - optName defName descr
    547   } else {
    548     lassign $args optName defName descr
    549   }
    550   if {"" eq $descr} {
    551     set descr $defName
    552   }
    553   #puts "optName=$optName defName=$defName descr=$descr"
    554   set rc 0
    555   msg-checking "[join $descr] ... "
    556   set rc [proj-opt-truthy $optName]
    557   if {$invert} {
    558     set rc [expr {!$rc}]
    559   }
    560   msg-result [string map {0 no 1 yes} $rc]
    561   define $defName $rc
    562   return $rc
    563 }
    564 
    565 #
    566 # @proj-check-module-loader
    567 #
    568 # Check for module-loading APIs (libdl/libltdl)...
    569 #
    570 # Looks for libltdl or dlopen(), the latter either in -ldl or built in
    571 # to libc (as it is on some platforms). Returns 1 if found, else
    572 # 0. Either way, it `define`'s:
    573 #
    574 #  - HAVE_LIBLTDL to 1 or 0 if libltdl is found/not found
    575 #  - HAVE_LIBDL to 1 or 0 if dlopen() is found/not found
    576 #  - LDFLAGS_MODULE_LOADER one of ("-lltdl", "-ldl", or ""), noting
    577 #    that -ldl may legally be empty on some platforms even if
    578 #    HAVE_LIBDL is true (indicating that dlopen() is available without
    579 #    extra link flags). LDFLAGS_MODULE_LOADER also gets "-rdynamic" appended
    580 #    to it because otherwise trying to open DLLs will result in undefined
    581 #    symbol errors.
    582 #
    583 # Note that if it finds LIBLTDL it does not look for LIBDL, so will
    584 # report only that is has LIBLTDL.
    585 #
    586 proc proj-check-module-loader {} {
    587   msg-checking "Looking for module-loader APIs... "
    588   if {99 ne [get-define LDFLAGS_MODULE_LOADER 99]} {
    589     if {1 eq [get-define HAVE_LIBLTDL 0]} {
    590       msg-result "(cached) libltdl"
    591       return 1
    592     } elseif {1 eq [get-define HAVE_LIBDL 0]} {
    593       msg-result "(cached) libdl"
    594       return 1
    595     }
    596     # else: wha???
    597   }
    598   set HAVE_LIBLTDL 0
    599   set HAVE_LIBDL 0
    600   set LDFLAGS_MODULE_LOADER ""
    601   set rc 0
    602   puts "" ;# cosmetic kludge for cc-check-XXX
    603   if {[cc-check-includes ltdl.h] && [cc-check-function-in-lib lt_dlopen ltdl]} {
    604     set HAVE_LIBLTDL 1
    605     set LDFLAGS_MODULE_LOADER "-lltdl -rdynamic"
    606     msg-result " - Got libltdl."
    607     set rc 1
    608   } elseif {[cc-with {-includes dlfcn.h} {
    609     cctest -link 1 -declare "extern char* dlerror(void);" -code "dlerror();"}]} {
    610     msg-result " - This system can use dlopen() without -ldl."
    611     set HAVE_LIBDL 1
    612     set LDFLAGS_MODULE_LOADER ""
    613     set rc 1
    614   } elseif {[cc-check-includes dlfcn.h]} {
    615     set HAVE_LIBDL 1
    616     set rc 1
    617     if {[cc-check-function-in-lib dlopen dl]} {
    618       msg-result " - dlopen() needs libdl."
    619       set LDFLAGS_MODULE_LOADER "-ldl -rdynamic"
    620     } else {
    621       msg-result " - dlopen() not found in libdl. Assuming dlopen() is built-in."
    622       set LDFLAGS_MODULE_LOADER "-rdynamic"
    623     }
    624   }
    625   define HAVE_LIBLTDL $HAVE_LIBLTDL
    626   define HAVE_LIBDL $HAVE_LIBDL
    627   define LDFLAGS_MODULE_LOADER $LDFLAGS_MODULE_LOADER
    628   return $rc
    629 }
    630 
    631 #
    632 # @proj-no-check-module-loader
    633 #
    634 # Sets all flags which would be set by proj-check-module-loader to
    635 # empty/falsy values, as if those checks had failed to find a module
    636 # loader. Intended to be called in place of that function when
    637 # a module loader is explicitly not desired.
    638 #
    639 proc proj-no-check-module-loader {} {
    640   define HAVE_LIBDL 0
    641   define HAVE_LIBLTDL 0
    642   define LDFLAGS_MODULE_LOADER ""
    643 }
    644 
    645 #
    646 # @proj-file-content ?-trim? filename
    647 #
    648 # Opens the given file, reads all of its content, and returns it.  If
    649 # the first arg is -trim, the contents of the file named by the second
    650 # argument are trimmed before returning them.
    651 #
    652 proc proj-file-content {args} {
    653   set trim 0
    654   set fname $args
    655   if {"-trim" eq [lindex $args 0]} {
    656     set trim 1
    657     lassign $args - fname
    658   }
    659   set fp [open $fname rb]
    660   set rc [read $fp]
    661   close $fp
    662   if {$trim} { return [string trim $rc] }
    663   return $rc
    664 }
    665 
    666 #
    667 # @proj-file-conent filename
    668 #
    669 # Returns the contents of the given file as an array of lines, with
    670 # the EOL stripped from each input line.
    671 #
    672 proc proj-file-content-list {fname} {
    673   set fp [open $fname rb]
    674   set rc {}
    675   while { [gets $fp line] >= 0 } {
    676     lappend rc $line
    677   }
    678   close $fp
    679   return $rc
    680 }
    681 
    682 #
    683 # @proj-file-write ?-ro? fname content
    684 #
    685 # Works like autosetup's [writefile] but explicitly uses binary mode
    686 # to avoid EOL translation on Windows. If $fname already exists, it is
    687 # overwritten, even if it's flagged as read-only.
    688 #
    689 proc proj-file-write {args} {
    690   if {"-ro" eq [lindex $args 0]} {
    691     lassign $args ro fname content
    692   } else {
    693     set ro ""
    694     lassign $args fname content
    695   }
    696   file delete -force -- $fname; # in case it's read-only
    697   set f [open $fname wb]
    698   puts -nonewline $f $content
    699   close $f
    700   if {"" ne $ro} {
    701     catch {
    702       exec chmod -w $fname
    703       #file attributes -w $fname; #jimtcl has no 'attributes'
    704     }
    705   }
    706 }
    707 
    708 #
    709 # @proj-check-compile-commands ?-assume-for-clang? ?configFlag?
    710 #
    711 # Checks the compiler for compile_commands.json support. If
    712 # $configFlag is not empty then it is assumed to be the name of an
    713 # autosetup boolean config which controls whether to run/skip this
    714 # check.
    715 #
    716 # If -assume-for-clang is provided and $configFlag is not empty and CC
    717 # matches *clang* and no --$configFlag was explicitly provided to the
    718 # configure script then behave as if --$configFlag had been provided.
    719 # To disable that assumption, either don't pass -assume-for-clang or
    720 # pass --$configFlag=0 to the configure script. (The reason for this
    721 # behavior is that clang supports compile-commands but some other
    722 # compilers report false positives with these tests.)
    723 #
    724 # Returns 1 if supported, else 0, and defines HAVE_COMPILE_COMMANDS to
    725 # that value. Defines MAKE_COMPILATION_DB to "yes" if supported, "no"
    726 # if not. The use of MAKE_COMPILATION_DB is deprecated/discouraged:
    727 # HAVE_COMPILE_COMMANDS is preferred.
    728 #
    729 # ACHTUNG: this test has a long history of false positive results
    730 # because of compilers reacting differently to the -MJ flag.  Because
    731 # of this, it is recommended that this support be an opt-in feature,
    732 # rather than an on-by-default default one. That is: in the
    733 # configure script define the option as
    734 # {--the-flag-name=0 => {Enable ....}}
    735 #
    736 proc proj-check-compile-commands {args} {
    737   set i 0
    738   set configFlag {}
    739   set fAssumeForClang 0
    740   set doAssume 0
    741   msg-checking "compile_commands.json support... "
    742   if {"-assume-for-clang" eq [lindex $args 0]} {
    743     lassign $args - configFlag
    744     incr fAssumeForClang
    745   } elseif {1 == [llength $args]} {
    746     lassign $args configFlag
    747   } else {
    748     proj-error "Invalid arguments"
    749   }
    750   if {1 == $fAssumeForClang && "" ne $configFlag} {
    751     if {[string match *clang* [get-define CC]]
    752         && ![proj-opt-was-provided $configFlag]
    753         && ![proj-opt-truthy $configFlag]} {
    754       proj-indented-notice [subst -nocommands -nobackslashes {
    755         CC appears to be clang, so assuming that --$configFlag is likely
    756         to work. To disable this assumption use --$configFlag=0.}]
    757       incr doAssume
    758     }
    759   }
    760   if {!$doAssume && "" ne $configFlag && ![proj-opt-truthy $configFlag]} {
    761     msg-result "check disabled. Use --${configFlag} to enable it."
    762     define HAVE_COMPILE_COMMANDS 0
    763     define MAKE_COMPILATION_DB no
    764     return 0
    765   } else {
    766     if {[cctest -lang c -cflags {/dev/null -MJ} -source {}]} {
    767       # This test reportedly incorrectly succeeds on one of
    768       # Martin G.'s older systems. drh also reports a false
    769       # positive on an unspecified older Mac system.
    770       msg-result "compiler supports -MJ. Assuming it's useful for compile_commands.json"
    771       define MAKE_COMPILATION_DB yes; # deprecated
    772       define HAVE_COMPILE_COMMANDS 1
    773       return 1
    774     } else {
    775       msg-result "compiler does not support compile_commands.json"
    776       define MAKE_COMPILATION_DB no
    777       define HAVE_COMPILE_COMMANDS 0
    778       return 0
    779     }
    780   }
    781 }
    782 
    783 #
    784 # @proj-touch filename
    785 #
    786 # Runs the 'touch' external command on one or more files, ignoring any
    787 # errors.
    788 #
    789 proc proj-touch {filename} {
    790   catch { exec touch {*}$filename }
    791 }
    792 
    793 #
    794 # @proj-make-from-dot-in ?-touch? infile ?outfile?
    795 #
    796 # Uses [make-template] to create makefile(-like) file(s) $outfile from
    797 # $infile but explicitly makes the output read-only, to avoid
    798 # inadvertent editing (who, me?).
    799 #
    800 # If $outfile is empty then:
    801 #
    802 # - If $infile is a 2-element list, it is assumed to be an in/out pair,
    803 #   and $outfile is set from the 2nd entry in that list. Else...
    804 #
    805 # - $outfile is set to $infile stripped of its extension.
    806 #
    807 # If the first argument is -touch then the generated file is touched
    808 # to update its timestamp. This can be used as a workaround for
    809 # cases where (A) autosetup does not update the file because it was
    810 # not really modified and (B) the file *really* needs to be updated to
    811 # please the build process.
    812 #
    813 # Failures when running chmod or touch are silently ignored.
    814 #
    815 proc proj-make-from-dot-in {args} {
    816   set fIn ""
    817   set fOut ""
    818   set touch 0
    819   if {[lindex $args 0] eq "-touch"} {
    820     set touch 1
    821     lassign $args - fIn fOut
    822   } else {
    823     lassign $args fIn fOut
    824   }
    825   if {"" eq $fOut} {
    826     if {[llength $fIn]>1} {
    827       lassign $fIn fIn fOut
    828     } else {
    829       set fOut [file rootname $fIn]
    830     }
    831   }
    832   #puts "filenames=$filename"
    833   if {[file exists $fOut]} {
    834     catch { exec chmod u+w $fOut }
    835   }
    836   #puts "making template: $fIn ==> $fOut"
    837   #define-push {top_srcdir} {
    838     #puts "--- $fIn $fOut top_srcdir=[get-define top_srcdir]"
    839     make-template $fIn $fOut
    840     #puts "--- $fIn $fOut top_srcdir=[get-define top_srcdir]"
    841     # make-template modifies top_srcdir
    842   #}
    843   if {$touch} {
    844     proj-touch $fOut
    845   }
    846   catch {
    847     exec chmod -w $fOut
    848     #file attributes -w $f; #jimtcl has no 'attributes'
    849   }
    850 }
    851 
    852 #
    853 # @proj-check-profile-flag ?flagname?
    854 #
    855 # Checks for the boolean configure option named by $flagname. If set,
    856 # it checks if $CC seems to refer to gcc. If it does (or appears to)
    857 # then it defines CC_PROFILE_FLAG to "-pg" and returns 1, else it
    858 # defines CC_PROFILE_FLAG to "" and returns 0.
    859 #
    860 # Note that the resulting flag must be added to both CFLAGS and
    861 # LDFLAGS in order for binaries to be able to generate "gmon.out".  In
    862 # order to avoid potential problems with escaping, space-containing
    863 # tokens, and interfering with autosetup's use of these vars, this
    864 # routine does not directly modify CFLAGS or LDFLAGS.
    865 #
    866 proc proj-check-profile-flag {{flagname profile}} {
    867   #puts "flagname=$flagname ?[proj-opt-truthy $flagname]?"
    868   if {[proj-opt-truthy $flagname]} {
    869     set CC [get-define CC]
    870     regsub {.*ccache *} $CC "" CC
    871     # ^^^ if CC="ccache gcc" then [exec] treats "ccache gcc" as a
    872     # single binary name and fails. So strip any leading ccache part
    873     # for this purpose.
    874     if { ![catch { exec $CC --version } msg]} {
    875       if {[string first gcc $CC] != -1} {
    876         define CC_PROFILE_FLAG "-pg"
    877         return 1
    878       }
    879     }
    880   }
    881   define CC_PROFILE_FLAG ""
    882   return 0
    883 }
    884 
    885 #
    886 # @proj-looks-like-windows ?key?
    887 #
    888 # Returns 1 if this appears to be a Windows environment (MinGw,
    889 # Cygwin, MSys), else returns 0. The optional argument is the name of
    890 # an autosetup define which contains platform name info, defaulting to
    891 # "host" (meaning, somewhat counterintuitively, the target system, not
    892 # the current host). The other legal value is "build" (the build
    893 # machine, i.e. the local host). If $key == "build" then some
    894 # additional checks may be performed which are not applicable when
    895 # $key == "host".
    896 #
    897 proc proj-looks-like-windows {{key host}} {
    898   global autosetup
    899   switch -glob -- [get-define $key] {
    900     *-*-ming* - *-*-cygwin - *-*-msys - *windows* {
    901       return 1
    902     }
    903   }
    904   if {$key eq "build"} {
    905     # These apply only to the local OS, not a cross-compilation target,
    906     # as the above check potentially can.
    907     if {$::autosetup(iswin)} { return 1 }
    908     if {[find-an-executable cygpath] ne "" || $::tcl_platform(os) eq "Windows NT"} {
    909       return 1
    910     }
    911   }
    912   return 0
    913 }
    914 
    915 #
    916 # @proj-looks-like-mac ?key?
    917 #
    918 # Looks at either the 'host' (==compilation target platform) or
    919 # 'build' (==the being-built-on platform) define value and returns if
    920 # if that value seems to indicate that it represents a Mac platform,
    921 # else returns 0.
    922 #
    923 proc proj-looks-like-mac {{key host}} {
    924   switch -glob -- [get-define $key] {
    925     *-*-darwin* {
    926       # https://sqlite.org/forum/forumpost/7b218c3c9f207646
    927       # There's at least one Linux out there which matches *apple*.
    928       return 1
    929     }
    930     default {
    931       return 0
    932     }
    933   }
    934 }
    935 
    936 #
    937 # @proj-exe-extension
    938 #
    939 # Checks autosetup's "host" and "build" defines to see if the build
    940 # host and target are Windows-esque (Cygwin, MinGW, MSys). If the
    941 # build environment is then BUILD_EXEEXT is [define]'d to ".exe", else
    942 # "". If the target, a.k.a. "host", is then TARGET_EXEEXT is
    943 # [define]'d to ".exe", else "".
    944 #
    945 proc proj-exe-extension {} {
    946   set rH ""
    947   set rB ""
    948   if {[proj-looks-like-windows host]} {
    949     set rH ".exe"
    950   }
    951   if {[proj-looks-like-windows build]} {
    952     set rB ".exe"
    953   }
    954   define BUILD_EXEEXT $rB
    955   define TARGET_EXEEXT $rH
    956 }
    957 
    958 #
    959 # @proj-dll-extension
    960 #
    961 # Works like proj-exe-extension except that it defines BUILD_DLLEXT
    962 # and TARGET_DLLEXT to one of (.so, ,dll, .dylib).
    963 #
    964 # Trivia: for .dylib files, the linker needs the -dynamiclib flag
    965 # instead of -shared.
    966 #
    967 proc proj-dll-extension {} {
    968   set inner {{key} {
    969     if {[proj-looks-like-mac $key]} {
    970       return ".dylib"
    971     }
    972     if {[proj-looks-like-windows $key]} {
    973       return ".dll"
    974     }
    975     return ".so"
    976   }}
    977   define BUILD_DLLEXT [apply $inner build]
    978   define TARGET_DLLEXT [apply $inner host]
    979 }
    980 
    981 #
    982 # @proj-lib-extension
    983 #
    984 # Static-library counterpart of proj-dll-extension. Defines
    985 # BUILD_LIBEXT and TARGET_LIBEXT to the conventional static library
    986 # extension for the being-built-on resp. the target platform.
    987 #
    988 proc proj-lib-extension {} {
    989   set inner {{key} {
    990     switch -glob -- [get-define $key] {
    991       *-*-ming* - *-*-cygwin - *-*-msys {
    992         return ".a"
    993         # ^^^ this was ".lib" until 2025-02-07. See
    994         # https://sqlite.org/forum/forumpost/02db2d4240
    995       }
    996       default {
    997         return ".a"
    998       }
    999     }
   1000   }}
   1001   define BUILD_LIBEXT [apply $inner build]
   1002   define TARGET_LIBEXT [apply $inner host]
   1003 }
   1004 
   1005 #
   1006 # @proj-file-extensions
   1007 #
   1008 # Calls all of the proj-*-extension functions.
   1009 #
   1010 proc proj-file-extensions {} {
   1011   proj-exe-extension
   1012   proj-dll-extension
   1013   proj-lib-extension
   1014 }
   1015 
   1016 #
   1017 # @proj-affirm-files-exist ?-v? filename...
   1018 #
   1019 # Expects a list of file names. If any one of them does not exist in
   1020 # the filesystem, it fails fatally with an informative message.
   1021 # Returns the last file name it checks. If the first argument is -v
   1022 # then it emits msg-checking/msg-result messages for each file.
   1023 #
   1024 proc proj-affirm-files-exist {args} {
   1025   set rc ""
   1026   set verbose 0
   1027   if {[lindex $args 0] eq "-v"} {
   1028     set verbose 1
   1029     set args [lrange $args 1 end]
   1030   }
   1031   foreach f $args {
   1032     if {$verbose} { msg-checking "Looking for $f ... " }
   1033     if {![file exists $f]} {
   1034       user-error "not found: $f"
   1035     }
   1036     if {$verbose} { msg-result "" }
   1037     set rc $f
   1038   }
   1039   return rc
   1040 }
   1041 
   1042 #
   1043 # @proj-check-emsdk
   1044 #
   1045 # Emscripten is used for doing in-tree builds of web-based WASM stuff,
   1046 # as opposed to WASI-based WASM or WASM binaries we import from other
   1047 # places. This is only set up for Unix-style OSes and is untested
   1048 # anywhere but Linux. Requires that the --with-emsdk flag be
   1049 # registered with autosetup.
   1050 #
   1051 # It looks for the SDK in the location specified by --with-emsdk.
   1052 # Values of "" or "auto" mean to check for the environment var EMSDK
   1053 # (which gets set by the emsdk_env.sh script from the SDK) or that
   1054 # same var passed to configure.
   1055 #
   1056 # If the given directory is found, it expects to find emsdk_env.sh in
   1057 # that directory, as well as the emcc compiler somewhere under there.
   1058 #
   1059 # If the --with-emsdk[=DIR] flag is explicitly provided and the SDK is
   1060 # not found then a fatal error is generated, otherwise failure to find
   1061 # the SDK is not fatal.
   1062 #
   1063 # Defines the following:
   1064 #
   1065 # - HAVE_EMSDK = 0 or 1 (this function's return value)
   1066 # - EMSDK_HOME = "" or top dir of the emsdk
   1067 # - EMSDK_ENV_SH = "" or $EMSDK_HOME/emsdk_env.sh
   1068 # - BIN_EMCC = "" or $EMSDK_HOME/upstream/emscripten/emcc
   1069 #
   1070 # Returns 1 if EMSDK_ENV_SH is found, else 0.  If EMSDK_HOME is not empty
   1071 # but BIN_EMCC is then emcc was not found in the EMSDK_HOME, in which
   1072 # case we have to rely on the fact that sourcing $EMSDK_ENV_SH from a
   1073 # shell will add emcc to the $PATH.
   1074 #
   1075 proc proj-check-emsdk {} {
   1076   set emsdkHome [opt-val with-emsdk]
   1077   define EMSDK_HOME ""
   1078   define EMSDK_ENV_SH ""
   1079   define BIN_EMCC ""
   1080   set hadValue [llength $emsdkHome]
   1081   msg-checking "Emscripten SDK? "
   1082   if {$emsdkHome in {"" "auto"}} {
   1083     # Check the environment. $EMSDK gets set by sourcing emsdk_env.sh.
   1084     set emsdkHome [get-env EMSDK ""]
   1085   }
   1086   set rc 0
   1087   if {$emsdkHome ne ""} {
   1088     define EMSDK_HOME $emsdkHome
   1089     set emsdkEnv "$emsdkHome/emsdk_env.sh"
   1090     if {[file exists $emsdkEnv]} {
   1091       msg-result "$emsdkHome"
   1092       define EMSDK_ENV_SH $emsdkEnv
   1093       set rc 1
   1094       set emcc "$emsdkHome/upstream/emscripten/emcc"
   1095       if {[file exists $emcc]} {
   1096         define BIN_EMCC $emcc
   1097       }
   1098     } else {
   1099       msg-result "emsdk_env.sh not found in $emsdkHome"
   1100     }
   1101   } else {
   1102     msg-result "not found"
   1103   }
   1104   if {$hadValue && 0 == $rc} {
   1105     # Fail if it was explicitly requested but not found
   1106     proj-fatal "Cannot find the Emscripten SDK"
   1107   }
   1108   define HAVE_EMSDK $rc
   1109   return $rc
   1110 }
   1111 
   1112 #
   1113 # @proj-cc-check-Wl-flag ?flag ?args??
   1114 #
   1115 # Checks whether the given linker flag (and optional arguments) can be
   1116 # passed from the compiler to the linker using one of these formats:
   1117 #
   1118 # - -Wl,flag[,arg1[,...argN]]
   1119 # - -Wl,flag -Wl,arg1 ...-Wl,argN
   1120 #
   1121 # If so, that flag string is returned, else an empty string is
   1122 # returned.
   1123 #
   1124 proc proj-cc-check-Wl-flag {args} {
   1125   cc-with {-link 1} {
   1126     # Try -Wl,flag,...args
   1127     set fli "-Wl"
   1128     foreach f $args { append fli ",$f" }
   1129     if {[cc-check-flags $fli]} {
   1130       return $fli
   1131     }
   1132     # Try -Wl,flag -Wl,arg1 ...-Wl,argN
   1133     set fli ""
   1134     foreach f $args { append fli "-Wl,$f " }
   1135     if {[cc-check-flags $fli]} {
   1136       return [string trim $fli]
   1137     }
   1138     return ""
   1139   }
   1140 }
   1141 
   1142 #
   1143 # @proj-check-rpath
   1144 #
   1145 # Tries various approaches to handling the -rpath link-time
   1146 # flag. Defines LDFLAGS_RPATH to that/those flag(s) or an empty
   1147 # string. Returns 1 if it finds an option, else 0.
   1148 #
   1149 # By default, the rpath is set to $prefix/lib. However, if either of
   1150 # --exec-prefix=... or --libdir=...  are explicitly passed to
   1151 # configure then [get-define libdir] is used (noting that it derives
   1152 # from exec-prefix by default).
   1153 #
   1154 proc proj-check-rpath {} {
   1155   if {[proj-opt-was-provided libdir]
   1156       || [proj-opt-was-provided exec-prefix]} {
   1157     set lp "[get-define libdir]"
   1158   } else {
   1159     set lp "[get-define prefix]/lib"
   1160   }
   1161   # If we _don't_ use cc-with {} here (to avoid updating the global
   1162   # CFLAGS or LIBS or whatever it is that cc-check-flags updates) then
   1163   # downstream tests may fail because the resulting rpath gets
   1164   # implicitly injected into them.
   1165   cc-with {-link 1} {
   1166     if {[cc-check-flags "-rpath $lp"]} {
   1167       define LDFLAGS_RPATH "-rpath $lp"
   1168     } else {
   1169       set wl [proj-cc-check-Wl-flag -rpath $lp]
   1170       if {"" eq $wl} {
   1171         set wl [proj-cc-check-Wl-flag -R$lp]
   1172       }
   1173       if {"" eq $wl} {
   1174         # HP-UX: https://sqlite.org/forum/forumpost/d80ecdaddd
   1175         set wl [proj-cc-check-Wl-flag +b $lp]
   1176       }
   1177       define LDFLAGS_RPATH $wl
   1178     }
   1179   }
   1180   expr {"" ne [get-define LDFLAGS_RPATH]}
   1181 }
   1182 
   1183 #
   1184 # @proj-check-soname ?libname?
   1185 #
   1186 # Checks whether CC supports the -Wl,-soname,lib... flag. If so, it
   1187 # returns 1 and defines LDFLAGS_SONAME_PREFIX to the flag's prefix, to
   1188 # which the client would need to append "libwhatever.N".  If not, it
   1189 # returns 0 and defines LDFLAGS_SONAME_PREFIX to an empty string.
   1190 #
   1191 # The libname argument is only for purposes of running the flag
   1192 # compatibility test, and is not included in the resulting
   1193 # LDFLAGS_SONAME_PREFIX. It is provided so that clients may
   1194 # potentially avoid some end-user confusion by using their own lib's
   1195 # name here (which shows up in the "checking..." output).
   1196 #
   1197 proc proj-check-soname {{libname "libfoo.so.0"}} {
   1198   cc-with {-link 1} {
   1199     if {[cc-check-flags "-Wl,-soname,${libname}"]} {
   1200       define LDFLAGS_SONAME_PREFIX "-Wl,-soname,"
   1201       return 1
   1202     } elseif {[cc-check-flags "-Wl,+h,${libname}"]} {
   1203       # HP-UX: https://sqlite.org/forum/forumpost/d80ecdaddd
   1204       define LDFLAGS_SONAME_PREFIX "-Wl,+h,"
   1205       return 1
   1206     } else {
   1207       define LDFLAGS_SONAME_PREFIX ""
   1208       return 0
   1209     }
   1210   }
   1211 }
   1212 
   1213 #
   1214 # @proj-check-fsanitize ?list-of-opts?
   1215 #
   1216 # Checks whether CC supports -fsanitize=X, where X is each entry of
   1217 # the given list of flags. If any of those flags are supported, it
   1218 # returns the string "-fsanitize=X..." where X... is a comma-separated
   1219 # list of all flags from the original set which are supported. If none
   1220 # of the given options are supported then it returns an empty string.
   1221 #
   1222 # Example:
   1223 #
   1224 #  set f [proj-check-fsanitize {address bounds-check just-testing}]
   1225 #
   1226 # Will, on many systems, resolve to "-fsanitize=address,bounds-check",
   1227 # but may also resolve to "-fsanitize=address".
   1228 #
   1229 proc proj-check-fsanitize {{opts {address bounds-strict}}} {
   1230   set sup {}
   1231   foreach opt $opts {
   1232     # -nooutput is used because -fsanitize=hwaddress will otherwise
   1233     # pass this test on x86_64, but then warn at build time that
   1234     # "hwaddress is not supported for this target".
   1235     cc-with {-nooutput 1} {
   1236       if {[cc-check-flags "-fsanitize=$opt"]} {
   1237         lappend sup $opt
   1238       }
   1239     }
   1240   }
   1241   if {[llength $sup] > 0} {
   1242     return "-fsanitize=[join $sup ,]"
   1243   }
   1244   return ""
   1245 }
   1246 
   1247 #
   1248 # Internal helper for proj-dump-defs-json. Expects to be passed a
   1249 # [define] name and the variadic $args which are passed to
   1250 # proj-dump-defs-json. If it finds a pattern match for the given
   1251 # $name in the various $args, it returns the type flag for that $name,
   1252 # e.g. "-str" or "-bare", else returns an empty string.
   1253 #
   1254 proc proj-defs-type_ {name spec} {
   1255   foreach {type patterns} $spec {
   1256     foreach pattern $patterns {
   1257       if {[string match $pattern $name]} {
   1258         return $type
   1259       }
   1260     }
   1261   }
   1262   return ""
   1263 }
   1264 
   1265 #
   1266 # Internal helper for proj-defs-format_: returns a JSON-ish quoted
   1267 # form of the given string-type values. It only performs the most
   1268 # basic of escaping. The input must not contain any control
   1269 # characters.
   1270 #
   1271 proc proj-quote-str_ {value} {
   1272   return \"[string map [list \\ \\\\ \" \\\"] $value]\"
   1273 }
   1274 
   1275 #
   1276 # An internal impl detail of proj-dump-defs-json. Requires a data
   1277 # type specifier, as used by make-config-header, and a value. Returns
   1278 # the formatted value or the value $::proj__Config(defs-skip) if the caller
   1279 # should skip emitting that value.
   1280 #
   1281 set ::proj__Config(defs-skip) "-proj-defs-format_ sentinel"
   1282 proc proj-defs-format_ {type value} {
   1283   switch -exact -- $type {
   1284     -bare {
   1285       # Just output the value unchanged
   1286     }
   1287     -none {
   1288       set value $::proj__Config(defs-skip)
   1289     }
   1290     -str {
   1291       set value [proj-quote-str_ $value]
   1292     }
   1293     -auto {
   1294       # Automatically determine the type
   1295       if {![string is integer -strict $value]} {
   1296         set value [proj-quote-str_ $value]
   1297       }
   1298     }
   1299     -array {
   1300       set ar {}
   1301       foreach v $value {
   1302         set v [proj-defs-format_ -auto $v]
   1303         if {$::proj__Config(defs-skip) ne $v} {
   1304           lappend ar $v
   1305         }
   1306       }
   1307       set value "\[ [join $ar {, }] \]"
   1308     }
   1309     "" {
   1310       set value $::proj__Config(defs-skip)
   1311     }
   1312     default {
   1313       proj-fatal "Unknown type in proj-dump-defs-json: $type"
   1314     }
   1315   }
   1316   return $value
   1317 }
   1318 
   1319 #
   1320 # @proj-dump-defs-json outfile ...flags
   1321 #
   1322 # This function works almost identically to autosetup's
   1323 # make-config-header but emits its output in JSON form. It is not a
   1324 # fully-functional JSON emitter, and will emit broken JSON for
   1325 # complicated outputs, but should be sufficient for purposes of
   1326 # emitting most configure vars (numbers and simple strings).
   1327 #
   1328 # In addition to the formatting flags supported by make-config-header,
   1329 # it also supports:
   1330 #
   1331 #  -array {patterns...}
   1332 #
   1333 # Any defines matching the given patterns will be treated as a list of
   1334 # values, each of which will be formatted as if it were in an -auto {...}
   1335 # set, and the define will be emitted to JSON in the form:
   1336 #
   1337 #  "ITS_NAME": [ "value1", ...valueN ]
   1338 #
   1339 # Achtung: if a given -array pattern contains values which themselves
   1340 # contains spaces...
   1341 #
   1342 #   define-append foo {"-DFOO=bar baz" -DBAR="baz barre"}
   1343 #
   1344 # will lead to:
   1345 #
   1346 #  ["-DFOO=bar baz", "-DBAR=\"baz", "barre\""]
   1347 #
   1348 # Neither is especially satisfactory (and the second is useless), and
   1349 # handling of such values is subject to change if any such values ever
   1350 # _really_ need to be processed by our source trees.
   1351 #
   1352 proc proj-dump-defs-json {file args} {
   1353   file mkdir [file dirname $file]
   1354   set lines {}
   1355   lappend args -bare {SIZEOF_* HAVE_DECL_*} -auto HAVE_*
   1356   foreach n [lsort [dict keys [all-defines]]] {
   1357     set type [proj-defs-type_ $n $args]
   1358     set value [proj-defs-format_ $type [get-define $n]]
   1359     if {$::proj__Config(defs-skip) ne $value} {
   1360       lappend lines "\"$n\": ${value}"
   1361     }
   1362   }
   1363   set buf {}
   1364   lappend buf [join $lines ",\n"]
   1365   write-if-changed $file $buf {
   1366     msg-result "Created $file"
   1367   }
   1368 }
   1369 
   1370 #
   1371 # @proj-xfer-option-aliases map
   1372 #
   1373 # Expects a list of pairs of configure flags which have been
   1374 # registered with autosetup, in this form:
   1375 #
   1376 #  { alias1 => canonical1
   1377 #    aliasN => canonicalN ... }
   1378 #
   1379 # The names must not have their leading -- part and must be in the
   1380 # form which autosetup will expect for passing to [opt-val NAME] and
   1381 # friends.
   1382 #
   1383 # Comment lines are permitted in the input.
   1384 #
   1385 # For each pair of ALIAS and CANONICAL, if --ALIAS is provided but
   1386 # --CANONICAL is not, the value of the former is copied to the
   1387 # latter. If --ALIAS is not provided, this is a no-op. If both have
   1388 # explicitly been provided a fatal usage error is triggered.
   1389 #
   1390 # Motivation: autosetup enables "hidden aliases" in [options] lists,
   1391 # and elides the aliases from --help output but does no further
   1392 # handling of them. For example, when --alias is a hidden alias of
   1393 # --canonical and a user passes --alias=X, [opt-val canonical] returns
   1394 # no value. i.e. the script must check both [opt-val alias] and
   1395 # [opt-val canonical].  The intent here is that this function be
   1396 # passed such mappings immediately after [options] is called, to carry
   1397 # over any values from hidden aliases into their canonical names, such
   1398 # that [opt-value canonical] will return X if --alias=X is passed to
   1399 # configure.
   1400 #
   1401 # That said: autosetup's [opt-str] does support alias forms, but it
   1402 # requires that the caller know all possible aliases. It's simpler, in
   1403 # terms of options handling, if there's only a single canonical name
   1404 # which each down-stream call of [opt-...] has to know.
   1405 #
   1406 proc proj-xfer-options-aliases {mapping} {
   1407   foreach {hidden - canonical} [proj-strip-hash-comments $mapping] {
   1408     if {[proj-opt-was-provided $hidden]} {
   1409       if {[proj-opt-was-provided $canonical]} {
   1410         proj-fatal "both --$canonical and its alias --$hidden were used. Use only one or the other."
   1411       } else {
   1412         proj-opt-set $canonical [opt-val $hidden]
   1413       }
   1414     }
   1415   }
   1416 }
   1417 
   1418 #
   1419 # Arguable/debatable...
   1420 #
   1421 # When _not_ cross-compiling and CC_FOR_BUILD is _not_ explicitly
   1422 # specified, force CC_FOR_BUILD to be the same as CC, so that:
   1423 #
   1424 # ./configure CC=clang
   1425 #
   1426 # will use CC_FOR_BUILD=clang, instead of cc, for building in-tree
   1427 # tools. This is based off of an email discussion and is thought to
   1428 # be likely to cause less confusion than seeing 'cc' invocations
   1429 # when when the user passes CC=clang.
   1430 #
   1431 # Sidebar: if we do this before the cc package is installed, it gets
   1432 # reverted by that package. Ergo, the cc package init will tell the
   1433 # user "Build C compiler...cc" shortly before we tell them otherwise.
   1434 #
   1435 proc proj-redefine-cc-for-build {} {
   1436   if {![proj-is-cross-compiling]
   1437       && [get-define CC] ne [get-define CC_FOR_BUILD]
   1438       && "nope" eq [get-env CC_FOR_BUILD "nope"]} {
   1439     user-notice "Re-defining CC_FOR_BUILD to CC=[get-define CC]. To avoid this, explicitly pass CC_FOR_BUILD=..."
   1440     define CC_FOR_BUILD [get-define CC]
   1441   }
   1442 }
   1443 
   1444 #
   1445 # @proj-which-linenoise headerFile
   1446 #
   1447 # Attempts to determine whether the given linenoise header file is of
   1448 # the "antirez" or "msteveb" flavor. It returns 2 for msteveb, else 1
   1449 # (it does not validate that the header otherwise contains the
   1450 # linenoise API).
   1451 #
   1452 proc proj-which-linenoise {dotH} {
   1453   set srcHeader [proj-file-content $dotH]
   1454   if {[string match *userdata* $srcHeader]} {
   1455     return 2
   1456   } else {
   1457     return 1
   1458   }
   1459 }
   1460 
   1461 #
   1462 # @proj-remap-autoconf-dir-vars
   1463 #
   1464 # "Re-map" the autoconf-conventional --XYZdir flags into something
   1465 # which is more easily overridable from a make invocation.
   1466 #
   1467 # Based off of notes in <https://sqlite.org/forum/forumpost/00d12a41f7>.
   1468 #
   1469 # Consider:
   1470 #
   1471 # $ ./configure --prefix=/foo
   1472 # $ make install prefix=/blah
   1473 #
   1474 # In that make invocation, $(libdir) would, at make-time, normally be
   1475 # hard-coded to /foo/lib, rather than /blah/lib. That happens because
   1476 # autosetup exports conventional $prefix-based values for the numerous
   1477 # autoconfig-compatible XYZdir vars at configure-time.  What we would
   1478 # normally want, however, is that --libdir derives from the make-time
   1479 # $(prefix).  The distinction between configure-time and make-time is
   1480 # the significant factor there.
   1481 #
   1482 # This function attempts to reconcile those vars in such a way that
   1483 # they will derive, at make-time, from $(prefix) in a conventional
   1484 # manner unless they are explicitly overridden at configure-time, in
   1485 # which case those overrides takes precedence.
   1486 #
   1487 # Each autoconf-relvant --XYZ flag which is explicitly passed to
   1488 # configure is exported as-is, as are those which default to some
   1489 # top-level system directory, e.g. /etc or /var.  All which derive
   1490 # from either $prefix or $exec_prefix are exported in the form of a
   1491 # Makefile var reference, e.g.  libdir=${exec_prefix}/lib. Ergo, if
   1492 # --exec-prefix=FOO is passed to configure, libdir will still derive,
   1493 # at make-time, from whatever exec_prefix is passed to make, and will
   1494 # use FOO if exec_prefix is not overridden at make-time.  Without this
   1495 # post-processing, libdir would be cemented in as FOO/lib at
   1496 # configure-time, so could be tedious to override properly via a make
   1497 # invocation.
   1498 #
   1499 proc proj-remap-autoconf-dir-vars {} {
   1500   set prefix [get-define prefix]
   1501   set exec_prefix [get-define exec_prefix $prefix]
   1502   # The following var derefs must be formulated such that they are
   1503   # legal for use in (A) makefiles, (B) pkgconfig files, and (C) TCL's
   1504   # [subst] command.  i.e. they must use the form ${X}.
   1505   foreach {flag makeVar makeDeref} {
   1506     exec-prefix     exec_prefix    ${prefix}
   1507     datadir         datadir        ${prefix}/share
   1508     mandir          mandir         ${datadir}/man
   1509     includedir      includedir     ${prefix}/include
   1510     bindir          bindir         ${exec_prefix}/bin
   1511     libdir          libdir         ${exec_prefix}/lib
   1512     sbindir         sbindir        ${exec_prefix}/sbin
   1513     sysconfdir      sysconfdir     /etc
   1514     sharedstatedir  sharedstatedir ${prefix}/com
   1515     localstatedir   localstatedir  /var
   1516     runstatedir     runstatedir    /run
   1517     infodir         infodir        ${datadir}/info
   1518     libexecdir      libexecdir     ${exec_prefix}/libexec
   1519   } {
   1520     if {[proj-opt-was-provided $flag]} {
   1521       define $makeVar [join [opt-val $flag]]
   1522     } else {
   1523       define $makeVar [join $makeDeref]
   1524     }
   1525     # Maintenance reminder: the [join] call is to avoid {braces}
   1526     # around the output when someone passes in,
   1527     # e.g. --libdir=\${prefix}/foo/bar. Debian's SQLite package build
   1528     # script does that.
   1529   }
   1530 }
   1531 
   1532 #
   1533 # @proj-env-file flag ?default?
   1534 #
   1535 # If a file named .env-$flag exists, this function returns a
   1536 # trimmed copy of its contents, else it returns $dflt. The intended
   1537 # usage is that things like developer-specific CFLAGS preferences can
   1538 # be stored in .env-CFLAGS.
   1539 #
   1540 proc proj-env-file {flag {dflt ""}} {
   1541   set fn ".env-${flag}"
   1542   if {[file readable $fn]} {
   1543     return [proj-file-content -trim $fn]
   1544   }
   1545   return $dflt
   1546 }
   1547 
   1548 #
   1549 # @proj-get-env var ?default?
   1550 #
   1551 # Extracts the value of "environment" variable $var from the first of
   1552 # the following places where it's defined:
   1553 #
   1554 # - Passed to configure as $var=...
   1555 # - Exists as an environment variable
   1556 # - A file named .env-$var (see [proj-env-file])
   1557 #
   1558 # If none of those are set, $dflt is returned.
   1559 #
   1560 proc proj-get-env {var {dflt ""}} {
   1561   get-env $var [proj-env-file $var $dflt]
   1562 }
   1563 
   1564 #
   1565 # @proj-scope ?lvl?
   1566 #
   1567 # Returns the name of the _calling_ proc from ($lvl + 1) levels up the
   1568 # call stack (where the caller's level will be 1 up from _this_
   1569 # call). If $lvl would resolve to global scope "global scope" is
   1570 # returned and if it would be negative then a string indicating such
   1571 # is returned (as opposed to throwing an error).
   1572 #
   1573 proc proj-scope {{lvl 0}} {
   1574   #uplevel [expr {$lvl + 1}] {lindex [info level 0] 0}
   1575   set ilvl [info level]
   1576   set offset [expr {$ilvl  - $lvl - 1}]
   1577   if { $offset < 0} {
   1578     return "invalid scope ($offset)"
   1579   } elseif { $offset == 0} {
   1580     return "global scope"
   1581   } else {
   1582     return [lindex [info level $offset] 0]
   1583   }
   1584 }
   1585 
   1586 #
   1587 # Deprecated name of [proj-scope].
   1588 #
   1589 proc proj-current-scope {{lvl 0}} {
   1590   puts stderr \
   1591     "Deprecated proj-current-scope called from [proj-scope 1]. Use proj-scope instead."
   1592   proj-scope [incr lvl]
   1593 }
   1594 
   1595 #
   1596 # Converts parts of tclConfig.sh to autosetup [define]s.
   1597 #
   1598 # Expects to be passed the name of a value tclConfig.sh or an empty
   1599 # string.  It converts certain parts of that file's contents to
   1600 # [define]s (see the code for the whole list). If $tclConfigSh is an
   1601 # empty string then it [define]s the various vars as empty strings.
   1602 #
   1603 proc proj-tclConfig-sh-to-autosetup {tclConfigSh} {
   1604   set shBody {}
   1605   set tclVars {
   1606     TCL_INCLUDE_SPEC
   1607     TCL_LIBS
   1608     TCL_LIB_SPEC
   1609     TCL_STUB_LIB_SPEC
   1610     TCL_EXEC_PREFIX
   1611     TCL_PREFIX
   1612     TCL_VERSION
   1613     TCL_MAJOR_VERSION
   1614     TCL_MINOR_VERSION
   1615     TCL_PACKAGE_PATH
   1616     TCL_PATCH_LEVEL
   1617     TCL_SHLIB_SUFFIX
   1618   }
   1619   # Build a small shell script which proxies the $tclVars from
   1620   # $tclConfigSh into autosetup code...
   1621   lappend shBody "if test x = \"x${tclConfigSh}\"; then"
   1622   foreach v $tclVars {
   1623     lappend shBody "$v= ;"
   1624   }
   1625   lappend shBody "else . \"${tclConfigSh}\"; fi"
   1626   foreach v $tclVars {
   1627     lappend shBody "echo define $v {\$$v} ;"
   1628   }
   1629   lappend shBody "exit"
   1630   set shBody [join $shBody "\n"]
   1631   #puts "shBody=$shBody\n"; exit
   1632   eval [exec echo $shBody | sh]
   1633 }
   1634 
   1635 #
   1636 # @proj-tweak-default-env-dirs
   1637 #
   1638 # This function is not useful before [use system] is called to set up
   1639 # --prefix and friends. It should be called as soon after [use system]
   1640 # as feasible.
   1641 #
   1642 # For certain target environments, if --prefix is _not_ passed in by
   1643 # the user, set the prefix to an environment-specific default. For
   1644 # such environments its does [define prefix ...]  and [proj-opt-set
   1645 # prefix ...], but it does not process vars derived from the prefix,
   1646 # e.g. exec-prefix. To do so it is generally necessary to also call
   1647 # proj-remap-autoconf-dir-vars late in the config process (immediately
   1648 # before ".in" files are filtered).
   1649 #
   1650 # Similar modifications may be made for --mandir.
   1651 #
   1652 # Returns >0 if it modifies the environment, else 0.
   1653 #
   1654 proc proj-tweak-default-env-dirs {} {
   1655   set rc 0
   1656   switch -glob -- [get-define host] {
   1657     *-haiku {
   1658       if {![proj-opt-was-provided prefix]} {
   1659         set hdir /boot/home/config/non-packaged
   1660         proj-opt-set prefix $hdir
   1661         define prefix $hdir
   1662         incr rc
   1663       }
   1664       if {![proj-opt-was-provided mandir]} {
   1665         set hdir /boot/system/documentation/man
   1666         proj-opt-set mandir $hdir
   1667         define mandir $hdir
   1668         incr rc
   1669       }
   1670     }
   1671   }
   1672   return $rc
   1673 }
   1674 
   1675 #
   1676 # @proj-dot-ins-append file ?fileOut ?postProcessScript??
   1677 #
   1678 # Queues up an autosetup [make-template]-style file to be processed
   1679 # at a later time using [proj-dot-ins-process].
   1680 #
   1681 # $file is the input file. If $fileOut is empty then this function
   1682 # derives $fileOut from $file, stripping both its directory and
   1683 # extension parts. i.e. it defaults to writing the output to the
   1684 # current directory (typically $::autosetup(builddir)).
   1685 #
   1686 # If $postProcessScript is not empty then, during
   1687 # [proj-dot-ins-process], it will be eval'd immediately after
   1688 # processing the file. In the context of that script, the vars
   1689 # $dotInsIn and $dotInsOut will be set to the input and output file
   1690 # names.  This can be used, for example, to make the output file
   1691 # executable or perform validation on its contents:
   1692 #
   1693 ##  proj-dot-ins-append my.sh.in my.sh {
   1694 ##    catch {exec chmod u+x $dotInsOut}
   1695 ##  }
   1696 #
   1697 # See [proj-dot-ins-process], [proj-dot-ins-list]
   1698 #
   1699 proc proj-dot-ins-append {fileIn args} {
   1700   set srcdir $::autosetup(srcdir)
   1701   switch -exact -- [llength $args] {
   1702     0 {
   1703       lappend fileIn [file rootname [file tail $fileIn]] ""
   1704     }
   1705     1 {
   1706       lappend fileIn [join $args] ""
   1707     }
   1708     2 {
   1709       lappend fileIn {*}$args
   1710     }
   1711     default {
   1712       proj-fatal "Too many arguments: $fileIn $args"
   1713     }
   1714   }
   1715   #puts "******* [proj-scope]: adding [llength $fileIn]-length item: $fileIn"
   1716   lappend ::proj__Config(dot-in-files) $fileIn
   1717 }
   1718 
   1719 #
   1720 # @proj-dot-ins-list
   1721 #
   1722 # Returns the current list of [proj-dot-ins-append]'d files, noting
   1723 # that each entry is a 3-element list of (inputFileName,
   1724 # outputFileName, postProcessScript).
   1725 #
   1726 proc proj-dot-ins-list {} {
   1727   return $::proj__Config(dot-in-files)
   1728 }
   1729 
   1730 #
   1731 # @proj-dot-ins-process ?-touch? ?-validate? ?-clear?
   1732 #
   1733 # Each file which has previously been passed to [proj-dot-ins-append]
   1734 # is processed, with its passing its in-file out-file names to
   1735 # [proj-make-from-dot-in].
   1736 #
   1737 # The intent is that a project accumulate any number of files to
   1738 # filter and delay their actual filtering until the last stage of the
   1739 # configure script, calling this function at that time.
   1740 #
   1741 # Optional flags:
   1742 #
   1743 # -touch: gets passed on to [proj-make-from-dot-in]
   1744 #
   1745 # -validate: after processing each file, before running the file's
   1746 #  associated script, if any, it runs the file through
   1747 #  proj-validate-no-unresolved-ats, erroring out if that does.
   1748 #
   1749 # -clear: after processing, empty the dot-ins list. This effectively
   1750 #  makes proj-dot-ins-append available for re-use.
   1751 #
   1752 proc proj-dot-ins-process {args} {
   1753   proj-parse-flags args flags {
   1754     -touch   "" {return "-touch"}
   1755     -clear    0 {expr 1}
   1756     -validate 0 {expr 1}
   1757   }
   1758   #puts "args=$args"; parray flags
   1759   if {[llength $args] > 0} {
   1760     error "Invalid argument to [proj-scope]: $args"
   1761   }
   1762   foreach f $::proj__Config(dot-in-files) {
   1763     proj-assert {3==[llength $f]} \
   1764       "Expecting proj-dot-ins-list to be stored in 3-entry lists. Got: $f"
   1765     lassign $f fIn fOut fScript
   1766     #puts "DOING $fIn  ==> $fOut"
   1767     proj-make-from-dot-in {*}$flags(-touch) $fIn $fOut
   1768     if {$flags(-validate)} {
   1769       proj-validate-no-unresolved-ats $fOut
   1770     }
   1771     if {"" ne $fScript} {
   1772       uplevel 1 [join [list set dotInsIn $fIn \; \
   1773                          set dotInsOut $fOut \; \
   1774                          eval \{${fScript}\} \; \
   1775                          unset dotInsIn dotInsOut]]
   1776     }
   1777   }
   1778   if {$flags(-clear)} {
   1779     set ::proj__Config(dot-in-files) [list]
   1780   }
   1781 }
   1782 
   1783 #
   1784 # @proj-validate-no-unresolved-ats filenames...
   1785 #
   1786 # For each filename given to it, it validates that the file has no
   1787 # unresolved @VAR@ references. If it finds any, it produces an error
   1788 # with location information.
   1789 #
   1790 # Exception: if a filename matches the pattern {*[Mm]ake*} AND a given
   1791 # line begins with a # (not including leading whitespace) then that
   1792 # line is ignored for purposes of this validation. The intent is that
   1793 # @VAR@ inside of makefile comments should not (necessarily) cause
   1794 # validation to fail, as it's sometimes convenient to comment out
   1795 # sections during development of a configure script and its
   1796 # corresponding makefile(s).
   1797 #
   1798 proc proj-validate-no-unresolved-ats {args} {
   1799   foreach f $args {
   1800     set lnno 1
   1801     set isMake [string match {*[Mm]ake*} $f]
   1802     foreach line [proj-file-content-list $f] {
   1803       if {!$isMake || ![string match "#*" [string trimleft $line]]} {
   1804         if {[regexp {(@[A-Za-z0-9_\.]+@)} $line match]} {
   1805           error "Unresolved reference to $match at line $lnno of $f"
   1806         }
   1807       }
   1808       incr lnno
   1809     }
   1810   }
   1811 }
   1812 
   1813 #
   1814 # @proj-first-file-found tgtVar fileList
   1815 #
   1816 # Searches $fileList for an existing file. If one is found, its name
   1817 # is assigned to tgtVar and 1 is returned, else tgtVar is set to ""
   1818 # and 0 is returned.
   1819 #
   1820 proc proj-first-file-found {tgtVar fileList} {
   1821   upvar $tgtVar tgt
   1822   foreach f $fileList {
   1823     if {[file exists $f]} {
   1824       set tgt $f
   1825       return 1
   1826     }
   1827   }
   1828   set tgt ""
   1829   return 0
   1830 }
   1831 
   1832 #
   1833 # Defines $defName to contain makefile recipe commands for re-running
   1834 # the configure script with its current set of $::argv flags.  This
   1835 # can be used to automatically reconfigure.
   1836 #
   1837 proc proj-setup-autoreconfig {defName} {
   1838   define $defName \
   1839     [join [list \
   1840              cd \"$::autosetup(builddir)\" \
   1841              && [get-define AUTOREMAKE "error - missing @AUTOREMAKE@"]]]
   1842 }
   1843 
   1844 #
   1845 # @prop-define-append defineName args...
   1846 #
   1847 # A proxy for Autosetup's [define-append]. Appends all non-empty $args
   1848 # to [define-append $defineName].
   1849 #
   1850 proc proj-define-append {defineName args} {
   1851   foreach a $args {
   1852     if {"" ne $a} {
   1853       define-append $defineName {*}$a
   1854     }
   1855   }
   1856 }
   1857 
   1858 #
   1859 # @prod-define-amend ?-p|-prepend? ?-d|-define? defineName args...
   1860 #
   1861 # A proxy for Autosetup's [define-append].
   1862 #
   1863 # Appends all non-empty $args to the define named by $defineName.  If
   1864 # one of (-p | -prepend) are used it instead prepends them, in their
   1865 # given order, to $defineName.
   1866 #
   1867 # If -define is used then each argument is assumed to be a [define]'d
   1868 # flag and [get-define X ""] is used to fetch it.
   1869 #
   1870 # Re. linker flags: typically, -lXYZ flags need to be in "reverse"
   1871 # order, with each -lY resolving symbols for -lX's to its left. This
   1872 # order is largely historical, and not relevant on all environments,
   1873 # but it is technically correct and still relevant on some
   1874 # environments.
   1875 #
   1876 # See: proj-define-append
   1877 #
   1878 proc proj-define-amend {args} {
   1879   set defName ""
   1880   set prepend 0
   1881   set isdefs 0
   1882   set xargs [list]
   1883   foreach arg $args {
   1884     switch -exact -- $arg {
   1885       "" {}
   1886       -p - -prepend { incr prepend }
   1887       -d - -define  { incr isdefs }
   1888       default {
   1889         if {"" eq $defName} {
   1890           set defName $arg
   1891         } else {
   1892           lappend xargs $arg
   1893         }
   1894       }
   1895     }
   1896   }
   1897   if {"" eq $defName} {
   1898     proj-error "Missing defineName argument in call from [proj-scope 1]"
   1899   }
   1900   if {$isdefs} {
   1901     set args $xargs
   1902     set xargs [list]
   1903     foreach arg $args {
   1904       lappend xargs [get-define $arg ""]
   1905     }
   1906     set args $xargs
   1907   }
   1908 #  puts "**** args=$args"
   1909 #  puts "**** xargs=$xargs"
   1910 
   1911   set args $xargs
   1912   if {$prepend} {
   1913     lappend args {*}[get-define $defName ""]
   1914     define $defName [join $args]; # join to eliminate {} entries
   1915   } else {
   1916     proj-define-append $defName {*}$args
   1917   }
   1918 }
   1919 
   1920 #
   1921 # @proj-define-to-cflag ?-list? ?-quote? ?-zero-undef? defineName...
   1922 #
   1923 # Treat each argument as the name of a [define] and renders it like a
   1924 # CFLAGS value in one of the following forms:
   1925 #
   1926 #  -D$name
   1927 #  -D$name=integer   (strict integer matches only)
   1928 #  '-D$name=value'   (without -quote)
   1929 #  '-D$name="value"' (with -quote)
   1930 #
   1931 # It treats integers as numbers and everything else as a quoted
   1932 # string, noting that it does not handle strings which themselves
   1933 # contain quotes.
   1934 #
   1935 # The -zero-undef flag causes no -D to be emitted for integer values
   1936 # of 0.
   1937 #
   1938 # By default it returns the result as string of all -D... flags,
   1939 # but if passed the -list flag it will return a list of the
   1940 # individual CFLAGS.
   1941 #
   1942 proc proj-define-to-cflag {args} {
   1943   set rv {}
   1944   proj-parse-flags args flags {
   1945     -list       0 {expr 1}
   1946     -quote      0 {expr 1}
   1947     -zero-undef 0 {expr 1}
   1948   }
   1949   foreach d $args {
   1950     set v [get-define $d ""]
   1951     set li {}
   1952     if {"" eq $d} {
   1953       set v "-D${d}"
   1954     } elseif {[string is integer -strict $v]} {
   1955       if {!$flags(-zero-undef) || $v ne "0"} {
   1956         set v "-D${d}=$v"
   1957       }
   1958     } elseif {$flags(-quote)} {
   1959       set v "'-D${d}=\"$v\"'"
   1960     } else {
   1961       set v "'-D${d}=$v'"
   1962     }
   1963     lappend rv $v
   1964   }
   1965   expr {$flags(-list) ? $rv : [join $rv]}
   1966 }
   1967 
   1968 
   1969 if {0} {
   1970   # Turns out that autosetup's [options-add] essentially does exactly
   1971   # this...
   1972 
   1973   # A list of lists of Autosetup [options]-format --flags definitions.
   1974   # Append to this using [proj-options-add] and use
   1975   # [proj-options-combine] to merge them into a single list for passing
   1976   # to [options].
   1977   #
   1978   set ::proj__Config(extra-options) {}
   1979 
   1980   # @proj-options-add list
   1981   #
   1982   # Adds a list of options to the pending --flag processing.  It must be
   1983   # in the format used by Autosetup's [options] function.
   1984   #
   1985   # This will have no useful effect if called from after [options]
   1986   # is called.
   1987   #
   1988   # Use [proj-options-combine] to get a combined list of all added
   1989   # options.
   1990   #
   1991   # PS: when writing this i wasn't aware of autosetup's [options-add],
   1992   # works quite similarly. Only the timing is different.
   1993   proc proj-options-add {list} {
   1994     lappend ::proj__Config(extra-options) $list
   1995   }
   1996 
   1997   # @proj-options-combine list1 ?...listN?
   1998   #
   1999   # Expects each argument to be a list of options compatible with
   2000   # autosetup's [options] function. This function concatenates the
   2001   # contents of each list into a new top-level list, stripping the outer
   2002   # list part of each argument, and returning that list
   2003   #
   2004   # If passed no arguments, it uses the list generated by calls to
   2005   # [proj-options-add].
   2006   proc proj-options-combine {args} {
   2007     set rv [list]
   2008     if {0 == [llength $args]} {
   2009       set args $::proj__Config(extra-options)
   2010     }
   2011     foreach e $args {
   2012       lappend rv {*}$e
   2013     }
   2014     return $rv
   2015   }
   2016 }; # proj-options-*
   2017 
   2018 # Internal cache for use via proj-cache-*.
   2019 array set proj__Cache {}
   2020 
   2021 #
   2022 # @proj-cache-key arg {addLevel 0}
   2023 #
   2024 # Helper to generate cache keys for [proj-cache-*].
   2025 #
   2026 # $addLevel should almost always be 0.
   2027 #
   2028 # Returns a cache key for the given argument:
   2029 #
   2030 #   integer: relative call stack levels to get the scope name of for
   2031 #   use as a key. [proj-scope [expr {1 + $arg + addLevel}]] is
   2032 #   then used to generate the key. i.e. the default of 0 uses the
   2033 #   calling scope's name as the key.
   2034 #
   2035 #   Anything else: returned as-is
   2036 #
   2037 proc proj-cache-key {arg {addLevel 0}} {
   2038   if {[string is integer -strict $arg]} {
   2039     return [proj-scope [expr {$arg + $addLevel + 1}]]
   2040   }
   2041   return $arg
   2042 }
   2043 
   2044 #
   2045 # @proj-cache-set ?-key KEY? ?-level 0? value
   2046 #
   2047 # Sets a feature-check cache entry with the given key.
   2048 #
   2049 # See proj-cache-key for -key's and -level's semantics, noting that
   2050 # this function adds one to -level for purposes of that call.
   2051 proc proj-cache-set {args} {
   2052   proj-parse-flags args flags {
   2053     -key => 0
   2054     -level => 0
   2055   }
   2056   lassign $args val
   2057   set key [proj-cache-key $flags(-key) [expr {1 + $flags(-level)}]]
   2058   #puts "** fcheck set $key = $val"
   2059   set ::proj__Cache($key) $val
   2060 }
   2061 
   2062 #
   2063 # @proj-cache-remove ?key? ?addLevel?
   2064 #
   2065 # Removes an entry from the proj-cache.
   2066 proc proj-cache-remove {{key 0} {addLevel 0}} {
   2067   set key [proj-cache-key $key [expr {1 + $addLevel}]]
   2068   set rv ""
   2069   if {[info exists ::proj__Cache($key)]} {
   2070     set rv $::proj__Cache($key)
   2071     unset ::proj__Cache($key)
   2072   }
   2073   return $rv;
   2074 }
   2075 
   2076 #
   2077 # @proj-cache-check ?-key KEY? ?-level LEVEL? tgtVarName
   2078 #
   2079 # Checks for a feature-check cache entry with the given key.
   2080 #
   2081 # If the feature-check cache has a matching entry then this function
   2082 # assigns its value to tgtVar and returns 1, else it assigns tgtVar to
   2083 # "" and returns 0.
   2084 #
   2085 # See proj-cache-key for $key's and $addLevel's semantics, noting that
   2086 # this function adds one to $addLevel for purposes of that call.
   2087 proc proj-cache-check {args} {
   2088   proj-parse-flags args flags {
   2089     -key => 0
   2090     -level => 0
   2091   }
   2092   lassign $args tgtVar
   2093   upvar $tgtVar tgt
   2094   set rc 0
   2095   set key [proj-cache-key $flags(-key) [expr {1 + $flags(-level)}]]
   2096   #puts "** fcheck get key=$key"
   2097   if {[info exists ::proj__Cache($key)]} {
   2098     set tgt $::proj__Cache($key)
   2099     incr rc
   2100   } else {
   2101     set tgt ""
   2102   }
   2103   return $rc
   2104 }
   2105 
   2106 #
   2107 # @proj-coalesce ...args
   2108 #
   2109 # Returns the first argument which is not empty (eq ""), or an empty
   2110 # string on no match.
   2111 proc proj-coalesce {args} {
   2112   foreach arg $args {
   2113     if {"" ne $arg} {
   2114       return $arg
   2115     }
   2116   }
   2117   return ""
   2118 }
   2119 
   2120 #
   2121 # @proj-parse-flags argvListName targetArrayName {prototype}
   2122 #
   2123 # A helper to parse flags from proc argument lists.
   2124 #
   2125 # The first argument is the name of a var holding the args to
   2126 # parse. It will be overwritten, possibly with a smaller list.
   2127 #
   2128 # The second argument is the name of an array variable to create in
   2129 # the caller's scope.
   2130 #
   2131 # The third argument, $prototype, is a description of how to handle
   2132 # the flags. Each entry in that list must be in one of the
   2133 # following forms:
   2134 #
   2135 #   -flag  defaultValue ?-literal|-call|-apply?
   2136 #                       script|number|incr|proc-name|{apply $aLambda}
   2137 #
   2138 #   -flag* ...as above...
   2139 #
   2140 #   -flag  => defaultValue ?-call proc-name-and-args|-apply lambdaExpr?
   2141 #
   2142 #   -flag* => ...as above...
   2143 #
   2144 #   :PRAGMA
   2145 #
   2146 # The first two forms represents a basic flag with no associated
   2147 # following argument. The third and fourth forms, called arg-consuming
   2148 # flags, extract the value from the following argument in $argvName
   2149 # (pneumonic: => points to the next argument.). The :PRAGMA form
   2150 # offers a way to configure certain aspects of this call.
   2151 #
   2152 # If $argv contains any given flag from $prototype, its default value
   2153 # is overridden depending on several factors:
   2154 #
   2155 #  - If the -literal flag is used, or the flag's script is a number,
   2156 #    value is used verbatim.
   2157 #
   2158 #  - Else if the -call flag is used, the argument must be a proc name
   2159 #    and any leading arguments, e.g. {apply $myLambda}.  The proc is passed
   2160 #    the (flag, value) as arguments (non-consuming flags will get
   2161 #    passed the flag's current/starting value and consuming flags will
   2162 #    get the next argument).  Its result becomes the result of the
   2163 #    flag.
   2164 #
   2165 #  - Else if -apply X is used, it's effectively shorthand for -call
   2166 #    {apply X}. Its argument may either be a $lambaRef or a {{f v}
   2167 #    {body}} construct.
   2168 #
   2169 #  - Else if $script is one of the following values, it is treated as
   2170 #    the result of...
   2171 #
   2172 #    - incr: increments the current value of the flag.
   2173 #
   2174 #  - Else $script is eval'd to get its result value. That result
   2175 #    becomes the new flag value for $tgtArrayName(-flag). This
   2176 #    function intercepts [return $val] from eval'ing $script.  Any
   2177 #    empty script will result in the flag having "" assigned to it.
   2178 #
   2179 # Unless the -flag has a trailing asterisk, e.g. -flag*, this function
   2180 # assumes that each flag is unique, and using a flag more than once
   2181 # causes an error to be triggered. the -flag* forms works similarly
   2182 # except that may appear in $argv any number of times:
   2183 #
   2184 #  - For non-arg-consuming flags, each invocation of -flag causes the
   2185 #    result of $script to overwrite the previous value. e.g. so
   2186 #    {-flag* {x} {incr foo}} has a default value of x, but passing in
   2187 #    -flag twice would change it to the result of incrementing foo
   2188 #    twice. This form can be used to implement, e.g., increasing
   2189 #    verbosity levels by passing -verbose multiple times.
   2190 #
   2191 #  - For arg-consuming flags, the given flag starts with value X, but
   2192 #    if the flag is provided in $argv, the default is cleared, then
   2193 #    each instance of -flag causes its value to be appended to the
   2194 #    result, so {-flag* => {a b c}} defaults to {a b c}, but passing
   2195 #    in -flag y -flag z would change it to {y z}, not {a b c y z}..
   2196 #
   2197 # By default, the args list is only inspected until the first argument
   2198 # which is not described by $prototype. i.e. the first "non-flag" (not
   2199 # counting values consumed for flags defined like -flag => default).
   2200 # The :all-flags pragma (see below) can modify this behavior.
   2201 #
   2202 # If a "--" flag is encountered, no more arguments are inspected as
   2203 # flags unless the :all-flags pragma (see below) is in effect. The
   2204 # first instance of "--" is removed from the target result list but
   2205 # all remaining instances of "--" are are passed through.
   2206 #
   2207 # Any argvName entries not described in $prototype are considered to
   2208 # be "non-flags" for purposes of this function, even if they
   2209 # ostensibly look like flags.
   2210 #
   2211 # Returns the number of flags it processed in $argvName, not counting
   2212 # "--".
   2213 #
   2214 # Example:
   2215 #
   2216 ## set args [list -foo -bar {blah} -z 8 9 10 -theEnd]
   2217 ## proj-parse-flags args flags {
   2218 ##   -foo    0  {expr 1}
   2219 ##   -bar    => 0
   2220 ##   -no-baz 1  {return 0}
   2221 ##   -z 0 2
   2222 ## }
   2223 #
   2224 # After that $flags would contain {-foo 1 -bar {blah} -no-baz 1 -z 2}
   2225 # and $args would be {8 9 10 -theEnd}.
   2226 #
   2227 # Pragmas:
   2228 #
   2229 # Passing :PRAGMAS to this function may modify how it works. The
   2230 # following pragmas are supported (note the leading ":"):
   2231 #
   2232 #   :all-flags indicates that the whole input list should be scanned,
   2233 #   not stopping at the first non-flag or "--".
   2234 #
   2235 proc proj-parse-flags {argvName tgtArrayName prototype} {
   2236   upvar $argvName argv
   2237   upvar $tgtArrayName outFlags
   2238   array set flags {}; # staging area
   2239   array set blob {}; # holds markers for various per-key state and options
   2240   set incrSkip 1; # 1 if we stop at the first non-flag, else 0
   2241   # Parse $prototype for flag definitions...
   2242   set n [llength $prototype]
   2243   set checkProtoFlag {
   2244     #puts "**** checkProtoFlag #$i of $n k=$k fv=$fv"
   2245     switch -exact -- $fv {
   2246       -literal {
   2247         proj-assert {![info exists blob(${k}.consumes)]}
   2248         set blob(${k}.script) [list expr [lindex $prototype [incr i]]]
   2249       }
   2250       -apply {
   2251         set fv [lindex $prototype [incr i]]
   2252         if {2 == [llength $fv]} {
   2253           # Treat this as a lambda literal
   2254           set fv [list $fv]
   2255         }
   2256         lappend blob(${k}.call) "apply $fv"
   2257       }
   2258       -call {
   2259         # arg is either a proc name or {apply $aLambda}
   2260         set fv [lindex $prototype [incr i]]
   2261         lappend blob(${k}.call) $fv
   2262       }
   2263       default {
   2264         proj-assert {![info exists blob(${k}.consumes)]}
   2265         set blob(${k}.script) $fv
   2266       }
   2267     }
   2268     if {$i >= $n} {
   2269       proj-error -up "[proj-scope]: Missing argument for $k flag"
   2270     }
   2271   }
   2272   for {set i 0} {$i < $n} {incr i} {
   2273     set k [lindex $prototype $i]
   2274     #puts "**** #$i of $n k=$k"
   2275 
   2276     # Check for :PRAGMA...
   2277     switch -exact -- $k {
   2278       :all-flags {
   2279         set incrSkip 0
   2280         continue
   2281       }
   2282     }
   2283 
   2284     proj-assert {[string match -* $k]} \
   2285       "Invalid argument: $k"
   2286 
   2287     if {[string match {*\*} $k]} {
   2288       # Re-map -foo* to -foo and flag -foo as a repeatable flag
   2289       set k [string map {* ""} $k]
   2290       incr blob(${k}.multi)
   2291     }
   2292 
   2293     if {[info exists flags($k)]} {
   2294       proj-error -up "[proj-scope]: Duplicated prototype for flag $k"
   2295     }
   2296 
   2297     switch -exact -- [lindex $prototype [expr {$i + 1}]] {
   2298       => {
   2299         # -flag => DFLT ?-subflag arg?
   2300         incr i 2
   2301         if {$i >= $n} {
   2302           proj-error -up "[proj-scope]: Missing argument for $k => flag"
   2303         }
   2304         incr blob(${k}.consumes)
   2305         set vi [lindex $prototype $i]
   2306         if {$vi in {-apply -call}} {
   2307           proj-error -up "[proj-scope]: Missing default value for $k flag"
   2308         } else {
   2309           set fv [lindex $prototype [expr {$i + 1}]]
   2310           if {$fv in {-apply -call}} {
   2311             incr i
   2312             eval $checkProtoFlag
   2313           }
   2314         }
   2315       }
   2316       default {
   2317         # -flag VALUE ?flag? SCRIPT
   2318         set vi [lindex $prototype [incr i]]
   2319         set fv [lindex $prototype [incr i]]
   2320         eval $checkProtoFlag
   2321       }
   2322     }
   2323     #puts "**** #$i of $n k=$k vi=$vi"
   2324     set flags($k) $vi
   2325   }
   2326   #puts "-- flags"; parray flags
   2327   #puts "-- blob"; parray blob
   2328   set rc 0
   2329   set rv {}; # staging area for the target argv value
   2330   set skipMode 0
   2331   set n [llength $argv]
   2332   # Now look for those flags in $argv...
   2333   for {set i 0} {$i < $n} {incr i} {
   2334     set arg [lindex $argv $i]
   2335     #puts "-- [proj-scope] arg=$arg"
   2336     if {$skipMode} {
   2337       lappend rv $arg
   2338     } elseif {"--" eq $arg} {
   2339       # "--" is the conventional way to end processing of args
   2340       if {[incr blob(--)] > 1} {
   2341         # Elide only the first one
   2342         lappend rv $arg
   2343       }
   2344       incr skipMode $incrSkip
   2345     } elseif {[info exists flags($arg)]} {
   2346       # A known flag...
   2347       set isMulti [info exists blob(${arg}.multi)]
   2348       incr blob(${arg}.seen)
   2349       if {1 < $blob(${arg}.seen) && !$isMulti} {
   2350         proj-error -up [proj-scope] "$arg flag was used multiple times"
   2351       }
   2352       set vMode 0; # 0=as-is, 1=eval, 2=call
   2353       set isConsuming [info exists blob(${arg}.consumes)]
   2354       if {$isConsuming} {
   2355         incr i
   2356         if {$i >= $n} {
   2357           proj-error -up [proj-scope] "is missing argument for $arg flag"
   2358         }
   2359         set vv [lindex $argv $i]
   2360       } elseif {[info exists blob(${arg}.script)]} {
   2361         set vMode 1
   2362         set vv $blob(${arg}.script)
   2363       } else {
   2364         set vv $flags($arg)
   2365       }
   2366 
   2367       if {[info exists blob(${arg}.call)]} {
   2368         set vMode 2
   2369         set vv [concat {*}$blob(${arg}.call) $arg $vv]
   2370       } elseif {$isConsuming} {
   2371         proj-assert {!$vMode}
   2372         # fall through
   2373       } elseif {"" eq $vv || [string is double -strict $vv]} {
   2374         set vMode 0
   2375       } elseif {$vv in {incr}} {
   2376         set vMode 0
   2377         switch -exact $vv {
   2378           incr {
   2379             set xx $flags($k); incr xx; set vv $xx; unset xx
   2380           }
   2381           default {
   2382             proj-error "Unhandled \$vv value $vv"
   2383           }
   2384         }
   2385       } else {
   2386         set vv [list eval $vv]
   2387         set vMode 1
   2388       }
   2389       if {$vMode} {
   2390         set code [catch [list uplevel 1 $vv] vv xopt]
   2391         if {$code ni {0 2}} {
   2392           return {*}$xopt $vv
   2393         }
   2394       }
   2395       if {$isConsuming && $isMulti} {
   2396         if {1 == $blob(${arg}.seen)} {
   2397           # On the first hit, overwrite the default with a new list.
   2398           set flags($arg) [list $vv]
   2399         } else {
   2400           # On subsequent hits, append to the list.
   2401           lappend flags($arg) $vv
   2402         }
   2403       } else {
   2404         set flags($arg) $vv
   2405       }
   2406       incr rc
   2407     } else {
   2408       # Non-flag
   2409       incr skipMode $incrSkip
   2410       lappend rv $arg
   2411     }
   2412   }
   2413   set argv $rv
   2414   array set outFlags [array get flags]
   2415   #puts "-- rv=$rv argv=$argv flags="; parray flags
   2416   return $rc
   2417 }; # proj-parse-flags
   2418 
   2419 #
   2420 # Older (deprecated) name of proj-parse-flags.
   2421 #
   2422 proc proj-parse-simple-flags {args} {
   2423   tailcall proj-parse-flags {*}$args
   2424 }
   2425 
   2426 if {$::proj__Config(self-tests)} {
   2427   set __ova $::proj__Config(verbose-assert);
   2428   set ::proj__Config(verbose-assert) 1
   2429   puts "Running [info script] self-tests..."
   2430   # proj-cache...
   2431   apply {{} {
   2432     #proj-warn "Test code for proj-cache"
   2433     proj-assert {![proj-cache-check -key here check]}
   2434     proj-assert {"here" eq [proj-cache-key here]}
   2435     proj-assert {"" eq $check}
   2436     proj-cache-set -key here thevalue
   2437     proj-assert {[proj-cache-check -key here check]}
   2438     proj-assert {"thevalue" eq $check}
   2439 
   2440     proj-assert {![proj-cache-check check]}
   2441     #puts "*** key = ([proj-cache-key 0])"
   2442     proj-assert {"" eq $check}
   2443     proj-cache-set abc
   2444     proj-assert {[proj-cache-check check]}
   2445     proj-assert {"abc" eq $check}
   2446 
   2447     #parray ::proj__Cache;
   2448     proj-assert {"" ne [proj-cache-remove]}
   2449     proj-assert {![proj-cache-check check]}
   2450     proj-assert {"" eq [proj-cache-remove]}
   2451     proj-assert {"" eq $check}
   2452   }}
   2453 
   2454   # proj-parse-flags ...
   2455   apply {{} {
   2456     set foo 3
   2457     set argv {-a "hi - world" -b -b -b -- -a {bye bye} -- -d -D c -a "" --}
   2458     proj-parse-flags argv flags {
   2459       :all-flags
   2460       -a* => "gets overwritten"
   2461       -b* 7 {incr foo}
   2462       -d 1 0
   2463       -D 0 1
   2464     }
   2465 
   2466     #puts "-- argv = $argv"; parray flags;
   2467     proj-assert {"-- c --" eq $argv}
   2468     proj-assert {$flags(-a) eq "{hi - world} {bye bye} {}"}
   2469     proj-assert {$foo == 6}
   2470     proj-assert {$flags(-b) eq $foo}
   2471     proj-assert {$flags(-d) == 0}
   2472     proj-assert {$flags(-D) == 1}
   2473     set foo 0
   2474     foreach x $flags(-a) {
   2475       proj-assert {$x in {{hi - world} {bye bye} {}}}
   2476       incr foo
   2477     }
   2478     proj-assert {3 == $foo}
   2479 
   2480     set argv {-a {hi world} -b -maybe -- -a {bye bye} -- -b c --}
   2481     set foo 0
   2482     proj-parse-flags argv flags {
   2483       -a => "aaa"
   2484       -b 0 {incr foo}
   2485       -maybe no -literal yes
   2486     }
   2487     #parray flags; puts "--- argv = $argv"
   2488     proj-assert {"-a {bye bye} -- -b c --" eq $argv}
   2489     proj-assert {$flags(-a) eq "hi world"}
   2490     proj-assert {1 == $flags(-b)}
   2491     proj-assert {"yes" eq $flags(-maybe)}
   2492 
   2493     set argv {-f -g -a aaa -M -M -M -L -H -A AAA a b c}
   2494     set foo 0
   2495     set myLambda {{flag val} {
   2496       proj-assert {$flag in {-f -g -M}}
   2497       #puts "myLambda flag=$flag val=$val"
   2498       incr val
   2499     }}
   2500     proc myNonLambda {flag val} {
   2501       proj-assert {$flag in {-A -a}}
   2502       #puts "myNonLambda flag=$flag val=$val"
   2503       concat $val $val
   2504     }
   2505     proj-parse-flags argv flags {
   2506       -f 0 -call {apply $myLambda}
   2507       -g 2 -apply $myLambda
   2508       -h 3 -apply $myLambda
   2509       -H 30 33
   2510       -a => aAAAa -apply {{f v} {
   2511         set v
   2512       }}
   2513       -A => AaaaA -call myNonLambda
   2514       -B => 17 -call myNonLambda
   2515       -M* 0 -apply $myLambda
   2516       -L "" -literal $myLambda
   2517     }
   2518     rename myNonLambda ""
   2519     #puts "--- argv = $argv"; parray flags
   2520     proj-assert {$flags(-f) == 1}
   2521     proj-assert {$flags(-g) == 3}
   2522     proj-assert {$flags(-h) == 3}
   2523     proj-assert {$flags(-H) == 33}
   2524     proj-assert {$flags(-a) == {aaa}}
   2525     proj-assert {$flags(-A) eq "AAA AAA"}
   2526     proj-assert {$flags(-B) == 17}
   2527     proj-assert {$flags(-M) == 3}
   2528     proj-assert {$flags(-L) eq $myLambda}
   2529 
   2530     set argv {-touch -validate}
   2531     proj-parse-flags argv flags {
   2532       -touch "" {return "-touch"}
   2533       -validate 0 1
   2534     }
   2535     #puts "----- argv = $argv"; parray flags
   2536     proj-assert {$flags(-touch) eq "-touch"}
   2537     proj-assert {$flags(-validate) == 1}
   2538     proj-assert {$argv eq {}}
   2539 
   2540     set argv {-i -i -i}
   2541     proj-parse-flags argv flags {
   2542       -i* 0 incr
   2543     }
   2544     proj-assert {3 == $flags(-i)}
   2545   }}
   2546   set ::proj__Config(verbose-assert) $__ova
   2547   unset __ova
   2548   puts "Done running [info script] self-tests."
   2549 }; # proj- API self-tests
   2550