[開発]GNU makeのMakefileに、シェルスクリプトを自然に書くたった一つの方法

たった一つとか嘘です。
ググッても中々出てこなくて、ちょっといじってたらできちゃったので書き残しておきます。


GNU makeでは1行毎に1つのシェルで実行されるので、

foo::
	if true ; then echo "make love" ; fi

foo::
	$(SHELL) -c 'if true ; then echo "make love" ; fi'

と同じような挙動になります。


よってシェルスクリプトが複数行に渡る場合、

foo::
	if true
	then
          echo "make love"
	fi

なんて書いてしまうと、

foo::
	$(SHELL) -c 'if true'
	$(SHELL) -c 'then'
	$(SHELL) -c '  echo "make love"'
	$(SHELL) -c 'fi'

という感じで実行されることになるので当然エラーになります。


そこで以下のような書き方が良く用いられています。

foo::
	if true ;\
	then \
	  echo "make love" ;\
	fi

これ見た目上は複数行に分けて書けてはいますが、セミコロンの有無がワケワカランことになる上に、長くなってくると非常に猛烈に見辛くなります。しんどいです。



ここで、GNU makeでヒアドキュメントのようなことをする方法を紹介しておきます。
defineを使うと、通常の変数宣言とは違って改行を含めることができます。GNU make 日本語訳(Coop編) - 変数の利用法

define FOO
a
b
c
endef

ただこれをそのまま変数として

foo::
	echo "$(FOO)"

のように使ってしまうと

foo::
	echo "a
b
c
"

と展開され、これまた各行が1行ずつシェルに実行されてしまうのでエラーになります。


そこで、変数をexportして環境変数として扱います。

export FOO
foo::
	echo "$${FOO}"

ここまでくれば後は簡単でしょう。
echoしたものをshに食わせれば良いだけです。

define MAKELOVE
if true
then
  echo "make love"
fi
endef
export MAKELOVE
foo::
	echo "$${MAKELOVE}" | $(SHELL)
あら?インタラクティブシェルじゃなくなってしまって困るぞ…。
foo::
	echo "$${MAKELOVE}" > /tmp/$$$$ ; $(SHELL) /tmp/$$$$ ; rm -f /tmp/$$$$
とりあえずこれでお茶を濁し中…

どや?
ちなみに、シェル変数は相変わらず$$varnameと$を重ねて書かないといけないので、完璧に自然なシェルスクリプトになるわけではありません。あしからず。


みんなもっとmake loveしようぜ。
make: don't know how to make love. Stop.