Makefile で任意の第2引数を扱えるようにする小技を紹介します。
簡単な表記で2つの引数を扱える上に複数個の対象をデフォルト指定したり変更することもできます。
この方法を使えば --jobs オプションで make タスクの並列化もできます。
本記事で紹介するスクリプトは gist で公開しています。
https://gist.github.com/algas/377ca9d3f11916af39951ccd599a3b88
解決した課題
- make で2つの引数(1つ固定+1つ任意)を扱えるようにした
- 第2引数を複数個デフォルト設定かつ変更可能にした
- 上記の2つに加えて jobs オプションでの make タスク並列化が効く形式を保った
対象読者
- Makefile を書く必要があるけどイマイチ書き方がよくわからない人
- シェルスクリプトの方が自由度が高いと思いつつ Makefile を書いている人
Makefileの不自由なところ
Makefile について挙げられる課題の1つに引数の自由度の低さがあります。
Makefile では通常はターゲットと呼ばれる1階層だけ書くことができます。
つまり make コマンドの引数(サブコマンドとも呼ぶ)は1階層だけ使えます。
たとえば次のように書きます。
make foo
複数の引数を make に使った場合にはコマンドを連続実行するものと解釈されます。
make foo bar
これは次のコマンドと同じように解釈されます。
make foo && make bar
コマンドの用途によっては第2引数まで使いたい場合があります。
たとえば FILEPATH を git の管理下に置きたい場合には次のコマンドを使います。
git add FILEPATH
それでは make で上記と同等のコマンド体系を実現するにはどうすればいいでしょうか?
一般的な手法
一般的に第2引数を使われる手法を紹介します。
これらで十分な場合もあります。
ハードコーディングする
対応する第2引数が少ない場合にはハードコーディングする方法が
使えます。
第2引数の種類が多かったり任意の個数がある場合には使えません。
command-foo:
@echo foo
command-bar:
@echo bar
$ make command-foo
foo
$ make command-bar
bar
make変数や環境変数を使う
make変数や環境変数を Makefile 内で呼び出して使います。
ここでは ARG という名前の変数を使います。
command:
@echo "command $(ARG)"
$ make command ARG=baz
command baz
この記事で提案する手法
make TARGET-SUBTARGET というコマンド体系が使えます。
TARGET は固定、SUBTARGET は任意または有限個から選択する場合に使います。
Makefile 本体
NAMES = foo bar baz
hello: $(addprefix hello-, $(NAMES))
hello-%:
@echo "hello ${@:hello-%=%}"
world: $(addprefix world-, $(NAMES))
world-%:
@echo "${@:hello-%=%} world"
.PHONY: hello hello-% world world-%
コマンドの実行結果
$ make hello-foo
hello foo
$ make hello-hoge
hello hoge
$ make hello
hello foo
hello bar
hello baz
$ make --jobs 2 hello
hello bar
hello foo
hello baz
$ make hello NAMES=hoge fuga
hello hoge
hello fuga
$ make world-bar
bar world
$ make world-123
123 world
$ make world
foo world
bar world
baz world
Makefile の解説
まずはこの部分から。
hello-%:
@echo "hello ${@:hello-%=%}"
Makefile で % は wildcard つまり任意の文字列を渡せます。
hello-%: は make hello-* でコマンドを実行できることを表しています。
@echo は Makefile で使う特殊な echo コマンドの表現で echo の実行コマンド群を表示しないで echo の対象のみを表示します。
$@ はターゲットを表す変数です。つまりここでは hello-% を意味します。
${@:hello-%=%} は $@ から先頭の hello- を除去した文字列になります。
たとえば make hello-foo は @echo "hello foo" と解釈されるので hello foo がコンソールに表示されます。
NAMES = foo bar baz
hello: $(addprefix hello-, $(NAMES))
NAMES という変数には foo, bar, baz の3つの文字列がデフォルトで入っています。
$(addprefix PREFIX, $(STRINGS)) は STRINGS に含まれる各文字列の先頭に PREFIX を加える関数です。つまりここでは NAMES がデフォルトの場合 hello-foo, hello-bar, hello-baz となります。
hello: の行を展開すると次のように書けます。
hello: hello-foo hello-bar hello-baz
make hello を実行すると make hello-foo, make hello-bar, make hello-baz がそれぞれ実行されます。
NAMES 引数は実行時に置き換えることができます。
make hello NAMES="hoge fuga" を実行すれば make hello-hoge, make hello-fuga がそれぞれ実行されます。
また make のオプションでコマンドを並列実行することもできます。
make --jobs 2 hello とすると make hello-foo, make hello-bar, make hello-baz のうち2つが同時に実行されることが期待できます。
問題点
この手法も万能ではありません。
特殊な状況で問題が発生する場合があります。
- wildcard target に
/(スラッシュ) を含めることができない
Makefile の仕様で wildcard target に/を含めることができません。
https://stackoverflow.com/questions/21182990/makefile-is-it-possible-to-have-stem-with-slash
たとえば次のようなエラーが発生します。
$ make hello-foo/bar
make: *** No rule to make target 'hello-foo/bar'. Stop.
第2引数に特殊な文字列を使う場合にはmake変数を使う方が良さそうです。
補足
- コマンドの区切り文字は
-(ハイフン) じゃなくても構いません
もっと簡単な表現方法や便利な使い方があればぜひコメントください。
Happy make life!