PHPerでも使えるMakefileのあれこれ

f:id:zoe302:20181224174614p:plain

この記事は Willgate Advent Calendar 2018 の21日目の記事です。

前日は cocoeyes02 さんの 「業務で初めてブラウザE2Eテストを触ったので、つまづいた点と対処した方法を書きます」 でしたがまだ投稿されていないようですね。。


みなさん Makefile 書いてますか?僕は Makefile 好きなのでよく書いてます!

自分はよく書くことが多くスムーズにかけるのですが、経験が少ない人にとってわかりづらいことも多いかと思います。

なので、この記事では Makefile でよく使う書き方の紹介をさせていただきます。

Makefile の役割

書き方を紹介すると言いましたが、そもそも Makefile の役割を先に解説します。

make はもともとプログラムのビルド作業を自動化するツールとして作られました。

make ではその目的のために

ということができます。

ファイルの生成

先ほど書いた通り、ビルド自動化のツールとしてルールに乗っ取ってファイルを生成することができます。またルールのことを Make ではマクロと呼びます。

具体的には下記のような形です。

$ cat Makefile
hoge.txt:
    touch hoge.txt
$ make hoge.txt
touch hoge.txt
$ ls -al hoge.txt
-rw-r--r--  1 zoe  wheel  0 12 24 16:29 hoge.txt

Makefile ではインデントはタブ文字出ないとエラーになるのでご注意を。

このように、

Makefile

{生成したいファイル名}:
    {任意のコマンド}

と記述することで make {生成したいファイル名} とコマンドを実行することで Makefile で記述した {任意のコマンド} が実行される。

また、すでにファイルが生成済みのルールは実行されません。

$ make hoge.txt
make: `hoge.txt' is up to date.

これは、最初に話したようにビルド自動化のためにできたツールであるため、すでにビルド済みのものを再度ビルドするのを防ぎ時間の短縮のための機能です。

依存関係

fileA を生成する前に fileB を生成しておく必要があるとします。その場合下記のように書くことで自動で依存解決をしてくれます。

$ cat Makefile
fileA: fileB
    touch fileA

fileB:
    touch fileB
$ make fileA
touch fileB
touch fileA

また、この場合でも先ほどと同様にすでにファイルがある場合は更新されません。

しかし、この際に依存しているファイルの更新日時の方が新しい場合は、再度実行されます。

$ ls -al file*
-rw-r--r--  1 zoe  wheel  0 12 24 16:44 fileA
-rw-r--r--  1 zoe  wheel  0 12 24 16:44 fileB
$ touch fileB
$ ls -al file*
-rw-r--r--  1 zoe  wheel  0 12 24 16:44 fileA
-rw-r--r--  1 zoe  wheel  0 12 24 16:49 fileB
$ make fileA
touch fileA
$ ls -al file*
-rw-r--r--  1 zoe  wheel  0 12 24 16:49 fileA
-rw-r--r--  1 zoe  wheel  0 12 24 16:49 fileB

ファイル生成しないマクロ

下記のようなコマンドとして使うことを想定したマクロ定義をしてみます。

$ cat Makefile
fileA: fileB
    touch fileA

fileB:
    touch fileB

clear:
    rm -rf file*
$ ls -al file*
-rw-r--r--  1 zoe  wheel  0 12 24 16:49 fileA
-rw-r--r--  1 zoe  wheel  0 12 24 16:49 fileB
$ make clear
rm -rf file*
$ ls -al file*
ls: file*: No such file or directory

このようにファイルを生成しないマクロを定義することもできます。

しかし一つ罠があります。 もともと Make はビルド自動化のためのツールでファイル生成するのが役割と伝えたように今回の make clear も Make からするとファイル生成のマクロと同じ扱いのため、下記のような問題が起きてしまいます。

$ make fileA
touch fileB
touch fileA
$ touch clear
$ ls -al
-rw-r--r--   1 zoe   wheel    69 12 24 17:08 Makefile
-rw-r--r--   1 zoe   wheel     0 12 24 17:11 clear
-rw-r--r--   1 zoe   wheel     0 12 24 17:11 fileA
-rw-r--r--   1 zoe   wheel     0 12 24 17:11 fileB
$ make clear
make: `clear' is up to date.

clear というファイルが仮に存在してしまうと make clear はファイル生成のマクロではないのに、実行できなくなってしまいます。

これを回避するためには、このマクロはファイル生成のマクロではないですよということを Make に伝える必要があります。

$ cat Makefile
fileA: fileB
    touch fileA

fileB:
    touch fileB

clear:
    rm -rf file*
.PHONY: clear
$ make clear
rm -rf file*

PHPでどういう場面で使うの?

私がPHPのプロジェクトでよく使うものとしては下記のようなものを作ることが多いです。

setup: vendor .env
.PHONY: setup

vendor:
    composer install --no-interaction

.env:
    cp .env.example .env

test: vendor .env
    vendor/bin/phpunit -c phpunit.xml
.PHONY: test

lint: vendor .env
    vendor/bin/phpcs --standard=phpcs.xml --extensions=php
.PHONY: lint

プロジェクト初期設定用の make setupvendor .env のマクロを実行し必要ファイルをセットアップ。

make lintphp の linter である phpcs を実行、 make testphpunit を実行するように設定されています。

また必要なコマンド一式を Make に書いておくことで、このプロジェクトではこういう技術が利用されている、この設定が必須だ、ということをプロジェクトメンバーに伝えやすくなるため、おすすめです。

まとめ

いかがでしょうか?今回紹介したもの以外にも make の便利な機能もあったりしますので、気になった方はドキュメントも参照してみてください。

また、php以外でも私はdockerやgolangなどのラッパーやプライベート開発のサービスのデプロイコマンドだったり、CIなどからのコマンドのAPIとしても利用したりしています。