Tcl offers a mechanism to look for and create packages—reusable pieces of code, either bundled with Tcl, created as additional projects or code you write and want to use in multiple applications. Packages are meant to provide an easy to use mechanism for using additional libraries—either your library or third-party libraries. Packages are identified using a package name and version.
Package name can be any string, but a good practice is to name the package using lowercase only and using the same name as the namespace used. For example, functionality to work with base64
encoded data is called base64
and all commands are in the base64 namespace.
Versions are one or more numbers separated by dots, where the first leftmost one indicates major versions, and remaining ones indicate additional versioning, with the leftmost being most significant, and the rightmost least significant. Tcl compares versions by separating the numbers using dots and comparing each part as numbers. version 3.2 is considered to be higher than 2.3 and version 10.1 is considered higher than 9.1. Tcl and Tk themselves are versioned in the same way.
Any code can be made into a package and this process is described in more detail later in this chapter.
Operations related to the package system are accessible via the package
command and its subcommands. In order to load a package, we need to invoke the package require
command, specifying a package name and, optionally, the minimum version that is required.
If not specifying a version, the latest one is automatically loaded. If specifying a version to load, the package version that is larger or equal to the required version, with same major version as the required version is loaded. For example, if the package md5
is available in 1.3, 1.4.4
, and 2.0.7
versions, then requiring any version would load the 2.0.7 version; and requiring 1.0
would load version 1.4.4
.
If you are using ActiveTcl, then its documentation contains a full list of available packages along with documentation for each of the packages. This book only covers a small subset of all available Tcl packages.
Tcl package lookup works by having a variable that lists all directories Tcl should look for packages in. It then goes into each subdirectory of that directory and checks if the pkgIndex.tcl
file exists and loads it. A list of these directories is stored in the global auto_path
Tcl variable. In order to add a directory, it is enough to add it using lappend ::auto_path /path/to/new/directory
.
Creating one or more Tcl packages is a trivial task and requires creating a directory, either in Tcl's lookup path, or in a place that we will later add to the list of directories. Our package can be stored in any directory. We need to have a file called pkgIndex.tcl
, which is a script that should invoke the package ifneeded
command, along with the name of the package, its version and the Tcl code to invoke to initialize a package. The scripts that provide a package need to invoke package provide
command, providing package name and version as arguments.
While it is possible to create the pkgIndex.tcl
file manually, Tcl provides a mechanism for building this file, which the authors recommend. Let's assume we want to build a package called mypackage, 1.0
version that will create a namespace called mypackage
and an add
command in it. What we need to start with is to set up a directory for our test packages and another for this particular package—for this example, we'll assume /tmp/packages
and /tmp/packages/mypackage1.0
for the package. First create a file called /tmp/packages/mypackage1.0/mypackage.tcl
containing the following code:
namespace eval mypackage {} proc mypackage::add {a b} { return [expr {$a + $b}] } package provide mypackage 1.0
Next run the following command in any Tcl interpreter:
% pkg_mkIndex /tmp/packages/mypackage1.0
This will cause /tmp/packages/mypackage1.0/pkgIndex.tcl
to be created. Every time you change your package names, package versions or filenames, the pkgIndex.tcl
file will need to be rebuilt using the previous example.
Now in the same or new session of Tcl interpreter, type in the following commands:
% lappend ::auto_path /tmp/packages
/opt/ActiveTcl-8.5/lib /tmp/packages
% package require mypackage
1.0
More complex packages can include multiple files. If multiple files state that they provide the same package in the same version, then pkgIndex.tcl
file will indicate that all those files need to be loaded. If multiple files provide different packages or different versions of the same package, then this will be reflected in the pkgIndex.tcl
file as well.
For larger packages, the authors recommend building multiple smaller packages and having one larger package that only requires the remaining ones—for example, mypackage::add
and mypackage::sub
will provide actual commands and main mypackage
command will just load the actual ones.
Tcl 8.5 also introduces a new concept called Tcl modules. This is a simplification of packaging system we previously mentioned, which assumes that all packages reside in individual files. Tcl will load this file by loading this script, so it either needs to be a Tcl script or it can be a Starkit archive with a native library embedded in it. Starkit archives are introduced in Chapter 3.
The main reason for creating a modules system in Tcl is to be able to clearly map the directory structure to package names, similar to how it is done in languages such as Java or Python—by knowing the package name you can easily find directory and the files that define a particular package.
The path and filename reflect the actual package name and version. For example, the package mypackage
with version 1.1
would reside in a file called mypackage-1.1.tm
. If the package name contains one or more ::
strings, then those are used to map packages to directories. For example, mypackage::common::misc
version 1.2
package would need to reside in mypackage/common
directory with name misc-1.2.tm
.
Loading a module does not differ in any way from loading a package—all we need to do is run the package requiremypackage
and Tcl will find and load it in the same way regardless of whether it is a package or module.
Tcl looks for these modules in different set of directories so that the package lookup mechanism is separated from the modules lookup. In order to add a directory where modules should be looked for, we need to invoke the tcl::tm::add
command with a directory where files should be looked for. For example, to create a package called mypackage
with version 1.1
, we can create directory called /tmp/tclmodules
and create a file called mypackage-1.1.tm
in it with the following contents:
namespace eval mypackage {} proc mypackage::add {a b} { return [expr {$a + $b}] } package provide mypackage 1.1
Then we can run the following code to test it:
% tcl::tm::add /tmp/tclmodules % package require mypackage 1.1
Even though, internally, Tcl packages and modules differ slightly from each other in how they are handled, the authors have decided not to distinguish those two concepts where it is not necessary and always use the term package, whether Tcl uses modules or packages to load it.
18.218.93.169