summaryrefslogtreecommitdiffstats
path: root/library/package.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'library/package.tcl')
-rw-r--r--library/package.tcl418
1 files changed, 331 insertions, 87 deletions
diff --git a/library/package.tcl b/library/package.tcl
index 68c5053..9ab8231 100644
--- a/library/package.tcl
+++ b/library/package.tcl
@@ -20,6 +20,24 @@
# files given as arguments.
#
# Arguments:
+# -direct (optional) If this flag is present, the generated
+# code in pkgMkIndex.tcl will cause the package to be
+# loaded when "package require" is executed, rather
+# than lazily when the first reference to an exported
+# procedure in the package is made.
+# -nopkgrequire (optional) If this flag is present, "package require"
+# commands are ignored. This flag is useful in some
+# situations, for example when there is a circularity
+# in package requires (package a requires package b,
+# which in turns requires package a).
+# -verbose (optional) Verbose output; the name of each file that
+# was successfully rocessed is printed out. Additionally,
+# if processing of a file failed a message is printed
+# out; a file failure may not indicate that the indexing
+# has failed, since pkg_mkIndex stores the list of failed
+# files and tries again. The second time the processing
+# may succeed, for example if a required package has been
+# indexed by a previous pass.
# 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
@@ -28,22 +46,57 @@
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"
+ set usage {"pkg_mkIndex ?-nopkgrequire? ?-direct? ?-verbose? dir ?pattern ...?"};
+
+ set argCount [llength $args]
+ if {$argCount < 1} {
+ return -code error "wrong # args: should be\n$usage"
}
- if {[llength $args] == 0} {
- return -code error "wrong # args: should be\
- \"pkg_mkIndex ?-direct? dir ?pattern ...?\"";
+
+ set more ""
+ set direct 0
+ set noPkgRequire 0
+ set doVerbose 0
+ for {set idx 0} {$idx < $argCount} {incr idx} {
+ set flag [lindex $args $idx]
+ switch -glob -- $flag {
+ -- {
+ # done with the flags
+ incr idx
+ break
+ }
+
+ -verbose {
+ set doVerbose 1
+ }
+
+ -direct {
+ set direct 1
+ append more " -direct"
+ }
+
+ -nopkgrequire {
+ set noPkgRequire 1
+ append more " -nopkgrequire"
+ }
+
+ -* {
+ return -code error "unknown flag $flag: should be\n$usage"
+ }
+
+ default {
+ # done with the flags
+ break
+ }
+ }
}
- set dir [lindex $args 0]
- set patternList [lrange $args 1 end]
+
+ set dir [lindex $args $idx]
+ set patternList [lrange $args [expr $idx + 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"
@@ -55,121 +108,312 @@ proc pkg_mkIndex {args} {
append index "# full path name of this file's directory.\n"
set oldDir [pwd]
cd $dir
+
+ # In order to support building of index files from scratch, we make
+ # repeated passes on the files to index, until either all have been
+ # indexed, or we can no longer make any headway.
+
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 toProcess($file) 1
+ }
- set c [interp create]
+ while {[array size toProcess] > 0} {
+ set processed 0
- # If Tk is loaded in the parent interpreter, load it into the
- # child also, in case the extension depends on it.
+ foreach file [array names toProcess] {
+ # 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. The interpeter uses a special version of
+ # tclPkgSetup to force loading of required packages at require
+ # time rather than lazily, so that we can keep track of commands
+ # and packages that are defined indirectly rather than from the
+ # file itself.
- foreach pkg [info loaded] {
- if {[lindex $pkg 1] == "Tk"} {
- $c eval {set argv {-geometry +0+0}}
- load [lindex $pkg 0] Tk $c
- break
+ set c [interp create]
+
+ # Load into the child all packages currently loaded in the parent
+ # interpreter, in case the extension depends on some of them.
+
+ foreach pkg [info loaded] {
+ if {[lindex $pkg 1] == "Tk"} {
+ $c eval {set argv {-geometry +0+0}}
+ }
+ load [lindex $pkg 0] [lindex $pkg 1] $c
}
- }
- $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 }
+
+ # We also call package ifneeded for all packages that have been
+ # identified so far. This way, each pass will have loaded the
+ # equivalent of the pkgIndex.tcl file that we are constructing,
+ # and packages whose processing failed in previous passes may
+ # be processed successfully now
+
+ foreach pkg [array names files] {
+ $c eval "package ifneeded $pkg\
+ \[list tclPkgSetup $dir \
+ [lrange $pkg 0 0] [lrange $pkg 1 1]\
+ [list $files($pkg)]\]"
+ }
+ if {$noPkgRequire == 1} {
+ $c eval {
+ rename package __package_orig
+ proc package {what args} {
+ switch -- $what {
+ require { return ; # ignore transitive requires }
+ default { eval __package_orig {$what} $args }
+ }
}
+ proc __dummy args {}
+ package unknown __dummy
}
- if {!$__direct} {
- proc __pkgGetAllNamespaces {{root {}}} {
- set list $root
- foreach ns [namespace children $root] {
- eval lappend list [__pkgGetAllNamespaces $ns]
+ } else {
+ $c eval {
+ rename package __package_orig
+ proc package {what args} {
+ switch -- $what {
+ require {
+ eval __package_orig require $args
+
+ # a package that was required needs to be
+ # placed in the list of packages to ignore.
+ # tclPkgSetup is unable to do it, so do it
+ # here.
+
+ set ::__ignorePkgs([lindex $args 0]) 1
+ }
+
+ provide {
+ # if package provide is called at level 1 and
+ # with two arguments, then this package is
+ # being provided by one of the files we are
+ # indexing, and therefore we need to add it
+ # to the list of packages to write out.
+ # We need to do this check because otherwise
+ # packages that are spread over multiple
+ # files are indexed only by their first file
+ # loaded.
+ # Note that packages that this cannot catch
+ # packages that are implemented by a
+ # combination of TCL files and DLLs
+
+ if {([info level] == 1) \
+ && ([llength $args] == 2)} {
+ lappend ::__providedPkgs [lindex $args 0]
+ }
+
+ eval __package_orig provide $args
+ }
+
+ default { eval __package_orig {$what} $args }
}
- return $list
}
- set __origCmds [info commands]
}
- package unknown __dummy
+ }
- set dir "" ;# in case file is pkgIndex.tcl
+ $c eval [list set __file $file]
+ $c eval [list set __direct $direct]
+ if {[catch {
+ $c eval {
+ set __doingWhat "loading or sourcing"
- # 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.
+ # override the tclPkgSetup procedure (which is called by
+ # package ifneeded statements from pkgIndex.tcl) to force
+ # loads of packages, and also keep track of
+ # packages/namespaces/commands that the load generated
- if {[string compare [file extension $__file] \
- [info sharedlibextension]] == 0} {
+ proc tclPkgSetup {dir pkg version files} {
+ # remember the current set of packages and commands,
+ # so that we can add any that were defined by the
+ # package files to the list of packages and commands
+ # to ignore
- # 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.
+ foreach __p [package names] {
+ set __localIgnorePkgs($__p) 1
+ }
+ foreach __ns [__pkgGetAllNamespaces] {
+ set __localIgnoreNs($__ns) 1
+
+ # if the namespace is already in the __ignoreNs
+ # array, its commands have already been imported
+
+ if {[info exists ::__ignoreNs($__ns)] == 0} {
+ namespace import ${__ns}::*
+ }
+ }
+ foreach __cmd [info commands] {
+ set __localIgnoreCmds($__cmd) 1
+ }
+
+ # load the files that make up the package
- if {[catch {load [file join . $__file]} __msg]} {
- tclLog "warning: error while loading $__file: $__msg"
+ package provide $pkg $version
+ foreach __fileInfo $files {
+ set __f [lindex $__fileInfo 0]
+ set __type [lindex $__fileInfo 1]
+ if {$__type == "load"} {
+ load [file join $dir $__f] $pkg
+ } else {
+ source [file join $dir $__f]
+ }
+ }
+
+ # packages and commands that were defined by these
+ # files are to be ignored.
+
+ foreach __p [package names] {
+ if {[info exists __localIgnorePkgs($__p)] == 0} {
+ set ::__ignorePkgs($__p) 1
+ }
+ }
+ foreach __ns [__pkgGetAllNamespaces] {
+ if {([info exists __localIgnoreNs($__ns)] == 0) \
+ && ([info exists ::__ignoreNs($__ns)] == 0)} {
+ namespace import ${__ns}::*
+ set ::__ignoreNs($__ns) 1
+ }
+ }
+ foreach __cmd [info commands] {
+ if {[info exists __localIgnoreCmds($__cmd)] == 0} {
+ lappend ::__ignoreCmds $__cmd
+ }
+ }
}
- set __type load
- } else {
- if {[catch {source $__file} __msg]} {
- tclLog "warning: error while sourcing $__file: $__msg"
+
+ # we need to track command defined by each package even in
+ # the -direct case, because they are needed internally by
+ # the "partial pkgIndex.tcl" step above.
+
+ proc __pkgGetAllNamespaces {{root {}}} {
+ set __list $root
+ foreach __ns [namespace children $root] {
+ eval lappend __list [__pkgGetAllNamespaces $__ns]
+ }
+ return $__list
}
- 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} {
+
+ # initialize the list of packages to ignore; these are
+ # packages that are present before the script/dll is loaded
+
+ set ::__ignorePkgs(Tcl) 1
+ set ::__ignorePkgs(Tk) 1
+ foreach __pkg [package names] {
+ set ::__ignorePkgs($__pkg) 1
+ }
+
+ # before marking the original commands, import all the
+ # namespaces that may have been loaded from the parent;
+ # these namespaces and their commands are to be ignored
+
foreach __ns [__pkgGetAllNamespaces] {
+ set ::__ignoreNs($__ns) 1
namespace import ${__ns}::*
}
+
+ set ::__ignoreCmds [info commands]
+
+ 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.
+
+ set __pkgs {}
+ set __providedPkgs {}
+ 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.
+
+ set __doingWhat loading
+ load [file join . $__file]
+ set __type load
+ } else {
+ set __doingWhat sourcing
+ source $__file
+ 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,...
+
+ foreach __ns [__pkgGetAllNamespaces] {
+ if {[info exists ::__ignoreNs($__ns)] == 0} {
+ namespace import ${__ns}::*
+ }
+ }
foreach __i [info commands] {
set __cmds($__i) 1
}
- foreach __i $__origCmds {
+ foreach __i $::__ignoreCmds {
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} {
+
+ # special case so that global names have no leading
+ # ::, this is required by the unknown command
+
+ set __absolute [auto_qualify $__absolute ::]
+
+ 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)} {
+
+ foreach __i $::__providedPkgs {
lappend __pkgs [list $__i [package provide $__i]]
+ set __ignorePkgs($__i) 1
+ }
+ foreach __i [package names] {
+ if {([string compare [package provide $__i] ""] != 0) \
+ && ([info exists ::__ignorePkgs($__i)] == 0)} {
+ lappend __pkgs [list $__i [package provide $__i]]
+ }
}
}
+ } msg] == 1} {
+ set what [$c eval set __doingWhat]
+ if {$doVerbose} {
+ tclLog "warning: error while $what $file: $msg\nthis file will be retried in the next pass"
+ }
+ } else {
+ 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]
+ }
+
+ incr processed
+ unset toProcess($file)
+
+ if {$doVerbose} {
+ tclLog "processed $file"
+ }
}
- } 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)"
+ interp delete $c
}
- foreach pkg $pkgs {
- # cmds is empty/not used in the direct case
- lappend files($pkg) [list $file $type $cmds]
+
+ if {$processed == 0} {
+ tclLog "this iteration could not process any files: giving up here"
+ break
}
- interp delete $c
}
+
foreach pkg [lsort [array names files]] {
append index "\npackage ifneeded $pkg "
if {$direct} {