Chapter 12. Script Libraries and Packages

Collections of Tcl commands are kept in libraries and organized into packages. Tcl automatically loads libraries as an application uses their commands. Tcl commands discussed are: package, pkg_mkIndex, auto_mkindex, unknown, and tcl_findLibrary.

Libraries group useful sets of Tcl procedures so that they can be used by multiple applications. For example, you could use any of the code examples that come with this book by creating a script library and then directing your application to check in that library for missing procedures. One way to structure a large application is to have a short main script and a library of support scripts. The advantage of this approach is that not all the Tcl code needs to be loaded to start the application. Applications start up quickly, and as new features are accessed, the code that implements them is loaded automatically.

The Tcl package facility supports version numbers and has a provide/require model of use. Typically, each file in a library provides one package with a particular version number. Packages also work with shared object libraries that implement Tcl commands in compiled code, which are described in Chapter 47. A package can be provided by a combination of script files and object files. Applications specify which packages they require and the libraries are loaded automatically. The package facility is an alternative to the auto loading scheme used in earlier versions of Tcl. You can use either mechanism, and this chapter describes them both.

If you create a package you may wish to use the namespace facility to avoid conflicts between procedures and global variables used in different packages. Namespaces are the topic of Chapter 14. Before Tcl 8.0 you had to use your own conventions to avoid conflicts. This chapter explains a simple coding convention for large Tcl programs. I use this convention in exmh, a mail user interface that has grown from about 2,000 to over 35,000 lines of Tcl code. A majority of the code has been contributed by the exmh user community. Such growth might not have been possible without coding conventions.

Locating Packages: The auto_path Variable

The package facility assumes that Tcl libraries are kept in well-known directories. The list of well-known directories is kept in the auto_path Tcl variable. This is initialized by tclsh and wish to include the Tcl script library directory, the Tk script library directory (for wish), and the parent directory of the Tcl script library directory. For example, on my Macintosh auto_path is a list of these three directories:

Disk:System Folder:Extensions:Tool Command Language:tcl8.4
Disk:System Folder:Extensions:Tool Command Language
Disk:System Folder:Extensions:Tool Command Language:tk8.4

On my Windows 95 machine the auto_path lists these directories:

c:Program FilesTcllibTcl8.4
c:Program FilesTcllib
c:Program FilesTcllibTk8.4

On my UNIX workstation the auto_path lists these directories:

/usr/local/tcl/lib/tcl8.4
/usr/local/tcl/lib
/usr/local/tcl/lib/tk8.4

The package facility searches these directories and their subdirectories for packages. The easiest way to manage your own packages is to create a directory at the same level as the Tcl library:

/usr/local/tcl/lib/welchbook

Packages in this location, for example, will be found automatically because the auto_path list includes /usr/local/tcl/lib. You can also add directories to the auto_path explicitly:

lappend auto_path directory

One trick I often use is to put the directory containing the main script into the auto_path. The following command sets this up:

lappend auto_path [file dirname [info script]]

If your code is split into bin and lib directories, then scripts in the bin directory can add the adjacent lib directory to their auto_path with this command:

lappend auto_path 
    [file join [file dirname [info script]] ../lib]

Using Packages

Each script file in a library declares what package it implements with the package provide command:

package provide name version

The name identifies the package, and the version has a major.minor format. The convention is that the minor version number can change and the package implementation will still be compatible. If the package changes in an incompatible way, then the major version number should change. For example, Chapter 17 defines several procedures that use the HTTP network protocol. These include http::geturl, http::wait, and http::cleanup. The file that contains the procedures starts with this command:

package provide http 2.4

Case is significant in package names. In particular, the package that comes with Tcl is named http — all lowercase.

More than one file can contribute to the same package simply by specifying the same name and version. In addition, different versions of the same package can be kept in the same directory but in different files.

An application specifies the packages it needs with the package require command:

package require name ?version? ?-exact?

If the version is left off, then the highest available version is loaded. Otherwise the highest version with the same major number is loaded. For example, if the client requires version 1.1, version 1.2 could be loaded if it exists, but versions 1.0 and 2.0 would not be loaded. You can restrict the package to a specific version with the -exact flag. If no matching version can be found, then the package require command raises an error.

Loading Packages Automatically

The package require command depends on an index to record which files implement which packages. The index must be maintained by you, your project librarian, or your system administrator when packages change. The index is created by the pkg_mkIndex command, which puts the index into a pkgIndex.tcl file in each library directory. The pkg_mkIndex command takes the name of a directory and one or more glob patterns that specify files within that directory. File name patterns are described on page 122. The syntax is:

