dotfilesのためのmake入門
最近自分のdotfilesを整備しているのだが、強い人たちのdotfilesを見ると、 make
コマンドで実行するようになっているものが多いことに気がついた。 make
は難しそうで敬遠していたが、少し調べてみるとdotfilesを作る時にとても便利であることがわかった。
makeとは
本来は、大規模なプログラムのコンパイルを自動化・効率化するための技術。 Makefile
にファイル名・コンパイル用のコマンド・その他の設定などを書いて、 make
コマンドで実行する。 Makefile
の書き方次第では、変更があった部分だけを再コンパイルする等もできる。make
は様々なOSにプリインストールされており、UNIX系のOSには GNU make
として最初から入っている。また、特定の言語に依存したものでもない。
つまり、端末が変わりほとんど何も入っていない環境になったとしても、 make
なら実行できる。dotfilesではポータビリティが非常に重視されるので、 make
がよく使われている。
もちろんシェルスクリプトに全部のコマンドを書いても良いのだが、 make
の場合、実行したいタスクごとに名前をつけることができ、全タスクの一括実行や、一部だけの実行もできる。そのため、 Makefile
を起点としたdotfiles全体の管理がしやすくなる。成長した Makefile
は、dotfiles全体のマニュアルのようになっていく。
Makefileの書き方
dotfilesで使う前に、本来の Makefile
の書き方を整理しておく。なお、今回はプログラムをコンパイルする用途ではないので、 make
自体の詳細まで立ち入ることはせず、dotfilesに関係ありそうな機能だけをまとめる。 例は公式ドキュメントから抜粋した。
rule
Makefile
内の1つのタスクを rule
という。 rule
は基本的に以下のような構文で書く。
targets : prerequisites
recipe
…
targets
はファイル名で、スペースで区切って複数指定できる。 prerequisites
にもファイル名を指定する。 recipe
には実行したいコマンドを書く。 recipe
の冒頭はタブによるインデントが必須であることに注意。
targets
のファイルが存在しないか、あるいは targets
のファイルの更新時刻が prerequisites
のファイルの更新時刻よりも古くなった場合、 recipe
に書いたコマンドが実行される。このため、 targets
にはコンパイル後に生成されるファイル、 prerequisites
には人間がコードを書いたファイルを指定することで、コードが更新されたファイルのみが再コンパイルされるようにできる。
foo.o : foo.c defs.h
cc -c -g foo.c
実行したいときは、 make ${targets名}
の形式で指定する。targets
を何も指定しないと、Makefile
内の一番上に書かれた targets
が実行される。
phony targets
先程 targets
にはファイル名が記載されると書いたが、ファイル削除など、新しいファイルを生成しないコマンドを Makefile
内の recipe
として指定したいという場合もある。例えば、以下のような rule
を書いたとする。
clean: rm *.o temp
このとき、make clean
とすればrecipeが実行される。しかし、万が一 clean
という名前のファイルができてしまった場合、 prerequisites
が指定されていないので、 recipe
は二度と実行されなくなる。
そこで、clean
を .PHONY
という特別な targets
名の prerequisites
にすることで、 clean
というファイルの有無に関係なく recipe
が実行されるようになる。
.PHONY: clean clean: rm *.o temp
また、以下のように all
を使って複数の targets
を指定し、それを .PHONY
に渡すことで、 make
だけですべての rule
を実行できる。もちろん、 make prog1
とすれば prog1
のみの実行もできる。
all : prog1 prog2 prog3 .PHONY : all prog1 : prog1.o utils.o cc -o prog1 prog1.o utils.o prog2 : prog2.o cc -o prog2 prog2.o prog3 : prog3.o sort.o utils.o cc -o prog3 prog3.o sort.o utils.o
dotfilesではどのように使うか
例えば、設定したいパッケージごとに Phony targets
を作り、それぞれの recipe
にコマンドを記載する。それらのパッケージを all
で指定すれば、 make
だけですべての設定が完了する。
.PHONY: all all: git vim tmux .PHONY: git git: ln -snfv ${PWD}/.gitconfig ${HOME} .PHONY: vim vim: ln -snfv ${PWD}/.vimrc ${HOME} curl -o ${HOME}/.vim/colors/iceberg.vim --create-dirs https://raw.githubusercontent.com/cocopon/iceberg.vim/master/colors/iceberg.vim .PHONY: tmux tmux: ln -snfv ${PWD}/.tmux.conf ${HOME}
上記のやり方はパッケージ単位で targets
を作っているが、それぞれの処理を単位として targets
を作っても良い。例えば、上記の例で各 recipe
の中に出てきている「設定ファイルをホームディレクトリにリンクする」作業を link.sh
等のシェルスクリプトにまとめ、以下のようにすれば make link
で全パッケージのリンク作業が完了する。
.PHONY: link link: bash ${PWD}/link.sh
また、 Makefile
の良いところは、 all
に含めない targets
も作れるところにある。いろいろな人の Makefile
を見ると、 help
や test
等の Phony targets
を作っている人もいるようだ。
環境移行はいつ起きるかわからず、移行作業自体にも時間をかけたくはない。今のうちから管理・実行のしやすいdotfilesを少しずつ作っておきたい。