パッケージとは?
 ここではTclスクリプトをパッケージにする方法を紹介しています。 C言語で拡張したライブラリをパッケージにする方法は こちらのページです。

 便利なTclプロシージャができると、 それらを使うスクリプトを作るたびにコピー&ペーストで書くのではなく、 個別のスクリプトファイルにしておき source コマンドで読みこむほうが管理も楽です。 たとえば、"1999/09/01 12:00:00" のような日時表記から例の clock secondsが返すような形式の整数の日時表記に変換する timelocal というプロシージャを作ったとします。

#
# timelocal.tcl
#
proc timelocal {ymddate {time 0:0:0}} {
    if {[regexp {([0-9]+)/([0-9]+)/([0-9]+)} \
                 $ymddate all year mon day]} {
	return [clock scan "$mon/$day/$year $time"]
    } else {
	error "illegal date format: $ymddate"
    }
}

proc isnewer {filename than} {
    if {[file mtime $filename] > [timelocal $than 0:0:0]} {
        return 1
    } else {
        return 0
    }
}
ついでにあるファイルが年/月/日表記の指定した日付より新しいかどうかを調べるプロシージャ isnewer もおまけでつけてやりました。 これを上のように独立したファイルtimelocal.tcl としておき、 どこか自分の好きなところにおいておきましょう。たとえば /usr/watasi/tcl/lib にしたとすると、
#
# app.tcl
#
source /usr/watasi/tcl/lib/timelocal.tcl

# ...
このように冒頭部に1行書くだけで timelocal プロシージャが使えるようになります。

 …というのが伝統的なプロシージャの管理方法だったのですが、 これには大きな問題点があります。もし、事情でtimelocal.tcl の置き場所を変えてしまったとすると、それをsourceコマンドで呼び出している Tclスクリプトを1つずつ全部書き換えないといけません。 これに対し古いTcl/Tkではインデックスファイルを自動的に作る機能があったり、 また置き場所を各スクリプトにべた書きするのではなく環境変数などにしておく、 などの対策もありましたが、 より統一的なプロシージャファイル(Perlでいうモジュール) の管理機構として Tcl/Tk 8.0 で パッケージという機構が整備されました。 パッケージを使うと、

  • 一度設定しておけば、 プロシージャファイルの置き場所を知らなくてもプロシージャが使える
  • どのプロシージャがどのプロシージャファイルに入っているかを調べなくてもよい
  • バージョン管理機構が備わっているので、長期の保守・拡張に適する
などの利点があります。


パッケージの作り方
 ●パッケージを宣言する
パッケージを作る手順ですが、まずプロシージャファイル内に package provideコマンドでこのファイルがパッケージになることを宣言します。
#
# timelocal.tcl
#
proc timelocal {ymddate {time 0:0:0}} {
    if {[regexp {([0-9]+)/([0-9]+)/([0-9]+)} \
                 $ymddate all year mon day]} {
	return [clock scan "$mon/$day/$year $time"]
    } else {
	error "illegal date format: $ymddate"
    }
}

proc isnewer {filename than} {
    if {[file mtime $filename] > [timelocal $than 0:0:0]} {
        return 1
    } else {
        return 0
    }
}

package provide timelocal 0.10
package provideの最初の引数は「パッケージ名」で、 普通英小文字からなる単語を使います。2つめは「バージョン番号」で、 新しくするたびに数字を増やすという約束さえ守れば、 どんなつけかたでも構いません。

●決めたディレクトリに「インストール」する
 ではこの timelocal.tcl を「インストール」します。というか、 好きなディレクトリにコピーするだけです。 オススメなのは、Tcl/Tkのライブラリがあるディレクトリか、 その上のlibディレクトリに専用のディレクトリを作り、 そこに置くようにすることです。 標準的なパスでTcl/Tkをインストールしてある場合、 Windowsなら \Program Files\Tcl\lib か \Program Files\Tcl\lib\tcl8.4 の下、UNIXなら /usr/local/lib か /usr/local/lib/tcl8.4 の下に mypkg とか common とか適当な名前でディレクトリを作ってそこにコピーします。