pkg_mkIndex ?options? directory pattern ?pattern ...?

For example:

pkg_mkIndex /usr/local/lib/welchbook *.tcl
pkg_mkIndex -lazy /usr/local/lib/Sybtcl *.so

The pkg_mkIndex command sources or loads all the files matched by the pattern, detects what packages they provide, and computes the index. You should be aware of this behavior because it works well only for libraries. If the pkg_mkIndex command hangs or starts random applications, it is because it sourced an application file instead of a library file.

The package index, pkgIndex.tcl, is sourced in response to a package require command. The index instructs the package loading mechanism how to define the package. By default, source or load commands are specified so that packages are defined immediately as a side effect of package require. This is called direct loading. However, the original package index system used a deferred loading scheme layered on the auto_load mechanism and the unknown command hook, which is described on page 178. If you want deferred loading, use the -lazy option to pkg_mkIndex. The default behavior of pkg_mkIndex switched from -lazy to -direct in Tcl 8.3. The pkg_mkIndex options are summarized in Table 12-1.

Table 12-1. Options to the pkg_mkIndex command

-direct

Generates an index with source and load commands in it. This results in packages being loaded directly as a result of package require. This is the default starting with Tcl 8.3.

-lazy

Generates an index that populates the auto_index array for deferred loading of commands. This behavior was the default prior to Tcl 8.3.

-load pattern

Dynamically loads packages that match pattern into the slave interpreter used to compute the index. A common reason to need this is with the tcbload package needed to load .tbc files compiled with TclPro Compiler.

-verbose

Displays the name of each file processed and any errors that occur.

Packages Implemented in C Code

The files in a library can be either script files that define Tcl procedures or binary files in shared library format that define Tcl commands in compiled code (i.e., a Dynamic Link Library (DLL)). Chapter 47 describes how to implement Tcl commands in C. There is a C API to the package facility that you use to declare the package name for your commands. This is shown in Example 47-1 on page 698. Chapter 37 also describes the Tcl load command that is used instead of source to link in shared libraries. The pkg_mkIndex command also handles shared libraries:

pkg_mkIndex directory *.tcl *.so *.shlib *.dll

In this example, .so, .shlib, and .dll are file suffixes for shared libraries on UNIX, Macintosh, and Windows systems, respectively. You can have packages that have some of their commands implemented in C, and some implemented as Tcl procedures. The script files and the shared library must simply declare that they implement the same package. The pkg_mkIndex procedure will detect this and set up the auto_index, so some commands are defined by sourcing scripts, and some are defined by loading shared libraries.

If your file servers support more than one machine architecture, such as Solaris and Linux systems, you probably keep the shared library files in machine-specific directories. In this case the auto_path should also list the machine-specific directory so that the shared libraries there can be loaded automatically. If your system administrator configured the Tcl installation properly, this should already be set up. If not, or you have your shared libraries in a nonstandard place, you must append the location to the auto_path variable.

Summary of Package Loading

The basic structure of package loading works like this:

  • An application does a package require command. If the package is already loaded, the command just returns the version number of the already loaded package. If is not loaded, the following steps occur.

  • The package facility checks to see if it knows about the package. If it does, then it runs the Tcl scripts registered with the package ifneeded command. These commands either load the package or set it up to be loaded automatically when its commands are first used.

  • If the package is unknown, the tclPkgUnknown procedure is called to find it. Actually, you can specify what procedure to call to do the lookup with the package unknown command, but the standard one is tclPkgUnknown.

  • The tclPkgUnknown procedure looks through the auto_path directories and their subdirectories for pkgIndex.tcl files. It sources those to build an internal database of packages and version information. The pkgIndex.tcl files contain calls to package ifneeded that specify what to do to define the package. You can use the pkg_mkIndex command to create your pkgIndex.tcl files, or you can create them by hand.

  • In the case of deferred package loading, the tclPkgSetup procedure defines the auto_index array to contain the correct source or load commands to define each command in the package. Automatic loading and the auto_index array are described in more detail later.

