Rakefileの勘所

あー、毎回watchコマンドとかbuildコマンドとかを打ち込むのはやだなー、ビルドスクリプト書きたいなー、Makefileで十分かCakefileにするかRakefileにするか、よっしゃRakefileにしよう、あれーどういうふうに書けばいいんだっけかなー

と、プロジェクトを作る度に思っている気がするので、自分用のメモをまとめることにします。

Rakeについて

Rakeは、RoRでヘビーに使われていることで一斉を風靡したMakeみたいなビルドツールです。元々、Jim Weirich氏が作成したサードパーティのユーティリティだったのですが、Ruby 1.9から標準ライブラリに組み込まれたようです*1

よく参考にするサイト

とりあえず以下のサイトを見ておけば間違いないと思います。

Rakeの勘所

使い方を一からステップアップで書くのは面倒なので、サンプルとして修論TeXコンパイルする時に使ったコードをそのまま載せることにし、その後でポイントを列挙することにします。

# -*- coding: utf-8 -*-

### 各種設定

# 拡張子を除いたターゲットファイル名(texとbibは同名に)
TARGET = "thesis"
# TeX関係の実行ファイルが入っているディレクトリ
TEX_BIN = "/Applications/UpTeX.app/teTeX/bin"
# 使用するTeXプログラムとオプション
TEX = "#{TEX_BIN}/platex --file-line-error"
# 使用するBibTeXプログラムとオプション
BIBTEX = "#{TEX_BIN}/jbibtex"
# dviをpdfに変換するプログラムとオプション
DISTILLER = "#{TEX_BIN}/dvipdfmx"

### タスク定義

def compile_tex
  sh "#{TEX} #{TARGET}.tex"
end

def compile_bibtex
  sh "#{BIBTEX} #{TARGET}"
end

def join_exts(filename, exts)
  exts.map { |x| "#{filename}.#{x}" }
end

task :default => :compile

desc "一式をコンパイルしてPDFを生成。"
task :compile => "#{TARGET}.pdf" do
  # force=trueなら強制的に一式をコンパイル
  if ENV['force'] == 'true'
    compile_tex
    compile_bibtex
    compile_tex
  end
end

file "#{TARGET}.pdf" => "#{TARGET}.dvi" do |t|
  sh "#{DISTILLER} #{t.prerequisites[0]}"
end

file "#{TARGET}.dvi" => join_exts(TARGET, ["tex", "bbl"]) do
  compile_tex
end

file "#{TARGET}.aux" => "#{TARGET}.tex" do
  # auxファイルが無いなら一度texファイルをコンパイル
  compile_tex
end

file "#{TARGET}.bbl" => join_exts(TARGET, ["aux", "bib"]) do
  compile_bibtex
  # 次にTARGET.dviディレクティブへ戻るが、
  # 戻った時点でTARGET.dviが存在するので処理が終わってしまう。
  # TeXではaux更新後にもう一度コンパイルする必要があるので代わりにここで行う。
  compile_tex
end

require 'rake/clean'
# pdfファイルは残す
CLEAN.include join_exts(TARGET, [
  "aux", "lof", "log", "lot", "out", "toc", "dvi", "bbl", "blg"
])
CLOBBER.include "#{TARGET}.pdf"

以下、ポイントを挙げていきます。

普通にRuby

Rakefileは普通にRubyなので、coding: utf-8とか*2変数宣言とか関数宣言とか自由にできます。

DSL

Rakeといえば言語内DSLですね。よく使われるメソッドとしてはtask, desc, file, shなどがあります。

  • グローバルに使える関数
    • Kernel*3に追加されるメソッド*4
      • desc, directory, file, file_create, import, multitask, namespace, rule, task
      • ちなみに私はdesc, file, taskしか使ったことないです。
    • FileUtilsモジュールのメソッド
      • 全てグローバルに展開されているので、rmやcpなどを裸で書けます。
      • Rakeでは ruby, safe_ln, sh, split_all の4つのメソッドがFileUtilsモジュールに追加されるので*5、これらもグローバルで使えます。
  • 必要に応じてrequireするモジュール
    • ドキュメントの「サブライブラリ」の項で列挙されているモジュール
    • サンプルではrake/cleanを使ってます。

オプション

サンプルでは、:compileタスクで、ENV[‘force’]を参照していますが、

$ rake compile force=true

のように、タスク名の後に、key-valueの形でオプションを書くと、ENVに格納される仕組みになっています。コマンドオプションのようにフラグだけというのは出来ません*6

ブロックの引数

taskやfileのブロックの引数には、Rake::Taskクラスインスタンスが渡されます。

このRake::Taskクラスですが、ドキュメントには書かれていないのですが、プロパティとして、依存しているファイルのリストを持っています。それが、t.prerequisitesです。他にもソースコードを見ると、色々値を取れることが分かります。

ブロックの手抜きはダメ

一度はやりそうなので書いておきますが、ブロックをdo-endではなく{}で書くと爆発します。

task :default => “hoge” { do_something } # 爆発
task :default => “hoge” do
  do_something # :-)
end

引数末尾のハッシュは括弧が省略できるのですが、括弧ブロックを使うのはエラーになります。

*1:リポジトリはどちらもアクティブなので、どの様に開発、保守がされているのかは良く分かりませんね

*2:Ruby 2.xでは要らない

*3:Rubyのグローバル関数が入るモジュール

*4:ドキュメントの最下部を参照

*5:ドキュメントの最下部をry

*6:key-valueの形でないとタスク名として解釈されます