こんにちは、あらいです。年の瀬ですが3月に書いた記事の続きです。
前回は基礎として、
- makeは「作りたいファイル、材料になるファイル、作るコマンド」をルールとして定義できること
- ファイルの更新時間をみてコマンドを実行すること
- 複数のルールを連鎖できること
などを書きました。 今回は、web開発の現場でmakeにどのような使い道があるかを書きます。
タスクランナーとしての用法
コマンドがファイルを作らない場合
前回の例で、コマンドが最終的に作りたいはずのファイルを作らなかったケースを考えます。
# helloを作るべきところ、typoしてhaloを作るコマンドになっている hello: hello.c cc $< -o halo
make
コマンドを実行してみます。
$ make cc hello.c -o halo $ make cc hello.c -o halo $ make cc hello.c -o halo
ターゲットのhelloがないと判定され、何度でも cc
が動いてしまいます。
(ゆえに、基本的には自動変数 $@
などの使用が推奨されます)
それってオレオレサブコマンドにできるのでは?
この仕組みを使って 実際にはファイルを作ったりしないけど面倒臭いコマンドに名前を付けると便利なのでは? という発想が生まれます。
hello: hello.c cc $< -o $@ # 生成されたバイナリを消したい clean: rm -f hello
こう定義することで、 $ make clean
というコマンドでrmコマンドを実行することができます。
$ make clean rm -f hello
今時のCLIツールっぽくサブコマンドが作れそうですね。
フォニーターゲットのお作法
オレオレコマンド用法はmakeの使われ方として一般的であり、正式には フォニーターゲット (Phony Target:偽りのターゲット)と呼ばれます。
ここで1つ問題があります。 もし偶然フォニーターゲットと同名のファイルが存在してしまうと、 ファイルの更新状態判定が誤作動してしまうのです。
$ touch clean # "clean" というファイルを作る $ make clean make: `clean' is up to date.
cleanターゲットの処理を実行してほしいのに、ローカルの clean
ファイルが邪魔をしています。
これを防ぐため、makeには .PHONY
という特殊なターゲットと仕組みが用意してあります。
hello: hello.c cc $< -o $@ clean: rm -f hello # cleanは実際にファイルがあろうがなかろうが関係ないことを示す。 # 関係だけ記述して、コマンドは省略する。 .PHONY: clean
これによりcleanターゲットが動作するようになります。
$ ls -l total 40 -rw-r--r-- 1 cw-arai wheel 66 Dec 26 19:42 Makefile -rw-r--r-- 1 cw-arai wheel 0 Dec 26 19:40 clean -rwxr-xr-x 1 cw-arai wheel 8432 Dec 26 19:44 hello -rw-r--r-- 1 cw-arai wheel 75 Dec 26 19:18 hello.c $ make clean rm -f hello
PHP開発者のためのMakefileの実例
前置きが長くなりました。私が開発で使っているのはC言語ではなくPHPです。
PHPはコンパイルしてバイナリを作る必要はありませんが、
モダンなPHP開発において $ composer install
のようなビルドプロセスはもはや当たり前です。
ここではPHPの開発プロジェクトでのタスクを make
で自動化してみましょう。
想定する状況
こんな状況を想定します。
- composerで各種ライブラリを管理している
- composerの実体としてpharファイルを使う
- phpdotenvを使って環境変数を読み込む
- 実際にアプリケーションが読むファイル
.env
はgitignoreされ、リポジトリに含まれない - 開発用のファイルが
.env.example
としてコミットされている
- 実際にアプリケーションが読むファイル
ここでは作業開始時に
- composer自体のインストール
composer install
コマンドの実行- .env.exampleを.envとしてコピーする
が必要です。 また、次のことを適宜やる必要があります。
- composer.jsonが更新されたら
composer install
を再実行する - .env.exampleが更新されたら.envにコピーし直す
これをmakeで自動化します。
具体例
次のようなMakefileを書き、composer.jsonと同じくプロジェクトのルートに置きます。
# PHP開発用のMakefileサンプル COMPOSER_CMD := ./composer.phar build: .env vendor .PHONY: build # composerのインストール ./composer.phar: curl 'https://getcomposer.org/installer' | php # composer installコマンド vendor: composer.json $(COMPOSER_CMD) $(COMPOSER_CMD) install # .envの初期化 .env: .env.example cp $< $@
使用方法
Makefileの置いてあるプロジェクトのルートで make
を実行します。
cw-arai@cw-arai-MBP:/tmp/example-project [0] $ make cp .env.example .env curl 'https://getcomposer.org/installer' | php % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 267k 100 267k 0 0 125k 0 0:00:02 0:00:02 --:--:-- 125k All settings correct for using Composer Downloading... Composer (version 1.9.1) successfully installed to: /private/tmp/example-project/composer.phar Use it: php composer.phar ./composer.phar install Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 50 installs, 0 updates, 0 removals - Installing aura/di (2.2.4): Loading from cache - Installing mtdowling/jmespath.php (2.4.0): Loading from cache (略) - Installing phpunit/phpunit (4.8.36): Loading from cache Generating autoload files cw-arai@cw-arai-MBP:/tmp/example-project [0] $
Makefileの最初のターゲット「build」が実行され、.envのコピー、composer自体のインストール、composer installコマンドが実行されました。
この状態でもう一度 make
をしても、何もやることないよ、と判定してくれます。
cw-arai@cw-arai-MBP:/tmp/example-project [0] $ make make: Nothing to be done for `build'.
git pullしたら .env.example
と composer.json
が更新されてました。もう一度 make
します。
cw-arai@cw-arai-MBP:/tmp/example-project [0] $ make cp .env.example .env ./composer.phar install Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 1 install, 7 updates, 2 removals - Removing symfony/stopwatch (v3.4.8) - Removing fabpot/php-cs-fixer (v1.13.3) (略)
.envのコピー、composer installだけが実行されました。
ちなみに別にインストールされたcomposerの実行ファイルを使いたい場合は
COMPOSER_CMD
を上書きして make COMPOSER_CMD=/path/to/composer
のようにできます。
cw-arai@cw-arai-MBP:/tmp/example-project [0] $ rm -rf vendor/ composer.phar # composer.pharもない状態とする cw-arai@cw-arai-MBP:/tmp/example-project [0] $ which composer /Users/cw-arai/work/bin/composer # ここにインストールされてるものを使いたい cw-arai@cw-arai-MBP:/tmp/example-project [0] $ make COMPOSER_CMD=/Users/cw-arai/work/bin/composer /Users/cw-arai/work/bin/composer install Loading composer repositories with package information Installing dependencies (including require-dev) from lock file Package operations: 49 installs, 0 updates, 0 removals - Installing aura/di (2.2.4): Loading from cache - Installing mtdowling/jmespath.php (2.4.0): Loading (略)
というわけで、何も考えずとりあえず make
しておけば作業環境が整うようになりました。
他のタスクランナーとの比較
composer自体にはカスタムコマンドを定義する機能があります。 これを使うと前述のcleanに相当するタスクをcomposer.jsonに定義することができます。 これとタスクランナーとしてのmakeを比較してみます。
makeのいいところ
- ファイルの更新時刻の判定の応用範囲が広い。ファイルがなければ作る、更新があったら新しくする、ということが簡単。
- Unix系環境にはだいたい入っており、パッケージ管理システムと独立しているので、composer自体などエコシステム自体のインストールができる。 つまりbrewやcomposerのインストール手順を自動化できる。
- 散らばったスクリプトを1つにまとめられる。
最後のやつを補足します。 ちょっと複雑なコマンド、忘れがちなコマンドを書いたshell scriptがリポジトリの中に散らばっていませんか? 深いディレクトリの中にある.shは忘れられがちですが、Makefileだと複数の目的のスクリプトを1つのファイルにまとめられます。 見つけやすいしメンテもしやすくなります。 またコメントを書けるので、README.mdを読んでコマンドをコピペするのではなく、 実行可能なドキュメントとすることができます。これが私の一番気に入ってるところです。
逆にcomposerのいいところ
- platform中立で、非Unix系環境(Windows)でも動きそうです*1。 makeの利点はインストール不要で最初から入っていること、およびshellとの親和性にあるので、そうでない環境の考慮が必要だと嬉しみが半減します。
- 言語親和性。例えばcomposer scriptでは
pre-dependencies-solving
など細かいタイミングで実行制御が可能です*2。 またシェルコマンドだけでなく、PHPの関数・メソッドの単位で実行することができます。これはmakeでは実現できません。
ケースバイケースですが、composer scriptsではこういった特徴を活かすことができます。
小技
最後に小技をいくつか紹介します。
helpの自動生成
次のようなターゲットをMakefileに追加します。
# ### ヘルプを表示する # # 行頭が###で始まる行を説明行、記号以外で始まるターゲットをタスクとして # コマンド一覧とヘルプメッセージを表示する # .PHONY: help help: @cat Makefile | awk -F: '/^###/{desc=substr($$0,4)} /^[a-zA-Z_-]+:/{print $$1,"\t",desc; desc=""}'
コメントにある通り、Makefileのコメントから make help
の結果を自動的に作ってくれます。
実行可能なドキュメント化がさらに捗るのでオススメです。
shebangを利用したオリジナルコマンド化
Laravelの artisan
をカッコイイと思ったことはありませんか?
プロジェクトでオリジナルなコマンドがあるとちょっとワクワクします。
それ、makeでできます。
#!/usr/bin/make -f
をMakefileの1行目に追加$ chmod +x Makefile
$ mv Makefile martisan
これでオリジナルの martisan
コマンドが完成です。
$ ./martisan build
のような要領で実行できます。
$ ./martisan deploy
でデプロイされるような仕組みも作れそうですね。
まとめ
makeは古く枯れたツールですが今なお有効に使うことができます。 コンテナやInfrastructure as Codeの進歩によってコマンドで操作できることは増え続けており、 shellと親和性の高いmakeは自動化&ドキュメント化にうってつけのツールだと言えるでしょう。
また、言語を超えた大統一ビルドツールのポテンシャルを持っていると思います。PHPならComposer、Scalaならsbt、frontendならnpmといったエコシステムの違いを乗り越え、「リポジトリをcloneしてとりあえず make
したら何かができる」「その詳細は実行可能なドキュメントとして書いてある」というDeveloper Experienceを実現できます。
MacやLinuxなどのUnix系環境では今すぐ使い始めることができるので、ぜひ試してみてください。 良いお年を。
*1:手元にWindowsマシンがないので確認できてませんが
*2:https://getcomposer.org/doc/articles/scripts.md#event-names