As you can see, there are several levels of processing involved in finding packages. The system is flexible enough that you can change the way packages are located and how packages are loaded. The -lazy scenario is complicated because it uses the delayed loading of source code that is described in the next section. Using the -direct flag to pkg_mkIndex simplifies the situation. In any case, it all boils down to three key steps:

  1. Use pkg_mkIndex to maintain your index files. Decide at this time whether or not to use direct or lazy package loading.

  2. Put the appropriate package require and package provide commands in your code.

  3. Ensure that your library directories, or their parent directories, are listed in the auto_path variable.

The package Command

The package command has several operations that are used primarily by the pkg_mkIndex procedure and the automatic loading facility. These operations are summarized in Table 12-2.

Table 12-2. The package command

package forget package

Deletes registration information for package.

package ifneeded package ?command?

Queries or sets the command used to set up automatic loading of a package.

package names

Returns the set of registered packages.

package provide package version

Declares that a script file defines commands for package with the given version.

package present package ?version? ?-exact?

Equivalent to package require, except that no attempt to load the package is made if it is not loaded.

package require package ?version? ?-exact?

Declares that a script uses package. The -exact flag specifies that the exact version must be loaded. Otherwise, the highest matching version is loaded.

package unknown ?command?

Queries or sets the command used to locate packages.

package vcompare v1 v2

Compares version v1 and v2. Returns 0 if they are equal, -1 if v1 is less than v2, or 1 if v1 is greater than v2.

package versions package

Returns which versions of the package are registered.

package vsatisfies v1 v2

Returns 1 if v1 is greater or equal to v2 and still has the same major version number. Otherwise returns 0.

Libraries Based on the tclIndex File

You can create libraries without using the package command. The basic idea is that a directory has a library of script files, and an index of the Tcl commands defined in the library is kept in a tclIndex file. The drawback is that versions are not supported and you may need to adjust the auto_path to list your library directory. The main advantage of this approach is that this mechanism has been part of Tcl since the earliest versions. If you currently maintain a library using tclIndex files, it will still work.

You must generate the index that records what procedures are defined in the library. The auto_mkindex procedure creates the index, which is stored in a file named tclIndex that is kept in the script library directory. (Watch out for the difference in capitalization between auto_mkindex and pkg_mkIndex!) Suppose all the examples from this book are in the directory /usr/local/tcl/welchbook. You can make the examples into a script library by creating the tclIndex file:

auto_mkindex /usr/local/tcl/welchbook *.tcl

You will need to update the tclIndex file if you add procedures or change any of their names. A conservative approach to this is shown in the next example. It is conservative because it re-creates the index if anything in the library has changed since the tclIndex file was last generated, whether or not the change added or removed a Tcl procedure.

Example 12-1. Maintaining a tclIndex file

proc Library_UpdateIndex { libdir } {
   set index [file join $libdir tclIndex]
   if {![file exists $index]} {
      set doit 1
   } else {
      set age [file mtime $index]
      set doit 0
      # Changes to directory may mean files were deleted
      if {[file mtime $libdir] > $age} {
         set doit 1
      } else {
         # Check each file for modification
         foreach file [glob [file join $libdir *.tcl]] {
            if {[file mtime $file] > $age} {
               set doit 1
               break
            }
         }
      }
   }
   if { $doit } {
      auto_mkindex $libdir *.tcl
   }
}

The auto_path variable contains a list of directories to search for unknown commands. To continue our example, you can make the procedures in the book examples available by putting this command at the beginning of your scripts:

lappend auto_path /usr/local/tcl/welchbook

This has no effect if you have not created the tclIndex file. If you want to be extra careful, you can call Library_UpdateIndex. This will update the index if you add new things to the library.

lappend auto_path /usr/local/tcl/welchbook
Library_UpdateIndex /usr/local/tcl/welchbook

This will not work if there is no tclIndex file at all because Tcl won't be able to find the implementation of Library_UpdateIndex. Once the tclIndex has been created for the first time, then this will ensure that any new procedures added to the library will be installed into tclIndex. In practice, if you want this sort of automatic update, it is wise to include something like the Library_UpdateIndex procedure directly into your application as opposed to loading it from the library it is supposed to be maintaining.

The unknown Command

The unknown command implements automatic loading of Tcl commands. Whenever the Tcl interpreter encounters a command that it does not know about, it calls the unknown command with the name of the missing command. The unknown command is implemented in Tcl, so you are free to provide your own mechanism to handle unknown commands. This chapter describes the behavior of the default implementation of unknown, which can be found in the init.tcl file in the Tcl library. The info library command returns the location of the library.

How Auto Loading Works

