Edited at
PerlDay 22

ビルドツールの裏側にいるMakefileを覗いてみる

More than 1 year has passed since last update.

この記事は1日遅れですが、Perl Advent Calendar 2017の22日目の記事です。

21日目は@booksさん今年Perlで困ったことでした。地道に困ったことと、解決策を記録していくって、意外とできないんですが、実は凄く大事なことですよね!


先日、VOYAGE GROUPの方とお話した際に、環境セットアップは色々なツールをmakeコマンドでラップしているので、全てmake installで完結するようになっている、ということを伺って、改めてmakeコマンドについて調べてみました。

ちょっと古いですが、1012年のVOYAGE GROUPのテックブログにmakeを使っている、という話題が載っています(あとはajitofmでも聴いたような…)。

超便利!Makefileを作ってmakeするのは想像よりもずっと簡単だった

そんなmakeですが、PerlのビルドツールであるExtUtils::MakeMakerも内部ではmakeコマンドを使っています。

今回は、どんなふうにmakeが使われているか、その裏側を覗いてみます。


Perlのモジュールインストールをおさらい

まず、そもそもビルドツールがどこで使われているか、そこからおさらいします。

Perlのモジュールのインストールといえば、最近ではcpanコマンド(cpanmや、cpmも有りますね)で実行するものになっていて、コマンド1つやるもの、という状況ですが、今でも歴史あるモジュールのREADMEのインストール方法にはtar.gzのアーカイブファイルをダウンロードしてきて、以下のコマンドを実行するように書かれています。

$ perl Makefile.PL

$ make
$ make test
$ make install

これはPerlのビルドツールであるExtUtils::MakeMakerがモジュールのビルド/インストールに必要なMakefileを生成し、あとはmakeコマンドにまかせて進める、という仕組みによるものです(だからperlは一回しか出てこない)。

一方で、pure perlで書かれたビルドツールであるModule::Buildだと、モジュールのインストール方法として以下のように書かれているはずです。

$ perl Build.PL

$ ./build
$ ./build test
$ ./build install

Makefileを生成せず、perlで書かれたbuildを生成し、そのコマンドからビルド/インストールを実行しています。

この辺の事情は、以下の記事が参考になるでしょう。

第23回 Module::Build:MakeMakerの後継者を目指して:モダンPerlの世界へようこそ

ただし、Module::Buildは、コアモジュールのスリム化の一環で現在(Perl 5.22以降)では、コアモジュールから外れています(当然、メンテナンスは継続しています)。


生成されたMakefileの中身を見る

元々ExtUtils::MakeMakerはC言語で書かれた拡張(XSモジュール)をビルドして、インストールするために作られた、という経緯がありますので、まずはXSモジュールで見てみましょう。


JSON::XS

pure perl版とXS版が別ディストリビューションになっている方が分かりやすいので、ここではJSON::XSを例に見てみます。

アーカイブファイルをダウンロードし、展開したら、展開したディレクトリへ移ります。

$ curl -O https://cpan.metacpan.org/authors/id/M/ML/MLEHMANN/JSON-XS-3.04.tar.gz

$ tar xvf JSON-XS-3.04.tar.gz
$ cd JSON-XS-3.04

Makefile.PLの中身がMakefileの元となる情報ですが、意外と情報量は少なめです。

WriteMakefile(

dist => {
PREOP => 'pod2text XS.pm | tee README >$(DISTVNAME)/README; chmod -R u=rwX,go=rX . ;',
COMPRESS => 'gzip -9v',
SUFFIX => '.gz',
},
EXE_FILES => [ "bin/json_xs" ],
VERSION_FROM => "XS.pm",
NAME => "JSON::XS",
PREREQ_PM => {
common::sense => 0,
Types::Serialiser => 0,
},
CONFIGURE_REQUIRES => { ExtUtils::MakeMaker => 6.52, Canary::Stability => 0 },
);

distはディストリビューションとしてパッケージングする時に使用する情報なので、インストールに使う情報としては実行コマンドをインストールするためのEXE_FILES程度です(REQUIRESは依存モジュールの事前チェック用で、インストール自体には影響しません)。

あとは、ディレクトリに配置されているファイルをイイ感じに解析して、必要なコマンドを生成してくれるようになっています。

では$ Perl MakefileMakefileを生成してみましょう(途中でCanary::Stabilityモジュールのメッセージが出ますが、yで先に進んで下さい)。

Makefileの中を見始めると、さまざまな変数定義が続いていて、どこから読み進めればいいのかわかりづらいですが、ターゲット部分はこのあたりから読み始めると良いでしょう。

引数無しのmakeコマンドで実行されるのはallというターゲットで、更に呼び出されているpure_allというターゲットの中でblibディレクトリへのモジュールのコピーなどが行われます。

all :: pure_all manifypods

...
pure_all :: config pm_to_blib subdirs linkext

実際のコンパイルなどは(XSの構造までは説明しませんが)、xs_o sectionxs_c sectionに書かれています。使われている変数が設定されている箇所と共に読み進めていくと、どうやってコンパイラを呼び出しているか?といった所が理解できると思います。

# --- MakeMaker xs_c section:


.xs.c:
$(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $(XSUBPP_EXTRA_ARGS) $*.xs > $*.xsc
$(MV) $*.xsc $*.c

# --- MakeMaker xs_o section:
.xs$(OBJ_EXT) :
$(XSUBPPRUN) $(XSPROTOARG) $(XSUBPPARGS) $*.xs > $*.xsc
$(MV) $*.xsc $*.c
$(CCCMD) $(CCCDLFLAGS) "-I$(PERL_INC)" $(PASTHRU_DEFINE) $(DEFINE) $*.c

なお、JSON::XSでは使われませんが、c_o sectionには通常のC言語や、C++でのコンパイルの設定が定義されています。


JSON::PP

次にPure Perl版の実装であるJSON::PPを見てみましょう。

$ curl https://cpan.metacpan.org/authors/id/I/IS/ISHIGAKI/JSON-PP-2.97001.tar.gz

$ tar xvf JSON-PP-2.97001.tar.gz
$ cd JSON-PP-2.97001

ExtUtils::MakeMakerのバージョンごとの差異を吸収するためのコードが随所に入っている関係で長く見えてしまいますが、中心となる箇所は以下の通りでJSON::XSとあまり変わりません。

WriteMakefile(

'NAME' => 'JSON::PP',
'VERSION_FROM' => 'lib/JSON/PP.pm', # finds $VERSION
'PREREQ_PM' => {
'Test::More' => 0,
%prereq,
},
'EXE_FILES' => [ 'bin/json_pp' ],

ここでもpure_allを見てみます。

# --- MakeMaker top_targets section:

all :: pure_all manifypods
...
pure_all :: config pm_to_blib subdirs linkext

先ほどのXS版と変わらないですね。ただし、その先で、先ほどのXS版に出てきたようなc_o sectionxs_o sectionxs_c sectionという箇所は全て空っぽになっています。pure perlではコンパイルが不要なので、単にモジュールをコピーするだけで終わっています。

# --- MakeMaker c_o section:

# --- MakeMaker xs_c section:

# --- MakeMaker xs_o section:


おわりに

最初の入り口だけでしたが、Perlのビルドツールの裏側を覗いてみました。ビルドツールは特にさまざまな小さなツールと(バッド)ノウハウの積み重ねでできているので、読み解くのが大変ですが、先人達の知恵が詰まっているので、読んでみてください。