Logo address

Plan 9 mk

目次

2003/11/05
Unix の make に対する Plan 9 のツールは mk である。Unix の make と異なり、Plan 9 の mk は Plan 9 のシェル rc との調和がはかられたツールである。mk はシンプルにして make よりも遙かに強力なツールである。

注意: この記事の読者は Unix の makefile に対するある程度の知識を持つことが想定されている。

mk 入門

mkmkfile の記述に従って、更新されたファイルに対する処理を実行する。このことを簡単な例から紹介する。

ルール

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 がどうかしている。)

書き方の注意

変数

mkfilemakefile と同様に変数を定義できる。
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 makefileinclude 指示に相当する。) 譜2に述べた mkfile の内容は
</$objtype/mkfile
$O.out: a.$O
	$LD a.$O
a.$O: a.c
	$CC a.c

譜3

と書くことができる。ここに $objtype は環境変数で、IBM/PC 互換機では 386 に設定されている。ルールを変数を使って間接的に書くことによって、いくらか分かりにくくなったが(これは慣れの問題である)、アーキテクチャの異なる CPU に対しても等しく適用できる。この利点はマルチアーキテクチャ環境では計り知れないほど大きいのである。

環境変数の参照

この例に見られるとおり、mkfilemakefile と同様に環境変数を取り込むことができる。そして 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.$OCFLAGS が二カ所で定義されている。多重に定義された場合には、最後の定義が使用される。従ってこの内容は
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 と呼ばれる。

mkmake と異なり、暗黙の生成規則は存在しない。つまり makea.o は a.c から cc によって生成されることを知っているのに対して、mk は何も知らない。全て mkfile の中で明示的に知らせるのである。mk はそのための強力な方法を持っている。これによって mkmake にない柔軟性を持つことになる。

注意1: ファイル名のパターン表現の中で % は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}
によって OFILESa1.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 ... を読んで貰いたい。

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

この場合、次ぎのようにしてコンバイル、インストール、生成ファイルの削除が行われる。
この場合、インストール先は $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.cb1.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)ターゲットを意味している。このターゲットは実際のファイルを意味していない。従って処理部を必要としていない。(処理部を書かなくてもエラーにはならない)
Unix の 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 で筆者がコンパイルしたものはここ

注意: Unix 版の mk はコマンドの実行部分に sh を使っています。たいていの応用では Plan 9 の rc との互換性がありますが、複雑な命令ではシンタックスの違いが発生します。

終わりに

この記事ではライブラリのコンパイルに特徴的な問題の解説を省略した。従って mklibmksyslib の使い方を取り上げていない。これらの問題に関心ある読者は以下の mk.pdf を読んで貰いたい。これは参考文献の PDF 版である。

参考資料

参考文献