RakeFileUtils の :noop, :verbose オプション

!注意!現在のRake(0.8.1.1)には、この記事のとおりにやっても一部うまく行かない問題があります。修正は後ほど公開します。id:ke-k:20080212:rakefileutils_fix で公開しています。

前にも書きましたが、Rakeではモジュール FileUtils(正確にはRakeFileUtils) がincludeされています。 FileUtils には、cp, mkdir, mv 等々のメソッドが定義されており、

file 'some-directory' do |t|
  mkdir t.name
end

のようにシェルのコマンドのように各メソッドを使うことができます。FileUtils について詳しくは以下のページを参照してください。

また、Rakeではメソッド sh, ruby も定義されており、シェルコマンドの実行やrubyコマンドの呼び出しも簡単にできるようになっています。

task :hoge do |t|
  sh 'echo hoge'
  ruby '-e', 'puts "hoge"'
end

これらのメソッドには、オプションを指定することができます。

mkdir t.name, :verbose => true, :noop => true

ほとんどのメソッドは、:noop, :verbose を指定できるようになっています。

  • :noop
    • コマンドを実際には実行しない
  • :verbose

cd 等の実行されないとその後のコマンドに影響を与えるコマンドには :noop オプションは無いようです。オプションについて詳しくはマニュアルに書いてあります。あと、コマンドごとに受け付けるオプションの一覧は、 FileUtils::OPT_TABLE にハッシュで入っているようです。

ただ、 :noop, :verbose などを実行するのは通常コマンドの実行内容を確認したいときであり、全体もしくは部分で指定できるとうれしいと思います。これを実現するのが、モジュール RakeFileUtils です。 FileUtils をincludeしており、以下の部分でラッパを生成しています。

module RakeFileUtils
  # ...
  FileUtils::OPT_TABLE.each do |name, opts|
    default_options = []
    if opts.include?('verbose')
      default_options << ':verbose => RakeFileUtils.verbose_flag'
    end
    if opts.include?('noop')
      default_options << ':noop => RakeFileUtils.nowrite_flag'
    end

    next if default_options.empty?
    module_eval(<<-EOS, __FILE__, __LINE__ + 1)
    def #{name}( *args, &block )
      super(
        *rake_merge_option(args,
          #{default_options.join(', ')}
          ), &block)
    end
    EOS
  end

前述のとおり、 FileUtils::OPT_TABLE には、各メソッドとそのメソッドが受け付けるオプションが入っています。これを見て、 :noop と :verbose を受け付けるメソッドには、デフォルト値を簡単に変更できるように、module_eval で同名のラッパメソッドを生成しているのが上記の部分です。デフォルト値は、 verbose, nowrite の各メソッドで設定します。

noop = nowrite # 現在の :noop デフォルト値を取得
v = verbose    # 現在の :verbose デフォルト値を取得
noop = nowrite(true)     # :noop デフォルト値を設定、noop には設定前のデフォルト値が入る
v = verbose(true)        # :verbose デフォルト値を設定、v には設定前のデフォルト値が入る
sh "gcc hoge.c -o hoge"  # sh "gcc hoge.c -o hoge", :noop => true, :verbose => true と同じ。コマンドが出力され、実際には実行されない
verbose(v)               # 元に戻す
nowrite(noop)            # 元に戻す

また、範囲で実行する場合には、ブロックで指定することもできます。

nowrite(true) do
  verbose(true) do
    sh "gcc hoge.c -o hoge"
  end
end

ブロックを抜けると、もとの値に戻ります。例外が起きた場合もちゃんと戻してくれます。

rake に --dry-run を付けて実行したときには、

module Rake
  # ...
  class Application
    # ...
    # Do the option defined by +opt+ and +value+.
    def do_option(opt, value)
      case opt
      # ...
      when '--dry-run'
        verbose(true)
        nowrite(true)
        options.dryrun = true
        options.trace = true

verbose(true), nowrite(true) が実行されています。 ただ、 options.dryrun = true もセットされているので、

module Rake
  # ...
  class Task
    # ...
    # Execute the actions associated with this task.
    def execute(args)
      if application.options.dryrun
        puts "** Execute (dry run) #{name}"
        return
      end
      ...

タスクの実行の部分も "** Execute (dry run) #{name}" と表示されるだけで、タスクのブロックそのものが実行はされないのですが。実行コマンドの確認もしたいので、verbose(true), nowrite(true) はするけど、options.dryrun = true はしないオプションが欲しいです。現在は、確認するときだけRakefileの先頭で nowrite(true) を入れるのが現実的な解でしょうか。
--verbose オプションでは verbose(true) が、 --quiet オプションでは verbose(false) が実行されてます。

さて、ここまで書いてきておいてなんですが、現在のバージョンには nowrite, verbose によって設定されるデフォルト値が sh, ruby 以外のメソッドに適用されない問題があります。対策について後ほど書きます。id:ke-k:20080212:rakefileutils_fix で書いています。