Bazelのrules_kotlinについてのメモ
Bazelのrules_kotlinにkotlin_android_library
rule 追加され、Kotlinで書かれたAndroidアプリがビルド可能になったので調べたメモ。
BazelでのAndroidアプリのビルド方法の復習
BazelでAndroidアプリをビルドするのは久しぶりなので、ビルド方法を復習してみた。対象はJavaで書かれたアプリとしてIntroduction to Bazel: Build an Android Appを参考に進める。
Bazelのインストール
brewでインストールする。
$ brew update $ brew install bazel
2017年9月18日時点でBazelのバージョンは0.5.4。Release notesはここから確認できる。
$ bazel version Build label: 0.5.4-homebrew Build target: bazel-out/darwin_x86_64-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar Build time: Fri Aug 25 16:55:29 2017 (1503680129) Build timestamp: 1503680129 Build timestamp as int: 1503680129
サンプルプロジェクトの用意
Bazelリポジトリからサンプルプロジェクトをcloneする。
$ git clone -b source-only https://github.com/bazelbuild/examples
examples以下のtutorial/android
がAndroidのサンプルプロジェクトになる。
workspaceのセットアップ
workspaceはディレクトリでソフトウェアのソースコードや、WORKSPACE
やBUILD
ファイルといったBazelがソフトウェアをビルドするために必要な情報が含まれる。また、workspaceにはoutput先のシンボリックリンクが貼られる。
workspaceディレクトリはファイルシステム上のどこに置いてもいいが、WORKSPACE
がrootに存在する必要がある。Introduction to Bazel: Build an Android Appチュートリアルでは、workspaceディレクトリをcloneしてきたサンプルプロジェクトの<YOUR_DIRECTORY_PATH>/examples/tutorial/
に指定する。
環境変数$WORKSPACE
にworkspaceディレクトリを指定すると以後便利なので設定しておく。
$ export WORKSPACE=<YOUR_DIRECTORY_PATH>/examples/tutorial
WORKSPACEファイルの作成
全てのworkspaceディレクトリはrootにWORKSPACE
ファイルを持つ必要がある。WORKSPACE
ファイルは空であるかソフトウェアのビルドに必要なexternal dependenciesへの参照を持つ。依存関係は後で追記するのでまずは空のWORKSPACE
ファイルを作成する。
$ touch $WORKSPACE/WORKSPACE
これで空のWORKSPACE
ファイルが作られる。
WORKSPACEファイルの更新
BazelがAndroidアプリをビルドするためには、 Android SDK build toolsとSDK librariesを使う必要がある。そのため、WORKSPACE
ファイルにAndroid SDKへの参照を追記する必要がある。
WORKSPACE
ファイルに以下の記述を追加することで自動的に環境変数ANDROID_HOME
を参照してAndroid SDKを使用するようになる。この時、インスール済みの最も高いAPIレベルのbuild toolsを自動的に使用する。
android_sdk_repository( name = "androidsdk" )
または、明示的にAndroid SDKのロケーションやAPIレベルbuild toolsバージョンをpath
, api_level
, build_tools_version
といったattributesで指定できる。
android_sdk_repository( name = "androidsdk", path = "/path/to/Android/sdk", api_level = 25, build_tools_version = "26.0.1" )
BUILDファイルの作成
BUILD
ファイルには、ビルド成果物とworkspaceディレクトリ中のソースコードや他の成果物といったdependenciesとの関係を記述する。BUILD
ファイルはBazel build languageによって書かれる。
BUILD
ファイルはpackage hierarchyと呼ばれるBazelのコンセプトの一部で、package hierarchyはworkspaceのディレクトリ構造を覆う論理的な構造?らしい(workspaceのディレクトリ構造がそのままpackage hierarchyとして表されるということかもしれない)。各packageはディレクトリで、関連するソースファイルとBUILD
ファイルが含まれる。packageには独自のBUILD
ファイルを持たないサブディレクトリも含む。package nameはBUILD
ファイルが位置するディレクトリ名となる。
Bazelのpacage階層は、AndroidアプリのJavaのパッケージ階層と異なるが、この2つは共存できる。
サンプルアプリは$WORKSPACE/android/
に全てのソースファイルがあるシンプルなBazel packageを構成している。より複雑なプロジェクトの場合よりネストが深いpackage構成となる。
android_library ruleの追加
BUILD
ファイルを編集する。
$ vi $WORKSPACE/android/BUILD
BUILD
ファイルにはBazelへのいくつかの異なるタイプの命令を書ける。その中で最も重要なのがbuild ruleでBazelにソースコードや依存から最終的な成果物や中間成果物をどうビルドするかを伝える。
AndroidについてはBazelはandroid_library
とandroid_binary
の2つのruleを提供していて、Androidアプリのビルドに利用できる。android_library ruleは、アプリのソースコードやリソースファイルからAndroid library moduleのビルド方法をBazelに伝える。そして、android_binary rule はAndroid application package(APK)のビルド方法をBazelに伝える。
android_library
rule を追加したBUILD
ファイルは以下のようになる。
android_library( name = "activities", srcs = glob(["src/main/java/com/google/bazel/example/android/activities/*.java"]), custom_package = "com.google.bazel.example.android.activities", manifest = "src/main/java/com/google/bazel/example/android/activities/AndroidManifest.xml", resource_files = glob(["src/main/java/com/google/bazel/example/android/activities/res/**"]), )
android_library
rule はBazelがソースファイルからlibrary moduleをビルドするために必要な情報がattributesとして指定する。また、name属性にはactivities
が指定されていて、android_binary
rule からこの名前で成果物を参照することができる。
android_binary
rule はAPKをビルドする。BUILD
ファイルには以下を追記する。
android_binary( name = "android", custom_package = "com.google.bazel.example.android", manifest = "src/main/java/com/google/bazel/example/android/AndroidManifest.xml", resource_files = glob(["src/main/java/com/google/bazel/example/android/res/**"]), visibility = ["//visibility:public"], deps = [":activities"], )
deps
属性は、activities
rule の成果物への参照を持つ。これはBazelがandroid_binary
rule でビルドする際にactivities
library rule でビルドされた成果物が最新かを最初にチェックすることを意味している。もし最新でなければactivities
rule でのビルドを実行し、その成果物をAPKのビルドに使用する。
最終的なBUILD
ファイルは以下のようになる。
アプリのビルド
workspaceディレクトリに移動し、bazel command-line toolでビルドを開始し、BazelでのUnitテストやその他の操作を実行する。
$ cd $WORKSPACE $ bazel build //android:android
build
コマンドは、Bazelにそれ以降で指定するtargetをビルドすることを指示する。targetはworkspaceディレクトリからのpackageの相対パスとBUILD
ファイルのbuild ruleの名前(target name)で指定する。
target nameやコマンドを実行するディレクトリによってはpackageパスやtarget nameを省略できる。詳細はLabelsで解説されている。
bazel build
を実行すると以下のようにビルドプロセスの進捗が表示され、成果物が出力される。
.............................................................. INFO: Found 1 target... Target //android:android up-to-date: bazel-bin/android/android_deploy.jar bazel-bin/android/android_unsigned.apk bazel-bin/android/android.apk INFO: Elapsed time: 47.792s, Critical Path: 4.48s
成果物の確認
Bazelはユーザーやworkspace毎に中間成果物と最終的な成果物の両方をディレクトリに保存していて、それらのディレクトリへの以下のようなシムリンクがworkspaceディレクリに追加される。
- $WORKSPACE/bazel-bin, which stores binary executables and other runnable build outputs
- $WORKSPACE/bazel-genfiles, which stores intermediary source files that are generated by Bazel rules
- $WORKSPACE/bazel-out, which stores other types of build outputs
android_binary rule で生成されたAPKは、bazel-bin/android
に保存されていて、サブディレクトリandroid
はBazelのpakcage構成と一致している。
bazel-bin/android
ディレクトリにandroid.apk
ファイルがあればビルドは成功している。
$ ls $WORKSPACE/bazel-bin/android
rules_kotlin
rules_kotlinでのAndroidアプリのビルドについて試していく。
rules_kotlinで使用できるrule
以下の5種類を使用できる。
Rule | Description |
---|---|
kotlin_repositories | workspaceディレクトリへの依存関係の読み込み |
kotlin_library | KotlinソースコードからのJavaライブラリのビルド |
kotlin_binary | KotlinソースコードからのJavaバイナリのビルド |
kotlin_android_library | Kotlinソースコードからのandroidライブラリのビルド |
kotlin_test | テストの実行 |
WORKSPACEの設定
rules_kotlin
を利用するにはWORKSPACE
ファイルに以下を追加する。
git_repository( name = "org_pubref_rules_kotlin", remote = "https://github.com/pubref/rules_kotlin.git", tag = "v0.4.0", # update as needed ) load("@org_pubref_rules_kotlin//kotlin:rules.bzl", "kotlin_repositories") kotlin_repositories()
kotlin_repositories()
が実行されるとkotlin releaseからコンパイラを取得しdaggerに関連する依存ライブラリを読み込む。(daggerはKotlinCompilerのBazel workerをビルドするために使われるらしい。)
BUILD rules
BUILD
ファイルに以下を追加することでkotlin_library
ruleが使えるようになる。
load("@org_pubref_rules_kotlin//kotlin:rules.bzl", "kotlin_library")
kotlin_library
kotlin_library( name = "my_kotlin_lib", srcs = ["kotlin_source_file.kt"], deps = [":some_other_kotlin_library_rule"], java_deps = [":some_other_java_library_rule", "@another_maven_jar//jar"], )
deps
属性には、使用したい成果物を提供する他のkotlin_library
targets のname属性の値を指定する。また、java_deps
属性には、他のjava_library
targets かjava_import
targetsのname属性の値を指定する。
Kotlinのソースをkotlincでコンパイルし、対応するjarファイルを出力するには以下のようにする。
$ bazel build :my_kotlin_lib Target :my_kotlin_lib up-to-date: bazel-bin/.../my_kotlin_lib.jar
kotlin_library
targets の成果物をjava_library
targets のインプットとして使用するには、deps
属性に追加したいkotlin_library
targets のname属性に_kt
を加えた値を指定する。これは、他のjava_library
targets についても同様になる。例えば、:my_kotlin_lib
の成果物を使用したいならば以下のように指定する。
android_binary( name = "foo", deps = [ ":my_kotlin_lib_kt`, ] )
kotlin_library
の属性一覧
Name | Type | Description |
---|---|---|
srcs | label_list | 拡張子が*.ktであるKotlinのソースファイル |
deps | label_list | kotlin_library targetsのリスト |
java_deps | label_list | Java provider targets (java_library, java_import, …)のリスト |
android_deps | label_list | Android provider targets (android_library)のリスト |
jars | label_list | jar file targets (*.jar)のリスト |
x_opts | string_list | Additional -X options to kotlincのリスト |
plugin_opts | string_dict | Additional -P options to kotlincのリスト |
kotlin_binary
kotlin_binary
はkotlin_library
と似ているが、main_class
属性(コンパイル済みのKotlinクラス名をJavaのpackage宣言で指定)が追加されている。main_class
属性で指定するクラスはエントリーポイントとしてfun main()
が宣言されていなければならない。
例としては以下のようになる。
kotlin_binary( name = "main_kt", main_class = "my.project.MainKt", srcs = ["main.kt"], deps = [":my_kotlin_lib"] java_deps = [":javalib"] )
自分自身を含む実行な脳なjarファイルを作成するには、暗黙的に宣言されている_deploy.jar
targetを実行する。rules_kotlin/examples/helloworldディレクトリでビルドを実行すると以下のようになる。
$ bazel build :main_kt_deploy.jar INFO: Found 1 target... Target //examples/helloworld:main_kt_deploy.jar up-to-date: bazel-bin/examples/helloworld/main_kt_deploy.jar INFO: Elapsed time: 54.341s, Critical Path: 6.97s
main_kt_deploy.jar
が$WORKSPACE/bazel-bin/examples/helloworld
以下に生成されるのでそれを実行すると以下のようになる。
$ java -jar main_kt_deploy.jar I am Kotlin! ...... ... But what is soy milk? What if soy milk is just regular milk introducing itself in Spanish?
kotlin_binary
の属性一覧
kotlin_library
の属性を全て含み、それに加えてmain_class
属性が追加される。
Name | Type | Description |
---|---|---|
main_class | string | Main class to run with the kotlin_binary rule |
kotlin_android_library
kotlin_android_library
は、kotlin_library
の属性に加えて、Androidに特化した属性が追加されている。aar_deps
、resource_files
、custom_package
、manifest
などがそれにあたる。
ソースコードとリソースからAPKをビルドする例は以下のようになる。RクラスをKotlinのコードで使用したい場合、kotlin_android_library
rule のresource_files
属性を指定する必要がある。
PACKAGE = "com.company.app" MANIFEST = "AndroidManifest.xml" kotlin_android_library( name = "src", srcs = glob(["src/**/*.kt"]), custom_package = PACKAGE, manifest = MANIFEST, resource_files = glob(["res/**/*"]), java_deps = [ "@com_squareup_okhttp3_okhttp//jar", "@com_squareup_okio_okio//jar", ], aar_deps = [ "@androidsdk//com.android.support:appcompat-v7-25.3.1", "@androidsdk//com.android.support:cardview-v7-25.3.1", "@androidsdk//com.android.support:recyclerview-v7-25.3.1", ], ) android_binary( name = "app", custom_package = PACKAGE, manifest = MANIFEST, deps = [ ":src", ], )
ソースコードとリソースを別々のBazel ruleで分ける場合には、android_library
rule を使い以下のようにする。name属性res
でリソースファイルを読み込み、kotlin_android_library
rule のandroid_deps
属性でそれを指定している。
PACKAGE = "com.company.app" MANIFEST = "AndroidManifest.xml" android_library( name = "res", custom_package = PACKAGE, manifest = MANIFEST, resource_files = glob(["res/**/*"]), aar_deps = [ "@androidsdk//com.android.support:appcompat-v7-25.3.1", "@androidsdk//com.android.support:cardview-v7-25.3.1", "@androidsdk//com.android.support:recyclerview-v7-25.3.1", ], ) android_library( name = "java", srcs = glob(["src/**/*.java"]), deps = [ ":res", # And other depedencies ] ) kotlin_android_library( name = "kt", srcs = glob(["src/**/*.kt"]), aar_deps = [ "@androidsdk//com.android.support:appcompat-v7-25.3.1", "@androidsdk//com.android.support:cardview-v7-25.3.1", "@androidsdk//com.android.support:recyclerview-v7-25.3.1", ], android_deps = [ ":res", ] ) android_binary( name = "app", custom_package = PACKAGE, manifest = MANIFEST, deps = [ ":java", ":kt", ":res", ], )
↑の例だとandroid_library
rule でaar_deps
属性を指定しているが、このまま実行するとエラーになるので削除する必要がある。また、kotlin_android_library
ruleのandroid_deps
属性に:res
を指定しても怒られる。これはkotlin_android_library ruleの定義中のkotlin_compile
rule のandroid_deps
属性と名前が重複するから。
kotlin_android_library
の属性一覧
android_library
、kotlin_library
の属性を全て含み、それに加えてaar_deps
属性が追加される。
Name | Type | Description |
---|---|---|
aar_deps | label_list | AAR library targetsのリスト |
kotlin_test
kotlin_test
rule はkotlin_binary
rule とほとんと同じruleになっている(内部的にjava_binary
ではなくjava_test
を呼び出すこと以外同じ)。
kotlin_test( name = "main_kt_test", test_class = "examples.helloworld.MainKtTest", srcs = ["MainKtTest.kt"], size = "small", deps = [ ":rules", ], java_deps = [ "@junit4//jar", ], )
bazel test
でテストを実行することができる。
$ bazel test :main_kt_test.jar ............................................................ INFO: Found 1 target and 0 test targets... Target //examples/helloworld:main_kt_test.jar up-to-date: bazel-bin/examples/helloworld/main_kt_test.jar INFO: Elapsed time: 31.513s, Critical Path: 0.30s ERROR: No test targets were found, yet testing was requested.
サンプルではテストが書かれていないのでERRORとなる。
kotlin_compile
kotlin_compile
rule は、Kotlinコンパイラを実行し.jar
ファイルをKotlinのソースコードから生成する。kotlin_library
rule では内部的にkotlin_compile
rule を実行している。そして、java_import
rule を介して他のJava rule で使用できるようにしている。
kotlin_compile
rule は、通常では直接使用することは無い。
Annotation Processing
現状サポートしていない。TODOにはkapt supportとあるのと、issuesでのやり取りを見る限るBazel 0.6.0でサポートされそう。Incremental compilationなんかもTODOに入ってるので、ビルドのパフォーマンスはもっと良くなりそう。
サンプルコードの実行
rules_kotlinのリポジトリをcloneすることでローカル環境で実行することができる。
$ git clone https://github.com/pubref/rules_kotlin $ cd rules_kotlin
bazel query
で実行可能なtargetの一覧を取得できる。
$ bazel query //... --output label_kind java_binary rule //kotlin:kotlinc java_test rule //examples/helloworld:main_test kotlin_compile rule //examples/helloworld:main_kt_test_kt java_test rule //examples/helloworld:main_kt_test kotlin_compile rule //examples/helloworld:main_kt_kt kotlin_compile rule //examples/helloworld:rules java_binary rule //java/org/pubref/rules/kotlin:worker java_library rule //java/org/pubref/rules/kotlin:preloader java_library rule //java/org/pubref/rules/kotlin:compiler java_library rule //java/io/bazel/rules/closure:BazelWorker java_library rule //java/io/bazel/rules/closure/program:program java_library rule //java/com/google/devtools/build/lib/worker:worker java_binary rule //examples/helloworld:main_kt java_binary rule //examples/helloworld:main_java java_import rule //examples/helloworld:rules_kt java_library rule //examples/helloworld:milk java_library rule //examples/helloworld:guava
以下のようにtargetを指定して実行することができる。bazel run
コマンドはビルドと実行をまとめてやってくれるので便利。
$ bazel run examples/helloworld:main_kt $ bazel run examples/helloworld:main_java $ bazel test examples/helloworld:main_test $ bazel test examples/helloworld:main_kt_test