package.tcl 23 KB

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