本稿では
今回はMakefileの基本的な書き方から、複数のRmdファイルをmakeを使って一括で変換する例を紹介します。締切直前に複数のRmdファイルを編集していたとしても、確実にすべてのレポートをコマンド1発で最新にできるようになるでしょう。
makeはすでにお使いの環境にインストール済みかもしれませんし、インストールされていなかったとしても、パッケージマネージャなどで簡単にインストールできるでしょう。本稿では再現性のため、Docker上でRStudioやmakeを動かします。動作確認はrocker/
で行っています。
以下を実行して、RStudio Serverを起動し、ブラウザでhttp://
を開き、Username:rstudio
, Password:foobar
でログインしてください。
docker run --rm -p 8787:8787 -e PASSWORD=foobar rocker/tidyverse:4.2.1
makeしてみよう
RStudioを使っていれば、通常はKnitボタンを押してRmdファイルを変換するでしょう。同じことはR Consoleからrmarkdown::
を呼び出すことでも行えます。
rmarkdown::render("example.Rmd")
また、以下のコマンドでターミナルから別のRのプロセスを起動してRmdファイルを変換することもできます。
Rscript -e 'rmarkdown::render("example.Rmd")'
Makefileには以下のように変換のルールを指定します。
target: dependencies ...
commands
- target:作成したいターゲット
- dependencies:ターゲットが依存するもの、つまりdependenciesを元にターゲットが作られる
- commands:作成するためのコマンド
今回の例ではRmdファイルをもとにhtmlファイルを生成したいので、以下のように記述します。Makefile
と名前を付けて保存してください。
example.html: example.Rmd
Rscript -e 'rmarkdown::render("example.Rmd")'
コマンドの先頭、つまりRscript
から始まる行の先頭にはタブ文字を入力します。スペースではなくタブです。RStudioではMakefile
という名前のファイルでTabを押すとタブ文字が入力されるはずです。タブ文字が正しく入力されているかを確認するには、Global Optionsの
makeを実行してみましょう。Terminalタブでmake <ターゲット>
を実行すると、makeはMakeFileを読み込み、指定されたターゲットを生成します。
make example.html
Rscript -e 'rmarkdown::render("example.Rmd")'
Rmdファイルを変換するコマンドが実行され、ターゲットであるhtmlファイルが出力されました。
ちなみにターゲットが指定されていない場合、Makefileで最初に定義されたターゲットを生成します。つまりこの場合は単にmake
を実行しても大丈夫です。
make
2回続けてmake
を実行するとどうなるでしょう。
make
make: 'example.html' is up to date.
makeは
このように、makeは何を編集したら、何を実行すべきかを判断して実行してくれるツールです。
パターンルール
ターゲットが1つだけの単純な例では、makeの恩恵を感じられないかもしれません。もう少し複雑な例を紹介します。
複数のRmdファイルを変換したいケースを考えましょう。もちろん以下のように変換したいRmdファイルの数だけルールを列挙することはできます。
example1.html: example1.Rmd
Rscript -e 'rmarkdown::render("example1.Rmd")'
example2.html: example2.Rmd
Rscript -e 'rmarkdown::render("example2.Rmd")'
しかし、これはほぼ同じ内容の繰り返しで冗長で分かりづらく、書き間違える可能性もあります。また、コマンド部分を修正したい場合、すべての該当箇所を書き換える必要があります。
抽象化しましょう。makeにはパターンルールというしくみがあります。パターンルールはターゲットの指定に%
を含むことができ、%
は空ではない任意の文字列にマッチします。つまり%.html: %.Rmd
のように記述でき、素直に
%.html: %.Rmd
Rscript -e 'rmarkdown::render("$<")'
$<
という見慣れない記号が現れました。これは自動変数と呼ばれるもののひとつで、ソースファイルの名前に置換されます[1]。つまりmake example1.
を実行したとき、$<
はexample1.
に置換され、結果コマンドRscript -e 'rmarkdown::
が実行されます。
パターンルールによって、任意の名前のRmdファイルが変換できるようになりました。
make example1.html make example2.html
より実用的に
これまでの例では、ターゲットは作成したいファイル名を指していました。makeでは擬似ターゲットという、実存しない抽象的なターゲットを定義できます。makeには.PHONY
という特殊なターゲットが用意されていて、.PHONY
の依存項目に指定したものを疑似ターゲットに定義できます。例を挙げます。
.PHONY: html
html: example1.html example2.html
ここでhtml
は疑似ターゲットで、htmlをターゲットに指定すると、依存項目であるexample1.
、example2.
を生成しようとします。Makefile全体は以下のようになります。
.PHONY: html
html: example1.html example2.html
%.html: %.Rmd
Rscript -e 'rmarkdown::render("$<")'
これでmake html
、または単にmake
を実行するだけで、変更のあったRmdファイルだけを変換できるようになりました。
なお、よく使われる疑似ターゲットにclean
があります。clean
は通常はmakeで生成したファイルをすべて削除して、makeを実行前の状態に戻すために使われます。今回の例では以下のように指定するとよいでしょう。
.PHONY: clean
clean:
rm -f *.html
このルールによって、make clean
でhtmlファイルすべてを削除できるようになります。html
ターゲットと違い、clean
には依存項目を指定していないため、make clean
を実行するとコマンドrm -f *.html
は必ず実行されます。
ここまで使いこなせると、Makefileがずいぶん実用的になります。make html
、または単にmake
を実行すれば常に成果物となるhtmlファイルは最新になります。複数のRmdファイルを更新してうっかりそのうちの1つを変換し忘れる、ということもなくなるでしょう。Makefileを書いたおかげで、何を生成するためにどんなコマンドを実行するべきかを覚えておく必要がなくなりました。
あともう少し工夫して、Makefileをより汎用的にしましょう。まずは変数です。他のプログラミング言語同様、makeは変数を扱うことができます。値に適切な名前を付けることで、それが何であるかを把握しやすくなります。先ほどのhtml
ターゲットを変数を使って書き換えてみましょう。
htmls = example1.html example2.html
.PHONY: html
html: $(htmls)
変数名を$()
で囲うことで値を取り出すことができます。
ところで、htmlファイルをひとつひとつ列挙するのは大変ですし、対象となるファイルが増えるたびにMakefileを修正するのも面倒です。ワイルドカードを使って、*.html
のようにすべてのhtmlファイルを指定したいですよね。変数の値にワイルドカードで指定したファイル名を展開するには、wildcard関数を使います。
htmls = $(wildcard *.html)
実はこの指定方法では十分ではありません。まだ一度もmake
を実行していない場合やmake clean
を実行した直後はワイルドカードにマッチするhtmlファイルが存在せず、htmls
ターゲットが変換する対象が空になってしまい、何も実行されないからです。まずはソースとなるRmdファイルを列挙し、その結果を元にターゲットのhtmlファイルのリストを作りましょう。以下のように指定します。
rmds = $(wildcard *.Rmd)
htmls = $(rmds:.Rmd=.html)
$(rmds:.Rmd=.html)
は置換参照という機能を使って、rmds
の値の中の.Rmd
をすべて.html
に置き換えています。
これで任意のRmdファイルをhtmlに変換できるようになりました。Makefile全体は以下のようになります。
rmds = $(wildcard *.Rmd)
htmls = $(rmds:.Rmd=.html)
.PHONY: html
html: $(htmls)
%.html: %.Rmd
Rscript -e 'rmarkdown::render("$<")'
.PHONY: clean
clean:
rm -f *.html
targetsとの使い分け
「#R登山本」
まとめ
本稿では、Makefileの基本的な書き方、そして実用的なMakefileを書くためのいくつかのしくみを説明しました。makeは一度書けば、欲しいもののために何をすべきかを判断して実行してくれる、動く手順書と言えるでしょう。
makeはとても多機能なツールで、この記事で紹介し切れなかった機能がたくさんあります。より使いこなしたい場合は公式のマニュアルを参照してください。
では良いコマンドラインライフを。