package.tcl 23 KB

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