Makefile自己文書化の仕組み(メモ)

Makefileを自己文書化するという翻訳記事を見つけた.

Makefileをこんな風に作ったとする:

help:
	@grep -E '^[0-9a-zA-Z_-]+[[:blank:]]*:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[1;32m%-30s\033[0m %s\n", $$1, $$2}'

foo : bar.cc baz.cc  ## compiles foo

bar : aaa.cc bbb.cc ddd.cc

baz : hoge.h poyo.cc  ## makes baz

$ make すると help が実行されて

baz                            makes baz
foo                            compiles foo

のように出力される.この help ターゲットに書かれてるワンライナーがこの仕事を引き受けてくれている.(なお,オリジナルのワンライナーを少し改造してある).あとで挙動を変えてみたくなるかもしれないので,このスクリプトが何をやっているのか調べてみた.

tricks in the 'self documented makefile'

Makefile自己文書化の鍵となるワンライナー

[[:blank:]]
オリジナルのワンライナーから変えたところの一つです.私は行儀が悪いので make のターゲットを
targetname: hogehoge
ではなく
targetname : hogehoge
のようにターゲット名の後ろにも空白を置く癖があります.grep で空白にマッチする正規表現は[:blank:]なので,任意個の空白は空白の文字クラス[[:blank:]]によって[[:blank:]]*と表されるわけです.

Field Separator
FS = ":.*?##" のようにフィールドセパレータが定義されている.これにより## に引き続いてコメントが置かれたMakefileのターゲットの行が,ターゲット名($1)とコメント部分($2)に分かれてくれます.

$(MAKEFILE_LIST)
makefile の名称を取り出します.言っていることがわかりにくいですか? ではこうしましょう.
OtherMakefile という名前のファイル

.PHONY : a
a :
    @echo "this file is: $(MAKEFILE_LIST)"

として作り,ファイル名指定オプション -f を使って make を呼びます:
$ make -f OtherMakefile
すると
this file is: OtherMakefile
と返ってくるはずです.

カラーコード
\033m[0m で色の設定が解除されています.次のMakefileで色々試せます:

attribute :
	@awk 'BEGIN{printf "\033[1;32mhello\033[0m\n"}'
	@awk 'BEGIN{printf "\033[2;32mhello\033[0m\n"}'
	@awk 'BEGIN{printf "\033[3;32mhello\033[0m\n"}'
	@awk 'BEGIN{printf "\033[4;32mhello\033[0m\n"}'
	@awk 'BEGIN{printf "\033[5;32mhello\033[0m\n"}'
	@awk 'BEGIN{printf "\033[7;32mhello\033[0m\n"}'

foreground :
	@awk 'BEGIN{printf "39:Default foreground color [\033[1;39mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "30:Black [\033[1;30mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "31:Red [\033[1;31mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "32:Green [\033[1;32mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "33:Yellow [\033[1;33mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "34:Blue [\033[1;34mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "35:Magenta [\033[1;35mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "36:Cyan [\033[1;36mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "37:Light Gray [\033[1;37mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "90:Dark Gray [\033[1;90mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "91:Light Red [\033[1;91mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "92:Light Green [\033[1;92mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "93:Light Yellow [\033[1;93mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "94:Light Blue [\033[1;94mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "95:Light Magenta [\033[1;95mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "96:Light Cyan [\033[1;96mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "97:White [\033[1;97mHere\033[0m]\n"}'

background:
	@awk 'BEGIN{printf "49:Default background color [\033[0;49mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "40:bg:Black [\033[0;40mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "41:bg:Red [\033[0;41mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "42:bg:Green [\033[0;42mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "43:bg:Yellow [\033[0;43mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "44:bg:Blue [\033[0;44mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "45:bg:Magenta [\033[0;45mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "46:bg:Cyan [\033[0;46mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "47:bg:Light gray [\033[0;47mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "100:bg:Dark gray [\033[0;100mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "101:bg:Light red [\033[0;101mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "102:bg:Light green [\033[0;102mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "103:bg:Light yellow [\033[0;103mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "104:bg:Light blue [\033[0;104mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "105:bg:Light magenta [\033[0;105mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "106:bg:Light cyan [\033[0;106mHere\033[0m]\n"}'
	@awk 'BEGIN{printf "107:bg:White [\033[0;107mHere\033[0m]\n"}'