M+フォントのビルドスクリプト構成を調査する
M+フォントはオープンソースで、すべての作成素材が公開されている。しかしその構造や動作を説明する文書は存在しないようだ。
説明がないから、動作を追ってみていくしかないのかな
http://sourceforge.jp/cvs/view/mplus-fonts/mplus_outline_fonts/ でソースの変更履歴を見れるぞ。
とりあえず、トップ(最上位)ディレクトリから見ていこう。
bdf.d/ cvs/ doc/ eps.d/ release/ sample.d/ scripts/ svg.d/ ucstable.d/ .cvsignore Makefile
この文字末尾に「/」がついているのはディレクトリ(フォルダ)だ。
ついてないのがファイル。
あと、「cvs/」というディレクトリと「.cvsignore」というファイルは履歴管理のために勝手に作られるファイルだ。無視していい。
ということは、このトップでは「Makefile」というのだけがファイルで、あとはディレクトリだね。
Makefile
Makefileというのは makeコマンドが読み込むテキストファイルだ。
詳しくは google:Makefile で説明を読むといい。
さっそく中身を見ていくぞ。まずは
UNABRIDGED_GROUPS:= hiragana1 katakana1 miscellaneous1 \ hiragana2 katakana2 miscellaneous2 \ katakana_half1 \ latin_proportional1 latin_proportional2 \ latin_clear1 latin_clear2 \ latin_fullwidth1 latin_fullwidth2 \ latin_full_clear1 latin_full_clear2
という行だ。
これは変数 UNABRIDGED_GROUPS に文字列を代入している。
代入される文字列は
"hiragana1 katakana1 miscellaneous1 hiragana2 katakana2 miscellaneous2 katakana_half1 latin_proportional1 latin_proportional2 latin_clear1 latin_clear2 latin_fullwidth1 latin_fullwidth2 latin_full_clear1 latin_full_clear2" だ。単語を半角空白で区切った1かたまりの文字列なのだ。
:= という記号は?
ほかのプログラミング言語でいう代入記号 = だ。http://www.ecoop.net/coop/translated/GNUMake3.77/make_6.jp.html#SEC58
に書いてある「単純展開変数」。
要するに makeファイルの「=」は「再帰展開」でちょっと扱いにくいので、もっと単純なのがほしい人のための代入記号。http://d.hatena.ne.jp/m-hiyama/20071024/1193202017 によると「:=」は変数への代入で、「=」は実は関数への代入だそうだ。
単なる変数代入かあ。
ifdef MPLUS_FULLSET UNABRIDGED_GROUPS+= ${OPTIONAL_GROUPS} endif
ifdef のところは変数が定義されているか調べるよ。
もしMakefileに
MPLUS_FULLSET=aiueo
とか、この変数に何かの文字列が入っていれば、endifまでの処理が実行されるよ。
ちなみに
MPLUS_FULLSET=
のカラ文字列の場合はダメだ。中身が必要なんだ。
でもこのMakefileの中では定義されてないよ。
その場合でもmakeコマンドを実行するときに
$ make MPLUS_FULLSET=abcde
とか指定すればいいんだ。
実際
http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/download/
で
$ MPLUS_FULLSET=yes make SPLIT_CONCURRENCY=2 -j2
と書いてあるだろ。
これは
$ make MPLUS_FULLSET=yes SPLIT_CONCURRENCY=2 -j2
と入力しても同じだよ。このyesは別にyでもnot_at_allでも実は同じく実行されるのだ。
確かに書いてある。
記号「+=」は文字列連結だね。つまりこの箇所は「もし変数 MPLUS_FULLSET がカラでなければ、変数 UNABRIDGED_GROUPS に、変数 OPTIONAL_GROUPS を追加しろ」ということ。
変数 UNABRIDGED_GROUPS の中身は文字列で
"hiragana1 katakana1 miscellaneous1 hiragana2 katakana2 miscellaneous2 katakana_half1 latin_proportional1 latin_proportional2 latin_clear1 latin_clear2 latin_fullwidth1 latin_fullwidth2 latin_full_clear1 latin_full_clear2"
だったから、それに変数 OPTIONAL_GROUPS の中身
"kanji/k1 kanji/k2 kanji/k3 kanji/k4 kanji/k5 kanji/k6 kanji/j1 kanji/j2 kanji/j3 kanji/j4 kanji/j5 kanji/l100 kanji/l101 kanji/l102 kanji/l103 kanji/l104 kanji/l105 kanji/l200 kanji/l201 kanji/l202 kanji/l203 kanji/l204 kanji/l205 kanji/l206 kanji/l207 kanji/l208 kanji/l209 kanji/l210 kanji/l211 kanji/l212 kanji/l213 kanji/l214 kanji/l215 kanji/l216"
という文字列を足すわけか。長い文字列だなあ。
次はここだね。
all: @($(MAKE) split-svgs ; $(MAKE) rebuild-ttf)
allという実行ファイルを作るのか?
この箇所は以下の効果がある。
http://d.hatena.ne.jp/sternheller/20090125/1232863249
複数の実行ファイルを一気に生成するMakefileの書き方‥‥
makeは引数なしで実行した場合、Makefile中の最初のTARGETのみコンパイルします。これを逆手にとって、最初のTARGETのコンパイルに他の全てのTARGETのコンパイルが必要であると指定してやればいいのです。上記の例では、allがダミーのTARGETになります。allをコンパイルするにはTARGET1とTARGET2が必要ということになるので、無事全てのTARGETがコンパイルされることになります。
all という語はよく使われる慣習的なダミーターゲット( .PHONY とMakefileでいうらしい http://www.ecoop.net/coop/translated/GNUMake3.77/make_4.jp.html#SEC32)。
ちなみに、もし all という名前のファイルが同じディレクトリにあれば、ビルドが進まずに停止する。Makefileの基本は、
ファイルA : ファイルB ファイルBなどを材料にして、ファイルAを作る作業 ファイルBなどを材料にして、ファイルAを作る作業..
だから今回の場合は、
- ファイルAがないか、
- ファイルAの更新時刻がファイルBより古い
ならば「ファイルBも材料にして、ファイルAを作る作業」を実行してくれるけど、実際には
- ファイルBがなくて
- ファイルA にあたる 'all' がある
から、もうやることないとmakeコマンドは思ってしまうんだ。
といっても'all'という名のファイルは存在しないのが普通だけどね。もしallというファイルがあっても動作させたいなら、
.PHONY : all
とMakefileに書けばいい。
$(MAKE) は?
これは変数MAKEの中身を取り出す書き方だ。変数MAKEはMakefileには定義がないけど、中身には make という文字列が入っている。
参考:
「makeの再帰利用」http://www.ecoop.net/coop/translated/GNUMake3.77/make_5.jp.html#SEC49
「Makefileにおけるユーティリティ」 http://www.ecoop.net/coop/translated/GNUMake3.77/make_14.jp.html#SEC114 にあるように、makeという直接な文字列ではなく $(MAKE) を使え、というルールらしい。
@( ) はなんだろう
@ はエコーバック抑止だ。参考: http://www.ecoop.net/coop/translated/GNUMake3.77/make_5.jp.html#SEC44
( ) はMakefileの記号というよりシェルスクリプトの記号で、( )の中でカレントディレクトリを変えても、()の外に出るとディレクトリが()に入る前に戻る、というものだ。
ふーむ、つまり makeコマンドを実行すると毎回必ず
make split-svgs ; make rebuild-ttf
を実行してくれるということか。
make split-svgs は
split-svgs: dirs perl -I $(SCRIPTS) $(SCRIPTS)/split-svg.pl $(SPLIT_CONCURRENCY) ${SVGFILES}
の部分を実行する。
今回は dirs というファイルも split-svgs というファイルも存在しないので、perlの部分は実行される。
「split-svgs:」の後に書いてある dirs という部分は依存ファイルというヤツだよね。
make dirs は実行されるんだろうか? 実行されるとしたら、perlの部分より前に実行されるんだろうか、それとも後?
簡単なスクリプト作って、実際に挙動を試してみるか
all: @echo "*all start*" @($(MAKE) test1; $(MAKE) test2) @echo "*all end*" test1: test3 @echo "*test1*" test3: test5 @echo "*test3*" test5: @echo "*test5*" test2: @echo "*test2*"
これ、makeコマンドを実行すると最初のallというターゲットだけを実行して終了する。つまり make all だけ実行して終了だ。
make all は make test1 と make test2 だけを実行する。ところが make test1は test3に依存しているのだ‥‥。make test3 は実行されるのか?
結果はこうなった。
% make *all start* make[1]: Entering directory `/home/foo/make20121009' *test5* *test3* *test1* make[1]: Leaving directory `/home/foo/make20121009' make[1]: Entering directory `/home/foo/make20121009' *test2* make[1]: Leaving directory `/home/foo/make20121009' *all end*
(make test1 が依存している)make test3 のほうが、make test1 より先に実行されてる。なるほどー。
test1: test3 @echo "*test1*"
と書いてあれば、「@echo "*test1*"」より先に make test3を実行するのか。これは勉強になった。
あと、@( ) に囲まれた部分が実行されたとき「Entering directory‥‥」とか表示されるんだね。
ここまでの理解だと
split-svgs: dirs perl -I $(SCRIPTS) $(SCRIPTS)/split-svg.pl $(SPLIT_CONCURRENCY) ${SVGFILES}
とあるとき、make dirs をまず実行するということだな。
make dirsの中身は
dirs: for w in $(NORMAL_WEIGHTS) ; do \ for g in $(GROUPS) ; do \ mkdir -p work.d/splitted/$$w/$$g/ ;\ done ;\ for t in $(TARGETS); do \ mkdir -p work.d/targets/$$t/$$w/ ;\ done \ done for w in $(BLACK_WEIGHTS) ; do \ for g in $(UNABRIDGED_GROUPS) ; do \ mkdir -p work.d/splitted/$$w/$$g/ ;\ done ;\ for t in $(TARGETS); do \ mkdir -p work.d/targets/$$t/$$w/ ;\ done \ done
mkdir しているだけだね。
$$w とか $$g ってなんだろ?
$w には変数NORMAL_WEIGHTSの中身「bold medium regular light thin」のうち、どれか一つが入るのだが‥‥。なんで $w じゃなくて $$wと書かれているのかはよくわからない、今回はスルーするぞ。
次は
split-svgs: dirs perl -I $(SCRIPTS) $(SCRIPTS)/split-svg.pl $(SPLIT_CONCURRENCY) ${SVGFILES}
のperlの行だ。
これは
perl -I scripts scripts/split-svg.pl 2 svg.d/*/*.svg svg.d/*/*/*.svg
のようになるぞ。scripts/split-svg.pl の動作は解析してないが、35文字くらい収録されているsvgファイルを読み込んで、1文字ごとに切り出して、ウェイト(太さ)と文字種別に分かれたフォルダの中に、 work.d/splitted/regular/latin_proportional1/u0021.svg のように保存する。
この次はなんだっけ?
「make split-svgs ; make rebuild-ttf」のうちの make split-svgs が終わったので、次は make rebuild-ttf さ。
rebuild-ttf: @($(MAKE) clean-targets ; $(MAKE) dirs ; $(MAKE) ttf)
make clean-targets; make dirs; make ttf || か。make dirsはすでに実行済みだから make clean-targets実行してから make ttf だね。 make clean-targets はこれだけ。 >|make| clean-targets: @rm -rf work.d/targets/
前回のビルドで生成した work.d/targets/mplus-1p/regular/mplus-1p-regular.ttf とかのファイルを削除するよ。
次に
ttf: mplus-1p mplus-2p mplus-1m mplus-2m mplus-1c mplus-2c mplus-1mn # mplus-2mn
# 以下はコメントなので無効だから、
make mplus-1p make mplus-2p make mplus-1m make mplus-2m make mplus-1c make mplus-2c make mplus-1mn
を実行するということだな。
が、私も簡単に説明してみるぞ。
簡単にいうと、以下のような記述が並んでいる。
ターゲット: 材料 材料に加える作業 材料に加える作業 ...
ここで最終的に作りたいもの(ここではターゲットと呼ぶぞ)を「カレーライス」としよう。材料はカレーソースとライスだ。
材料っていうのはふつう、カレールーやにんじん、たまねぎ、肉、じゃがいも、とかを言うんだけど‥‥
じゃあ、こうなるな。
カレーライス : カレーース ライス ライスとカレーソースを一つの皿であわせる。 カレーソース : ニンジン タマネギ 肉 ジャガイモ カレールー 肉、ニンジン、タマネギを炒める。 水をいれる。 煮る。 ジャガイモを入れる。 カレールーを割って入れる。 煮込む。 ライス : 米 米を研ぐ。 米を炊飯器で炊く。 米 : もし台所に備蓄がなければ、スーパーに買いに行く。 ニンジン : もし冷蔵庫に備蓄がなければ、スーパーに買いに行く。 タマネギ : もし冷蔵庫に備蓄がなければ、スーパーに買いに行く。 肉 : もし冷蔵庫に備蓄がなければ、スーパーに買いに行く。 ジャガイモ : もし冷蔵庫に備蓄がなければ、スーパーに買いに行く。 カレールー : もし冷蔵庫に備蓄がなければ、スーパーに買いに行く。
で、このカレー調理がmakeコマンドとなんの関係があるんだっけ?
これは「依存関係」をあらわしたテキストになってるんだ。
にんじん、たまねぎ、肉、じゃがいも、米とカレールーが揃っていても、
いきなりカレーソースをライスにかけることはできないだろ?
?? そりゃ まずはカレーソースを作ってからでないと、載せられないよ
つまり、カレーライスを作るにはカレーソースとライスが必要。しかしカレーソースを作るには肉、ニンジン‥が必要。というふうに必要となるものが連なっている。
さっき
ターゲット: 材料 材料に加える作業 材料に加える作業 ...
と書いたけど、実際のパソコンで使うMakefileは
ターゲット: 依存ファイル コマンド行 コマンド行 ...
という説明になっている。
「依存ファイル」というのは、ターゲットを作るために必要なファイル、つまりターゲットは「依存ファイル」に依存しているわけだ。
なんか説明がややこしくなってるよ。カレーの例のほうがいいや。
ところで、Makefileを作るとき、いちいち全部の文を書いてたらめんどうなので、ラクをするために記号が導入されたんだ。
ターゲットは「$@」と書けばいいし、依存ファイルすべては「$^」と書ける。依存ファイルの最初のひとつは「$<」。
この記号を使うと‥
カレーライス : カレーソース ライス $^を一つの皿であわせる。 カレーソース : 肉 ニンジン タマネギ ジャガイモ カレールー &<を炒める。ニンジン タマネギを炒める。 煮る。 ジャガイモ、カレールーを入れる。 煮込む。 ライス : 米 $^を研ぐ。 $^を炊飯器で炊く。
え゛ー。読みにくくなってるよー。ラクになってないよ。
まあ確かに慣れないと読みにくいな。この例では使う意味があまりないってのもあるが。
でも読むのはちょっと面倒になるが、Makefileを書いたり管理するのはラクになるんだ。ファイル名を少し変えるたびに、あちこち書き直すのは、修正ミスが発生するだろ。この記号を使っておけば、書き直すところが減るわけ。
あー、そういう「ラク」なのか。
同じくラクをするために、マクロとか変数と呼ばれているものもある。
変数を使って書き換えると‥
FRIED = ニンジン タマネギ 肉 BOILED = ジャガイモ カレールー SOUP = カレーソース 夕食 : $(SOUP) ライス $^を一つの皿であわせる。 $(SOUP) : $(FRIED) $(BOILED) $(FRIED) を炒める。 煮る。 $(BOILED)を入れる。 煮込む。 ライス : 米 $^を研ぐ。 $^を炊飯器で炊く。
と、なるぞ。
確かに「ニンジン タマネギ 肉」と毎回書くよりは、書くのがちょっとラクかも‥。
それだけじゃない。
いったん変数にすると、makeコマンドを実行するときに変数の中身を変更できるのだ。
たとえばさっきのMakefile
$ make
とすればカレーライスができあがるが、
$ make FRIED="タマネギ 肉" BOILED="赤ワイン ドミグラスソース" SAUCE="ハヤシソース"
とすると、同じ調理法というか同じMakefileなのに、ハヤシライスができあがってしまうのだ!
な、なんだってーΩΩ
そうか、Makefileを書き換えなくてもいいのは確かにMakefile書く人や管理する人にはラクかも。
さて、荒っぽい説明だがMakefileの説明はこんなところ。
パソコンのファイルをビルド(作成)するのもこれと要領は同じさ。
さっきのMakefileを見てみよう。
http://sourceforge.jp/cvs/view/mplus-fonts/mplus_outline_fonts/Makefile?revision=1.24&view=markup
最初のほうにある
UNABRIDGED_GROUPS:=
というのは変数定義だね。
「:=」というのは?
http://www.ecoop.net/coop/translated/GNUMake3.77/make_6.jp.html#SEC58
に書いてある「単純展開変数」だ。
要するに makeファイルの「=」は「再帰展開」でちょっと扱いにくいので、もっと単純なのがほしい人のための代入記号。
単なる代入かあ。
これは?
ifdef MPLUS_FULLSET UNABRIDGED_GROUPS+= ${OPTIONAL_GROUPS} endif
ifdef のところは変数が定義されているか調べるよ。
もしMakefileに
MPLUS_FULLSET=abcde
とか、この変数を定義して何かの文字列が入っていれば、endifまでの処理が実行されるよ。
ちなみに
MPLUS_FULLSET=
のカラ文字列の場合はダメだ。中身が必要なんだ。
でもこのMakefileの中では定義されてないよ。
その場合でもmakeコマンドを実行するときに
$ make MPLUS_FULLSET=abcde
とか指定すればいいんだ。
実際
http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/download/
で
$ MPLUS_FULLSET=yes make SPLIT_CONCURRENCY=2 -j2
と書いてあるだろ。
これは
$ make MPLUS_FULLSET=yes SPLIT_CONCURRENCY=2 -j2
と入力しても同じだよ。このyesは別にyeeesでもnot_at_allでも実は同じく実行されるのだ。
確かに書いてある。
「+=」は文字列連結だよね。