comparison third_party/sqlite3/autosetup/proj.tcl @ 167:589bab390fb4

[ThirdParty] Added sqlite3 to the third_party.
author MrJuneJune <me@mrjunejune.com>
date Mon, 19 Jan 2026 16:28:45 -0800
parents
children
comparison
equal deleted inserted replaced
166:78ea8d5ccc87 167:589bab390fb4
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-append-to 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-append-to
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