はじめに

C言語/C++でコンパイルするときは、makeするのが当たり前だと思っていた昨今、rakeなるビルドツールがあるとのことで、調査してみた。

まずは、世の中の人気度。。。。makefileの5%にも満たないシェア(2013年現在)

環境

  • Cygwin
  • Ruby 1.9.3
  • Rake version 10.0.4

目次

rakeとmakeの比較とメリット

rakeとは、makefileをrubyの文法で記述するためのツール。もう少し正当にいうと、繰り返し使うようなスクリプトを「タスク」にまとめて、必要に応じて実行できるツール。

makeとrakeを比較すると、

  • makeはmakefileを作ってmakeする。
  • rakeはrakefileを作ってrakeする。

なんだか偽物のミッキーマウスみたい(-_-;)しかし、makeに対するrakeのメリットは以下。

  • 記述にRubyが使える

    汎用的なRubyの文法をそのまま使ってビルド手順がかける。なのでmakeのような独特な言語を覚えて(しかも他では使えない)使う必要はない。

  • 美しく記述できる

    Ruby自体が見やすい記法を心がけて開発されているので、記述がわかりやすい。

Let’s インストール!━⊂( ・∀・)彡

> gem install rake

はじめてのrake

まずは、hogehoge.c hogehoge.h uhauha.c uhauha.h をgccでコンパイルして、hogehoge.exeを作成して実行する、というものをやってみる。

ディレクトリ構成

% tree

.

├── include

│ ├── hogehoge.h

│ └── uhauha.h

└── src

├── hogehoge.c

└── uhauha.c

コマンドライン

% gcc -Iinclude src/uhauha.c src/hogehoge.c -o hogehoge.exe && ./hogehoge.exe

makefile

C_COMPILER = gcc
TARGET=hogehoge.exe

#Source FIles
SRCS  =src/hogehoge.c
SRCS +=src/uhauha.c

#Include Files
INC_DIR =-Iinclude

all: target

target:
        $(C_COMPILER) $(INC_DIR) $(SRCS) -o $(TARGET)
        ./$(TARGET)

clean:
        rm $(TARGET)

rakefile

C_COMPILER = "gcc"
TARGET="hogehoge.exe"
INC_DIR ="include"

task :all => ["clean", "run"]
task :default => "run"

task :run => "hogehoge" do
  puts "---- run ----"
  sh "./#{TARGET}"
end

task :clean do
  puts "---- clean ----"
  sh "rm src/*.o *.exe"
end

file "hogehoge" => ["src/hogehoge.o", "src/uhauha.o"] do |t|
  puts "---- link ----"
  sh "#{C_COMPILER} -o #{t.name} #{t.prerequisites.join(' ')}"
end

rule '.o' => '.c' do |t|
  sh "#{C_COMPILER} -I#{INC_DIR} -c #{t.source} -o #{t.name}"
end

rakefileの解説

rakefileについて、順に解説。

ちなみに、一番網羅的でわかりやすいのは、Ruby公式リファレンス。

https://doc.ruby-lang.org/ja/1.9.3/library/rake.html

Rake Tasks

rakefileはタスクからできている。タスクを順次、実行するための手順を書くのがrakefile。ということで、タスクの概念はrakeには必須。

  • rakeではタスクを見つけると、そのタスクを実行させるために処理を芋づる式に探して実行していく。
  • タスクの定義方法は、 「task :(タスク名) 」。
  • タスクに依存関係を記述する場合は、「task :(タスク名) => “(依存するタスク)"」。依存関係は配列で渡すこともできる。
  • タスクに実施させる処理(action)は、ブロック(do end)で与える。
task :all => ["clean", "run"]
task :default => "run"

task :run => "hogehoge" do
  puts "---- run ----"
  sh "./#{TARGET}"
end

task :clean do
  puts "---- clean ----"
  sh "rm src/*.o *.exe"
end

File Tasks

ファイル作成のためのタスクかファイルタスク。定義の仕方はタスクと同じ。

rakeタスクは必ず実行されるが、ファイルタスクはタイムスタンプをチェックする。ファイルに変更があった場合のみ実行される。これが、makeと同じところ。

