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