Rubyのrakeについての基礎知識

## rakeとは

> Rake は Make によく似た機能を持つ Ruby で書かれたシンプルなビルドツールです。

https://docs.ruby-lang.org/ja/2.7.0/library/rake.html


> 世間では、ビルドツールというとMakeやApache Antが有名で、よく使われている。 Rakeは、これらのいいとこ取りをした上で、特有のフィーチャーを追加した新しいビルドツールであり、複雑なビルドを柔軟に書きこなすことができる。その秘密は内部DSLという仕組みにあり、このおかげでビルドの記述にRubyの強力な文法をそのまま使うことができる。この自由度の高さは、ビルドの記述に独自の言語の使用を選択したMakeとAntには無い強みだ。

http://www2s.biglobe.ne.jp/~idesaku/sss/tech/rake/


rakeとはrubyで書いたビルドツールのこと。
makeでいうMakefileの代わりに、Rakefileというファイルを作成して実行することができる
RakefileはRubyのDSLで書くことができるのでrubyさえわかれば記述が可能なので楽

## rakeの用語についてのまとめ

|用語|意味|
|:--|:--|
|task|Rakeファイルの基本単位。|
|ファイルタスク|ファイル生成に特化したタスク|
|アクション|taskのブロック内の実行されるコードのこと|
|invoke|タスクを実行する。実行されたことのあるアクションは実行しない。依存しているタスクも呼び出だす|
|execute|タスクを実行する。常にアクションを実行する。依存関係にあるタスクは実行しない|


invokeとexcuteについてはこちらの記事が非常にわかりやすい。

rakeタスク内で別のタスクを呼び出す
http://qiita.com/paty-fakename/items/5df189681c92ce1e8004

## Rakefileを作成してみる

`Rakefile`という名称でファイルを作成し、下記を記述する

```ruby
task :hello do
  puts 'hello world'
end
```

rakeコマンドを入力してタスクを実行すると結果が返ってくる

```
$ rake hello

#=> do task hello!
```


### 依存関係を作って実行してみる

今度は以下のコードをRakefileに書いてください

```ruby
task "dist" => ["init", "compile"]

task "init"

task "compile"
```

以下で実行。
`-t`オプションをつけると実行内容が表示される。

```
$ rake -t dist

** Invoke dist (first_time)
** Invoke init (first_time)
** Execute init
** Invoke compile (first_time)
** Execute compile
** Execute dist
```


`dist`を実行すると、initとcompileが順番に実行されたのがわかると思います
taskはハッシュを引数にすると、task同士の依存を書くことができます


ちなみに`rake`というコマンドを入力した場合は、`default`という名前のタスクが実行されるようになっている

```ruby
task "default" => "dist"

task "dist" => ["init", "compile"] do
  puts "dist"
end

task "init" do
  puts "init"
end

task "compile" do
  puts "compile"
end
```

なので上記のようにRakefileを書き換えて`rake`を実行するとdefaultのタスクが実行されて依存関係のあるdistが実行される

```
$ rake

#=> init
#=> compile
#=> dist
```


## fileタスク

先ほどまでtaskブロックで実行してきたが、rakeにはファイル作成に特化したタスクもある

fileタスクの第一引数には実行するファイルのパスを指定する
taskと結びつけることによって、rake実行時に`rake test_txt`として名称を指定して実行できる

```ruby
# fileタスクをtaskと結びつける
task test_txt: './test.txt'

desc 'test.txtの中身を読み込んで、read.txtを作成する'
file './test.txt' do
  puts 'write read.txt'

  File.open('./test.txt', 'w') do |f|
  	f << "success"
  end
end
```

`desc`というのが新たに出てきているが、ここにはタスクの説明を書くのに使用する
実行すると、test_dir/test.txtが作成される

```
$ rake test_txt

#=> write test.txt
```


### fileタスクの利点

このfileタスクは下記の場合のみ実行される

* 指定したファイルパスにファイルがない場合
* ファイルが作成されている場合、依存先のファイルの更新日時を調べそれが目的ファイルより新しくなっている場合

