"Rust Android Gradle Plugin" を使ってみる

Twitter で Rust の Gradle プラグインがあるという話を聞いたので試してみました。

* [Rust Android Gradle Plugin](https://github.com/mozilla/rust-android-gradle)

Rust で Android 向けバイナリをビルドするという話はすでにいくつか記事があげられています (ありがとうございます) 。

* [RustでAndroidアプリを作る](https://qiita.com/nacika_ins/items/7aaad419fac128ac5b6d)
* [RustをクロスコンパイルしてAndroidで動かす - totechite's blog](https://totechite.hatenablog.com/entry/2019/01/07/000000)

Gradle でやってみたものはなかったっぽいので備忘録的にまとめておこうと思います。

環境:

* Windows 10
* Android Studio 3.5.3
    * Android SDK Tools 26.1.1
    * Android SDK Platfrom Tools 29.0.5
    * NDK 21.0.6113669
    * Rust plugin v0.2.106.2135-191
    * Toml plugin v0.2.114.37-92
* rustc 1.40.0 (73528e339 2019-12-16)
* [rust-android-gradle 0.8.3](https://github.com/mozilla/rust-android-gradle/releases/tag/v0.8.3)
* [Crate jni 0.14.0](https://docs.rs/jni/0.14.0/jni/index.html)


## 手順

基本的には [README.md](https://github.com/mozilla/rust-android-gradle/blob/master/README.md) に従っていけば OK です。

rustup (rustc, cargo) 、 Android Studio 及び SDK、 NDK は事前にインストール済みとします。

### Rust ターゲットプラットフォームのインストール

Rust は通常のデスクトップ向けのビルドではなく Android 用バイナリビルドをすることになるのでクロスコンパイル環境が必要になります。まずこれらのインストールをします。

rustup を使える状態にしてコンソールを開き、下記コマンドを実行します。

```
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add aarch64-linux-android
rustup target add x86_64-linux-android
```

ARM, x86 の 32bit/64bit の 4 パターンですが、必要なものだけインストールでもよいと思います。

注意点としてはターゲットプラットフォームは rustup の各 tool chain 毎にインストールすることになり、かつ現在 default に設定されている tool chain にインストールすることになります。特に Windows では gnu と msvc を切り替えて使う人は結構いそうな気がしますが、そのような場合は注意してください。

### Android Project の作成～プラグイン設定

Android Studio で適当にプロジェクトを作成します。この時注意ですが、 **Minimum Version は NDK にインストールされている platform バージョン ($NDK_HOME/platforms) で有効なものを指定する** 必要があります。

次に gradle の設定を行います。プロジェクトルートの build.gradle に Rust Android Gradle Plugin の導入設定を記述します。

```gradle:build.gradle
buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath 'gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.3'
    }
}
```

### Cargo Project の作成

Rust で書くために Cargo Project を作成します。コンソールを開いて Android Project のルートに移動し、 Cargo でプロジェクト生成をします。

```
cargo new --lib rust
```

名前はなんでもよいのですが、 "rust" とします。

### Cargo.toml の設定

* [lib] セクションに crate-type を指定する
* [dependencies] セクションに Crate jni を入れる

```toml:Cargo.toml
[lib]
crate-type = ["cdylib"]

[dependencies]
jni = { version = "0.14.0" }
```

### Android Project (app) の設定

app 下の build.gradle に cargo の設定を入れます。

```gradle:app/build.gradle
apply plugin: 'org.mozilla.rust-android-gradle.rust-android'

cargo {
    module  = "../rust"
    libname = "rust"
    targets = ["arm", "x86"]
}

preBuild.dependsOn "cargoBuild"
```

* "module" は app/build.gradle から見た Cargo Project の位置を指定します
* "libname" は Cargo で出力するライブラリ名を指定します
* targets はビルドターゲットとなる CPU アーキテクチャを指定します (複数可)

### コードを書く

あとは通常の Anroid アプリを書くのと同じようにコードを実装していきます。違うのはネイティブコードを書くための言語が Rust というだけです。

試しに [Crate jni](https://docs.rs/jni/0.14.0/jni/index.html) のサイトで書いてあるサンプルの Rust のコードをそのまま lib.rs にコピペしてみましょう。 
※実際は未使用パラメーターである "class: JClass" の部分はコンパイル時にエラーになるので "_: JClass" とする

このネイティブコードをインポートするコードを書きます。

```kotlin
object HelloWorld {
    external fun hello(input: String?): String?

    init {
        System.loadLibrary("rust")
    }
}
```

あとは実際に使ってみます。

```kotlin
var text = findViewById<android.widget.TextView>(R.id.text_view)
text.setText(HelloWorld.hello("Rust"))
```

使えてました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211064/4a76d8fa-2d45-54cb-564c-938ae6095fe6.png)

## トラブル時の対応

build.gradle の cargo プロパティに "verbose" を定義すると "cargo build --verbose" 相当の動作になるのでコンパイルオプションの内容を確認できます。

```gradle
cargo {
    verbose = true
}
```

または "--stacktrace" や "--info" 付きで実行してみて詳細情報を取得するのもよいでしょう。

```
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
```

## Cargo Project を認識しない

私のところだけかもしれませんが、認識してくれないので依存ライブラリの追跡とかが機能してくれません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211064/d9d50dcf-57a8-8280-a796-7c5a5caa1ba9.png)

"No Cargo projects found" と出ているのが "Attach" を押すと IDE が例外を出して失敗しました。

IntelliJ IDEA で開くと Attach できて認識するようになり、 Android Studio で開き直しても大丈夫になりました。 ".idea/workspace.xml" の project タグ内に下記を追加すればよいようです (パスは適宜修正) 。

```xml
  <component name="CargoProjects">
    <cargoProject FILE="$PROJECT_DIR$/rust/Cargo.toml" />
  </component>
```
 
## デバッガでブレイクしたい

Rust のデバッガは今のところ CLion のみのようなので無理かなあと思われます。 Android のネイティブデバッガが有効にできたとしても IDE 上でブレイクポイントが設定できません (Android Studio, IntelliJ IDEA 共に) 。

CLion からだったらもしかしたら、という気もしたのですが・・・

## おわりに

従来の Rust の Android 向けビルドは結構準備が手間だったり、 .so のビルドは Android Studio とは無関係にコンソールで cargo の実行をして生成物を手でコピーする必要がありましたが、 Rust Android Gradle Plugin を使うと C++ でネイティブプラグイン書いているのとほぼ同じ感覚で Rust のコードを書いてビルドできるので非常に使い勝手がよいです。デバッガがなんとかなれば普通使えそうですので今後に期待したいです。