The unknown command uses an array named auto_index. One element of the array is defined for each procedure that can be automatically loaded. The auto_index array is initialized by the package mechanism or by tclIndex files. The value of an auto_index element is a command that defines the procedure. Typical commands are:

source [file join $dir bind_ui.tcl]
load [file join $dir mime.so] Mime

The $dir gets substituted with the name of the directory that contains the library file, so the result is a source or load command that defines the missing Tcl command. The substitution is done with eval, so you could initialize auto_index with any commands at all. Example 12-2 is a simplified version of the code that reads the tclIndex file.

Example 12-2. Loading a tclIndex file

# This is a simplified part of the auto_load_index procedure.
# Go through auto_path from back to front.
set i [expr [llength $auto_path]-1]
for {} {$i >= 0} {incr i -1} {
   set dir [lindex $auto_path $i]
   if [catch {open [file join $dir tclIndex]} f] {
      # No index
      continue
   }
   # eval the file as a script. Because eval is
   # used instead of source, an extra round of
   # substitutions is performed and $dir gets expanded
   # The real code checks for errors here.
   eval [read $f]
   close $f
}

Disabling the Library Facility: auto_noload

If you do not want the unknown procedure to try and load procedures, you can set the auto_noload variable to disable the mechanism:

set auto_noload anything

Auto loading is quite fast. I use it regularly on applications both large and small. A large application will start faster if you only need to load the code necessary to start it up. As you access more features of your application, the code will load automatically. Even a small application benefits from auto loading because it encourages you to keep commonly used code in procedure libraries.

Interactive Conveniences

The unknown command provides a few other conveniences. These are used only when you are typing commands directly. They are disabled once execution enters a procedure or if the Tcl shell is not being used interactively. The convenience features are automatic execution of programs, command history, and command abbreviation. These options are tried, in order, if a command implementation cannot be loaded from a script library.

Auto Execute

The unknown procedure implements a second feature: automatic execution of external programs. This makes a Tcl shell behave more like other UNIX shells that are used to execute programs. The search for external programs is done using the standard PATH environment variable that is used by other shells to find programs. If you want to disable the feature all together, set the auto_noexec variable:

set auto_noexec anything

History

The history facility described in Chapter 13 is implemented by the unknown procedure.

Abbreviations

If you type a unique prefix of a command, unknown recognizes it and executes the matching command for you. This is done after automatic program execution is attempted and history substitutions are performed.

Tcl Shell Library Environment

Tcl searches for its script library directory when it starts up. In early versions of Tcl you had to compile in the correct location, set a Windows registry value, or set the TCL_LIBRARY environment variable to the correct location. Recent versions of Tcl use a standard searching scheme to locate the script library. The search understands the standard installation and build environments for Tcl, and it should eliminate the need to use the TCL_LIBRARY environment variable. On Windows the search for the library used to depend on registry values, but this has also been discontinued in favor of a standard search. In summary, "it should just work." However, this section explains how Tcl finds its script library so that you can troubleshoot problems.

Locating the Tcl Script Library

The default library location is defined when you configure the source distribution, which is explained on page 732. At this time an initial value for the auto_path variable is defined. (This default value appears in tcl_pkgPath, but changing this variable has no effect once Tcl has started. I just pretend tcl_pkgPath does not exist.) These values are just hints; Tcl may use other directories depending on what it finds in the file system.

When Tcl starts up, it searches for a directory that contains its init.tcl startup script. You can short-circuit the search by defining the TCL_LIBRARY environment variable. If this is defined, Tcl uses it only for its script library directory. However, you should not need to define this with normal installations of Tcl 8.0.5 or later. In my environment I'm often using several different versions of Tcl for various applications and testing purposes, so setting TCL_LIBRARY is never correct for all possibilities. If I find myself setting this environment variable, I know something is wrong with my Tcl installations!

The standard search starts with the default value that is compiled into Tcl (e.g., /usr/local/lib/tcl8.4.) After that, the following directories are examined for an init.tcl file. These example values assume Tcl version 8.4 and patch level 8.4.1:

../lib/tcl8.4
../../lib/tcl8.4
../library
../../tcl8.4.1/library
../../../tcl8.4.1/library

The first two directories correspond to the standard installation directories, while the last three correspond to the standard build environment for Tcl or Tk. The first directory in the list that contains a valid init.tcl file becomes the Tcl script library. This directory location is saved in the tcl_library global variable, and it is also returned by the info library command.