つまり、ファイルを新規に作成しなければならないときか、ファイルが変更されたときのみ実行できる。なので、ファイル操作の条件判定などを自分で書く必要がなくなる


なので先ほどのtest_txtを2回やっても、test.txtが作成済みなので何も実行されない。

```
$ rake test_txt

#=> (何も実行されない)
```


### 依存関係のあるfileタスクを書く

fileタスクの引数をハッシュ形式にすると依存が書ける
`'実行予定のファイルパス' => '依存しているファイルパス'`という形式で書く

先ほどのファイルを少し改変して、test_dir/test.txtを作成したあと、それを利用してread.txtというファイルを作成する


```ruby
task read: './read.txt'

desc 'test.txtの中身を読み込んで、read.txtを作成する'
file './read.txt' => './test.txt' do
  puts 'write read.txt'

  File.open('./read.txt', 'w') do |f|
  	# test.txtの中身を読みこんでread.txtに書き込む
    f << File.read('./test.txt')
  end
end

desc 'test.txtを作成する'
file './test.txt' do
  puts 'create test.txt'
  File.open('./test.txt', 'w') do |f|
  	f << "success"
  end
end
```

test.txtがないときにこれおを実行すると、test.txtとread.txtが作成される

```
$ rake read

#=> write test.txt
#=> create read.txt
```

上記で書いたようにfileタスクはファイルがない場合か、依存ファイルが変更された場合のみ実行される。なのでもう一度実行してもタスクは実行されない

```
$ rake read

#=> (何も実行されない)
```

試しに依存ファイルのtest_dir/test.txtの中身を変更してみる

```test.txt
success
success2 # この行を追加
```

するとread.txtに関するタスクだけが実行されるように表示される。
もちろんread.txtの中身も書き換わっている

```
rake read

#=> write read.txt
```


### fileタスクを利用してc言語のソースをコンパイルしてみる

冒頭でMakeに似たビルドツールという風に書いたように、C言語のソースをコンパイルするタスクも書くことができる。下記のようにhello worldを実行するだけのC言語のファイルがあったとする

```hello.c
#include <stdio.h>

int main(int argc, const char * argv[]) {

  printf("hello world!\n");
  return 0;
}
```

先ほどの依存関係の実行と同じように下記のように書く

```ruby
CC = "gcc"

task :default => "hello"

# helloというファイルを作成するためにはhello.oが必要
file "hello" => "hello.o" do
  sh "#{CC} -o hello hello.o"
end

# hello.oというオブジェクトファイルをコンパイルするためにはhello.cというファイルが必要
file "hello.o" => "hello.c" do
  sh "#{CC} -c hello.c"
end
```


実行するとhelloというファイルができる
taskにdefaultを指定しているので、rakeだけで実行できる

```
$ rake
```

ファイルが作成されたので実行して世界にあいさつ

```
$ ./hello

# => hello world!
```

## RailsのRakeタスクはどう動作するのか

Rakefileがあるとrakeコマンドが実行できることはわかったと思うが、Railsでタスクを作成するときは`lib/tasks`に作成するのが基本である。

RailsのRakfileは以下のようになっている。

```ruby
require_relative "config/application"

Rails.application.load_tasks
```

このload_tasksは以下で定義されている

https://github.com/rails/rails/blob/c0d91a4f9da10094ccdb80e34d1be42ce1016c9a/railties/lib/rails/engine.rb


```rails/railties/lib/rails/engine.rb
def load_tasks(app = self)
  require "rake"
  run_tasks_blocks(app)
  self
end

# ~省略~

def run_tasks_blocks(*) #:nodoc:
  super
  paths["lib/tasks"].existent.sort.each { |ext| load(ext) }
end

```

`paths`に`lib/tasks`があるかどうか`existent`で判断して、あればloadするという流れになっている。


## 参考

rubyリファレンス rake
https://docs.ruby-lang.org/ja/2.7.0/library/rake.html

RubyによるビルドツールRakeの覚え書き
http://www2s.biglobe.ne.jp/~idesaku/sss/tech/rake/