file "hogehoge" => ["src/hogehoge.o", "src/uhauha.o"] do |t|
  puts "---- link ----"
  sh "#{C_COMPILER} -o #{t.name} #{t.prerequisites.join(' ')}"
end
  • t.nameは、タスク名(hoehoge)
  • t.prerequisitesは、依存先タスク名の配列 [“src/hogehoge.o”, “src/uhauha.o”]

をそれぞれ指す。詳しくは以下のURL。

class Rake::Task

rule

異なるファイルに対して、同一の処理を実行するためには、ruleを定義する。ruleを満たす場合に実行される処理を書く。

rule '.o' => '.c' do |t|
  sh "#{C_COMPILER} -I#{INC_DIR} -c #{t.source} -o #{t.name}"
end

t.sourceは、依存するファイルパス(src/c)

Rakeのもう一歩進んだ使い方

コメント

タスクにコメントを記述するには、desc “hogehoge"と書きます。

desc "Clean and Run"
task :all => ["clean", "run"]

desc "Run build"
task :default => "run"

記述は、 rake -Tで見れます。

[tsu-nera]% rake -T
rake all      # Clean and Run
rake clean    # Remove old files
rake default  # Run build
rake run      # Build Main

clean

.oや.exeは普通、rmコマンドで削除するが、rakeにはこれを補助する機能がある。

使い方は、‘rake/clean’ を requireで取り込んで、CLEAN、CLOBBERに削除したいファイルを登録する。すると、rake clean/rake clobberタスクが実行できるようになる。

rake clean    # Remove any temporary products.
rake clobber  # Remove any generated file.

cleanコマンドはCLEANに登録されたファイルを削除。clobberはCLEANとCLOBBERに登録されたファイルを削除する。以下の3行を追加。

require 'rake/clean'
CLEAN.include("src/*.o")
CLOBBER.include("*.exe")

実行結果

[tsu-nera]% rake clean
rm -r src/hogehoge.o
rm -r src/uhauha.o
[tsu-nera]% rake clobber
rm -r hogehoge.exe

library rake/clean

ファイルリスト

ディレクトリ配下のファイルに対してまとめて同じ処理を実行するためには、ファイルリストを使うと便利。

こんな感じで宣言する。これは、src配下にあるすべてのソースをSRCSというファイルリストに入れている。考え方はArrayと同じ。

SRCS = FileList[“src/*.c”]

リストの拡張子を変更するために、extメゾッドが用意されている。これで、.cファイルが.oファイルに置き換えられる。

OBJS = SRCS.ext(‘o’)

ファイルリストにファイルを追加するためには、includeメゾッド、除外するためにはexcluleメゾッドを利用する。はじめに、srcs = FileList["**/*.c”]で全てのファイルをリストに加えてから、srcs.exclude(“src/hogehoge.c”)と書いて除外したりできる。

SRCS = FileList["src/*.c"]
OBJS = SRCS.ext('o')

file "hogehoge" => OBJS do |t|
  puts "---- link ----"
  sh "#{C_COMPILER} -o #{t.name} #{t.prerequisites.join(' ')}"
end

class Rake::FileList

まとめ

今までのをまとめると、こうなった。

C_COMPILER = "gcc"
TARGET = "hogehoge.exe"
INC_DIR ="include"

require 'rake/clean'
CLEAN.include(["src/*.o", "*.exe"])
#CLOBBER.include("*.exe")

desc "Clean and Run"
task :all => ["clean", "run"]

desc "Run build"
task :default => "run"

desc "Build Main"
task :run => "hogehoge" do
  puts "---- run  ----"
  sh "./#{TARGET}"
end

SRCS = FileList["src/*.c"]
OBJS = SRCS.ext('o')

file "hogehoge" => OBJS do |t|
  puts "---- link ----"
  sh "#{C_COMPILER} -o #{t.name} #{t.prerequisites.join(' ')}"
end

rule '.o' => '.c' do |t|
  puts "---- compile ----"
  sh "#{C_COMPILER} -I#{INC_DIR} -c #{t.source} -o #{t.name}"
end

依存関係を美しく記述デキる点がとても気に入った。また、今まさにRubyを勉強中なので、makefileよりもモチベーションが上がる。ということで、もう少しいろいろ調べてみます。

参考