環境変数の設定値とカレントディレクトリの一致をチェックする

目的

build時に、ツリーの位置を環境変数で指定する必要があるとする。
この場合に、環境変数の設定が不適切ならMakeが中止されるようにしたい。

結果

GNU Makeの関数(if, shell, error, call)を組み合わせて、include用のMakefileを作成した。

check-env.mk
https://sssvn.jp/svn/spikelet/make/check-env/check-env.mk

以下、詳細。

詳細な仕様

Make 実行時に、カレントディレクトリと環境変数 MAKETOP の値を比較する。
設定内容に特定のサフィックスを付けた文字列が、カレントディレクトリと先頭一致しなければ、Make を異常終了する。

例えば、"MAKETOP=~/spikelet/make/check-env"、サフィックスを "/usr/src" とした時に、

  • ~/spikelet/make/check-env/usr/src で make 実行 → 正しい
  • ~/spikelet/make/check-env/usr/src/sub-dir で make 実行 → 正しい
  • ~/spikelet/make/check-env/usr で make 実行 → 異常終了

となればOk。

Make 内での実現方法

以下の内容のMakefileを用意して、各ディレクトリのMakefileからincludeした。

compare_env_cwd = $(if \
  $(shell pwd | grep "^`echo $$$(strip $(1)) | sed 's+/$$++'`$(strip $(2))"), ,\
  $(error Env `$(strip $(1))' does't point current directory))
$(call compare_env_cwd, MAKETOP, /usr/src)

これを使う側(ソースツリー内のMakefile)では、以下のように include する:

include ../../check-env.mk
....

もしくは、他にツリー全体での共通Makefileがあるなら、そのファイル内にcompare_env_cwd(定義とcall)を書いても良い。

compare_env_cwd の細かい話
  • 複数の環境変数をチェックしたい場合を想定して、チェック関数は変数に入れておいて $(call) で呼んでいる
  • カレントディレクトリの取得と変数との比較は、単純に $(shell) で pwd, grep コマンドを使った
  • 環境変数の末尾に '/' があると grep にひっかからないので、sedを間に挟んでいる

実行結果

テストスクリプトhttps://sssvn.jp/svn/spikelet/make/check-env 以下に置いた。

実行結果は以下:

% ./do-test.sh
==== success case: cwd is $MAKETOP ====
make: Entering directory `/home/taiyo/spikelet/make/check-env/usr/src'
All green
make: Leaving directory `/home/taiyo/spikelet/make/check-env/usr/src'
==== success case: cwd is under $MAKETOP ====
make: Entering directory `/home/taiyo/spikelet/make/check-env/usr/src/subdir'
All green
make: Leaving directory `/home/taiyo/spikelet/make/check-env/usr/src/subdir'
==== FAIL case: cwd is above $MAKETOP ====
make: Entering directory `/home/taiyo/spikelet/make/check-env/usr'
../check-env.mk:6: *** Env `MAKETOP' does't point current directory.  Stop.
make: Leaving directory `/home/taiyo/spikelet/make/check-env/usr'
==== FAIL case: $MAKETOP is not set ====
make: Entering directory `/home/taiyo/spikelet/make/check-env/usr/src'
../../check-env.mk:6: *** Env `MAKETOP' does't point current directory.  Stop.
make: Leaving directory `/home/taiyo/spikelet/make/check-env/usr/src'
==== success case: cwd is under $MAKETOP/ ====
make: Entering directory `/home/taiyo/spikelet/make/check-env/usr/src/subdir'
All green
make: Leaving directory `/home/taiyo/spikelet/make/check-env/usr/src/subdir'

"All green" という表示がMake成功、"*** ... Stop." の表示がMakeの異常終了を示している。

たしかに、「詳細な仕様」に示したような結果になっている。

その他メモ

Conditional Parts of Makefiles

GNU Make の conditional(if, ifeq の「文」)は、あくまで文であって、関数内部では使えない。
その代わりに $(if) という関数があるので、そちらを使った。

The call Function は関数中では使えない

$(call) の引数の前後の空白は、付けたまま関数に渡される。
受け側で$(strip)をしないと、変な空白に悩むことになる。

※第一引数(関数を定義したmake変数の名前)の前後の空白は、$(call)のC側実装の内部で削除している。

function.c: func_call()

  /* There is no way to define a variable with a space in the name, so strip
     leading and trailing whitespace as a favor to the user.  */
  fname = argv[0];
  while (*fname != '\0' && isspace ((unsigned char)*fname))
    ++fname;
存在しないmake変数への$(call)は空文字列になる

$(call)の第一引数(関数を定義したmake変数の名前)を間違えると、Warning などを出さずに、空文字列に置換される。

これは、以下の処理のせい:
function.c: func_call()

  v = lookup_variable (fname, flen);

  if (v == 0)
    warn_undefined (fname, flen);

  if (v == 0 || *v->value == '\0')
    return o;

上記のように、lookup_variable に失敗した時には variable_buffer_output() を読んでいない。
参考: id:taiyo:20080402

warn_undefined() のマクロは以下のようになっている。

#define warn_undefined(n,l) do{\
                              if (warn_undefined_variables_flag) \
                                error (reading_file, \
                                       _("warning: undefined variable `%.*s'"), \
                                (int)(l), (n)); \
                              }while(0)

warn_undefined_variables_flag は、"make --warn-undefined-variables" とするとセットされる。

つまり、--warn-undefined-variables を付けない限り、$(call) での名前間違いは警告されない。