KOTET'S PERSONAL BLOG

#cpplang 環境変数が設定されると再コンパイルするmakefile

Created: , Last modified:
#cpplang #tech

これは1年以上前の記事です

ここに書かれている情報、見解は現在のものとは異なっている場合があります。

最近は主にRoboDragonsにフォーマッタを導入したりコンパイル時チェックを増やしたりしています。 RoboDragons は何年も日本1位を維持しているのに他学部の人間にはほぼ認知されていないかわいそうなロボサッカーチームです。

そのなかでひとつ便利そうな知識を得たので書いておこうと思います。

環境変数を元にコンパイルされるターゲット

たとえば以下のようなC++コードがあるとしましょう。

#include <iostream>

#if defined(NUMBER)
constexpr int number = NUMBER;
#else
constexpr int number = 42;
#endif

int main(int argc, char const *argv[])
{
    std::cout << number << std::endl;
    return 0;
}

これを以下のように-Dオプションをつけてコンパイルすると、指定したとおりの数字が出力されます。

g++ hello.cpp -o hello -DNUMBER=7
$ ./hello 
7

これをmakefileに書くと、コンパイルの自動化ができます。

hello: hello.cpp
	g++ $< -o $@ -DNUMBER=7

.PHONY: clean

clean:
	rm -f hello
$ make
g++ hello.cpp -o hello -DNUMBER=7

さて、ここで渡す数値を環境変数で指定したくなりました。 まあ以下のように書けばいいでしょう。

hello: NUMBER?=7 # デフォルト値
hello: hello.cpp
	g++ $< -o $@ -DNUMBER=$(NUMBER)
$ NUMBER=33 make
g++ hello.cpp -o hello -DNUMBER=33
$ ./hello 
33

しかしここで問題が発生します。環境変数を変更してみましょう。

$ NUMBER=4 make
make: 'hello' is up to date.

そう、環境変数が書き換わってもmakeは再コンパイルをしてくれないのです。 この問題のちょっとマシな解決策を見つけました。

makefile - How do I force a target to be rebuilt if a variable is set? - Stack Overflow

この記事ではその手法を解説します。

up to dateにならないターゲット

make cleanなどは何度実行しても同じコマンドが実行されます。

$ make clean
rm -f hello
$ make clean
rm -f hello
$ make clean
rm -f hello

何度実行してもup to dateにならないのです。 この「何度実行してもup to dateにならない」という性質だけを取り出すとこんな感じのターゲットになります。

.PHONY: do_nothing_and_never_up_to_date
do_nothing_and_never_up_to_date:
    @:

: はなにもしないコマンドです。なにもしないのでなにもしません。

このターゲットを依存に入れるとどんなときも必ずコンパイルされるようになります。

hello: hello.cpp do_nothing_and_never_up_to_date
	g++ $< -o $@ -DNUMBER=$(NUMBER)
$ NUMBER=33 make
g++ hello.cpp -o hello -DNUMBER=33
$ make
g++ hello.cpp -o hello -DNUMBER=7
$ make
g++ hello.cpp -o hello -DNUMBER=7

patsubst

patsubstという組み込み関数があります。 これは文字列中の特定のパターンを特定の文字列に置き換える関数です。

以下のように書くとNUMBER中のすべてがtestに置き換わった文字列が返ります。

$(patsubst %,test,$(NUMBER))

123testに、1 2 3test test testになります。 空文字列を渡すと空文字列が返ります。

組み合わせる

上2つを組み合わせてできたmakefileがこちらになります。

hello: NUMBER?=7
hello: hello.cpp $(patsubst %,depends_on,$(NUMBER))
	g++ $< -o $@ -DNUMBER=$(NUMBER)

.PHONY: clean depends_on

depends_on:
    @:

clean:
	rm -f hello

変数NUMBERが設定されていないときは通常動作、設定されているときはdepends_onターゲットが依存に入るため毎回リビルドされます。

$ make
g++ hello.cpp -o hello -DNUMBER=7
$ make
make: 'hello' is up to date.
$ NUMBER=1 make
g++ hello.cpp -o hello -DNUMBER=1
$ NUMBER=1 make
g++ hello.cpp -o hello -DNUMBER=1