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コマンドは思ってしまうんだ。
f:id:itouhiro:20121001021637p:plain


といっても'all'という名のファイルは存在しないのが普通だけどね。もしallというファイルがあっても動作させたいなら、

.PHONY : all

Makefileに書けばいい。
f:id:itouhiro:20121001021706p:plain



[合いの手] $(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の記号というよりシェルスクリプトの記号で、( )の中でカレントディレクトリを変えても、()の外に出るとディレクトリが()に入る前に戻る、というものだ。
f:id:itouhiro:20121001021723p:plain


[合いの手] ふーむ、つまり 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*"

f:id:itouhiro:20121009190152p:plain


これ、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*

f:id:itouhiro:20121009190408p:plain


[合いの手] (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でも実は同じく実行されるのだ。


確かに書いてある。
「+=」は文字列連結だよね。