jpayne@69: # package.tcl -- jpayne@69: # jpayne@69: # utility procs formerly in init.tcl which can be loaded on demand jpayne@69: # for package management. jpayne@69: # jpayne@69: # Copyright (c) 1991-1993 The Regents of the University of California. jpayne@69: # Copyright (c) 1994-1998 Sun Microsystems, Inc. jpayne@69: # jpayne@69: # See the file "license.terms" for information on usage and redistribution jpayne@69: # of this file, and for a DISCLAIMER OF ALL WARRANTIES. jpayne@69: # jpayne@69: jpayne@69: namespace eval tcl::Pkg {} jpayne@69: jpayne@69: # ::tcl::Pkg::CompareExtension -- jpayne@69: # jpayne@69: # Used internally by pkg_mkIndex to compare the extension of a file to a given jpayne@69: # extension. On Windows, it uses a case-insensitive comparison because the jpayne@69: # file system can be file insensitive. jpayne@69: # jpayne@69: # Arguments: jpayne@69: # fileName name of a file whose extension is compared jpayne@69: # ext (optional) The extension to compare against; you must jpayne@69: # provide the starting dot. jpayne@69: # Defaults to [info sharedlibextension] jpayne@69: # jpayne@69: # Results: jpayne@69: # Returns 1 if the extension matches, 0 otherwise jpayne@69: jpayne@69: proc tcl::Pkg::CompareExtension {fileName {ext {}}} { jpayne@69: global tcl_platform jpayne@69: if {$ext eq ""} {set ext [info sharedlibextension]} jpayne@69: if {$tcl_platform(platform) eq "windows"} { jpayne@69: return [string equal -nocase [file extension $fileName] $ext] jpayne@69: } else { jpayne@69: # Some unices add trailing numbers after the .so, so jpayne@69: # we could have something like '.so.1.2'. jpayne@69: set root $fileName jpayne@69: while {1} { jpayne@69: set currExt [file extension $root] jpayne@69: if {$currExt eq $ext} { jpayne@69: return 1 jpayne@69: } jpayne@69: jpayne@69: # The current extension does not match; if it is not a numeric jpayne@69: # value, quit, as we are only looking to ignore version number jpayne@69: # extensions. Otherwise we might return 1 in this case: jpayne@69: # tcl::Pkg::CompareExtension foo.so.bar .so jpayne@69: # which should not match. jpayne@69: jpayne@69: if {![string is integer -strict [string range $currExt 1 end]]} { jpayne@69: return 0 jpayne@69: } jpayne@69: set root [file rootname $root] jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: # pkg_mkIndex -- jpayne@69: # This procedure creates a package index in a given directory. The package jpayne@69: # index consists of a "pkgIndex.tcl" file whose contents are a Tcl script that jpayne@69: # sets up package information with "package require" commands. The commands jpayne@69: # describe all of the packages defined by the files given as arguments. jpayne@69: # jpayne@69: # Arguments: jpayne@69: # -direct (optional) If this flag is present, the generated jpayne@69: # code in pkgMkIndex.tcl will cause the package to be jpayne@69: # loaded when "package require" is executed, rather jpayne@69: # than lazily when the first reference to an exported jpayne@69: # procedure in the package is made. jpayne@69: # -verbose (optional) Verbose output; the name of each file that jpayne@69: # was successfully rocessed is printed out. Additionally, jpayne@69: # if processing of a file failed a message is printed. jpayne@69: # -load pat (optional) Preload any packages whose names match jpayne@69: # the pattern. Used to handle DLLs that depend on jpayne@69: # other packages during their Init procedure. jpayne@69: # dir - Name of the directory in which to create the index. jpayne@69: # args - Any number of additional arguments, each giving jpayne@69: # a glob pattern that matches the names of one or jpayne@69: # more shared libraries or Tcl script files in jpayne@69: # dir. jpayne@69: jpayne@69: proc pkg_mkIndex {args} { jpayne@69: set usage {"pkg_mkIndex ?-direct? ?-lazy? ?-load pattern? ?-verbose? ?--? dir ?pattern ...?"} jpayne@69: jpayne@69: set argCount [llength $args] jpayne@69: if {$argCount < 1} { jpayne@69: return -code error "wrong # args: should be\n$usage" jpayne@69: } jpayne@69: jpayne@69: set more "" jpayne@69: set direct 1 jpayne@69: set doVerbose 0 jpayne@69: set loadPat "" jpayne@69: for {set idx 0} {$idx < $argCount} {incr idx} { jpayne@69: set flag [lindex $args $idx] jpayne@69: switch -glob -- $flag { jpayne@69: -- { jpayne@69: # done with the flags jpayne@69: incr idx jpayne@69: break jpayne@69: } jpayne@69: -verbose { jpayne@69: set doVerbose 1 jpayne@69: } jpayne@69: -lazy { jpayne@69: set direct 0 jpayne@69: append more " -lazy" jpayne@69: } jpayne@69: -direct { jpayne@69: append more " -direct" jpayne@69: } jpayne@69: -load { jpayne@69: incr idx jpayne@69: set loadPat [lindex $args $idx] jpayne@69: append more " -load $loadPat" jpayne@69: } jpayne@69: -* { jpayne@69: return -code error "unknown flag $flag: should be\n$usage" jpayne@69: } jpayne@69: default { jpayne@69: # done with the flags jpayne@69: break jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: set dir [lindex $args $idx] jpayne@69: set patternList [lrange $args [expr {$idx + 1}] end] jpayne@69: if {![llength $patternList]} { jpayne@69: set patternList [list "*.tcl" "*[info sharedlibextension]"] jpayne@69: } jpayne@69: jpayne@69: try { jpayne@69: set fileList [glob -directory $dir -tails -types {r f} -- \ jpayne@69: {*}$patternList] jpayne@69: } on error {msg opt} { jpayne@69: return -options $opt $msg jpayne@69: } jpayne@69: foreach file $fileList { jpayne@69: # For each file, figure out what commands and packages it provides. jpayne@69: # To do this, create a child interpreter, load the file into the jpayne@69: # interpreter, and get a list of the new commands and packages that jpayne@69: # are defined. jpayne@69: jpayne@69: if {$file eq "pkgIndex.tcl"} { jpayne@69: continue jpayne@69: } jpayne@69: jpayne@69: set c [interp create] jpayne@69: jpayne@69: # Load into the child any packages currently loaded in the parent jpayne@69: # interpreter that match the -load pattern. jpayne@69: jpayne@69: if {$loadPat ne ""} { jpayne@69: if {$doVerbose} { jpayne@69: tclLog "currently loaded packages: '[info loaded]'" jpayne@69: tclLog "trying to load all packages matching $loadPat" jpayne@69: } jpayne@69: if {![llength [info loaded]]} { jpayne@69: tclLog "warning: no packages are currently loaded, nothing" jpayne@69: tclLog "can possibly match '$loadPat'" jpayne@69: } jpayne@69: } jpayne@69: foreach pkg [info loaded] { jpayne@69: if {![string match -nocase $loadPat [lindex $pkg 1]]} { jpayne@69: continue jpayne@69: } jpayne@69: if {$doVerbose} { jpayne@69: tclLog "package [lindex $pkg 1] matches '$loadPat'" jpayne@69: } jpayne@69: try { jpayne@69: load [lindex $pkg 0] [lindex $pkg 1] $c jpayne@69: } on error err { jpayne@69: if {$doVerbose} { jpayne@69: tclLog "warning: load [lindex $pkg 0]\ jpayne@69: [lindex $pkg 1]\nfailed with: $err" jpayne@69: } jpayne@69: } on ok {} { jpayne@69: if {$doVerbose} { jpayne@69: tclLog "loaded [lindex $pkg 0] [lindex $pkg 1]" jpayne@69: } jpayne@69: } jpayne@69: if {[lindex $pkg 1] eq "Tk"} { jpayne@69: # Withdraw . if Tk was loaded, to avoid showing a window. jpayne@69: $c eval [list wm withdraw .] jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: $c eval { jpayne@69: # Stub out the package command so packages can require other jpayne@69: # packages. jpayne@69: jpayne@69: rename package __package_orig jpayne@69: proc package {what args} { jpayne@69: switch -- $what { jpayne@69: require { jpayne@69: return; # Ignore transitive requires jpayne@69: } jpayne@69: default { jpayne@69: __package_orig $what {*}$args jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: proc tclPkgUnknown args {} jpayne@69: package unknown tclPkgUnknown jpayne@69: jpayne@69: # Stub out the unknown command so package can call into each other jpayne@69: # during their initialilzation. jpayne@69: jpayne@69: proc unknown {args} {} jpayne@69: jpayne@69: # Stub out the auto_import mechanism jpayne@69: jpayne@69: proc auto_import {args} {} jpayne@69: jpayne@69: # reserve the ::tcl namespace for support procs and temporary jpayne@69: # variables. This might make it awkward to generate a jpayne@69: # pkgIndex.tcl file for the ::tcl namespace. jpayne@69: jpayne@69: namespace eval ::tcl { jpayne@69: variable dir ;# Current directory being processed jpayne@69: variable file ;# Current file being processed jpayne@69: variable direct ;# -direct flag value jpayne@69: variable x ;# Loop variable jpayne@69: variable debug ;# For debugging jpayne@69: variable type ;# "load" or "source", for -direct jpayne@69: variable namespaces ;# Existing namespaces (e.g., ::tcl) jpayne@69: variable packages ;# Existing packages (e.g., Tcl) jpayne@69: variable origCmds ;# Existing commands jpayne@69: variable newCmds ;# Newly created commands jpayne@69: variable newPkgs {} ;# Newly created packages jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: $c eval [list set ::tcl::dir $dir] jpayne@69: $c eval [list set ::tcl::file $file] jpayne@69: $c eval [list set ::tcl::direct $direct] jpayne@69: jpayne@69: # Download needed procedures into the child because we've just deleted jpayne@69: # the unknown procedure. This doesn't handle procedures with default jpayne@69: # arguments. jpayne@69: jpayne@69: foreach p {::tcl::Pkg::CompareExtension} { jpayne@69: $c eval [list namespace eval [namespace qualifiers $p] {}] jpayne@69: $c eval [list proc $p [info args $p] [info body $p]] jpayne@69: } jpayne@69: jpayne@69: try { jpayne@69: $c eval { jpayne@69: set ::tcl::debug "loading or sourcing" jpayne@69: jpayne@69: # we need to track command defined by each package even in the jpayne@69: # -direct case, because they are needed internally by the jpayne@69: # "partial pkgIndex.tcl" step above. jpayne@69: jpayne@69: proc ::tcl::GetAllNamespaces {{root ::}} { jpayne@69: set list $root jpayne@69: foreach ns [namespace children $root] { jpayne@69: lappend list {*}[::tcl::GetAllNamespaces $ns] jpayne@69: } jpayne@69: return $list jpayne@69: } jpayne@69: jpayne@69: # init the list of existing namespaces, packages, commands jpayne@69: jpayne@69: foreach ::tcl::x [::tcl::GetAllNamespaces] { jpayne@69: set ::tcl::namespaces($::tcl::x) 1 jpayne@69: } jpayne@69: foreach ::tcl::x [package names] { jpayne@69: if {[package provide $::tcl::x] ne ""} { jpayne@69: set ::tcl::packages($::tcl::x) 1 jpayne@69: } jpayne@69: } jpayne@69: set ::tcl::origCmds [info commands] jpayne@69: jpayne@69: # Try to load the file if it has the shared library extension, jpayne@69: # otherwise source it. It's important not to try to load jpayne@69: # files that aren't shared libraries, because on some systems jpayne@69: # (like SunOS) the loader will abort the whole application jpayne@69: # when it gets an error. jpayne@69: jpayne@69: if {[::tcl::Pkg::CompareExtension $::tcl::file [info sharedlibextension]]} { jpayne@69: # The "file join ." command below is necessary. Without jpayne@69: # it, if the file name has no \'s and we're on UNIX, the jpayne@69: # load command will invoke the LD_LIBRARY_PATH search jpayne@69: # mechanism, which could cause the wrong file to be used. jpayne@69: jpayne@69: set ::tcl::debug loading jpayne@69: load [file join $::tcl::dir $::tcl::file] jpayne@69: set ::tcl::type load jpayne@69: } else { jpayne@69: set ::tcl::debug sourcing jpayne@69: source [file join $::tcl::dir $::tcl::file] jpayne@69: set ::tcl::type source jpayne@69: } jpayne@69: jpayne@69: # As a performance optimization, if we are creating direct jpayne@69: # load packages, don't bother figuring out the set of commands jpayne@69: # created by the new packages. We only need that list for jpayne@69: # setting up the autoloading used in the non-direct case. jpayne@69: if {!$::tcl::direct} { jpayne@69: # See what new namespaces appeared, and import commands jpayne@69: # from them. Only exported commands go into the index. jpayne@69: jpayne@69: foreach ::tcl::x [::tcl::GetAllNamespaces] { jpayne@69: if {![info exists ::tcl::namespaces($::tcl::x)]} { jpayne@69: namespace import -force ${::tcl::x}::* jpayne@69: } jpayne@69: jpayne@69: # Figure out what commands appeared jpayne@69: jpayne@69: foreach ::tcl::x [info commands] { jpayne@69: set ::tcl::newCmds($::tcl::x) 1 jpayne@69: } jpayne@69: foreach ::tcl::x $::tcl::origCmds { jpayne@69: unset -nocomplain ::tcl::newCmds($::tcl::x) jpayne@69: } jpayne@69: foreach ::tcl::x [array names ::tcl::newCmds] { jpayne@69: # determine which namespace a command comes from jpayne@69: jpayne@69: set ::tcl::abs [namespace origin $::tcl::x] jpayne@69: jpayne@69: # special case so that global names have no jpayne@69: # leading ::, this is required by the unknown jpayne@69: # command jpayne@69: jpayne@69: set ::tcl::abs \ jpayne@69: [lindex [auto_qualify $::tcl::abs ::] 0] jpayne@69: jpayne@69: if {$::tcl::x ne $::tcl::abs} { jpayne@69: # Name changed during qualification jpayne@69: jpayne@69: set ::tcl::newCmds($::tcl::abs) 1 jpayne@69: unset ::tcl::newCmds($::tcl::x) jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: # Look through the packages that appeared, and if there is a jpayne@69: # version provided, then record it jpayne@69: jpayne@69: foreach ::tcl::x [package names] { jpayne@69: if {[package provide $::tcl::x] ne "" jpayne@69: && ![info exists ::tcl::packages($::tcl::x)]} { jpayne@69: lappend ::tcl::newPkgs \ jpayne@69: [list $::tcl::x [package provide $::tcl::x]] jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: } on error msg { jpayne@69: set what [$c eval set ::tcl::debug] jpayne@69: if {$doVerbose} { jpayne@69: tclLog "warning: error while $what $file: $msg" jpayne@69: } jpayne@69: } on ok {} { jpayne@69: set what [$c eval set ::tcl::debug] jpayne@69: if {$doVerbose} { jpayne@69: tclLog "successful $what of $file" jpayne@69: } jpayne@69: set type [$c eval set ::tcl::type] jpayne@69: set cmds [lsort [$c eval array names ::tcl::newCmds]] jpayne@69: set pkgs [$c eval set ::tcl::newPkgs] jpayne@69: if {$doVerbose} { jpayne@69: if {!$direct} { jpayne@69: tclLog "commands provided were $cmds" jpayne@69: } jpayne@69: tclLog "packages provided were $pkgs" jpayne@69: } jpayne@69: if {[llength $pkgs] > 1} { jpayne@69: tclLog "warning: \"$file\" provides more than one package ($pkgs)" jpayne@69: } jpayne@69: foreach pkg $pkgs { jpayne@69: # cmds is empty/not used in the direct case jpayne@69: lappend files($pkg) [list $file $type $cmds] jpayne@69: } jpayne@69: jpayne@69: if {$doVerbose} { jpayne@69: tclLog "processed $file" jpayne@69: } jpayne@69: } jpayne@69: interp delete $c jpayne@69: } jpayne@69: jpayne@69: append index "# Tcl package index file, version 1.1\n" jpayne@69: append index "# This file is generated by the \"pkg_mkIndex$more\" command\n" jpayne@69: append index "# and sourced either when an application starts up or\n" jpayne@69: append index "# by a \"package unknown\" script. It invokes the\n" jpayne@69: append index "# \"package ifneeded\" command to set up package-related\n" jpayne@69: append index "# information so that packages will be loaded automatically\n" jpayne@69: append index "# in response to \"package require\" commands. When this\n" jpayne@69: append index "# script is sourced, the variable \$dir must contain the\n" jpayne@69: append index "# full path name of this file's directory.\n" jpayne@69: jpayne@69: foreach pkg [lsort [array names files]] { jpayne@69: set cmd {} jpayne@69: lassign $pkg name version jpayne@69: lappend cmd ::tcl::Pkg::Create -name $name -version $version jpayne@69: foreach spec [lsort -index 0 $files($pkg)] { jpayne@69: foreach {file type procs} $spec { jpayne@69: if {$direct} { jpayne@69: set procs {} jpayne@69: } jpayne@69: lappend cmd "-$type" [list $file $procs] jpayne@69: } jpayne@69: } jpayne@69: append index "\n[eval $cmd]" jpayne@69: } jpayne@69: jpayne@69: set f [open [file join $dir pkgIndex.tcl] w] jpayne@69: puts $f $index jpayne@69: close $f jpayne@69: } jpayne@69: jpayne@69: # tclPkgSetup -- jpayne@69: # This is a utility procedure use by pkgIndex.tcl files. It is invoked as jpayne@69: # part of a "package ifneeded" script. It calls "package provide" to indicate jpayne@69: # that a package is available, then sets entries in the auto_index array so jpayne@69: # that the package's files will be auto-loaded when the commands are used. jpayne@69: # jpayne@69: # Arguments: jpayne@69: # dir - Directory containing all the files for this package. jpayne@69: # pkg - Name of the package (no version number). jpayne@69: # version - Version number for the package, such as 2.1.3. jpayne@69: # files - List of files that constitute the package. Each jpayne@69: # element is a sub-list with three elements. The first jpayne@69: # is the name of a file relative to $dir, the second is jpayne@69: # "load" or "source", indicating whether the file is a jpayne@69: # loadable binary or a script to source, and the third jpayne@69: # is a list of commands defined by this file. jpayne@69: jpayne@69: proc tclPkgSetup {dir pkg version files} { jpayne@69: global auto_index jpayne@69: jpayne@69: package provide $pkg $version jpayne@69: foreach fileInfo $files { jpayne@69: set f [lindex $fileInfo 0] jpayne@69: set type [lindex $fileInfo 1] jpayne@69: foreach cmd [lindex $fileInfo 2] { jpayne@69: if {$type eq "load"} { jpayne@69: set auto_index($cmd) [list load [file join $dir $f] $pkg] jpayne@69: } else { jpayne@69: set auto_index($cmd) [list source [file join $dir $f]] jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: # tclPkgUnknown -- jpayne@69: # This procedure provides the default for the "package unknown" function. It jpayne@69: # is invoked when a package that's needed can't be found. It scans the jpayne@69: # auto_path directories and their immediate children looking for pkgIndex.tcl jpayne@69: # files and sources any such files that are found to setup the package jpayne@69: # database. As it searches, it will recognize changes to the auto_path and jpayne@69: # scan any new directories. jpayne@69: # jpayne@69: # Arguments: jpayne@69: # name - Name of desired package. Not used. jpayne@69: # version - Version of desired package. Not used. jpayne@69: # exact - Either "-exact" or omitted. Not used. jpayne@69: jpayne@69: proc tclPkgUnknown {name args} { jpayne@69: global auto_path env jpayne@69: jpayne@69: if {![info exists auto_path]} { jpayne@69: return jpayne@69: } jpayne@69: # Cache the auto_path, because it may change while we run through the jpayne@69: # first set of pkgIndex.tcl files jpayne@69: set old_path [set use_path $auto_path] jpayne@69: while {[llength $use_path]} { jpayne@69: set dir [lindex $use_path end] jpayne@69: jpayne@69: # Make sure we only scan each directory one time. jpayne@69: if {[info exists tclSeenPath($dir)]} { jpayne@69: set use_path [lrange $use_path 0 end-1] jpayne@69: continue jpayne@69: } jpayne@69: set tclSeenPath($dir) 1 jpayne@69: jpayne@69: # Get the pkgIndex.tcl files in subdirectories of auto_path directories. jpayne@69: # - Safe Base interpreters have a restricted "glob" command that jpayne@69: # works in this case. jpayne@69: # - The "catch" was essential when there was no safe glob and every jpayne@69: # call in a safe interp failed; it is retained only for corner jpayne@69: # cases in which the eventual call to glob returns an error. jpayne@69: catch { jpayne@69: foreach file [glob -directory $dir -join -nocomplain \ jpayne@69: * pkgIndex.tcl] { jpayne@69: set dir [file dirname $file] jpayne@69: if {![info exists procdDirs($dir)]} { jpayne@69: try { jpayne@69: source $file jpayne@69: } trap {POSIX EACCES} {} { jpayne@69: # $file was not readable; silently ignore jpayne@69: continue jpayne@69: } on error msg { jpayne@69: tclLog "error reading package index file $file: $msg" jpayne@69: } on ok {} { jpayne@69: set procdDirs($dir) 1 jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: set dir [lindex $use_path end] jpayne@69: if {![info exists procdDirs($dir)]} { jpayne@69: set file [file join $dir pkgIndex.tcl] jpayne@69: # safe interps usually don't have "file exists", jpayne@69: if {([interp issafe] || [file exists $file])} { jpayne@69: try { jpayne@69: source $file jpayne@69: } trap {POSIX EACCES} {} { jpayne@69: # $file was not readable; silently ignore jpayne@69: continue jpayne@69: } on error msg { jpayne@69: tclLog "error reading package index file $file: $msg" jpayne@69: } on ok {} { jpayne@69: set procdDirs($dir) 1 jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: set use_path [lrange $use_path 0 end-1] jpayne@69: jpayne@69: # Check whether any of the index scripts we [source]d above set a new jpayne@69: # value for $::auto_path. If so, then find any new directories on the jpayne@69: # $::auto_path, and lappend them to the $use_path we are working from. jpayne@69: # This gives index scripts the (arguably unwise) power to expand the jpayne@69: # index script search path while the search is in progress. jpayne@69: set index 0 jpayne@69: if {[llength $old_path] == [llength $auto_path]} { jpayne@69: foreach dir $auto_path old $old_path { jpayne@69: if {$dir ne $old} { jpayne@69: # This entry in $::auto_path has changed. jpayne@69: break jpayne@69: } jpayne@69: incr index jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: # $index now points to the first element of $auto_path that has jpayne@69: # changed, or the beginning if $auto_path has changed length Scan the jpayne@69: # new elements of $auto_path for directories to add to $use_path. jpayne@69: # Don't add directories we've already seen, or ones already on the jpayne@69: # $use_path. jpayne@69: foreach dir [lrange $auto_path $index end] { jpayne@69: if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} { jpayne@69: lappend use_path $dir jpayne@69: } jpayne@69: } jpayne@69: set old_path $auto_path jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: # tcl::MacOSXPkgUnknown -- jpayne@69: # This procedure extends the "package unknown" function for MacOSX. It scans jpayne@69: # the Resources/Scripts directories of the immediate children of the auto_path jpayne@69: # directories for pkgIndex files. jpayne@69: # jpayne@69: # Arguments: jpayne@69: # original - original [package unknown] procedure jpayne@69: # name - Name of desired package. Not used. jpayne@69: # version - Version of desired package. Not used. jpayne@69: # exact - Either "-exact" or omitted. Not used. jpayne@69: jpayne@69: proc tcl::MacOSXPkgUnknown {original name args} { jpayne@69: # First do the cross-platform default search jpayne@69: uplevel 1 $original [linsert $args 0 $name] jpayne@69: jpayne@69: # Now do MacOSX specific searching jpayne@69: global auto_path jpayne@69: jpayne@69: if {![info exists auto_path]} { jpayne@69: return jpayne@69: } jpayne@69: # Cache the auto_path, because it may change while we run through the jpayne@69: # first set of pkgIndex.tcl files jpayne@69: set old_path [set use_path $auto_path] jpayne@69: while {[llength $use_path]} { jpayne@69: set dir [lindex $use_path end] jpayne@69: jpayne@69: # Make sure we only scan each directory one time. jpayne@69: if {[info exists tclSeenPath($dir)]} { jpayne@69: set use_path [lrange $use_path 0 end-1] jpayne@69: continue jpayne@69: } jpayne@69: set tclSeenPath($dir) 1 jpayne@69: jpayne@69: # get the pkgIndex files out of the subdirectories jpayne@69: # Safe interpreters do not use tcl::MacOSXPkgUnknown - see init.tcl. jpayne@69: foreach file [glob -directory $dir -join -nocomplain \ jpayne@69: * Resources Scripts pkgIndex.tcl] { jpayne@69: set dir [file dirname $file] jpayne@69: if {![info exists procdDirs($dir)]} { jpayne@69: try { jpayne@69: source $file jpayne@69: } trap {POSIX EACCES} {} { jpayne@69: # $file was not readable; silently ignore jpayne@69: continue jpayne@69: } on error msg { jpayne@69: tclLog "error reading package index file $file: $msg" jpayne@69: } on ok {} { jpayne@69: set procdDirs($dir) 1 jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: set use_path [lrange $use_path 0 end-1] jpayne@69: jpayne@69: # Check whether any of the index scripts we [source]d above set a new jpayne@69: # value for $::auto_path. If so, then find any new directories on the jpayne@69: # $::auto_path, and lappend them to the $use_path we are working from. jpayne@69: # This gives index scripts the (arguably unwise) power to expand the jpayne@69: # index script search path while the search is in progress. jpayne@69: set index 0 jpayne@69: if {[llength $old_path] == [llength $auto_path]} { jpayne@69: foreach dir $auto_path old $old_path { jpayne@69: if {$dir ne $old} { jpayne@69: # This entry in $::auto_path has changed. jpayne@69: break jpayne@69: } jpayne@69: incr index jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: # $index now points to the first element of $auto_path that has jpayne@69: # changed, or the beginning if $auto_path has changed length Scan the jpayne@69: # new elements of $auto_path for directories to add to $use_path. jpayne@69: # Don't add directories we've already seen, or ones already on the jpayne@69: # $use_path. jpayne@69: foreach dir [lrange $auto_path $index end] { jpayne@69: if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} { jpayne@69: lappend use_path $dir jpayne@69: } jpayne@69: } jpayne@69: set old_path $auto_path jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: # ::tcl::Pkg::Create -- jpayne@69: # jpayne@69: # Given a package specification generate a "package ifneeded" statement jpayne@69: # for the package, suitable for inclusion in a pkgIndex.tcl file. jpayne@69: # jpayne@69: # Arguments: jpayne@69: # args arguments used by the Create function: jpayne@69: # -name packageName jpayne@69: # -version packageVersion jpayne@69: # -load {filename ?{procs}?} jpayne@69: # ... jpayne@69: # -source {filename ?{procs}?} jpayne@69: # ... jpayne@69: # jpayne@69: # Any number of -load and -source parameters may be jpayne@69: # specified, so long as there is at least one -load or jpayne@69: # -source parameter. If the procs component of a module jpayne@69: # specifier is left off, that module will be set up for jpayne@69: # direct loading; otherwise, it will be set up for lazy jpayne@69: # loading. If both -source and -load are specified, the jpayne@69: # -load'ed files will be loaded first, followed by the jpayne@69: # -source'd files. jpayne@69: # jpayne@69: # Results: jpayne@69: # An appropriate "package ifneeded" statement for the package. jpayne@69: jpayne@69: proc ::tcl::Pkg::Create {args} { jpayne@69: append err(usage) "[lindex [info level 0] 0] " jpayne@69: append err(usage) "-name packageName -version packageVersion" jpayne@69: append err(usage) "?-load {filename ?{procs}?}? ... " jpayne@69: append err(usage) "?-source {filename ?{procs}?}? ..." jpayne@69: jpayne@69: set err(wrongNumArgs) "wrong # args: should be \"$err(usage)\"" jpayne@69: set err(valueMissing) "value for \"%s\" missing: should be \"$err(usage)\"" jpayne@69: set err(unknownOpt) "unknown option \"%s\": should be \"$err(usage)\"" jpayne@69: set err(noLoadOrSource) "at least one of -load and -source must be given" jpayne@69: jpayne@69: # process arguments jpayne@69: set len [llength $args] jpayne@69: if {$len < 6} { jpayne@69: error $err(wrongNumArgs) jpayne@69: } jpayne@69: jpayne@69: # Initialize parameters jpayne@69: array set opts {-name {} -version {} -source {} -load {}} jpayne@69: jpayne@69: # process parameters jpayne@69: for {set i 0} {$i < $len} {incr i} { jpayne@69: set flag [lindex $args $i] jpayne@69: incr i jpayne@69: switch -glob -- $flag { jpayne@69: "-name" - jpayne@69: "-version" { jpayne@69: if {$i >= $len} { jpayne@69: error [format $err(valueMissing) $flag] jpayne@69: } jpayne@69: set opts($flag) [lindex $args $i] jpayne@69: } jpayne@69: "-source" - jpayne@69: "-load" { jpayne@69: if {$i >= $len} { jpayne@69: error [format $err(valueMissing) $flag] jpayne@69: } jpayne@69: lappend opts($flag) [lindex $args $i] jpayne@69: } jpayne@69: default { jpayne@69: error [format $err(unknownOpt) [lindex $args $i]] jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: # Validate the parameters jpayne@69: if {![llength $opts(-name)]} { jpayne@69: error [format $err(valueMissing) "-name"] jpayne@69: } jpayne@69: if {![llength $opts(-version)]} { jpayne@69: error [format $err(valueMissing) "-version"] jpayne@69: } jpayne@69: jpayne@69: if {!([llength $opts(-source)] || [llength $opts(-load)])} { jpayne@69: error $err(noLoadOrSource) jpayne@69: } jpayne@69: jpayne@69: # OK, now everything is good. Generate the package ifneeded statment. jpayne@69: set cmdline "package ifneeded $opts(-name) $opts(-version) " jpayne@69: jpayne@69: set cmdList {} jpayne@69: set lazyFileList {} jpayne@69: jpayne@69: # Handle -load and -source specs jpayne@69: foreach key {load source} { jpayne@69: foreach filespec $opts(-$key) { jpayne@69: lassign $filespec filename proclist jpayne@69: jpayne@69: if { [llength $proclist] == 0 } { jpayne@69: set cmd "\[list $key \[file join \$dir [list $filename]\]\]" jpayne@69: lappend cmdList $cmd jpayne@69: } else { jpayne@69: lappend lazyFileList [list $filename $key $proclist] jpayne@69: } jpayne@69: } jpayne@69: } jpayne@69: jpayne@69: if {[llength $lazyFileList]} { jpayne@69: lappend cmdList "\[list tclPkgSetup \$dir $opts(-name)\ jpayne@69: $opts(-version) [list $lazyFileList]\]" jpayne@69: } jpayne@69: append cmdline [join $cmdList "\\n"] jpayne@69: return $cmdline jpayne@69: } jpayne@69: jpayne@69: interp alias {} ::pkg::create {} ::tcl::Pkg::Create