# package.tcl -- # # utility procs formerly in init.tcl which can be loaded on demand # for package management. # # SCCS: @(#) package.tcl 1.5 98/01/28 17:07:30 # # Copyright (c) 1991-1993 The Regents of the University of California. # Copyright (c) 1994-1998 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # # pkg_mkIndex -- # This procedure creates a package index in a given directory. The # package index consists of a "pkgIndex.tcl" file whose contents are # a Tcl script that sets up package information with "package require" # commands. The commands describe all of the packages defined by the # files given as arguments. # # Arguments: # dir - Name of the directory in which to create the index. # args - Any number of additional arguments, each giving # a glob pattern that matches the names of one or # more shared libraries or Tcl script files in # dir. proc pkg_mkIndex {args} { global errorCode errorInfo set first [lindex $args 0] set direct [string match "-d*" $first] set more "" if {$direct} { set args [lrange $args 1 end] set more " -direct" } if {[llength $args] == 0} { return -code error "wrong # args: should be\ \"pkg_mkIndex ?-direct? dir ?pattern ...?\""; } set dir [lindex $args 0] set patternList [lrange $args 1 end] if {[llength $patternList] == 0} { set patternList [list "*.tcl" "*[info sharedlibextension]"] } append index "# Tcl package index file, version 1.1\n" append index "# This file is generated by the \"pkg_mkIndex$more\" command\n" append index "# and sourced either when an application starts up or\n" append index "# by a \"package unknown\" script. It invokes the\n" append index "# \"package ifneeded\" command to set up package-related\n" append index "# information so that packages will be loaded automatically\n" append index "# in response to \"package require\" commands. When this\n" append index "# script is sourced, the variable \$dir must contain the\n" append index "# full path name of this file's directory.\n" set oldDir [pwd] cd $dir foreach file [eval glob $patternList] { # For each file, figure out what commands and packages it provides. # To do this, create a child interpreter, load the file into the # interpreter, and get a list of the new commands and packages # that are defined. Define an empty "package unknown" script so # that there are no recursive package inclusions. set c [interp create] # If Tk is loaded in the parent interpreter, load it into the # child also, in case the extension depends on it. foreach pkg [info loaded] { if {[lindex $pkg 1] == "Tk"} { $c eval {set argv {-geometry +0+0}} load [lindex $pkg 0] Tk $c break } } $c eval [list set __file $file] $c eval [list set __direct $direct] if {[catch { $c eval { proc __dummy args {} rename package __package_orig proc package {what args} { switch -- $what { require { return ; # ignore transitive requires } default { eval __package_orig [list $what] $args } } } if {!$__direct} { proc __pkgGetAllNamespaces {{root {}}} { set list $root foreach ns [namespace children $root] { eval lappend list [__pkgGetAllNamespaces $ns] } return $list } set __origCmds [info commands] } package unknown __dummy set dir "" ;# in case file is pkgIndex.tcl # Try to load the file if it has the shared library extension, # otherwise source it. It's important not to try to load # files that aren't shared libraries, because on some systems # (like SunOS) the loader will abort the whole application # when it gets an error. if {[string compare [file extension $__file] \ [info sharedlibextension]] == 0} { # The "file join ." command below is necessary. Without # it, if the file name has no \'s and we're on UNIX, the # load command will invoke the LD_LIBRARY_PATH search # mechanism, which could cause the wrong file to be used. if {[catch {load [file join . $__file]} __msg]} { tclLog "warning: error while loading $__file: $__msg" } set __type load } else { if {[catch {source $__file} __msg]} { tclLog "warning: error while sourcing $__file: $__msg" } set __type source } # Using __ variable names to avoid potential namespaces # clash, even here in post processing because the # loaded package could have set up traces,... if {!$__direct} { foreach __ns [__pkgGetAllNamespaces] { namespace import ${__ns}::* } foreach __i [info commands] { set __cmds($__i) 1 } foreach __i $__origCmds { catch {unset __cmds($__i)} } foreach __i [array names __cmds] { # reverse engineer which namespace a command comes from set __absolute [namespace origin $__i] if {[string compare ::$__i $__absolute] != 0} { set __cmds($__absolute) 1 unset __cmds($__i) } } } set __pkgs {} foreach __i [package names] { if {([string compare [package provide $__i] ""] != 0) && ([string compare $__i Tcl] != 0) && ([string compare $__i Tk] != 0)} { lappend __pkgs [list $__i [package provide $__i]] } } } } msg]} { tclLog "error while loading or sourcing $file: $msg" } set type [$c eval set __type] set cmds [lsort [$c eval array names __cmds]] set pkgs [$c eval set __pkgs] if {[llength $pkgs] > 1} { tclLog "warning: \"$file\" provides more than one package ($pkgs)" } foreach pkg $pkgs { # cmds is empty/not used in the direct case lappend files($pkg) [list $file $type $cmds] } interp delete $c } foreach pkg [lsort [array names files]] { append index "\npackage ifneeded $pkg " if {$direct} { set cmdList {} foreach elem $files($pkg) { set file [lindex $elem 0] set type [lindex $elem 1] lappend cmdList "\[list $type \[file join \$dir\ [list $file]\]\]" } append index [join $cmdList "\\n"] } else { append index "\[list tclPkgSetup \$dir [lrange $pkg 0 0]\ [lrange $pkg 1 1] [list $files($pkg)]\]" } } set f [open pkgIndex.tcl w] puts $f $index close $f cd $oldDir } # tclPkgSetup -- # This is a utility procedure use by pkgIndex.tcl files. It is invoked # as part of a "package ifneeded" script. It calls "package provide" # to indicate that a package is available, then sets entries in the # auto_index array so that the package's files will be auto-loaded when # the commands are used. # # Arguments: # dir - Directory containing all the files for this package. # pkg - Name of the package (no version number). # version - Version number for the package, such as 2.1.3. # files - List of files that constitute the package. Each # element is a sub-list with three elements. The first # is the name of a file relative to $dir, the second is # "load" or "source", indicating whether the file is a # loadable binary or a script to source, and the third # is a list of commands defined by this file. proc tclPkgSetup {dir pkg version files} { global auto_index package provide $pkg $version foreach fileInfo $files { set f [lindex $fileInfo 0] set type [lindex $fileInfo 1] foreach cmd [lindex $fileInfo 2] { if {$type == "load"} { set auto_index($cmd) [list load [file join $dir $f] $pkg] } else { set auto_index($cmd) [list source [file join $dir $f]] } } } } # tclMacPkgSearch -- # The procedure is used on the Macintosh to search a given directory for files # with a TEXT resource named "pkgIndex". If it exists it is sourced in to the # interpreter to setup the package database. proc tclMacPkgSearch {dir} { foreach x [glob -nocomplain [file join $dir *.shlb]] { if {[file isfile $x]} { set res [resource open $x] foreach y [resource list TEXT $res] { if {$y == "pkgIndex"} {source -rsrc pkgIndex} } catch {resource close $res} } } } # tclPkgUnknown -- # This procedure provides the default for the "package unknown" function. # It is invoked when a package that's needed can't be found. It scans # the auto_path directories and their immediate children looking for # pkgIndex.tcl files and sources any such files that are found to setup # the package database. (On the Macintosh we also search for pkgIndex # TEXT resources in all files.) # # Arguments: # name - Name of desired package. Not used. # version - Version of desired package. Not used. # exact - Either "-exact" or omitted. Not used. proc tclPkgUnknown {name version {exact {}}} { global auto_path tcl_platform env if {![info exists auto_path]} { return } for {set i [expr {[llength $auto_path] - 1}]} {$i >= 0} {incr i -1} { # we can't use glob in safe interps, so enclose the following # in a catch statement catch { foreach file [glob -nocomplain [file join [lindex $auto_path $i] \ * pkgIndex.tcl]] { set dir [file dirname $file] if {[catch {source $file} msg]} { tclLog "error reading package index file $file: $msg" } } } set dir [lindex $auto_path $i] set file [file join $dir pkgIndex.tcl] # safe interps usually don't have "file readable", nor stderr channel if {[interp issafe] || [file readable $file]} { if {[catch {source $file} msg] && ![interp issafe]} { tclLog "error reading package index file $file: $msg" } } # On the Macintosh we also look in the resource fork # of shared libraries # We can't use tclMacPkgSearch in safe interps because it uses glob if {(![interp issafe]) && ($tcl_platform(platform) == "macintosh")} { set dir [lindex $auto_path $i] tclMacPkgSearch $dir foreach x [glob -nocomplain [file join $dir *]] { if {[file isdirectory $x]} { set dir $x tclMacPkgSearch $dir } } } } }