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

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


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

ちょっと古いですが、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のビルドツールの裏側を覗いてみました。ビルドツールは特にさまざまな小さなツールと(バッド)ノウハウの積み重ねでできているので、読み解くのが大変ですが、先人達の知恵が詰まっているので、読んでみてください。