読者です 読者をやめる 読者になる 読者になる

makefileわりと便利だよという話

makefile memo shell

はじめに

makeはmacにもlinuxにもデフォルトであって便利*1。 とは言え、色々とわかりづらいところがある。 便利なところだけ見繕って使うと便利。

個人的なmakefileの使い方は2種類

個人的には以下のような2つの目的でmakefileを使っている。

  • 複数の目的のためのタスクを記述して手軽に実行できるようにする
  • 1つの目的のためにまじめに依存関係を定義して使う成果物を生成する

後者がたぶん本命だけれど。前者で使うことも多い。

複数の目的のためのタスクを記述して手軽に実行できるようにする

利用するコマンドのオプションを省略したい場合に使う。 脳みそのリソースの削減。細かなオプションの意味など後で見返した時に覚えていないことが多いのでメモがわりにmakefileを書く。

依存関係の書き方は以下の様な感じ。

<タスク名や生成されるファイル>: <依存するタスクや依存するファイル> ...
    <何か実行したい処理>

たいていcleanとdefaultを定義する。記述部分がごちゃっとしてきたら変数にする

SRC = x.txt y.txt

clean:
  rm -f *.out
default:
  python script.py ${SRC} > x.out

デフォルトの設定値(ただし実行時に変えたい)

以下のように実行時に値を渡したい場合がある。

$ make
10!!
# 10ではなく1にしたい
$ FOO=1 make
1!!

このような場合には ?= を使って設定する。実行するコマンド自体のエコー出力を消したい場合には @ をつける。

FOO ?= 10

default:
   @echo ${FOO}!!

phony

makefileにただclean,defaultと書くとそれが生成物としてのファイル名を表しているのか。タスク名を表しているのかわからない。同名のファイルなどがあると困る。なのでphonyを書いておく。

.PHONY clean default

1つの目的のためにまじめに依存関係を定義して使う

一連のタスク依存関係を持ちつつ連動して実行されて欲しい場合にまじめに依存関係を定義する。 対象となるものがファイルの場合には、成果物としてのファイルのutimeよりも依存先のファイルのutimeの方が新しい場合にのみ実行される。便利。

ただしこの目的の為に書く場合には、わりとまじめに使う場合が多いのでその場合はそれぞれの環境のツールを使うという感じでも良い気もする。とは言え、簡単なレベルならmakefileでもわりとどうにかなるので便利なことは便利。

以下のような謎の定義を書いておくと、依存関係にあるファイル拡張子によって実行する処理を記述できる。例えば、.pyの拡張子を持つファイルのpythonを実行した結果をリダイレクトした結果を.py.outに出力するなど。

%.py.out: %.py
  python $< > $@
  
hello.py.out: hello.py

clean:
  rm -f *.py.out

make -p

おもむろに make -p と実行してみると良い。内部で利用できる関係性を見る事ができる。

$ make -p # 一部だけ表示

%.py.out: %.py
#   commands to execute (from `Makefile', line 2):
    python $< > $@

ちなみに 無引数で実行した時に実行されるタスクは DEFAULT_GOALとして表示される。

$ make -p    # 一部だけ表示

# makefile
.DEFAULT_GOAL := hello.py.out

$ make clean
rm -f *.py.out
$ make
python hello.py > hello.py.out
# 2度目の実行はskip
$ make
make: `hello.py.out' is up to date.

シェルの実行結果をつかって定義を記述

シェルの実行結果をつかって定義を記述することもできる。 $(shell ...) を使う。

例えば以下のような状況で。

$ ls
Makefile  bye.py      hello.py

make を実行すると hello.py.outbye.py.out が生成されるようなmakefileは以下。

%.py.out: %.py
  python $< > $@

default: hello.py.out bye.py.out

hello.py.out: hello.py
bye.py.out: bye.py

clean:
  rm -f *.py.out
.PHONY: clean

これが面倒な場合には以下のようにも書ける。関係性が自明なのでhello.py.outなどの記述は省略できる。

%.py.out: %.py
  python $< > $@

default: hello.py.out bye.py.out

clean:
  rm -f *.py.out
.PHONY: clean

さらに依存先部分のhello.py.outなども書きたくない場合には $(shell ...) を使って以下の様にも書ける。 これはシェルスクリプトの実行結果に置き換えるというような意味。

%.py.out: %.py
  python $< > $@

default: $(shell ls *.py | sed 's/$$/.out/g')

clean:
  rm -f *.py.out
.PHONY: clean

注意点として $ は2つ必要(sedの記述は sed 's/$/.out/g' で良いところを)。 markdownで書いたreadmeなどを作ったりするのに便利。

よく分からない自動変数

今まで放置してきた謎の変数について紹介。以下の様になっている。

  • $@ 出力対象(生成されるファイル)の名前
  • $^ 入力対象(依存されるファイル)の名前全部
  • $< 入力対象の内1つ目のものの名前

おそらくよく使うのはこの辺の3つ。詳細を知りたければ このあたり

上の変数などを使って、出力結果をmarkdownでまとめてみたreadmeを作ってみる。

%.py.out: %.py
  python $< > $@

readme.md: $(shell ls *.py | sed 's/$$/.out/g')
  rm -f tmp
  touch tmp
  for i in $^; do echo $$i >> tmp; echo '```' >> tmp; cat $$i >> tmp; echo '```' >> tmp; done
  mv tmp $@

clean:
  rm -f *.py.out
  rm -f readme.md
  
.PHONY: clean

実行結果をまとめたreadmeを生成。

$ make
python bye.py > bye.py.out
python hello.py > hello.py.out
rm -f tmp
touch tmp
for i in bye.py.out hello.py.out; do echo $i >> tmp; echo '```' >> tmp; cat $i >> tmp; echo '```' >> tmp; done
mv tmp readme.md

$ cat readme.md
 bye.py.out
 ```
 bye
 ```
 hello.py.out
 ```
 hello
 ```

makefileわりと便利だよという話。

*1:macもdefaultでgnu makeっぽい