The primary thing defined by init.tcl is the implementation of the unknown procedure. It also initializes auto_path to contain $tcl_library and the parent directory of $tcl_library. There may be additional directories added to auto_path depending on the compiled in value of tcl_pkgPath.

tcl_findLibrary

A generalization of this search is implemented by tcl_findLibrary. This procedure is designed for use by extensions like Tk and [incr Tcl]. Of course, Tcl cannot use tcl_findLibrary itself because it is defined in init.tcl!

The tcl_findLibrary procedure searches relative to the location of the main program (e.g., tclsh or wish) and assumes a standard installation or a standard build environment. It also supports an override by an environment variable, and it takes care of sourcing an initialization script. The usage of tcl_findLibrary is:

tcl_findLibrary base version patch script enVar varName

The base is the prefix of the script library directory name. The version is the main version number (e.g., "8.0"). The patch is the full patch level (e.g., "8.0.3"). The script is the initialization script to source from the directory. The enVar names an environment variable that can be used to override the default search path. The varName is the name of a variable to set to name of the directory found by tcl_findLibrary. A side effect of tcl_findLibrary is to source the script from the directory. An example call is:

tcl_findLibrary tk 8.0 8.0.3 tk.tcl TK_LIBRARY tk_library

This call first checks to see whether TK_LIBRARY is defined in the environment. If so, it uses its value. Otherwise, it searches the following directories for a file named tk.tcl. It sources the script and sets the tk_library variable to the directory containing that file. The search is relative to the value returned by info nameofexecutable:

../lib/tk8.0
../../lib/tk8.0
../library
../../tk8.0.3/library
../../../tk8.0.3/library

Tk also adds $tk_library to the end of auto_path, so the other script files in that directory are available to the application:

lappend auto_path $tk_library

Coding Style

If you supply a package, you need to follow some simple coding conventions to make your library easier to use by other programmers. You can use the namespace facility introduced in Tcl 8.0. You can also use conventions to avoid name conflicts with other library packages and the main application. This section describes the conventions I developed before namespaces were added to Tcl.

A Module Prefix for Procedure Names

The first convention is to choose an identifying prefix for the procedures in your package. For example, the preferences package in Chapter 45 uses Pref as its prefix. All the procedures provided by the library begin with Pref. This convention is extended to distinguish between private and exported procedures. An exported procedure has an underscore after its prefix, and it is acceptable to call this procedure from the main application or other library packages. Examples include Pref_Add, Pref_Init, and Pref_Dialog. A private procedure is meant for use only by the other procedures in the same package. Its name does not have the underscore. Examples include PrefDialogItem and PrefXres.

This naming convention precludes casual names like doit, setup, layout, and so on. Without using namespaces, there is no way to hide procedure names, so you must maintain the naming convention for all procedures in a package.

A Global Array for State Variables

You should use the same prefix on the global variables used by your package. You can alter the capitalization; just keep the same prefix. I capitalize procedure names and use lowercase letters for variables. By sticking with the same prefix you identify what variables belong to the package and you avoid conflict with other packages.

Note

A Global Array for State Variablesarrayglobalglobal arrays

Collect state in a global or namespaced array.

In general, I try to use a single global or namespaced array for a package (namespaces are discussed in Chapter 14). The array provides a convenient place to collect a set of related variables, much as a struct is used in C. For example, the preferences package uses the pref array to hold all its state information. It is also a good idea to keep the use of the array private. It is better coding practice to provide exported procedures than to let other modules access your data structures directly. This makes it easier to change the implementation of your package without affecting its clients. When choosing a namespace name, try to make it significant to your application.

If you do need to export a few key variables from your module, use the underscore convention to distinguish exported variables. If you need more than one global variable, just stick with the prefix convention to avoid conflicts, or provide accessor functions instead.

The Official Tcl Style Guide

John Ousterhout has published two programming style guides, one for C programming known as The Engineering Manual and one for Tcl scripts known as The Style Guide. These describe details about file structure as well as naming conventions for modules, procedures, and variables. The Tcl Style Guide conventions use Tcl namespaces to separate packages. Namespaces automatically provide a way to avoid conflict between procedure names. Namespaces also support collections of variables without having to use arrays for grouping.

You can find these style guides on the CD-ROM and also in ftp://ftp.tcl.tk/pub/tcl/doc. The Engineering Manual is distributed as a compressed tar file, engManual.tar.Z, that contains sample files as well as the main document. The Style Guide is distributed as styleGuide.ps (or .pdf).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.36.192