Neri を使って Ruby スクリプトを実行ファイル（.exe）に変換して配布する

Ruby で書かれたスクリプトを Ruby の入っていない Windows 環境に向けて配布するツールとしては、[OCRA](https://github.com/larsch/ocra) という gem が有名です。

しかし OCRA にはいくつかの欠点[^1]があったため、[Neri](https://github.com/nodai2hITC/neri/blob/main/README.ja.md) という gem を作成しました。このたび ver 1.0 を公開し、今までより多くの Ruby スクリプトを実行ファイルに変換できるようになったので、Neri を使って Ruby スクリプトを実行ファイルに変換するチュートリアルを書いてみたいと思います。

[^1]: 作成当時は OCRA には「日本語ユーザー名の環境では実行できない」という欠点があったのですが、現在は解消されたようです。ただ、x64 版の Ruby を使っていると libssp-0.dll や libgmp-10.dll がデフォルトでは同梱されずエラーになることがあるようで、その場合
 
 ```
 ocra foo.rb --dll ruby_builtin_dlls\libssp-0.dll --dll ruby_builtin_dlls\libgmp-10.dll
 ```
 
 のように明示的に dll を追加する必要があります。（以下参照）
 
 - [Ruby ocraで作ったexeを実行したら “libssp-0.dllが見つからない”と怒られた](https://gordiustears.net/ruby-ocra%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%9Fexe%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%97%E3%81%9F%E3%82%89-libssp-0-dll%E3%81%8C%E8%A6%8B%E3%81%A4%E3%81%8B%E3%82%89%E3%81%AA%E3%81%84%E3%81%A8%E6%80%92/)
 - [OCRA で Ruby スクリプトを .exe に変換するときの注意点 ](https://rabbit-note.com/2021/07/28/ocra-loaderror/)
 
 Neri はその点を（力技ですが）対処しているので、x64 環境でも問題なく実行ファイルを作成できます。

# Neri の目的

Neri は、ちょっとした CUI スクリプトや、あるいは [DXRuby](https://dxruby.osdn.jp/) や [Gosu](https://www.libgosu.org/ruby.html) を用いたゲーム、[LibUI](https://github.com/kojix2/LibUI)（あるいはそのラッパーである [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-libui)）を用いた GUI プログラムなどを実行ファイルに変換し、Ruby の無い Windows 環境でも容易に動作させられるようにすることが目的です。

多くのライブラリを用いた大規模で複雑なプログラム（例えば Rails）を実行ファイル化することには向かないので、ご了承ください。

# 実行環境

Neri が動作するのは、[Ruby Installer](https://rubyinstaller.org/) を用いてインストールされた Ruby 環境だけです。Windows 以外の環境（含 WSL）では動作しません。

また、.exe ファイルを作成するには Devkit 付きの Ruby Installer が必要です。ただし .bat ファイルを作成する機能もあり、それを使うのであれば Devkit は必ずしも必要ではありません。

この記事は「DXRuby で作ったゲームを .exe 化する」という状況を例にして書いていきます。Ruby Installer with Devkit および DXRuby はインストール済みなものとします。Neri は以下のように gem コマンドからインストールしてください。

```
gem install neri
```

:::note info
DXRuby は、この記事を書いている 2022年01月09日現在、Ruby 最新版である 3.1 には対応していません。（2.5～3.0 に対応。）
また、64bit(x64) 版 Ruby では不安定な動作も多いので、32bit(x86) 版を使用することをオススメします。
（※ なお、Neri は Ruby のバージョンや 32/64bit に関係なく動作します。）

なお、最近の多くの環境では DXRuby を使用するには d3dx9_40.dll が必要になります。その導入方法については下記リンク先を御覧ください。（d3dx9_40.dll は、インストールしている Ruby のフォルダの中の bin フォルダにコピーすることをオススメします。）
:::

- [Windows10では、DXRubyを動かすのに d3dx9_40.dllが必要](https://github.com/mirichi/dxruby/issues/3)

# サンプルプログラム

[⭐ ほしあつめ - GitHub](https://github.com/nodai2hITC/neri_example1)

以下のようなファイル構成になっています。

- `image` フォルダ：画像ファイルの入ったフォルダ
- `src` フォルダ：メイン以外のスクリプトの入ったフォルダ
- `data` フォルダ：その他のファイルの入ったフォルダ
- `star.ico`：実行ファイルのアイコン
- `ほしあつめ.rb`：メインの実行スクリプト

![neri1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/21a45d0a-3766-504d-7428-09d668c2014b.png)

# Neri を用いて実行ファイルを作成する

まず最初に一度、

```
ridk enable
```

として開発環境にパスを通しておく必要があります。

## 基本

```
neri ほしあつめ.rb
```

基本は上のように、`neri` コマンドに続けて実行スクリプトを指定するだけです。

するとまずは一度スクリプトが実行されゲームが起動するので、× をクリックして終了してしまいましょう。

![neri2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/68043287-f6c2-c0e4-c852-8ead9edfc98e.png)

これで実行ファイル `ほしあつめ.exe` と、実行に必要なファイルを収めた `system` フォルダが作成されます。実際に .exe ファイルを実行して、ゲームが動作することを確かめてください。

![neri3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/a962f445-34b6-1235-ac25-06cbbb3bf121.png)

## アイコンを設定する

```
neri --icon star.ico ほしあつめ.rb
```

と、`--icon` オプションを使用することで .exe ファイルにアイコンを設定することができます。

![neri4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/3c8220c5-2385-a821-8b9e-85399d029346.png)

## 実行に必要なファイルをデータファイルにまとめる

### ソースコード (.rb) をまとめる

今のままでは、配布するにあたっては

- `data` フォルダ
- `image` フォルダ
- `src` フォルダ
- `system` フォルダ
- `ほしあつめ.exe`

をすべてまとめて配布する必要があります。実際にはここにさらに Readme とかも追加することになるでしょう。ちょっとゴチャゴチャしている感がありますね。

また、ソースコードが（`ほしあつめ.rb` も `src` フォルダ内のものも）そのままになってしまっているため、Ruby がちょっと分かる人であれば簡単に書き換えて改造することができてしまいます。

そこで、Neri にはそうしたファイルをデータファイルにまとめる機能があるので、それを使って配布するファイルを減らし、簡単には改造できないようにすることができます。

```
neri --icon star.ico ほしあつめ.rb src
```

[^2]こうすると、`system` フォルダ内に `ほしあつめ.dat` というファイルが作成されます。`src` 内のファイル（および `ほしあつめ.rb`）はこのデータファイルに入っているのですが、`require` 等でそのまま読み込めるようになっています。つまりファイルを隠蔽したにもかかわらず、ソースコードを書き換える必要なくそのまま動作させることができます。

[^2]: 実行するスクリプトは、（オプションをのぞいて）一番最初に書く必要があります。つまり
 
 ```
 neri --icon star.ico src ほしあつめ.rb
 ```
 
 と順番を変えるとエラーになります。オプションは後ろに書いても大丈夫なので
 
 ```
 neri ほしあつめ.rb src --icon star.ico
 ```
 
 でも大丈夫です。

### 画像・音声ファイルをまとめる (DXRuby 使用時)

さらに画像（あるいは音声）ファイルについても、DXRuby を使っている場合はソースコードを書き換えることなしに、ファイルをデータファイルに入れてしまうことができます。つまり

```
neri --icon star.ico ほしあつめ.rb src image
```

とすれば `src` と `image` フォルダ内のファイルがデータファイルに入るので、配布に必要なものは以下だけになります。

- `data` フォルダ
- `system` フォルダ
- `ほしあつめ.exe`

### それ以外のファイルをまとめる

まだ `data` フォルダが残っているので、これもまとめてしまいたいところです。しかし残念ながらソースコードや画像ファイル以外のファイルは、そのまま

```
neri --icon star.ico ほしあつめ.rb src image data
```

のようにしてデータファイルに追加しただけでは、うまく動作しません。

上のコマンドで作成した .exe ファイルを実行してみてもゲーム画面はおろかエラーメッセージも何も表示されないので、このままでは原因がわかりにくいですね。そうした場合には `--bat` オプションを使って .exe ではなく .bat を作ると、エラーメッセージが表示されるようになるので原因がわかりやすくなるでしょう。

```
neri --bat --icon star.ico ほしあつめ.rb src image data
```

作成された `ほしあつめ.bat` を実行すると、`ほしあつめ.rb:27:in 'read': No such file or directory @ rb_sysopen - data/secret.txt (Errno::ENOENT)` とのエラーで、`data/secret.txt` を読み込めていないことがわかります。こうした場合はソースコードにも手を入れる必要があります。

`ほしあつめ.rb` に、まずは一番上に

```ruby:ほしあつめ.rb
require "neri"
```

を追加しましょう。こうするとデータファイル内のデータを扱うためのメソッドが使えるようになります。

そして、25行目の

```ruby:ほしあつめ.rb
SECRET_MESSAGE = File.read("data/secret.txt", encoding: "utf-8")
```

を、以下のように変更します。

```ruby:ほしあつめ.rb
SECRET_MESSAGE = Neri.file_read("data/secret.txt", "utf-8")
```

`Neri.file_read` は、データファイルがあるときにはそちらから、無ければ実際のファイルからデータを読み込みます。そのため普通に Ruby から実行した際にも、Neri で作った .exe ファイルを実行した際にも、同じ様にデータを読み込むことができます。

この上で

```
neri --icon star.ico ほしあつめ.rb src image data
```

と `data` フォルダ内のファイルもデータファイルに入れておけば、配布に必要なものは

- `system` フォルダ
- `ほしあつめ.exe`

の 2 つだけになりました。非常にすっきりしましたね。

## データファイルを暗号化する

しかし、今のままでは問題があります。`data/secret.txt` は、10 点以上の点を取った人だけに見せたい秘密のメッセージです。しかし無理やりデータファイルを開いてみると、この秘密のメッセージがそのまま保存されてしまっていることがわかります。

![neri5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/f34719dd-35b4-e541-3bd2-cddd37f92384.png)

それが気になるのであれば、`--encryption-key` オプションを使って暗号化しましょう。[^3]

[^3]: 暗号化と言っても簡易的なものなので、つよつよエンジニアの手にかかれば解読可能です。重要なデータの暗号化には使わないでください。

```
neri --encryption-key tekitounakey --icon star.ico ほしあつめ.rb src image data
```

こうすればデータファイルを開いても、何が書いてあるのかわからなくなります。

![neri6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/1af33ac8-057e-83b3-0ed2-b61a2efc306c.png)

## 出力先を別フォルダにする

デフォルトでは Neri はカレントフォルダに実行ファイル等を作成しますが、`--output-dir` オプションを使って別のフォルダ内に作成するようにしてみましょう。

```
neri --output-dir ../starcollector --encryption-key tekitounakey --icon star.ico ほしあつめ.rb src image data
```

## 設定ファイルを作る

先程のような長いコマンドを何度も入力するのは大変です。そこで `neri.config` というファイルを作っておくと、そこから設定を読み込んでくれます。

``` neri.config
--output-dir ../starcollector
--encryption-key tekitounakey
--icon star.ico
ほしあつめ.rb
src
image
data
```

こうしておけば、

```
neri
```

だけで実行ファイルを作ってくれます。

一部の設定だけを `neri.config` に書いておき、残りの設定を Neri 実行時に指定することもできます。

## いちいち × をクリックして終了しなくても済むようにする

今の状態では、実行ファイルを作成するたびにいちいちゲームが起動してしまい、そのたびに × をクリックして終了する必要があります。

実行ファイル作成時には module `NeriBuild` が定義されているので、これの有無をチェックし 有るなら早期に[^4]終了させてしまえば、いちいち × をクリックする必要がなくなります。

[^4]: 一通りライブラリのロードが終わってからにする必要があります。

```ruby:ほしあつめ.rbの6行目に追加
exit if defined? NeriBuild
```

# LibUI を使ったスクリプトを .exe 化する

GUI ライブラリ LibUI を使ったスクリプトも問題なく .exe 化できることを確かめています。LibUI の [Usage](https://github.com/kojix2/LibUI#usage) にある以下のサンプル（※ ただし 1 行目を追加）を Neri で .exe 化できることを確かめてみてください。

```ruby:libui_example.rb
require 'rbconfig'
require 'libui'

UI = LibUI

UI.init

main_window = UI.new_window('hello world', 200, 100, 1)

button = UI.new_button('Button')

UI.button_on_clicked(button) do
  UI.msg_box(main_window, 'Information', 'You clicked the button')
end

UI.window_on_closing(main_window) do
  puts 'Bye Bye'
  UI.control_destroy(main_window)
  UI.quit
  0
end

UI.window_set_child(main_window, button)
UI.control_show(main_window)

UI.main
UI.quit
```

![neri7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180658/f76e53d8-d83b-dc3a-c336-99f73409125b.png)

:::note info
1 行目の require 'rbconfig' が無いと、スクリプトを実行した際は問題なく動作しますが、Neri で作った .exe ファイルを実行しようとするとうまくいきません。

Neri はデフォルトでは軽量化のために Rubygems を取り除いてしまうのですが、Rubygems が RbConfig を require しており、そして現行バージョン（0.0.13）の LibUI は RbConfig を（require 無しで）使用しているためです。

今後のバージョンでは、この点が改善され require 'rbconfig' が不要になるかもしれません。
:::

# 最後に

この Neri は実は 3 年ほど完全に放置してしまっていたプロジェクトだったのですが、GitHub でプルリクを送ってくださった方がいて、それで 3 年ぶりにやる気を出して正式版の公開にまでこぎつけることができました。この場を借りて御礼申し上げます。