jpayne@68
|
1 # package.tcl --
|
jpayne@68
|
2 #
|
jpayne@68
|
3 # utility procs formerly in init.tcl which can be loaded on demand
|
jpayne@68
|
4 # for package management.
|
jpayne@68
|
5 #
|
jpayne@68
|
6 # Copyright (c) 1991-1993 The Regents of the University of California.
|
jpayne@68
|
7 # Copyright (c) 1994-1998 Sun Microsystems, Inc.
|
jpayne@68
|
8 #
|
jpayne@68
|
9 # See the file "license.terms" for information on usage and redistribution
|
jpayne@68
|
10 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
jpayne@68
|
11 #
|
jpayne@68
|
12
|
jpayne@68
|
13 namespace eval tcl::Pkg {}
|
jpayne@68
|
14
|
jpayne@68
|
15 # ::tcl::Pkg::CompareExtension --
|
jpayne@68
|
16 #
|
jpayne@68
|
17 # Used internally by pkg_mkIndex to compare the extension of a file to a given
|
jpayne@68
|
18 # extension. On Windows, it uses a case-insensitive comparison because the
|
jpayne@68
|
19 # file system can be file insensitive.
|
jpayne@68
|
20 #
|
jpayne@68
|
21 # Arguments:
|
jpayne@68
|
22 # fileName name of a file whose extension is compared
|
jpayne@68
|
23 # ext (optional) The extension to compare against; you must
|
jpayne@68
|
24 # provide the starting dot.
|
jpayne@68
|
25 # Defaults to [info sharedlibextension]
|
jpayne@68
|
26 #
|
jpayne@68
|
27 # Results:
|
jpayne@68
|
28 # Returns 1 if the extension matches, 0 otherwise
|
jpayne@68
|
29
|
jpayne@68
|
30 proc tcl::Pkg::CompareExtension {fileName {ext {}}} {
|
jpayne@68
|
31 global tcl_platform
|
jpayne@68
|
32 if {$ext eq ""} {set ext [info sharedlibextension]}
|
jpayne@68
|
33 if {$tcl_platform(platform) eq "windows"} {
|
jpayne@68
|
34 return [string equal -nocase [file extension $fileName] $ext]
|
jpayne@68
|
35 } else {
|
jpayne@68
|
36 # Some unices add trailing numbers after the .so, so
|
jpayne@68
|
37 # we could have something like '.so.1.2'.
|
jpayne@68
|
38 set root $fileName
|
jpayne@68
|
39 while {1} {
|
jpayne@68
|
40 set currExt [file extension $root]
|
jpayne@68
|
41 if {$currExt eq $ext} {
|
jpayne@68
|
42 return 1
|
jpayne@68
|
43 }
|
jpayne@68
|
44
|
jpayne@68
|
45 # The current extension does not match; if it is not a numeric
|
jpayne@68
|
46 # value, quit, as we are only looking to ignore version number
|
jpayne@68
|
47 # extensions. Otherwise we might return 1 in this case:
|
jpayne@68
|
48 # tcl::Pkg::CompareExtension foo.so.bar .so
|
jpayne@68
|
49 # which should not match.
|
jpayne@68
|
50
|
jpayne@68
|
51 if {![string is integer -strict [string range $currExt 1 end]]} {
|
jpayne@68
|
52 return 0
|
jpayne@68
|
53 }
|
jpayne@68
|
54 set root [file rootname $root]
|
jpayne@68
|
55 }
|
jpayne@68
|
56 }
|
jpayne@68
|
57 }
|
jpayne@68
|
58
|
jpayne@68
|
59 # pkg_mkIndex --
|
jpayne@68
|
60 # This procedure creates a package index in a given directory. The package
|
jpayne@68
|
61 # index consists of a "pkgIndex.tcl" file whose contents are a Tcl script that
|
jpayne@68
|
62 # sets up package information with "package require" commands. The commands
|
jpayne@68
|
63 # describe all of the packages defined by the files given as arguments.
|
jpayne@68
|
64 #
|
jpayne@68
|
65 # Arguments:
|
jpayne@68
|
66 # -direct (optional) If this flag is present, the generated
|
jpayne@68
|
67 # code in pkgMkIndex.tcl will cause the package to be
|
jpayne@68
|
68 # loaded when "package require" is executed, rather
|
jpayne@68
|
69 # than lazily when the first reference to an exported
|
jpayne@68
|
70 # procedure in the package is made.
|
jpayne@68
|
71 # -verbose (optional) Verbose output; the name of each file that
|
jpayne@68
|
72 # was successfully rocessed is printed out. Additionally,
|
jpayne@68
|
73 # if processing of a file failed a message is printed.
|
jpayne@68
|
74 # -load pat (optional) Preload any packages whose names match
|
jpayne@68
|
75 # the pattern. Used to handle DLLs that depend on
|
jpayne@68
|
76 # other packages during their Init procedure.
|
jpayne@68
|
77 # dir - Name of the directory in which to create the index.
|
jpayne@68
|
78 # args - Any number of additional arguments, each giving
|
jpayne@68
|
79 # a glob pattern that matches the names of one or
|
jpayne@68
|
80 # more shared libraries or Tcl script files in
|
jpayne@68
|
81 # dir.
|
jpayne@68
|
82
|
jpayne@68
|
83 proc pkg_mkIndex {args} {
|
jpayne@68
|
84 set usage {"pkg_mkIndex ?-direct? ?-lazy? ?-load pattern? ?-verbose? ?--? dir ?pattern ...?"}
|
jpayne@68
|
85
|
jpayne@68
|
86 set argCount [llength $args]
|
jpayne@68
|
87 if {$argCount < 1} {
|
jpayne@68
|
88 return -code error "wrong # args: should be\n$usage"
|
jpayne@68
|
89 }
|
jpayne@68
|
90
|
jpayne@68
|
91 set more ""
|
jpayne@68
|
92 set direct 1
|
jpayne@68
|
93 set doVerbose 0
|
jpayne@68
|
94 set loadPat ""
|
jpayne@68
|
95 for {set idx 0} {$idx < $argCount} {incr idx} {
|
jpayne@68
|
96 set flag [lindex $args $idx]
|
jpayne@68
|
97 switch -glob -- $flag {
|
jpayne@68
|
98 -- {
|
jpayne@68
|
99 # done with the flags
|
jpayne@68
|
100 incr idx
|
jpayne@68
|
101 break
|
jpayne@68
|
102 }
|
jpayne@68
|
103 -verbose {
|
jpayne@68
|
104 set doVerbose 1
|
jpayne@68
|
105 }
|
jpayne@68
|
106 -lazy {
|
jpayne@68
|
107 set direct 0
|
jpayne@68
|
108 append more " -lazy"
|
jpayne@68
|
109 }
|
jpayne@68
|
110 -direct {
|
jpayne@68
|
111 append more " -direct"
|
jpayne@68
|
112 }
|
jpayne@68
|
113 -load {
|
jpayne@68
|
114 incr idx
|
jpayne@68
|
115 set loadPat [lindex $args $idx]
|
jpayne@68
|
116 append more " -load $loadPat"
|
jpayne@68
|
117 }
|
jpayne@68
|
118 -* {
|
jpayne@68
|
119 return -code error "unknown flag $flag: should be\n$usage"
|
jpayne@68
|
120 }
|
jpayne@68
|
121 default {
|
jpayne@68
|
122 # done with the flags
|
jpayne@68
|
123 break
|
jpayne@68
|
124 }
|
jpayne@68
|
125 }
|
jpayne@68
|
126 }
|
jpayne@68
|
127
|
jpayne@68
|
128 set dir [lindex $args $idx]
|
jpayne@68
|
129 set patternList [lrange $args [expr {$idx + 1}] end]
|
jpayne@68
|
130 if {![llength $patternList]} {
|
jpayne@68
|
131 set patternList [list "*.tcl" "*[info sharedlibextension]"]
|
jpayne@68
|
132 }
|
jpayne@68
|
133
|
jpayne@68
|
134 try {
|
jpayne@68
|
135 set fileList [glob -directory $dir -tails -types {r f} -- \
|
jpayne@68
|
136 {*}$patternList]
|
jpayne@68
|
137 } on error {msg opt} {
|
jpayne@68
|
138 return -options $opt $msg
|
jpayne@68
|
139 }
|
jpayne@68
|
140 foreach file $fileList {
|
jpayne@68
|
141 # For each file, figure out what commands and packages it provides.
|
jpayne@68
|
142 # To do this, create a child interpreter, load the file into the
|
jpayne@68
|
143 # interpreter, and get a list of the new commands and packages that
|
jpayne@68
|
144 # are defined.
|
jpayne@68
|
145
|
jpayne@68
|
146 if {$file eq "pkgIndex.tcl"} {
|
jpayne@68
|
147 continue
|
jpayne@68
|
148 }
|
jpayne@68
|
149
|
jpayne@68
|
150 set c [interp create]
|
jpayne@68
|
151
|
jpayne@68
|
152 # Load into the child any packages currently loaded in the parent
|
jpayne@68
|
153 # interpreter that match the -load pattern.
|
jpayne@68
|
154
|
jpayne@68
|
155 if {$loadPat ne ""} {
|
jpayne@68
|
156 if {$doVerbose} {
|
jpayne@68
|
157 tclLog "currently loaded packages: '[info loaded]'"
|
jpayne@68
|
158 tclLog "trying to load all packages matching $loadPat"
|
jpayne@68
|
159 }
|
jpayne@68
|
160 if {![llength [info loaded]]} {
|
jpayne@68
|
161 tclLog "warning: no packages are currently loaded, nothing"
|
jpayne@68
|
162 tclLog "can possibly match '$loadPat'"
|
jpayne@68
|
163 }
|
jpayne@68
|
164 }
|
jpayne@68
|
165 foreach pkg [info loaded] {
|
jpayne@68
|
166 if {![string match -nocase $loadPat [lindex $pkg 1]]} {
|
jpayne@68
|
167 continue
|
jpayne@68
|
168 }
|
jpayne@68
|
169 if {$doVerbose} {
|
jpayne@68
|
170 tclLog "package [lindex $pkg 1] matches '$loadPat'"
|
jpayne@68
|
171 }
|
jpayne@68
|
172 try {
|
jpayne@68
|
173 load [lindex $pkg 0] [lindex $pkg 1] $c
|
jpayne@68
|
174 } on error err {
|
jpayne@68
|
175 if {$doVerbose} {
|
jpayne@68
|
176 tclLog "warning: load [lindex $pkg 0]\
|
jpayne@68
|
177 [lindex $pkg 1]\nfailed with: $err"
|
jpayne@68
|
178 }
|
jpayne@68
|
179 } on ok {} {
|
jpayne@68
|
180 if {$doVerbose} {
|
jpayne@68
|
181 tclLog "loaded [lindex $pkg 0] [lindex $pkg 1]"
|
jpayne@68
|
182 }
|
jpayne@68
|
183 }
|
jpayne@68
|
184 if {[lindex $pkg 1] eq "Tk"} {
|
jpayne@68
|
185 # Withdraw . if Tk was loaded, to avoid showing a window.
|
jpayne@68
|
186 $c eval [list wm withdraw .]
|
jpayne@68
|
187 }
|
jpayne@68
|
188 }
|
jpayne@68
|
189
|
jpayne@68
|
190 $c eval {
|
jpayne@68
|
191 # Stub out the package command so packages can require other
|
jpayne@68
|
192 # packages.
|
jpayne@68
|
193
|
jpayne@68
|
194 rename package __package_orig
|
jpayne@68
|
195 proc package {what args} {
|
jpayne@68
|
196 switch -- $what {
|
jpayne@68
|
197 require {
|
jpayne@68
|
198 return; # Ignore transitive requires
|
jpayne@68
|
199 }
|
jpayne@68
|
200 default {
|
jpayne@68
|
201 __package_orig $what {*}$args
|
jpayne@68
|
202 }
|
jpayne@68
|
203 }
|
jpayne@68
|
204 }
|
jpayne@68
|
205 proc tclPkgUnknown args {}
|
jpayne@68
|
206 package unknown tclPkgUnknown
|
jpayne@68
|
207
|
jpayne@68
|
208 # Stub out the unknown command so package can call into each other
|
jpayne@68
|
209 # during their initialilzation.
|
jpayne@68
|
210
|
jpayne@68
|
211 proc unknown {args} {}
|
jpayne@68
|
212
|
jpayne@68
|
213 # Stub out the auto_import mechanism
|
jpayne@68
|
214
|
jpayne@68
|
215 proc auto_import {args} {}
|
jpayne@68
|
216
|
jpayne@68
|
217 # reserve the ::tcl namespace for support procs and temporary
|
jpayne@68
|
218 # variables. This might make it awkward to generate a
|
jpayne@68
|
219 # pkgIndex.tcl file for the ::tcl namespace.
|
jpayne@68
|
220
|
jpayne@68
|
221 namespace eval ::tcl {
|
jpayne@68
|
222 variable dir ;# Current directory being processed
|
jpayne@68
|
223 variable file ;# Current file being processed
|
jpayne@68
|
224 variable direct ;# -direct flag value
|
jpayne@68
|
225 variable x ;# Loop variable
|
jpayne@68
|
226 variable debug ;# For debugging
|
jpayne@68
|
227 variable type ;# "load" or "source", for -direct
|
jpayne@68
|
228 variable namespaces ;# Existing namespaces (e.g., ::tcl)
|
jpayne@68
|
229 variable packages ;# Existing packages (e.g., Tcl)
|
jpayne@68
|
230 variable origCmds ;# Existing commands
|
jpayne@68
|
231 variable newCmds ;# Newly created commands
|
jpayne@68
|
232 variable newPkgs {} ;# Newly created packages
|
jpayne@68
|
233 }
|
jpayne@68
|
234 }
|
jpayne@68
|
235
|
jpayne@68
|
236 $c eval [list set ::tcl::dir $dir]
|
jpayne@68
|
237 $c eval [list set ::tcl::file $file]
|
jpayne@68
|
238 $c eval [list set ::tcl::direct $direct]
|
jpayne@68
|
239
|
jpayne@68
|
240 # Download needed procedures into the child because we've just deleted
|
jpayne@68
|
241 # the unknown procedure. This doesn't handle procedures with default
|
jpayne@68
|
242 # arguments.
|
jpayne@68
|
243
|
jpayne@68
|
244 foreach p {::tcl::Pkg::CompareExtension} {
|
jpayne@68
|
245 $c eval [list namespace eval [namespace qualifiers $p] {}]
|
jpayne@68
|
246 $c eval [list proc $p [info args $p] [info body $p]]
|
jpayne@68
|
247 }
|
jpayne@68
|
248
|
jpayne@68
|
249 try {
|
jpayne@68
|
250 $c eval {
|
jpayne@68
|
251 set ::tcl::debug "loading or sourcing"
|
jpayne@68
|
252
|
jpayne@68
|
253 # we need to track command defined by each package even in the
|
jpayne@68
|
254 # -direct case, because they are needed internally by the
|
jpayne@68
|
255 # "partial pkgIndex.tcl" step above.
|
jpayne@68
|
256
|
jpayne@68
|
257 proc ::tcl::GetAllNamespaces {{root ::}} {
|
jpayne@68
|
258 set list $root
|
jpayne@68
|
259 foreach ns [namespace children $root] {
|
jpayne@68
|
260 lappend list {*}[::tcl::GetAllNamespaces $ns]
|
jpayne@68
|
261 }
|
jpayne@68
|
262 return $list
|
jpayne@68
|
263 }
|
jpayne@68
|
264
|
jpayne@68
|
265 # init the list of existing namespaces, packages, commands
|
jpayne@68
|
266
|
jpayne@68
|
267 foreach ::tcl::x [::tcl::GetAllNamespaces] {
|
jpayne@68
|
268 set ::tcl::namespaces($::tcl::x) 1
|
jpayne@68
|
269 }
|
jpayne@68
|
270 foreach ::tcl::x [package names] {
|
jpayne@68
|
271 if {[package provide $::tcl::x] ne ""} {
|
jpayne@68
|
272 set ::tcl::packages($::tcl::x) 1
|
jpayne@68
|
273 }
|
jpayne@68
|
274 }
|
jpayne@68
|
275 set ::tcl::origCmds [info commands]
|
jpayne@68
|
276
|
jpayne@68
|
277 # Try to load the file if it has the shared library extension,
|
jpayne@68
|
278 # otherwise source it. It's important not to try to load
|
jpayne@68
|
279 # files that aren't shared libraries, because on some systems
|
jpayne@68
|
280 # (like SunOS) the loader will abort the whole application
|
jpayne@68
|
281 # when it gets an error.
|
jpayne@68
|
282
|
jpayne@68
|
283 if {[::tcl::Pkg::CompareExtension $::tcl::file [info sharedlibextension]]} {
|
jpayne@68
|
284 # The "file join ." command below is necessary. Without
|
jpayne@68
|
285 # it, if the file name has no \'s and we're on UNIX, the
|
jpayne@68
|
286 # load command will invoke the LD_LIBRARY_PATH search
|
jpayne@68
|
287 # mechanism, which could cause the wrong file to be used.
|
jpayne@68
|
288
|
jpayne@68
|
289 set ::tcl::debug loading
|
jpayne@68
|
290 load [file join $::tcl::dir $::tcl::file]
|
jpayne@68
|
291 set ::tcl::type load
|
jpayne@68
|
292 } else {
|
jpayne@68
|
293 set ::tcl::debug sourcing
|
jpayne@68
|
294 source [file join $::tcl::dir $::tcl::file]
|
jpayne@68
|
295 set ::tcl::type source
|
jpayne@68
|
296 }
|
jpayne@68
|
297
|
jpayne@68
|
298 # As a performance optimization, if we are creating direct
|
jpayne@68
|
299 # load packages, don't bother figuring out the set of commands
|
jpayne@68
|
300 # created by the new packages. We only need that list for
|
jpayne@68
|
301 # setting up the autoloading used in the non-direct case.
|
jpayne@68
|
302 if {!$::tcl::direct} {
|
jpayne@68
|
303 # See what new namespaces appeared, and import commands
|
jpayne@68
|
304 # from them. Only exported commands go into the index.
|
jpayne@68
|
305
|
jpayne@68
|
306 foreach ::tcl::x [::tcl::GetAllNamespaces] {
|
jpayne@68
|
307 if {![info exists ::tcl::namespaces($::tcl::x)]} {
|
jpayne@68
|
308 namespace import -force ${::tcl::x}::*
|
jpayne@68
|
309 }
|
jpayne@68
|
310
|
jpayne@68
|
311 # Figure out what commands appeared
|
jpayne@68
|
312
|
jpayne@68
|
313 foreach ::tcl::x [info commands] {
|
jpayne@68
|
314 set ::tcl::newCmds($::tcl::x) 1
|
jpayne@68
|
315 }
|
jpayne@68
|
316 foreach ::tcl::x $::tcl::origCmds {
|
jpayne@68
|
317 unset -nocomplain ::tcl::newCmds($::tcl::x)
|
jpayne@68
|
318 }
|
jpayne@68
|
319 foreach ::tcl::x [array names ::tcl::newCmds] {
|
jpayne@68
|
320 # determine which namespace a command comes from
|
jpayne@68
|
321
|
jpayne@68
|
322 set ::tcl::abs [namespace origin $::tcl::x]
|
jpayne@68
|
323
|
jpayne@68
|
324 # special case so that global names have no
|
jpayne@68
|
325 # leading ::, this is required by the unknown
|
jpayne@68
|
326 # command
|
jpayne@68
|
327
|
jpayne@68
|
328 set ::tcl::abs \
|
jpayne@68
|
329 [lindex [auto_qualify $::tcl::abs ::] 0]
|
jpayne@68
|
330
|
jpayne@68
|
331 if {$::tcl::x ne $::tcl::abs} {
|
jpayne@68
|
332 # Name changed during qualification
|
jpayne@68
|
333
|
jpayne@68
|
334 set ::tcl::newCmds($::tcl::abs) 1
|
jpayne@68
|
335 unset ::tcl::newCmds($::tcl::x)
|
jpayne@68
|
336 }
|
jpayne@68
|
337 }
|
jpayne@68
|
338 }
|
jpayne@68
|
339 }
|
jpayne@68
|
340
|
jpayne@68
|
341 # Look through the packages that appeared, and if there is a
|
jpayne@68
|
342 # version provided, then record it
|
jpayne@68
|
343
|
jpayne@68
|
344 foreach ::tcl::x [package names] {
|
jpayne@68
|
345 if {[package provide $::tcl::x] ne ""
|
jpayne@68
|
346 && ![info exists ::tcl::packages($::tcl::x)]} {
|
jpayne@68
|
347 lappend ::tcl::newPkgs \
|
jpayne@68
|
348 [list $::tcl::x [package provide $::tcl::x]]
|
jpayne@68
|
349 }
|
jpayne@68
|
350 }
|
jpayne@68
|
351 }
|
jpayne@68
|
352 } on error msg {
|
jpayne@68
|
353 set what [$c eval set ::tcl::debug]
|
jpayne@68
|
354 if {$doVerbose} {
|
jpayne@68
|
355 tclLog "warning: error while $what $file: $msg"
|
jpayne@68
|
356 }
|
jpayne@68
|
357 } on ok {} {
|
jpayne@68
|
358 set what [$c eval set ::tcl::debug]
|
jpayne@68
|
359 if {$doVerbose} {
|
jpayne@68
|
360 tclLog "successful $what of $file"
|
jpayne@68
|
361 }
|
jpayne@68
|
362 set type [$c eval set ::tcl::type]
|
jpayne@68
|
363 set cmds [lsort [$c eval array names ::tcl::newCmds]]
|
jpayne@68
|
364 set pkgs [$c eval set ::tcl::newPkgs]
|
jpayne@68
|
365 if {$doVerbose} {
|
jpayne@68
|
366 if {!$direct} {
|
jpayne@68
|
367 tclLog "commands provided were $cmds"
|
jpayne@68
|
368 }
|
jpayne@68
|
369 tclLog "packages provided were $pkgs"
|
jpayne@68
|
370 }
|
jpayne@68
|
371 if {[llength $pkgs] > 1} {
|
jpayne@68
|
372 tclLog "warning: \"$file\" provides more than one package ($pkgs)"
|
jpayne@68
|
373 }
|
jpayne@68
|
374 foreach pkg $pkgs {
|
jpayne@68
|
375 # cmds is empty/not used in the direct case
|
jpayne@68
|
376 lappend files($pkg) [list $file $type $cmds]
|
jpayne@68
|
377 }
|
jpayne@68
|
378
|
jpayne@68
|
379 if {$doVerbose} {
|
jpayne@68
|
380 tclLog "processed $file"
|
jpayne@68
|
381 }
|
jpayne@68
|
382 }
|
jpayne@68
|
383 interp delete $c
|
jpayne@68
|
384 }
|
jpayne@68
|
385
|
jpayne@68
|
386 append index "# Tcl package index file, version 1.1\n"
|
jpayne@68
|
387 append index "# This file is generated by the \"pkg_mkIndex$more\" command\n"
|
jpayne@68
|
388 append index "# and sourced either when an application starts up or\n"
|
jpayne@68
|
389 append index "# by a \"package unknown\" script. It invokes the\n"
|
jpayne@68
|
390 append index "# \"package ifneeded\" command to set up package-related\n"
|
jpayne@68
|
391 append index "# information so that packages will be loaded automatically\n"
|
jpayne@68
|
392 append index "# in response to \"package require\" commands. When this\n"
|
jpayne@68
|
393 append index "# script is sourced, the variable \$dir must contain the\n"
|
jpayne@68
|
394 append index "# full path name of this file's directory.\n"
|
jpayne@68
|
395
|
jpayne@68
|
396 foreach pkg [lsort [array names files]] {
|
jpayne@68
|
397 set cmd {}
|
jpayne@68
|
398 lassign $pkg name version
|
jpayne@68
|
399 lappend cmd ::tcl::Pkg::Create -name $name -version $version
|
jpayne@68
|
400 foreach spec [lsort -index 0 $files($pkg)] {
|
jpayne@68
|
401 foreach {file type procs} $spec {
|
jpayne@68
|
402 if {$direct} {
|
jpayne@68
|
403 set procs {}
|
jpayne@68
|
404 }
|
jpayne@68
|
405 lappend cmd "-$type" [list $file $procs]
|
jpayne@68
|
406 }
|
jpayne@68
|
407 }
|
jpayne@68
|
408 append index "\n[eval $cmd]"
|
jpayne@68
|
409 }
|
jpayne@68
|
410
|
jpayne@68
|
411 set f [open [file join $dir pkgIndex.tcl] w]
|
jpayne@68
|
412 puts $f $index
|
jpayne@68
|
413 close $f
|
jpayne@68
|
414 }
|
jpayne@68
|
415
|
jpayne@68
|
416 # tclPkgSetup --
|
jpayne@68
|
417 # This is a utility procedure use by pkgIndex.tcl files. It is invoked as
|
jpayne@68
|
418 # part of a "package ifneeded" script. It calls "package provide" to indicate
|
jpayne@68
|
419 # that a package is available, then sets entries in the auto_index array so
|
jpayne@68
|
420 # that the package's files will be auto-loaded when the commands are used.
|
jpayne@68
|
421 #
|
jpayne@68
|
422 # Arguments:
|
jpayne@68
|
423 # dir - Directory containing all the files for this package.
|
jpayne@68
|
424 # pkg - Name of the package (no version number).
|
jpayne@68
|
425 # version - Version number for the package, such as 2.1.3.
|
jpayne@68
|
426 # files - List of files that constitute the package. Each
|
jpayne@68
|
427 # element is a sub-list with three elements. The first
|
jpayne@68
|
428 # is the name of a file relative to $dir, the second is
|
jpayne@68
|
429 # "load" or "source", indicating whether the file is a
|
jpayne@68
|
430 # loadable binary or a script to source, and the third
|
jpayne@68
|
431 # is a list of commands defined by this file.
|
jpayne@68
|
432
|
jpayne@68
|
433 proc tclPkgSetup {dir pkg version files} {
|
jpayne@68
|
434 global auto_index
|
jpayne@68
|
435
|
jpayne@68
|
436 package provide $pkg $version
|
jpayne@68
|
437 foreach fileInfo $files {
|
jpayne@68
|
438 set f [lindex $fileInfo 0]
|
jpayne@68
|
439 set type [lindex $fileInfo 1]
|
jpayne@68
|
440 foreach cmd [lindex $fileInfo 2] {
|
jpayne@68
|
441 if {$type eq "load"} {
|
jpayne@68
|
442 set auto_index($cmd) [list load [file join $dir $f] $pkg]
|
jpayne@68
|
443 } else {
|
jpayne@68
|
444 set auto_index($cmd) [list source [file join $dir $f]]
|
jpayne@68
|
445 }
|
jpayne@68
|
446 }
|
jpayne@68
|
447 }
|
jpayne@68
|
448 }
|
jpayne@68
|
449
|
jpayne@68
|
450 # tclPkgUnknown --
|
jpayne@68
|
451 # This procedure provides the default for the "package unknown" function. It
|
jpayne@68
|
452 # is invoked when a package that's needed can't be found. It scans the
|
jpayne@68
|
453 # auto_path directories and their immediate children looking for pkgIndex.tcl
|
jpayne@68
|
454 # files and sources any such files that are found to setup the package
|
jpayne@68
|
455 # database. As it searches, it will recognize changes to the auto_path and
|
jpayne@68
|
456 # scan any new directories.
|
jpayne@68
|
457 #
|
jpayne@68
|
458 # Arguments:
|
jpayne@68
|
459 # name - Name of desired package. Not used.
|
jpayne@68
|
460 # version - Version of desired package. Not used.
|
jpayne@68
|
461 # exact - Either "-exact" or omitted. Not used.
|
jpayne@68
|
462
|
jpayne@68
|
463 proc tclPkgUnknown {name args} {
|
jpayne@68
|
464 global auto_path env
|
jpayne@68
|
465
|
jpayne@68
|
466 if {![info exists auto_path]} {
|
jpayne@68
|
467 return
|
jpayne@68
|
468 }
|
jpayne@68
|
469 # Cache the auto_path, because it may change while we run through the
|
jpayne@68
|
470 # first set of pkgIndex.tcl files
|
jpayne@68
|
471 set old_path [set use_path $auto_path]
|
jpayne@68
|
472 while {[llength $use_path]} {
|
jpayne@68
|
473 set dir [lindex $use_path end]
|
jpayne@68
|
474
|
jpayne@68
|
475 # Make sure we only scan each directory one time.
|
jpayne@68
|
476 if {[info exists tclSeenPath($dir)]} {
|
jpayne@68
|
477 set use_path [lrange $use_path 0 end-1]
|
jpayne@68
|
478 continue
|
jpayne@68
|
479 }
|
jpayne@68
|
480 set tclSeenPath($dir) 1
|
jpayne@68
|
481
|
jpayne@68
|
482 # Get the pkgIndex.tcl files in subdirectories of auto_path directories.
|
jpayne@68
|
483 # - Safe Base interpreters have a restricted "glob" command that
|
jpayne@68
|
484 # works in this case.
|
jpayne@68
|
485 # - The "catch" was essential when there was no safe glob and every
|
jpayne@68
|
486 # call in a safe interp failed; it is retained only for corner
|
jpayne@68
|
487 # cases in which the eventual call to glob returns an error.
|
jpayne@68
|
488 catch {
|
jpayne@68
|
489 foreach file [glob -directory $dir -join -nocomplain \
|
jpayne@68
|
490 * pkgIndex.tcl] {
|
jpayne@68
|
491 set dir [file dirname $file]
|
jpayne@68
|
492 if {![info exists procdDirs($dir)]} {
|
jpayne@68
|
493 try {
|
jpayne@68
|
494 source $file
|
jpayne@68
|
495 } trap {POSIX EACCES} {} {
|
jpayne@68
|
496 # $file was not readable; silently ignore
|
jpayne@68
|
497 continue
|
jpayne@68
|
498 } on error msg {
|
jpayne@68
|
499 tclLog "error reading package index file $file: $msg"
|
jpayne@68
|
500 } on ok {} {
|
jpayne@68
|
501 set procdDirs($dir) 1
|
jpayne@68
|
502 }
|
jpayne@68
|
503 }
|
jpayne@68
|
504 }
|
jpayne@68
|
505 }
|
jpayne@68
|
506 set dir [lindex $use_path end]
|
jpayne@68
|
507 if {![info exists procdDirs($dir)]} {
|
jpayne@68
|
508 set file [file join $dir pkgIndex.tcl]
|
jpayne@68
|
509 # safe interps usually don't have "file exists",
|
jpayne@68
|
510 if {([interp issafe] || [file exists $file])} {
|
jpayne@68
|
511 try {
|
jpayne@68
|
512 source $file
|
jpayne@68
|
513 } trap {POSIX EACCES} {} {
|
jpayne@68
|
514 # $file was not readable; silently ignore
|
jpayne@68
|
515 continue
|
jpayne@68
|
516 } on error msg {
|
jpayne@68
|
517 tclLog "error reading package index file $file: $msg"
|
jpayne@68
|
518 } on ok {} {
|
jpayne@68
|
519 set procdDirs($dir) 1
|
jpayne@68
|
520 }
|
jpayne@68
|
521 }
|
jpayne@68
|
522 }
|
jpayne@68
|
523
|
jpayne@68
|
524 set use_path [lrange $use_path 0 end-1]
|
jpayne@68
|
525
|
jpayne@68
|
526 # Check whether any of the index scripts we [source]d above set a new
|
jpayne@68
|
527 # value for $::auto_path. If so, then find any new directories on the
|
jpayne@68
|
528 # $::auto_path, and lappend them to the $use_path we are working from.
|
jpayne@68
|
529 # This gives index scripts the (arguably unwise) power to expand the
|
jpayne@68
|
530 # index script search path while the search is in progress.
|
jpayne@68
|
531 set index 0
|
jpayne@68
|
532 if {[llength $old_path] == [llength $auto_path]} {
|
jpayne@68
|
533 foreach dir $auto_path old $old_path {
|
jpayne@68
|
534 if {$dir ne $old} {
|
jpayne@68
|
535 # This entry in $::auto_path has changed.
|
jpayne@68
|
536 break
|
jpayne@68
|
537 }
|
jpayne@68
|
538 incr index
|
jpayne@68
|
539 }
|
jpayne@68
|
540 }
|
jpayne@68
|
541
|
jpayne@68
|
542 # $index now points to the first element of $auto_path that has
|
jpayne@68
|
543 # changed, or the beginning if $auto_path has changed length Scan the
|
jpayne@68
|
544 # new elements of $auto_path for directories to add to $use_path.
|
jpayne@68
|
545 # Don't add directories we've already seen, or ones already on the
|
jpayne@68
|
546 # $use_path.
|
jpayne@68
|
547 foreach dir [lrange $auto_path $index end] {
|
jpayne@68
|
548 if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} {
|
jpayne@68
|
549 lappend use_path $dir
|
jpayne@68
|
550 }
|
jpayne@68
|
551 }
|
jpayne@68
|
552 set old_path $auto_path
|
jpayne@68
|
553 }
|
jpayne@68
|
554 }
|
jpayne@68
|
555
|
jpayne@68
|
556 # tcl::MacOSXPkgUnknown --
|
jpayne@68
|
557 # This procedure extends the "package unknown" function for MacOSX. It scans
|
jpayne@68
|
558 # the Resources/Scripts directories of the immediate children of the auto_path
|
jpayne@68
|
559 # directories for pkgIndex files.
|
jpayne@68
|
560 #
|
jpayne@68
|
561 # Arguments:
|
jpayne@68
|
562 # original - original [package unknown] procedure
|
jpayne@68
|
563 # name - Name of desired package. Not used.
|
jpayne@68
|
564 # version - Version of desired package. Not used.
|
jpayne@68
|
565 # exact - Either "-exact" or omitted. Not used.
|
jpayne@68
|
566
|
jpayne@68
|
567 proc tcl::MacOSXPkgUnknown {original name args} {
|
jpayne@68
|
568 # First do the cross-platform default search
|
jpayne@68
|
569 uplevel 1 $original [linsert $args 0 $name]
|
jpayne@68
|
570
|
jpayne@68
|
571 # Now do MacOSX specific searching
|
jpayne@68
|
572 global auto_path
|
jpayne@68
|
573
|
jpayne@68
|
574 if {![info exists auto_path]} {
|
jpayne@68
|
575 return
|
jpayne@68
|
576 }
|
jpayne@68
|
577 # Cache the auto_path, because it may change while we run through the
|
jpayne@68
|
578 # first set of pkgIndex.tcl files
|
jpayne@68
|
579 set old_path [set use_path $auto_path]
|
jpayne@68
|
580 while {[llength $use_path]} {
|
jpayne@68
|
581 set dir [lindex $use_path end]
|
jpayne@68
|
582
|
jpayne@68
|
583 # Make sure we only scan each directory one time.
|
jpayne@68
|
584 if {[info exists tclSeenPath($dir)]} {
|
jpayne@68
|
585 set use_path [lrange $use_path 0 end-1]
|
jpayne@68
|
586 continue
|
jpayne@68
|
587 }
|
jpayne@68
|
588 set tclSeenPath($dir) 1
|
jpayne@68
|
589
|
jpayne@68
|
590 # get the pkgIndex files out of the subdirectories
|
jpayne@68
|
591 # Safe interpreters do not use tcl::MacOSXPkgUnknown - see init.tcl.
|
jpayne@68
|
592 foreach file [glob -directory $dir -join -nocomplain \
|
jpayne@68
|
593 * Resources Scripts pkgIndex.tcl] {
|
jpayne@68
|
594 set dir [file dirname $file]
|
jpayne@68
|
595 if {![info exists procdDirs($dir)]} {
|
jpayne@68
|
596 try {
|
jpayne@68
|
597 source $file
|
jpayne@68
|
598 } trap {POSIX EACCES} {} {
|
jpayne@68
|
599 # $file was not readable; silently ignore
|
jpayne@68
|
600 continue
|
jpayne@68
|
601 } on error msg {
|
jpayne@68
|
602 tclLog "error reading package index file $file: $msg"
|
jpayne@68
|
603 } on ok {} {
|
jpayne@68
|
604 set procdDirs($dir) 1
|
jpayne@68
|
605 }
|
jpayne@68
|
606 }
|
jpayne@68
|
607 }
|
jpayne@68
|
608 set use_path [lrange $use_path 0 end-1]
|
jpayne@68
|
609
|
jpayne@68
|
610 # Check whether any of the index scripts we [source]d above set a new
|
jpayne@68
|
611 # value for $::auto_path. If so, then find any new directories on the
|
jpayne@68
|
612 # $::auto_path, and lappend them to the $use_path we are working from.
|
jpayne@68
|
613 # This gives index scripts the (arguably unwise) power to expand the
|
jpayne@68
|
614 # index script search path while the search is in progress.
|
jpayne@68
|
615 set index 0
|
jpayne@68
|
616 if {[llength $old_path] == [llength $auto_path]} {
|
jpayne@68
|
617 foreach dir $auto_path old $old_path {
|
jpayne@68
|
618 if {$dir ne $old} {
|
jpayne@68
|
619 # This entry in $::auto_path has changed.
|
jpayne@68
|
620 break
|
jpayne@68
|
621 }
|
jpayne@68
|
622 incr index
|
jpayne@68
|
623 }
|
jpayne@68
|
624 }
|
jpayne@68
|
625
|
jpayne@68
|
626 # $index now points to the first element of $auto_path that has
|
jpayne@68
|
627 # changed, or the beginning if $auto_path has changed length Scan the
|
jpayne@68
|
628 # new elements of $auto_path for directories to add to $use_path.
|
jpayne@68
|
629 # Don't add directories we've already seen, or ones already on the
|
jpayne@68
|
630 # $use_path.
|
jpayne@68
|
631 foreach dir [lrange $auto_path $index end] {
|
jpayne@68
|
632 if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} {
|
jpayne@68
|
633 lappend use_path $dir
|
jpayne@68
|
634 }
|
jpayne@68
|
635 }
|
jpayne@68
|
636 set old_path $auto_path
|
jpayne@68
|
637 }
|
jpayne@68
|
638 }
|
jpayne@68
|
639
|
jpayne@68
|
640 # ::tcl::Pkg::Create --
|
jpayne@68
|
641 #
|
jpayne@68
|
642 # Given a package specification generate a "package ifneeded" statement
|
jpayne@68
|
643 # for the package, suitable for inclusion in a pkgIndex.tcl file.
|
jpayne@68
|
644 #
|
jpayne@68
|
645 # Arguments:
|
jpayne@68
|
646 # args arguments used by the Create function:
|
jpayne@68
|
647 # -name packageName
|
jpayne@68
|
648 # -version packageVersion
|
jpayne@68
|
649 # -load {filename ?{procs}?}
|
jpayne@68
|
650 # ...
|
jpayne@68
|
651 # -source {filename ?{procs}?}
|
jpayne@68
|
652 # ...
|
jpayne@68
|
653 #
|
jpayne@68
|
654 # Any number of -load and -source parameters may be
|
jpayne@68
|
655 # specified, so long as there is at least one -load or
|
jpayne@68
|
656 # -source parameter. If the procs component of a module
|
jpayne@68
|
657 # specifier is left off, that module will be set up for
|
jpayne@68
|
658 # direct loading; otherwise, it will be set up for lazy
|
jpayne@68
|
659 # loading. If both -source and -load are specified, the
|
jpayne@68
|
660 # -load'ed files will be loaded first, followed by the
|
jpayne@68
|
661 # -source'd files.
|
jpayne@68
|
662 #
|
jpayne@68
|
663 # Results:
|
jpayne@68
|
664 # An appropriate "package ifneeded" statement for the package.
|
jpayne@68
|
665
|
jpayne@68
|
666 proc ::tcl::Pkg::Create {args} {
|
jpayne@68
|
667 append err(usage) "[lindex [info level 0] 0] "
|
jpayne@68
|
668 append err(usage) "-name packageName -version packageVersion"
|
jpayne@68
|
669 append err(usage) "?-load {filename ?{procs}?}? ... "
|
jpayne@68
|
670 append err(usage) "?-source {filename ?{procs}?}? ..."
|
jpayne@68
|
671
|
jpayne@68
|
672 set err(wrongNumArgs) "wrong # args: should be \"$err(usage)\""
|
jpayne@68
|
673 set err(valueMissing) "value for \"%s\" missing: should be \"$err(usage)\""
|
jpayne@68
|
674 set err(unknownOpt) "unknown option \"%s\": should be \"$err(usage)\""
|
jpayne@68
|
675 set err(noLoadOrSource) "at least one of -load and -source must be given"
|
jpayne@68
|
676
|
jpayne@68
|
677 # process arguments
|
jpayne@68
|
678 set len [llength $args]
|
jpayne@68
|
679 if {$len < 6} {
|
jpayne@68
|
680 error $err(wrongNumArgs)
|
jpayne@68
|
681 }
|
jpayne@68
|
682
|
jpayne@68
|
683 # Initialize parameters
|
jpayne@68
|
684 array set opts {-name {} -version {} -source {} -load {}}
|
jpayne@68
|
685
|
jpayne@68
|
686 # process parameters
|
jpayne@68
|
687 for {set i 0} {$i < $len} {incr i} {
|
jpayne@68
|
688 set flag [lindex $args $i]
|
jpayne@68
|
689 incr i
|
jpayne@68
|
690 switch -glob -- $flag {
|
jpayne@68
|
691 "-name" -
|
jpayne@68
|
692 "-version" {
|
jpayne@68
|
693 if {$i >= $len} {
|
jpayne@68
|
694 error [format $err(valueMissing) $flag]
|
jpayne@68
|
695 }
|
jpayne@68
|
696 set opts($flag) [lindex $args $i]
|
jpayne@68
|
697 }
|
jpayne@68
|
698 "-source" -
|
jpayne@68
|
699 "-load" {
|
jpayne@68
|
700 if {$i >= $len} {
|
jpayne@68
|
701 error [format $err(valueMissing) $flag]
|
jpayne@68
|
702 }
|
jpayne@68
|
703 lappend opts($flag) [lindex $args $i]
|
jpayne@68
|
704 }
|
jpayne@68
|
705 default {
|
jpayne@68
|
706 error [format $err(unknownOpt) [lindex $args $i]]
|
jpayne@68
|
707 }
|
jpayne@68
|
708 }
|
jpayne@68
|
709 }
|
jpayne@68
|
710
|
jpayne@68
|
711 # Validate the parameters
|
jpayne@68
|
712 if {![llength $opts(-name)]} {
|
jpayne@68
|
713 error [format $err(valueMissing) "-name"]
|
jpayne@68
|
714 }
|
jpayne@68
|
715 if {![llength $opts(-version)]} {
|
jpayne@68
|
716 error [format $err(valueMissing) "-version"]
|
jpayne@68
|
717 }
|
jpayne@68
|
718
|
jpayne@68
|
719 if {!([llength $opts(-source)] || [llength $opts(-load)])} {
|
jpayne@68
|
720 error $err(noLoadOrSource)
|
jpayne@68
|
721 }
|
jpayne@68
|
722
|
jpayne@68
|
723 # OK, now everything is good. Generate the package ifneeded statment.
|
jpayne@68
|
724 set cmdline "package ifneeded $opts(-name) $opts(-version) "
|
jpayne@68
|
725
|
jpayne@68
|
726 set cmdList {}
|
jpayne@68
|
727 set lazyFileList {}
|
jpayne@68
|
728
|
jpayne@68
|
729 # Handle -load and -source specs
|
jpayne@68
|
730 foreach key {load source} {
|
jpayne@68
|
731 foreach filespec $opts(-$key) {
|
jpayne@68
|
732 lassign $filespec filename proclist
|
jpayne@68
|
733
|
jpayne@68
|
734 if { [llength $proclist] == 0 } {
|
jpayne@68
|
735 set cmd "\[list $key \[file join \$dir [list $filename]\]\]"
|
jpayne@68
|
736 lappend cmdList $cmd
|
jpayne@68
|
737 } else {
|
jpayne@68
|
738 lappend lazyFileList [list $filename $key $proclist]
|
jpayne@68
|
739 }
|
jpayne@68
|
740 }
|
jpayne@68
|
741 }
|
jpayne@68
|
742
|
jpayne@68
|
743 if {[llength $lazyFileList]} {
|
jpayne@68
|
744 lappend cmdList "\[list tclPkgSetup \$dir $opts(-name)\
|
jpayne@68
|
745 $opts(-version) [list $lazyFileList]\]"
|
jpayne@68
|
746 }
|
jpayne@68
|
747 append cmdline [join $cmdList "\\n"]
|
jpayne@68
|
748 return $cmdline
|
jpayne@68
|
749 }
|
jpayne@68
|
750
|
jpayne@68
|
751 interp alias {} ::pkg::create {} ::tcl::Pkg::Create
|