●pkgIndex.tcl を作る
 次に、そのディレクトリにインデックスファイル pkgIndex.tcl を作ります。これはTcl言語のpkg_mkIndex というコマンドで自動的に作れます。

shell% tclsh
% cd /progra~1/tcl/lib/mypkg
% pkg_mkIndex .
% exit
shell%
実際にできたpkgIndex.tclはこんな内容です。
# (コメントが入る)

package ifneeded timelocal 0.10 [list source [file join $dir timelocal.tcl]]
このファイルには、 なんという名前のパッケージが呼び出されたらどのファイルを読めばよいか、 またそのパッケージにはどんな名前のプロシージャが含まれているか、 という情報が書かれています。 複数のプロシージャファイルが同一ディレクトリにある場合に上の作業をすると、 ファイルそれぞれについて上のような package ifneededコマンド行が pkgIndex.tclに書き出されます。
 もちろん、 上の内容のファイルを自分でテキストエディタで書いても一向に構いません。

 Tcl/Tk処理系が起動すると、 上に書いたTcl標準のライブラリの下にあるディレクトリ内にあるパッケージは、 自動的に検索されます。 (階層的に検索されるので、 そのまたサブディレクトリ、そのまたサブディレクトリ…でもOKです) しかし、それ以外の場所、たとえば /home/watasi/lib/mypkg などに置いてしまうと、処理系はパッケージがそこにある、 ということ自体がわからないのでパッケージがロードできません。 その場合には、WindowsでもUNIXでも環境変数TCLLIBPATH にそのディレクトリをセットしておけばOKです。

set TCLLIBPATH=C:/home/watasi/lib/mypkg
Windowsでもパスは/区切りにしておきます。また、複数のディレクトリを TCLLIBPATHで指定する場合には、
set TCLLIBPATH=C:/home/watasi/lib/mypkg C:/usr/common/lib
このように空白で区切ります。
set TCLLIBPATH="C:/home/watasi/lib/mypkg C:/usr/common/lib"
ではダメなので注意が必要です。

●パッケージを使う

#
# app.tcl
#
package require timelocal

# ...
 パッケージを使うときには、timelocalとisnewer というプロシージャがtimelocalというパッケージに入っていることだけ知っていれば十分です。 上のようにpackage requireコマンドで自分はこのパッケージに含まれているプロシージャを使うよんと宣言します。 すると、このときには Tcl/Tk処理系がそのパッケージを探し、見つかればバージョン番号を返し、 見つからなければエラーを起こします。 そして最初にtimelocalかisnewerを使って処理をしようとした段階で、 実際に timelocal.tcl が自動的に source され、 その中のコードが実行されることになります。

●パッケージ機構のしくみ
 パッケージの動作はマニュアルを見てもなかなか難解ですが、 ものすごく簡単にいうとこんな感じです。

  1. Tcl/Tk処理系は、起動したときに、Tcl/Tkの標準のライブラリのパスと、 環境変数TCLLIBPATHにセットされているパスをTcl言語のグローバル変数 auto_path にリストとしてセットします。 また、package unknownという特殊なコマンドが実行され、 パッケージが見つからない場合にどんな処理をして探すかを定義してある特殊なプロシージャの名前が記憶されます。
  2. package requireコマンドが使われると、 そのパッケージがまだロードされていなければ、 package unknown によって記憶されたプロシージャが実行され、 auto_pathの各ディレクトリ以下を再帰的に降りながら、 その中のpkgIndex.tclを読んでパッケージを探します。 パッケージを定義しているpackage ifneeded行が見つかれば、 その情報を記憶してパッケージの自動ロード設定が完了します。
  3. その後最初にそのパッケージに属するコマンドが呼び出されようとした瞬間に、 自動ロードが実際に行われ、source(パッケージがTclで書かれている場合) なりload(C言語で書かれている場合)なりでコードが処理系にロードされます。
…ちょっと略しすぎですね。

やねうらの top
(first uploaded 1999/09/03 last updated 2000/07/17 , Urano398 )