comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/tcl8.6/tm.tcl @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
comparison
equal deleted inserted replaced
67:0e9998148a16 69:33d812a61356
1 # -*- tcl -*-
2 #
3 # Searching for Tcl Modules. Defines a procedure, declares it as the primary
4 # command for finding packages, however also uses the former 'package unknown'
5 # command as a fallback.
6 #
7 # Locates all possible packages in a directory via a less restricted glob. The
8 # targeted directory is derived from the name of the requested package, i.e.
9 # the TM scan will look only at directories which can contain the requested
10 # package. It will register all packages it found in the directory so that
11 # future requests have a higher chance of being fulfilled by the ifneeded
12 # database without having to come to us again.
13 #
14 # We do not remember where we have been and simply rescan targeted directories
15 # when invoked again. The reasoning is this:
16 #
17 # - The only way we get back to the same directory is if someone is trying to
18 # [package require] something that wasn't there on the first scan.
19 #
20 # Either
21 # 1) It is there now: If we rescan, you get it; if not you don't.
22 #
23 # This covers the possibility that the application asked for a package
24 # late, and the package was actually added to the installation after the
25 # application was started. It shoukld still be able to find it.
26 #
27 # 2) It still is not there: Either way, you don't get it, but the rescan
28 # takes time. This is however an error case and we dont't care that much
29 # about it
30 #
31 # 3) It was there the first time; but for some reason a "package forget" has
32 # been run, and "package" doesn't know about it anymore.
33 #
34 # This can be an indication that the application wishes to reload some
35 # functionality. And should work as well.
36 #
37 # Note that this also strikes a balance between doing a glob targeting a
38 # single package, and thus most likely requiring multiple globs of the same
39 # directory when the application is asking for many packages, and trying to
40 # glob for _everything_ in all subdirectories when looking for a package,
41 # which comes with a heavy startup cost.
42 #
43 # We scan for regular packages only if no satisfying module was found.
44
45 namespace eval ::tcl::tm {
46 # Default paths. None yet.
47
48 variable paths {}
49
50 # The regex pattern a file name has to match to make it a Tcl Module.
51
52 set pkgpattern {^([_[:alpha:]][:_[:alnum:]]*)-([[:digit:]].*)[.]tm$}
53
54 # Export the public API
55
56 namespace export path
57 namespace ensemble create -command path -subcommands {add remove list}
58 }
59
60 # ::tcl::tm::path implementations --
61 #
62 # Public API to the module path. See specification.
63 #
64 # Arguments
65 # cmd - The subcommand to execute
66 # args - The paths to add/remove. Must not appear querying the
67 # path with 'list'.
68 #
69 # Results
70 # No result for subcommands 'add' and 'remove'. A list of paths for
71 # 'list'.
72 #
73 # Sideeffects
74 # The subcommands 'add' and 'remove' manipulate the list of paths to
75 # search for Tcl Modules. The subcommand 'list' has no sideeffects.
76
77 proc ::tcl::tm::add {args} {
78 # PART OF THE ::tcl::tm::path ENSEMBLE
79 #
80 # The path is added at the head to the list of module paths.
81 #
82 # The command enforces the restriction that no path may be an ancestor
83 # directory of any other path on the list. If the new path violates this
84 # restriction an error wil be raised.
85 #
86 # If the path is already present as is no error will be raised and no
87 # action will be taken.
88
89 variable paths
90
91 # We use a copy of the path as source during validation, and extend it as
92 # well. Because we not only have to detect if the new paths are bogus with
93 # respect to the existing paths, but also between themselves. Otherwise we
94 # can still add bogus paths, by specifying them in a single call. This
95 # makes the use of the new paths simpler as well, a trivial assignment of
96 # the collected paths to the official state var.
97
98 set newpaths $paths
99 foreach p $args {
100 if {$p in $newpaths} {
101 # Ignore a path already on the list.
102 continue
103 }
104
105 # Search for paths which are subdirectories of the new one. If there
106 # are any then the new path violates the restriction about ancestors.
107
108 set pos [lsearch -glob $newpaths ${p}/*]
109 # Cannot use "in", we need the position for the message.
110 if {$pos >= 0} {
111 return -code error \
112 "$p is ancestor of existing module path [lindex $newpaths $pos]."
113 }
114
115 # Now look for existing paths which are ancestors of the new one. This
116 # reverse question forces us to loop over the existing paths, as each
117 # element is the pattern, not the new path :(
118
119 foreach ep $newpaths {
120 if {[string match ${ep}/* $p]} {
121 return -code error \
122 "$p is subdirectory of existing module path $ep."
123 }
124 }
125
126 set newpaths [linsert $newpaths 0 $p]
127 }
128
129 # The validation of the input is complete and successful, and everything
130 # in newpaths is either an old path, or added. We can now extend the
131 # official list of paths, a simple assignment is sufficient.
132
133 set paths $newpaths
134 return
135 }
136
137 proc ::tcl::tm::remove {args} {
138 # PART OF THE ::tcl::tm::path ENSEMBLE
139 #
140 # Removes the path from the list of module paths. The command is silently
141 # ignored if the path is not on the list.
142
143 variable paths
144
145 foreach p $args {
146 set pos [lsearch -exact $paths $p]
147 if {$pos >= 0} {
148 set paths [lreplace $paths $pos $pos]
149 }
150 }
151 }
152
153 proc ::tcl::tm::list {} {
154 # PART OF THE ::tcl::tm::path ENSEMBLE
155
156 variable paths
157 return $paths
158 }
159
160 # ::tcl::tm::UnknownHandler --
161 #
162 # Unknown handler for Tcl Modules, i.e. packages in module form.
163 #
164 # Arguments
165 # original - Original [package unknown] procedure.
166 # name - Name of desired package.
167 # version - Version of desired package. Can be the
168 # empty string.
169 # exact - Either -exact or ommitted.
170 #
171 # Name, version, and exact are used to determine satisfaction. The
172 # original is called iff no satisfaction was achieved. The name is also
173 # used to compute the directory to target in the search.
174 #
175 # Results
176 # None.
177 #
178 # Sideeffects
179 # May populate the package ifneeded database with additional provide
180 # scripts.
181
182 proc ::tcl::tm::UnknownHandler {original name args} {
183 # Import the list of paths to search for packages in module form.
184 # Import the pattern used to check package names in detail.
185
186 variable paths
187 variable pkgpattern
188
189 # Without paths to search we can do nothing. (Except falling back to the
190 # regular search).
191
192 if {[llength $paths]} {
193 set pkgpath [string map {:: /} $name]
194 set pkgroot [file dirname $pkgpath]
195 if {$pkgroot eq "."} {
196 set pkgroot ""
197 }
198
199 # We don't remember a copy of the paths while looping. Tcl Modules are
200 # unable to change the list while we are searching for them. This also
201 # simplifies the loop, as we cannot get additional directories while
202 # iterating over the list. A simple foreach is sufficient.
203
204 set satisfied 0
205 foreach path $paths {
206 if {![interp issafe] && ![file exists $path]} {
207 continue
208 }
209 set currentsearchpath [file join $path $pkgroot]
210 if {![interp issafe] && ![file exists $currentsearchpath]} {
211 continue
212 }
213 set strip [llength [file split $path]]
214
215 # Get the module files out of the subdirectories.
216 # - Safe Base interpreters have a restricted "glob" command that
217 # works in this case.
218 # - The "catch" was essential when there was no safe glob and every
219 # call in a safe interp failed; it is retained only for corner
220 # cases in which the eventual call to glob returns an error.
221
222 catch {
223 # We always look for _all_ possible modules in the current
224 # path, to get the max result out of the glob.
225
226 foreach file [glob -nocomplain -directory $currentsearchpath *.tm] {
227 set pkgfilename [join [lrange [file split $file] $strip end] ::]
228
229 if {![regexp -- $pkgpattern $pkgfilename --> pkgname pkgversion]} {
230 # Ignore everything not matching our pattern for
231 # package names.
232 continue
233 }
234 try {
235 package vcompare $pkgversion 0
236 } on error {} {
237 # Ignore everything where the version part is not
238 # acceptable to "package vcompare".
239 continue
240 }
241
242 if {([package ifneeded $pkgname $pkgversion] ne {})
243 && (![interp issafe])
244 } {
245 # There's already a provide script registered for
246 # this version of this package. Since all units of
247 # code claiming to be the same version of the same
248 # package ought to be identical, just stick with
249 # the one we already have.
250 # This does not apply to Safe Base interpreters because
251 # the token-to-directory mapping may have changed.
252 continue
253 }
254
255 # We have found a candidate, generate a "provide script"
256 # for it, and remember it. Note that we are using ::list
257 # to do this; locally [list] means something else without
258 # the namespace specifier.
259
260 # NOTE. When making changes to the format of the provide
261 # command generated below CHECK that the 'LOCATE'
262 # procedure in core file 'platform/shell.tcl' still
263 # understands it, or, if not, update its implementation
264 # appropriately.
265 #
266 # Right now LOCATE's implementation assumes that the path
267 # of the package file is the last element in the list.
268
269 package ifneeded $pkgname $pkgversion \
270 "[::list package provide $pkgname $pkgversion];[::list source -encoding utf-8 $file]"
271
272 # We abort in this unknown handler only if we got a
273 # satisfying candidate for the requested package.
274 # Otherwise we still have to fallback to the regular
275 # package search to complete the processing.
276
277 if {($pkgname eq $name)
278 && [package vsatisfies $pkgversion {*}$args]} {
279 set satisfied 1
280
281 # We do not abort the loop, and keep adding provide
282 # scripts for every candidate in the directory, just
283 # remember to not fall back to the regular search
284 # anymore.
285 }
286 }
287 }
288 }
289
290 if {$satisfied} {
291 return
292 }
293 }
294
295 # Fallback to previous command, if existing. See comment above about
296 # ::list...
297
298 if {[llength $original]} {
299 uplevel 1 $original [::linsert $args 0 $name]
300 }
301 }
302
303 # ::tcl::tm::Defaults --
304 #
305 # Determines the default search paths.
306 #
307 # Arguments
308 # None
309 #
310 # Results
311 # None.
312 #
313 # Sideeffects
314 # May add paths to the list of defaults.
315
316 proc ::tcl::tm::Defaults {} {
317 global env tcl_platform
318
319 regexp {^(\d+)\.(\d+)} [package provide Tcl] - major minor
320 set exe [file normalize [info nameofexecutable]]
321
322 # Note that we're using [::list], not [list] because [list] means
323 # something other than [::list] in this namespace.
324 roots [::list \
325 [file dirname [info library]] \
326 [file join [file dirname [file dirname $exe]] lib] \
327 ]
328
329 if {$tcl_platform(platform) eq "windows"} {
330 set sep ";"
331 } else {
332 set sep ":"
333 }
334 for {set n $minor} {$n >= 0} {incr n -1} {
335 foreach ev [::list \
336 TCL${major}.${n}_TM_PATH \
337 TCL${major}_${n}_TM_PATH \
338 ] {
339 if {![info exists env($ev)]} continue
340 foreach p [split $env($ev) $sep] {
341 path add $p
342 }
343 }
344 }
345 return
346 }
347
348 # ::tcl::tm::roots --
349 #
350 # Public API to the module path. See specification.
351 #
352 # Arguments
353 # paths - List of 'root' paths to derive search paths from.
354 #
355 # Results
356 # No result.
357 #
358 # Sideeffects
359 # Calls 'path add' to paths to the list of module search paths.
360
361 proc ::tcl::tm::roots {paths} {
362 regexp {^(\d+)\.(\d+)} [package provide Tcl] - major minor
363 foreach pa $paths {
364 set p [file join $pa tcl$major]
365 for {set n $minor} {$n >= 0} {incr n -1} {
366 set px [file join $p ${major}.${n}]
367 if {![interp issafe]} {set px [file normalize $px]}
368 path add $px
369 }
370 set px [file join $p site-tcl]
371 if {![interp issafe]} {set px [file normalize $px]}
372 path add $px
373 }
374 return
375 }
376
377 # Initialization. Set up the default paths, then insert the new handler into
378 # the chain.
379
380 if {![interp issafe]} {::tcl::tm::Defaults}