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