I have been trying to run a Tcl script that requires a package and it keeps saying that it can not find the required package, this happens even when I add the package directory to the auto_path
(this works with binary files) or put them both in the same folder. This seems to happen with any Tcl package.
Package file:
package provide test 1.0
Using file:
package require test 1.0
Result:
$ tclsh test1.tcl
can't find package test 1.0
while executing
"package require test 1.0"
(file "test1.tcl" line 1)
Using Tcl 8.6.13 with MINGW64 MSYS2 on Windows 10
Just putting the implementation of the package in the same directory as your main script isn't enough. You also need a pkgIndex.tcl
file, and to add the directory to the auto_path
global variable.
There's two key parts to a package in Tcl, the index script and the package provide
(or its C API equivalent).
The index script is run by the internals of package unknown
, and is typically in a file called pkgIndex.tcl
that's located in a directory with the package implementation (DLL, script files, etc.) It calls package ifneeded
to tell Tcl "if you need version x.y.z
of package abc
, run this code to get it". Doing it like that means you can have many versions of the same package installed at once.
The code to get the package working in the interpreter typically calls load
, source
or both. Somewhere inside that process of getting the package working, package provide
is called with the same package name and version; this marks the point at which the package is determined to be present and operational. The package
command checks that what it has is exactly what it expected, and prohibits circular dependencies; having both of these prevents a lot of difficult-to-diagnose bugs.
It used to be common practice to use pkg_mkIndex
to scan for package provide
calls and generate the pkgIndex.tcl
scripts... but that was always error-prone and dumb. It isn't difficult to write those scripts as a package author, especially now all current versions of Tcl support the apply
command.
When you package require
some package that isn't already known about, the package unknown
handler is called. That scans the auto_path
list of directories to locate all pkgIndex.tcl
files that it hasn't already seen, and source
s each one in a context where $dir
is set to the directory containing that script. Those call package ifneeded
to add entries into the internal database of known packages. If that then includes a package with an acceptable version, the script defined by the package ifneeded
call to import them implementation is called in the global scope. (If the package is still not known or the version constraints are unsatisfiable, you get an error.)
To add the directory containing your main script to the auto_path
global, do this outside any procedure (usually first thing in the script):
lappend ::auto_path [file dirname [info script]]
The index scripts are really simple in practice! The usual pattern for a pkgIndex.tcl
script is something like this:
package ifneeded Foo 1.2.3 [list source [file join $dir fooImpl.tcl]]
Or this:
package ifneeded Foo 1.2.3 [list load [file join $dir foo.dll]]
A pkgIndex.tcl
script may define how to make many different packages available. It doesn't have to be one-to-one.
There's an environment variable (TCLLIBPATH
) for initialising the auto_path
but that isn't recommended for an application to find its internal packages. Instead, it exists to allow the application to find installations of packages at the system or user level.
There's another environment variable that can also initialise the auto_path
, TCL_LIBRARY
, but that should not be set outside of testing Tcl itself as that really controls where Tcl finds part of its implementation. Especially from Tcl 9.0 onwards, it is obsolete for any other use.
You can stamp extra information onto the end of a Tcl script. Put a Ctrl+Z on the end, then attach your data afterwards. In particular, in Tcl 9.0 you can put a ZIP archive there that you can use the zipfs
command to mount as a virtual filesystem, and pull the rest of your package implementation out of (including DLLs!) In earlier versions, other technologies were needed to do this (e.g., tclkits).