Bazelがバズる前にチュートリアル動かしてみた


Diverse Advent Calendar 2020、6日目の記事になります。

先月、Android Open Source Project(AOSP)がBazelに置き換わったニュースが発表されました。

[Welcome Android Open Source Project (AOSP) to the Bazel ecosystem](https://blog.bazel.build/2020/11/12/aosp_migrating_to_bazel.html)

すごいですね！

このニュースを受け、Bazelって何？ 何ができるの？ この先どうなるの？ って期待と不安を交えてザワザワしてしまった方もいらっしゃるのではないでしょうか？

そこで本記事では、Bazelと仲良くなるための第一歩として、Bazelのチュートリアルを触れてみました！



## Bazelとは

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139890/3f9b28ea-385d-911d-dbe3-c59153598bbf.png" width="200">


[Bazel](https://bazel.build/) は、Googleが社内開発に使用していた独自のビルドツールをオープンソース化したプロダクトです。

特徴として以下のとおりです。

・高度なローカルおよび分散キャッシング、最適化された依存関係分析、および並列実行により、高速でインクリメンタルなビルドが可能
・多くのプログラミング言語に対応
・複数のリポジトリまたは巨大なサイズのコードにも対応できるスケーラビリティ
・対応言語やプラットフォームを簡単に追加できる拡張性


## 環境

・macOS 10.15.7
・Xcode 12.2
・Java 10.0.1
・Python 2.7.17
・Homebrew 2.6.0
・(Bazel 3.7.1-homebrew)
・Tulsi 0.20200930.88

## インストール

macなので Homebrewでさくっといれれます。

```sh
$ brew install bazel
$ bazel --version # bazel 3.7.1-homebrew
```

公式ドキュメントは、[こちら](https://docs.bazel.build/versions/master/install.html) です。

以下、チュートリアルの[リポジトリ](https://github.com/bazelbuild/examples)内の [README](https://github.com/bazelbuild/examples/blob/master/tutorial/README.md) に沿って進めます。 

:warning: 公式ドキュメントにもチュートリアルの記載がありますが、リポジトリの内容と大きく内容が異なるのため注意です。


## ビルド

### 1. ソースもってくる

```sh
$ git clone git@github.com:bazelbuild/examples.git
$ cd example/tutorial
```

チュートリアルには、Android/iOSアプリが含まれており、さらにAndroidアプリの機能でローカルサーバとやりとりする用のサーバ側のコードが含まれています。[^1]
サーバ側のコードはビルドしなくてもアプリ自体は起動することができます。


### 2. ローカルサーバを起動させる

```sh
$ bazel build //backend
$ bazel-bin/backend/backend
```

:warning: 以下のエラーが出る場合があります。

```
java.lang.IllegalArgumentException: the Java7 runtime is not supported anymore.
	at com.google.appengine.tools.development.SharedMain.configureRuntime(SharedMain.java:258)
	at com.google.appengine.tools.development.DevAppServerMain$StartAction.apply(DevAppServerMain.java:374)
	at com.google.appengine.tools.util.Parser$ParseResult.applyArgs(Parser.java:45)
	at com.google.appengine.tools.development.DevAppServerMain.run(DevAppServerMain.java:257)
	at com.google.appengine.tools.development.DevAppServerMain.main(DevAppServerMain.java:248)
```

Google App Engineを使用してビルドする際に、デフォルトのJava 7(古い！)でビルドしようとして起こってるもので、以下のように修正します。

```xml:tutorial/backend/webapp/WEB-INF/appengine-web.xml
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>some-app</application>
    <version>1</version>
    <threadsafe>true</threadsafe>
    <runtime>java10</runtime><!-- 追加 -->
</appengine-web-app>

```

Javaのバージョンに関しては7以上であれば問題ないので、使用してるversionに合わせてください。



### 3-1. Androidのビルド

```sh
$ bazel build //android
```

上記コマンドが成功すると、以下の場所にapkファイルが生成されます。

```
bazel-bin/android/android_unsigned.apk
bazel-bin/android/android.apk
```

さらに、Androidはコマンドが充実していて、以下のコマンドで、ビルド + 端末にインストール + アプリ起動してくれます。

```sh
$ bazel mobile-install //android --start_app
```

:warning: ビルドの際に、以下のエラーが出る場合があります。

```
ERROR: /path/to/examples/tutorial/android/BUILD:8:15: Installing //android:android failed (Exit 1): incremental_install failed: error executing command bazel-out/darwin-py2-opt-exec-2B5CBBC6/bin/external/bazel_tools/tools/android/incremental_install --output_marker bazel-out/darwin-fastbuild/bin/android/android_files/full_deploy_marker --dexmanifest ... (remaining 11 argument(s) skipped)
Traceback (most recent call last):
  File "/private/var/tmp/_bazel_hoge/774bebc909ac897800e5e234203df705/execroot/__main__/bazel-out/darwin-py2-opt-exec-2B5CBBC6/bin/external/bazel_tools/tools/android/incremental_install.runfiles/bazel_tools/tools/android/incremental_install.py", line 25, in <module>
    from concurrent import futures
ImportError: No module named concurrent
Target //android:android failed to build
Use --verbose_failures to see the command lines of failed build steps.
```

生成されるPython スクリプト内で `concurrent.futures` モジュールが使われているようで、このモジュールはPython3.2から搭載されてますが、それ以前のversionを使っている場合は手動でいれる必要があります。

```sh
$ pip install futures
```

#### Androidスクリーンショット


| 起動後 | OptionMenu押下後 |
|:---:|:---:|
| ![スクリーンショット 2020-12-02 14.03.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139890/a8ed86c0-14fb-5912-4ce5-8fab2c062d49.png) | ![スクリーンショット 2020-12-02 14.03.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139890/0e09fc8a-0b13-1102-7c94-26473a22414b.png) |

アプリの実装がIP固定でローカルホストにリクエストするようになっていて、エミュレータ上での起動かつ [2](#2-%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%82%B5%E3%83%BC%E3%83%90%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%95%E3%81%9B%E3%82%8B) でローカルサーバを起動させていると、右側のようにリクエストした際のパスが表示されるようになります。[^2]


### 3-2. iOSのビルド

```sh
$ bazel build //ios-app
```

上記コマンドが成功すると、以下の場所にipaファイルが生成されます。

```
bazel-out/applebin_ios-ios_x86_64-fastbuild-ST-c4cf2ec641b4/bin/ios-app/ios-app.ipa
```

ipaファイルができるものの、動かしての確認することができません。
さらに、 [README](https://github.com/bazelbuild/examples/blob/master/tutorial/README.md) に書かれている、

> Bazel will generate some output files, most notably bazel-bin/ios-app/ios-app.xcodeproj

この `ios-app.xcodeproj` も生成されませんでした :sob:

そこで、 [Tulsi](https://tulsi.bazel.build/) というツールをつかってxcodeprojファイルを生成し、Xcodeを起動させてSimuratorで実行します。[^3]


#### iOSスクリーンショット

| 起動後 | ボタン押下後 |
|:---:|:---:|
| ![スクリーンショット 2020-12-04 14.17.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139890/ef117da1-1588-4505-c1eb-2e5805004425.png) | ![スクリーンショット 2020-12-04 14.11.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139890/db44f78f-a1f5-35f2-6c09-63cc26d05e46.png) |

iOS側のサンプルはAndroidのサンプルとは異なり、TextFieldに自由入力してサーバのレスポンスを表示させるようになってます。
[2](#2-%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%82%B5%E3%83%BC%E3%83%90%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%95%E3%81%9B%E3%82%8B) でローカルサーバを起動させている場合、 `127.0.0.1`でアクセスすると、右側のように表示されます。

## 最後に

いかがでしたでしょうか。
プラットフォームごとに用意されてるものを使うことが多いと思いますが、いかなるソフトウェアもBazelで安全にかつ高速でビルドできるようになったらすごいですね！

さて明日は、 @mu-suke08 のお話です！ 


[^1]: その他、CI上でビルドできるスクリプトも含まれています。
[^2]: ローカルサーバを起動していない場合、タイムアウト後 `???` が表示されます。
[^3]: Tulsiの使い方に関しては、[公式ドキュメント](https://tulsi.bazel.build/docs/gettingstarted.html)をご覧ください。