Plan 9 mk
目次- 1.0.0
mk
入門 - 1.1.0 ルール
- 1.2.0 変数
- 1.3.0 ファイルの取り込み
- 1.4.0 環境変数の参照
- 1.5.0 コメント
- 2.0.0 ルールや変数を二重に定義した場合の動作
- 3.0.0 未定義の変数を使用した場合の動作
- 4.0.0 複数のファイルから1つの実行ファイルを生成する場合
- 4.1.0 メタルール
- 4.2.0 名前リスト(namelist)
- 4.3.0
$prereq
と$target
- 4.4.0
mkone
mkmany
mklib
mksyslib
- 4.5.0
mkone
を使用したmkfile
の例 - 5.0.0 複数の実行ファイルを生成する場合
- 5.1.0
mkmany
を使用したmkfile
の例 - 6.0.0
mkfile
のプロトタイプ - 7.0.0 属性
- 7.1.0
V
- 7.2.0
N
- 7.3.0
Q
- 7.4.0
D
- 7.5.0
R
- 8.0.0 コマンド出力を変数に割り付ける
- 9.0.0 応用
- 9.1.0
mkh
- 10.0.0 Unix に移植された
mk
- 11.0.0 終わりに
- 12.0.0 参考資料
- 13.0.0 参考文献
2003/11/05
Unix の make
に対する Plan 9 のツールは mk
である。Unix の make
と異なり、Plan 9 の mk
は Plan 9 のシェル rc
との調和がはかられたツールである。mk
はシンプルにして make
よりも遙かに強力なツールである。
makefile
に対するある程度の知識を持つことが想定されている。
mk
入門
mk
は mkfile
の記述に従って、更新されたファイルに対する処理を実行する。このことを簡単な例から紹介する。ルール
Plan 9 では C プログラムa.c
を IBM/PC 互換機でコンバイルするには8c a.c 8l a.8の2つを実行する。
8c a.c
によってオブジェクトファイル a.8 が生成され、8l a.8
によって実行ファイル 8.out
が生成される。(Unix でおなじみの cc
を使用しないのは Plan 9 は様々な CPU の混成システムを前提としているからである。) この場合 Unix の makefile
と同様に mkfile
の記述は8.out: a.8 8l a.8 a.8: a.c 8c a.c
譜1
と書くことができる。mkfile
は基本的にはターゲット: 依存ファイルの一覧 処理または
ターゲット:属性: 依存ファイルの一覧 処理
の形式を持ったファイル生成規則の集まりである。(以下、「ルール」と言う)
前者の形式は makefile
と同様であるが、後者の形式は makefile
には存在しない。譜1に現れたのは前者の形式である。「ターゲット」は生成するファイルの名称であり、「依存ファイルの一覧」は、生成に必要なファイルの一覧である。この一覧は空白で区切って与える。「処理」は Plan 9 の標準シェル rc
にそのまま渡される。処理の内容に関しては mk
は関知しないので、コメントであっても、単に ;
であっても、空白以外に何も書かれていなくても、構わない。「属性」に関しては後に解説する。
Unix の makefile
が処理部を TAB で始まらなければならないのに対して Plan 9 の mkfile
では単にインデントされていれば構わない。(当然の改善点です。Unix の makefile
がどうかしている。)
書き方の注意
- ターゲットは複数書いてよい。その場合、空白で区切る。
- 依存ファイルの一覧の左の
:
の後には1つ以上の空白が必要である。
変数
mkfile
は makefile
と同様に変数を定義できる。LD=8l CC=8c O=8 $O.out: a.$O $LD a.$O a.$O: a.c $CC a.c
譜2
makefile
と異なり変数の参照には$(CC)
のように ( ) を付加する必要はない。シェルで行っているのと同じシンタックスに従えばよいのである。(Unix の makefile
における変数参照の異様さは誰もが感じてきたはずである。)このような小さな
mkfile
では変数の効用は分かりづらいが、大きな mkfile
では、規則の変更を容易にするはずである。ファイルの取り込み
mk
では様々なアーキテクチャの CPU に対して共通の mkfile
が使えるように工夫されている。ここに現れる 8
は 386 アーキテクチャを意味している。/386/mkfile
には 8
を 386 と結びつける記述が含まれている。このファイルの内容は、</sys/src/mkfile.proto CC=8c LD=8l O=8 AS=8a
/386/mkfile
の内容
<
はファイルを取り込むことを意味している。(Unix makefile
の include
指示に相当する。) 譜2に述べた mkfile
の内容は</$objtype/mkfile $O.out: a.$O $LD a.$O a.$O: a.c $CC a.c
譜3
と書くことができる。ここに $objtype は環境変数で、IBM/PC 互換機では386
に設定されている。ルールを変数を使って間接的に書くことによって、いくらか分かりにくくなったが(これは慣れの問題である)、アーキテクチャの異なる CPU に対しても等しく適用できる。この利点はマルチアーキテクチャ環境では計り知れないほど大きいのである。環境変数の参照
この例に見られるとおり、mkfile
は makefile
と同様に環境変数を取り込むことができる。そして makefile
と異なり、環境変数の参照には $(HOME)
のように ( ) を付加する必要はない。コメント
# で始まる行はコメントである。
ルールや変数を二重に定義した場合の動作
次のmkfile
について考えてみよう。LD=8l CC=8c O=8 CFLAGS= $O.out: a.$O $LD a.$O a.$O: a.c $CC a.c a.$O: a.c a.h $CC $CFLAGS a.c CFLAGS=-w
譜4
ここではa.$O
と CFLAGS
が二カ所で定義されている。多重に定義された場合には、最後の定義が使用される。従ってこの内容はLD=8l CC=8c O=8 CFLAGS=-w $O.out: a.$O $LD a.$O a.$O: a.c a.h $CC $CFLAGS a.c
譜5
と同じである。ルールの多重定義は Unix の
makefile
では警告されるが Plan 9 の mkfile
では警告されない。なぜか? 後に見るように、Plan 9 の mkfile
では変数や規則が多重に定義できることが積極的に利用されているからである。
未定義の変数を使用した場合の動作
LD=8l CC=8c O=8 $O.out: a.$O $LD a.$O a.$O: a.c a.h $LIB $CC $CFLAGS a.c
譜6
この例では$LIB
と $CFLAGS
の二カ所で未定義変数の参照が行われている。この場合にはエラーにはならないで、空の文字列を値とする。この動作はシェルの未定義変数の参照の動作と同じである。
複数のファイルから1つの実行ファイルを生成する場合
3つのファイルa1.c
a2.c
a3.c
から1つの実行ファイル 8.out
を生成する処理8c a1.c 8c a2.c 8c a3.c 8l a1.8 a2.8 a3.8ついて考えよう。ここでは
8c
が3回現れているが、そのようにしたのは、8c a1.c a2.c a3.cとしたのでは1個のファイルが更新されるたびに全てのファイルのコンパイルがやり直されるからである。コンパイルのプロセスの中には明示的にはヘッダファイルが含まれていないが、ヘッダファイルが更新されると再コンバイルが必要になる。ここではヘッダファイル
a.h
が使用されているものとする。このような問題は mkfile
の中では</$objtype/mkfile OFILES=a1.$O a2.$O a3.$O HFILES=a.h $O.out: $OFILES $LD $prereq %.$O: %.c $HFILES $CC $stem.c
譜7
と表現できる。メタルール
ルール%.$O: %.c $CC $stem.cの中には記号
%
が現れている。この部分は、拡張子 $O
を持つファイルが拡張子 c
を持つファイルから $CC $stem.c
によって生成されることを mk
に教えている。この部分は1つ1つ書くとa1.$O: a1.c $CC a1.c a2.$O: a2.c $CC a2.c a3.$O: a3.c $CC a3.cとなる所である。
$stem
は %
の内容そのものであるが、%.$O: %.c $CC %.cと書けないのは、シェルのシンタックスに違反するからである。
このようなファイル名のパターンに応じた処理規則をメタルールと言う。メタルールにおける
%
はシェルのワイルドカード *
とよく似た働きをしている。但し%
は *
と異なり一文字以上にマッチする(*
は 0 文字以上にマッチする)。 マッチした文字列は stem と呼ばれる。mk
は make
と異なり、暗黙の生成規則は存在しない。つまり make
が a.o
は a.c から cc によって生成されることを知っているのに対して、mk
は何も知らない。全て mkfile
の中で明示的に知らせるのである。mk
はそのための強力な方法を持っている。これによって mk
は make
にない柔軟性を持つことになる。
- 例1:
%.8
はa.8
やfoo.8
にマッチする。この時の stem は各々a
とfoo
である。
- 例2:
a%.8
はalice.8
にマッチし、その stem はlice
であるが、a.8
やfoo.8
にはマッチしない。
%
は1つだけしか使えない
メタルールにおけるパターン表現は %
の他に &
と正規表現が存在する。&
は %
と同様の使い方をするが、%
と異なり、/
と .
にはマッチしない。正規表現については後に「属性」のところで解説する。
名前リスト(namelist)
譜7は</$objtype/mkfile SRC=a1.c a2.c a3.c OFILES=${SRC:%.c=%.$O} HFILES=a.h $O.out: $OFILES $LD $prereq %.$O: %.c $HFILES $CC $stem.c
譜8
とも表現できる。ここではオブジェクトファイルではなくSRC
によってよって与えられたソースファイルによってコンバイル対象が定められている。すなわち、OFILES=${SRC:%.c=%.$O}によって
OFILES
に a1.8 a2.8 a3.8
が割り当てられる。(コンパイルの対象をこのようにソースファイルによって与えた方が親しみやすいかもしれない。)名前リストとは
${変数: A%B=C%D}の形式の名前の変換規則である。ここに
A
B
C
D
は文字列である。名前リストにおける記号 %
はメタルールでも使用されているが、メタルールの場合と異なり、0文字以上の任意の文字列にマッチする。
$prereq
と $target
$prereq
は依存ファイルの一覧を表している。$O.out: $OFILES $LD $prereqであれば、
$prereq
は $FILES
に一致するが、一般には $O.out
は $OFILES
だけではなくてライブラリなどにも依存するはずである。$LIB
を $O.out
を作成するのに使用されるライブラリの一覧とすれば$O.out: $OFILES $LIB $LD $prereqこの場合の
$prereq
は $OFILES
と $LIB
の値の両方を含むことになる。mk
では以上の他に特殊変数として $target
が準備されている。この値はターゲットファイルそのものである。これを使用して $LD
の出力ファイルを明示的に書くと、$O.out: $OFILES $LIB $LD -o $target $prereqとなるであろう。
$target
はなくてもよいような気がするのだが... この例では$O.out: $OFILES $LIB $LD -o $O.out $prereqでも構わないのだ。
mkone
mkmany
mklib
mksyslib
Plan 9 のシステムには定型的な処理内容を書き表したファイル/sys/src/cmd/mkone
/sys/src/cmd/mkmany
/sys/src/cmd/mklib
/sys/src/cmd/mksyslib
TARG
OFILES
HFILES
LIB
BIN
/sys/src/cmd/mkone ...
を読んで貰いたい。
mkone
を使用した mkfile
の例
1個の実行ファイルを生成する場合には /sys/src/cmd/mkone
が利用できる。これを利用した場合の典型的な mkfile
の書き方は次のようなものである。</$objtype/mkfile TARG=a SRC=a1.c a2.c a3.c HFILES=a.h LIB= BIN=$home/bin/$objtype OFILES=${SRC:%.c=%.$O} </sys/src/cmd/mkone CFLAGS=-w
譜9
この場合、次ぎのようにしてコンバイル、インストール、生成ファイルの削除が行われる。mk
mk install
mk clean
$home/bin/386
でインストール名は a
となる。
複数の実行ファイルを生成する場合
a1.c
a2.c
a3.c
から 8.a
を、b1.c
b2.c
から 8.b
を生成する問題。これらが同一のディレクトリに置かれ、1つの mkfile
で記述されると言うことは、2つの組(a1.c
a2.c
a3.c
と b1.c
b2.c
) が完全に分離しておらず、共通項が存在する事が多い。ここでは共通のヘッダファイル ab.h
が使用されているものとする。こうした問題に対しては、いろいろな考え方、書き方が存在する。初等的な方から議論しよう。</$objtype/mkfile TARG=a b HFILES=ab.h SRC1=a1.c a2.c a3.c SRC2=b1.c b2.c PROGS=${TARG:%=$O.%} OFILES1=${SRC1:%.c=%.$O} OFILES2=${SRC2:%.c=%.$O} all:V: $PROGS $O.a: $OFILES1 $LD -o $target $prereq $O.b: $OFILES2 $LD -o $target $prereq %.$O: %.c $HFILES $CC $CFLAGS $stem.c CFLAGS=-w
譜10
名前リストによって$PROGS
$OFILES1
$OFILES2
には次のようになる。PROGS=$O.a $O.b OFILES1=a1.$O a2.$O a3.$O OFILES2=b1.$O b2.$Oこの例では属性
V
が現れている。この V は仮想(virtual)ターゲットを意味している。このターゲットは実際のファイルを意味していない。従って処理部を必要としていない。(処理部を書かなくてもエラーにはならない)makefile
では all
を使用するとき、名前が all
のファイルやディレクトリが存在しないものと暗に仮定されている。mk
の作者はそのようないい加減さを嫌ったのであろう。
生成する実行ファイルの名前がファイル名で与えられていれば(普通はそうであろう)、そしてa2.c
a3.c
b2.c
に重複した関数名が存在しなければ、もっと簡潔な別の書き方が存在する。a1.c
b1.c
が関数 main
を含むとせよ。そして $O.a1
$O.b1
を生成目標とせよ。
TARG=a1 b1 HFILES=ab.h SRC=a2.c a3.c b2.c PROGS=${TARG:%=$O.%} OFILES=${SRC:%.c=%.$O} all:V: $PROGS $O.%: %.$O $OFILES $LD -o $target $prereq %.$O: %.c $HFILES $CC $CFLAGS $stem.c CFLAGS=-w
譜11
生成される実行ファイルは膨れあがらないか? 心配無用である。Plan 9 のリンカーは使用していない関数をリンクしない。
mkmany
を使用した mkfile
の例
/sys/src/cmd/mkmany
は譜11の考え方で構成されている。従って</$objtype/mkfile TARG=a1 b1 HFILES=ab.h SRC=a2.c a3.c b2.c OFILES=${SRC:%.c=%.$O} </sys/src/cmd/mkmany CFLAGS=-w
譜12
mkfile
のプロトタイプ
筆者は次の mkfile
のプロトタイプを準備して、これを修正して使用している。# mkfile template </$objtype/mkfile TARG=a SRC=a.c b.c OFILES=${SRC:%.c=%.$O} HFILES= LIB= #BIN=/$objtype/bin BIN=$home/bin/$objtype # Select one of four: </sys/src/cmd/mkone #</sys/src/cmd/mkmany #</sys/src/cmd/mklib #</sys/src/cmd/mksyslib CFLAGS=
譜13
属性
既に述べたようにルールはターゲット:属性: 依存ファイルの一覧 処理と書くこともできる。以下に
mkfile
で許されている属性を纏める。V
属性 V
については既に述べたように仮想ターゲットを意味する。N
属性 N
はエラーが発生しても mkfile
の処理を継続することを意味する。
Q
mk
は特に指定しない限り mkfile
の処理部を標準出力に書き出す。属性 Q
はその出力を押さえる。
D
属性 D
は、処理に失敗したときにターゲットを削除する。この機能はターゲットの生成がリダイレクションによって与えられる時に必要になる。
R
属性 R
は正規表現(regular expression) がターゲットの表現で使用されていることを意味する。例
メタルールの
%.$O:%.cは
(.+)\.$O:R: \1.cと同じである。正規表現は極めて強力であるが筆者は
mkfile
の中での使用経験はない。
コマンド出力を変数に割り付ける
mk
は変数の変換に関して極めて強力な方法を持っている。すなわち、変換を行うに当たって任意の Plan 9 のコマンドを利用できるのである。変数=`{コマンド}ここに「コマンド」は Plan 9 のコマンドであり、シンタックスはPlan 9 の標準シェル
rc
に従う。U
を何か値を割り付けられた変数、A
B
C
D
を文字列とするとV=${U:A%B=C%D}は次のように書ける
V=`{echo $U | sed 's/A(.*)B/C\1D/'}
応用
mkh
筆者は Web のページを作成するのに HTML 文書を生成するツールを使用している(参照)。 これによって Web ページの作成がうんとやりやすくなった。生成の元になるのは筆者が TT 形式と呼んでいるファイルでファイル名は .tt
で終わる。ディレクトリの中には多数の TT 形式のファイルが存在し、更新されたものはコマンド1つで全て HTML 形式に変換できれば理想的である。筆者は文書の作成は Mac OSX を使用している。(フロントエンドとしては Mac OSX が一番良くできていると思う。NeXT に比べて改悪された部分も多くて残念だが... )
最近 Plan 9 の
mk
が Unix に移植されたので、早速 Unix でも mk
を使用することにした。以下は筆者が作成した mk
スクリプトである。mkfile
をディレクトリごとに作らなくてもよいのが自慢である。#!/usr/local/plan9/bin/mk -f /Users/arisawa/bin/mkh T=`{ls *.tt} S=${T:%.tt=%.html} P=t2h all:VQ: $S %.html:D: %.tt $P $stem.tt >$target clean:V: rm $S
譜14. /Users/arisawa/bin/mkh
mkh
であり、/Users/arisawa/bin
に置かれている。このファイルは実行ファイルであり、mk
を実行し、自分自身をそのデータ(mkfile) として与えている。この中に現れる t2h
は TT 形式のファイルを HTML 形式のファイルに変換するツールである。mkh
のおかげで、ディレクトリごとに mkfile
を作らなくても、単にそのディレクトリで mkh
を実行すれば、そのディレクトリの全ての HTML ファイルが TT の更新に応じて更新されるようになった。Unix の
make
では、このような仕掛けは作ることができない。貧弱すぎるからである。Unix に移植された mk
From: "Russ Cox" <rsc@swtch.com> Date: 2003.10.14 03:31:08 Japan To: 9fans@cse.psu.edu Subject: [9fans] plan 9 ports to unix (including libdraw) Reply-To: 9fans@cse.psu.edu I've been using Unix (FreeBSD) for day-to-day work for a few weeks now, and have started to bring over some Plan 9 tools to make life a bit more hospitable. Mk was already done, and Scott Schwartz did sam over the summer. ... http://pdos.lcs.mit.edu/~rsc/software/plan9/ Russ
Mac OSX で筆者がコンパイルしたものはここ
終わりに
この記事ではライブラリのコンパイルに特徴的な問題の解説を省略した。従ってmklib
と mksyslib
の使い方を取り上げていない。これらの問題に関心ある読者は以下の mk.pdf
を読んで貰いたい。これは参考文献の PDF 版である。
参考資料
参考文献
- Andrew G. Hume: "Maintaining Files on Plan 9 with mk", (Plan 9 - The documents - Volume Two, Second Edition)