きれいなRakefileを書く方法

3498 ワード

Rakeはもう紹介しません.RubyのMakeは、Makeよりも多くの面で使われています.Makefileとは異なり、Rakefile自体が実はRubyコードであるというメリットはたくさんありますが、Rakeの中ではどんなRubyでも直接できることができる一方で、RubyはDSLによくサポートされているため、Rakefileは通常それほど「コード」に見えません.
しかし、コードは常にコードであり、Makefileはまだめちゃくちゃに書くことができます.Rakefileはめちゃくちゃに書くのが簡単です.幸いなことに、RakeはRakefileに組織的な仕事をする機能を提供しています.
その一つがimport機能で、異なる機能のtaskを異なるファイルに書きます.例えば、このようにします.
 
  
Rakefile
task/
  +-- doc.rake
  +-- compile.rake
  `-- deploy.rake

と、Rakefileに
 
  
import("task/doc.rake")

このような文は各サブタスクにインポートすればよいが,異なるタスクを異なるファイルに書き込むとめちゃくちゃにならない.また、importはRuby自身のrequireとは異なり、importはすぐにインポートするのではなく、Rakefile全体の実行が完了してからすべてインポートするので、依存関係を心配することなく任意の場所にimportを書くことができ、共有する変数などはメインRakefileで定義すればよい.
importは異なる機能モジュールを組織しているほか、Rakeはいくつかの重複したタスクを抽象化することができます.具体的には、カスタムtaskです.通常、Rakeが提供する汎用taskとファイルtaskを使用して、rdocの構築やtestの実行など、特定のタスクに対するtaskタイプを構築します.実際、1つのタスクは通常のRubyクラスであり、RakeのTaskクラスを継承し、関連する関数を再定義してカスタムtaskタイプを実現することができます.しかし、これは多少面倒ですが、実際には、定義するタスクをいくつかの小さなタスクに分解して内蔵の汎用taskとfile taskで実現することが多く、このときTasklibでカスタムタスクをより簡単に定義することができます.
具体的には、Tasklib(実際には約束であって必須ではないが)から継承されたクラスを書き、このクラスの初期化関数でtaskまたはfileで実際にタスクを完了したサブtaskを定義すればよい.実際の例では、例えば、いくつかのErlangファイルをディレクトリの下にコンパイルし、cleanのときに自動的にコンパイルできるErlcTaskを定義することができます.beamファイルのクリーンアップ:
 
  
require 'rake'
require 'rake/clean'
require 'rake/tasklib'

class ErlcTask < Rake::TaskLib
  attr_accessor :name
  attr_accessor :sources
  attr_accessor :dest_dir
  attr_accessor :include_path
  attr_accessor :flags
  attr_accessor :extra_dep

  def initialize(name = :erlc)
    # default values
    if name.is_a? Hash
      @name = name.keys.first
      @extra_dep = name.values.first
    else
      @name = name
      @extra_dep = []
    end
    @sources = FileList[]
    @dest_dir = '.'
    @include_path = []
    @flags = "-W +warn_unused_vars +warn_unused_import"

    yield self if block_given?
    define
  end

 
  def define
    beams = @sources.pathmap(File.join(@dest_dir, '%n.beam'))

    include_path = Array(@include_path).map{|incl|"-I"+incl}.join(" ")

    directory @dest_dir
    beams.zip(@sources).each do |beam, source|
      file beam => source do
        sh "erlc -pa #{@dest_dir} #{@flags} #{include_path} -o #{@dest_dir} #{source}"
      end
    end

    task @name => beams + Array(@extra_dep)
    CLEAN.include(beams)
  end
end

まずTask関連のプロパティを定義し、初期化関数に初期値を設定し、blockを呼び出して実際の値を入力し、最後にdefine関数を呼び出します.define関数はdirectory、file、taskを使用してディレクトリの作成、コンパイル、クリーンアップのタスクをそれぞれ定義します.ルビーとレイクの基本的な文法を知っていれば、分かりやすいはずです.
次にこのファイルをあるファイルに保存します.rbの中で、それからRakefileの中でquireのをrequireして、このように書くことができます:
 
  
ErlcTask.new :compile do |t|
    t.sources = FileList['src/*.erl']
    t.dest_dir = '../ebin'
    t.include_path = '../include'
    t.extra_dep = :library
end

さわやかに見えます!再利用も可能です.最後に、ついでに感嘆します.最近はPythonを使っていますが、ルビーを書くたびに書くのが気持ちがいいと感じます.Pythonでは見つけられない感じですね.