複数の出力をもつプログラムのルールの記述にGNU Parallelを使ってみる

Makefileに複数の出力をもつプログラムのルールを記述する方法を調べてみたところ結構ややこしかった。

以下のリンクによると、

fooコマンドが入力としてdata.fooを受け取ってdata.cとdata.hを出力する場合、ルールを次のように書ければ簡単なのだが、

data.c data.h: data.foo
        foo data.foo

これは、次のように解釈され make -j で並列に実行するとfooが同時に2回実行されて失敗してしまうことがある。

data.c: data.foo
        foo data.foo
data.h: data.foo
        foo data.foo

リンク先にはこの問題を解決するためのやり方が幾つか書かれていて、最終的な完成形が以下の書き方だった。

data.stamp: data.foo data.bar
        @rm -f data.tmp
        @touch data.tmp
        foo data.foo data.bar
        @mv -f data.tmp $@

data.c data.h data.w data.x: data.stamp
## Recover from the removal of $@
        @if test -f $@; then :; else \
          trap 'rm -rf data.lock data.stamp' 1 2 13 15; \
## mkdir is a portable test-and-set
          if mkdir data.lock 2>/dev/null; then \
## This code is being executed by the first process.
            rm -f data.stamp; \
            $(MAKE) $(AM_MAKEFLAGS) data.stamp; \
            result=$$?; rm -rf data.lock; exit $$result; \
          else \
## This code is being executed by the follower processes.
## Wait until the first process is done.
            while test -d data.lock; do sleep 1; done; \
## Succeed if and only if the first process succeeded.
            test -f data.stamp; \
          fi; \
        fi

実際に欲しいファイル以外にdata.stampとdata.lockという2つのファイルとディレクトリを作っていてルールがややこしい。
また、ルールが2つになって依存とターゲットが別れてしまっている。

これを解決するためにGNU Parallelのsemaphore(semコマンド)を使って次のように書いてみた。

http://www.gnu.org/software/parallel/sem.html (semコマンドのドキュメント)

data.c data.h data.w data.x: data.foo data.bar
	sem --fg --id data_lock 'if [ -e $@ ] && [ $@ = `ls -td $@ $+ | head -1` ]; then :; \
                        else \
                            foo data.foo data.bar; \
                        fi'

依存とターゲット以外のファイルをルールに書かずに済み(代わりにidを書いているが)、1つのルールで済むようになった。

いまのところ、うまく動いているようなので、しばらく試してみようと思う。