この記事は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 Makefile
でMakefile
を生成してみましょう(途中でCanary::Stability
モジュールのメッセージが出ますが、y
で先に進んで下さい)。
Makefile
の中を見始めると、さまざまな変数定義が続いていて、どこから読み進めればいいのかわかりづらいですが、ターゲット部分はこのあたりから読み始めると良いでしょう。
引数無しのmakeコマンド
で実行されるのはall
というターゲットで、更に呼び出されているpure_all
というターゲットの中でblib
ディレクトリへのモジュールのコピーなどが行われます。
all :: pure_all manifypods
...
pure_all :: config pm_to_blib subdirs linkext
実際のコンパイルなどは(XSの構造までは説明しませんが)、xs_o section
、xs_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 section
、xs_o section
、xs_c section
という箇所は全て空っぽになっています。pure perlではコンパイルが不要なので、単にモジュールをコピーするだけで終わっています。
# --- MakeMaker c_o section:
# --- MakeMaker xs_c section:
# --- MakeMaker xs_o section:
おわりに
最初の入り口だけでしたが、Perlのビルドツールの裏側を覗いてみました。ビルドツールは特にさまざまな小さなツールと(バッド)ノウハウの積み重ねでできているので、読み解くのが大変ですが、先人達の知恵が詰まっているので、読んでみてください。