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

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

21日目は[@booksさん](https://qiita.com/books)の[今年Perlで困ったこと](https://qiita.com/books/items/1a0407bda1d6671b1168)でした。地道に困ったことと、解決策を記録していくって、意外とできないんですが、実は凄く大事なことですよね！

---

先日、[VOYAGE GROUP](https://voyagegroup.com)の方とお話した際に、環境セットアップは色々なツールを[makeコマンド](https://linuxjm.osdn.jp/html/GNU_make/man1/make.1.html)でラップしているので、全て`make install`で完結するようになっている、ということを伺って、改めて`make`コマンドについて調べてみました。

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

[超便利！Makefileを作ってmakeするのは想像よりもずっと簡単だった](http://tech.voyagegroup.com/archives/6286346.html)

そんなmakeですが、Perlのビルドツールである[`ExtUtils::MakeMaker`](http://search.cpan.org/dist/ExtUtils-MakeMaker/lib/ExtUtils/MakeMaker.pm)も内部では`make`コマンドを使っています。

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

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

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

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

```sh
$ perl Makefile.PL
$ make
$ make test
$ make install
```

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

一方で、pure perlで書かれたビルドツールである[Module::Build](http://search.cpan.org/dist/Module-Build/lib/Module/Build.pm)だと、モジュールのインストール方法として以下のように書かれているはずです。

```sh
$ perl Build.PL
$ ./build
$ ./build test
$ ./build install
```

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

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

[第23回　Module::Build：MakeMakerの後継者を目指して：モダンPerlの世界へようこそ](http://gihyo.jp/dev/serial/01/modern-perl/0023)

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


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

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

### JSON::XS

pure perl版とXS版が別ディストリビューションになっている方が分かりやすいので、ここでは[`JSON::XS`](https://metacpan.org/pod/JSON::XS)を例に見てみます。

[アーカイブファイルをダウンロード](https://cpan.metacpan.org/authors/id/M/ML/MLEHMANN/JSON-XS-3.04.tar.gz)し、展開したら、展開したディレクトリへ移ります。

```sh
$ 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`の元となる情報ですが、意外と情報量は少なめです。

```perl
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`に書かれています。使われている変数が設定されている箇所と共に読み進めていくと、どうやってコンパイラを呼び出しているか？といった所が理解できると思います。

```makefile
# --- 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`を見てみましょう。

```sh
$ 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`](http://search.cpan.org/dist/ExtUtils-MakeMaker/lib/ExtUtils/MakeMaker.pm)のバージョンごとの差異を吸収するためのコードが随所に入っている関係で長く見えてしまいますが、中心となる箇所は以下の通りで`JSON::XS`とあまり変わりません。

```perl
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`を見てみます。

```make
# --- 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ではコンパイルが不要なので、単にモジュールをコピーするだけで終わっています。

```makefile
# --- MakeMaker c_o section:


# --- MakeMaker xs_c section:


# --- MakeMaker xs_o section:

```

## おわりに

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