BazelでiOSアプリをビルドしてみる

[モバイルDevOps Advent Calendar](http://qiita.com/advent-calendar/2015/mobile-devops) 11日目の記事です

# はじめに

今回は[Bazel](http://bazel.io/)でiOSアプリのビルドを行う方法について、一部実際に[Bazelのソースコード](https://github.com/bazelbuild/bazel)を読みながら解説します。
基本的に[公式のチュートリアル](http://bazel.io/docs/tutorial/ios-app.html)に沿ったBUILDスクリプトを作成しますが、このチュートリアルではiPhoneシミュレータで動作可能なipaファイルが作成されてしまうので、今回ここでは<b>実機で動作可能なAdHoc署名されたipaファイルの作成</b>をしてみます。

具体的には、[DeployGate](https://deploygate.com)で配信可能なipaファイルの作成を行います。

また、BazelによるiOSアプリのビルドを行うことでのメリットですが、現状はBUILDファイルの作成がなかなか難しいため、会社のアプリなどで実際に運用するなどはコストが高すぎると思います。
ですが、Provisioning Profileを明示的に指定したり、Xcodeに依存しない(当然xcodebuildには依存する)ビルドなどを行えるメリットはあります。(GUIが嫌いな方ならこのメリットでかいのではないでしょうか)
今後のBazelプロジェクトの発展で、BUILDファイルの作成コストが低くなることを祈りましょう。

# 今回の環境

以下が今回使用したプロジェクトです。
https://github.com/henteko/bazeliOSSample
Xcodeで新規プロジェクトを作成したまんまのプロジェクト構成になっています。

また、Xcodeなどのバージョンは以下の通りです。

## Xcode

Version 7.1.1 (7B1005)

## Bazel

```
Build label: 0.1.2
Build target: bazel-out/local_darwin-fastbuild/bin/src/main/java/com/google/devtools/build/lib/bazel-main_deploy.jar
Build time: Fri Dec 4 23:20:19 2015 (1449271219)
Build timestamp: 1449271219
Build timestamp as int: 1449271219
```

## JDK

jdk1.8.0_65.jdk

# BUILDファイルを作成しよう

さてここから実際にbazelのBUILDファイルの説明になります。
実際に使用するBUILDファイルの全体は以下の通りです。

```BUILD
objc_library(
  name = "BazelSampleClasses",
  srcs = [
    "bazelSample/AppDelegate.m",
    "bazelSample/ViewController.m",
  ],
  hdrs = glob(["bazelSample/*.h"]),
  storyboards = ["bazelSample/Base.lproj/LaunchScreen.storyboard", "bazelSample/Base.lproj/Main.storyboard"],
)

objc_binary(
  name = "ios-app-binary",
  srcs = [
    "bazelSample/main.m",
  ],
  deps = [
    ":BazelSampleClasses",
  ],
)

ios_application(
  name = "ios-app",
  binary = ":ios-app-binary",
  bundle_id = "com.henteko07.bazelSample",
  infoplist = "bazelSample/Info.plist",
  provisioning_profile = "bazelSample.mobileprovision",
)
```

細かく見ていきましょう。

## objc_library

```
objc_library(
  name = "BazelSampleClasses",
  srcs = [
    "bazelSample/AppDelegate.m",
    "bazelSample/ViewController.m",
  ],
  hdrs = glob(["bazelSample/*.h"]),
  storyboards = ["bazelSample/Base.lproj/LaunchScreen.storyboard", "bazelSample/Base.lproj/Main.storyboard"],
)
```

ここではObjectice-Cのソースファイルの設定などをしています。
srcsに、各ソースファイルを、hdrsにヘッダーファイル、storyboardsには各ストーリーボードまでのpathを指定します。
また、ストーリーボード使っていないプロジェクトや、xibなどがある場合は、別途xibsという引き数名で指定します。(実際に[チュートリアル](http://bazel.io/docs/tutorial/ios-app.html)ではxibsを使用してます)

## objc_binary

```
objc_binary(
  name = "ios-app-binary",
  srcs = [
    "bazelSample/main.m",
  ],
  deps = [
    ":BazelSampleClasses",
  ],
)
```

ここではバイナリを指定しています。
実際には、プロジェクト内のmain.mを指定しています。
また、depsには上で作成したobjc_libraryのnameに指定した名前を使います。
これでobjc_binary(:ios-app-binary)が実行されると自動でobjc_library(:BazelSampleClasses)も実行されるようになります。

## ios_application

```
ios_application(
  name = "ios-app",
  binary = ":ios-app-binary",
  bundle_id = "com.henteko07.bazelSample",
  infoplist = "bazelSample/Info.plist",
  provisioning_profile = "bazelSample.mobileprovision",
)
```

最後にアプリケーションの設定です。
ここでは、binaryに上で作成したobjc_binary(:ios-app-binary)を指定します。
また、bundle_idにはアプリのbundle identifierを指定します。
ちなみに、ここでbundle_idを指定しないと、アプリ内のInfo.plistのCFBundleIdentifierがデフォルトで使われるようです。
なので、現状のXcodeで作られる新規プロジェクトではInfo.plistの中のCFBundleIdentifierが$(PRODUCT_BUNDLE_IDENTIFIER)と指定されているので、これがそのまま使われてしまうみたいです。(要検証)

provisioning_profileには使用したいProvisioning Profileをpathで指定します。
また、この値は実機で動作可能なipaファイル(build時のオプションによって変更可能)を作る時に使用されるようです。

# 実際にビルドを走らせてみる

さて、実際にビルドをしてみましょう。
ここではBUILDファイルの説明のみしましたが、当然BazelにはWORKSPACEファイルも必要です。
が、今回のプロジェクトではWORKSPACEファイルが空でいいので、touchなどで作成しておきましょう。

## ビルドの実行
以下のコマンドで、実際に実機で動作可能なipaファイルのビルドが可能です。

```shell-session
$ bazel build //:ios-app --ios_sdk_version=9.1 --ios_cpu="arm64" --default_ios_provisiong_profile=":bazelSample.mobileprovision"
```

//:ios-appでBUILDで作成したios_application(:ios-app)を指定しています。
また、以下のオプションを使用しています。

### ios_sdk_version

これでiOSのSDK Versionを指定します。
現状のBazel(0.1.2)ではデフォルトでDEFAULT_IOS_SDK_VERSION="8.4"が指定されています。
[ソースコードを参照](https://github.com/bazelbuild/bazel/blob/dfefaa98e8e1cd315c0e6be0e7ce89368d4d4d7f/src/main/java/com/google/devtools/build/lib/rules/apple/AppleCommandLineOptions.java#L56)

iOS SDKの8.4がインストールされている環境では問題ないのですが、筆者の環境だと9.1しかインストールされていなかったので、9.1を指定するようにしています。
もちろん、8.4のダウンロードをXcodeで行えば、そのままでも動作可能です。

### ios_cpu

これでビルド対象のCPUアーキテクチャを指定します。
現状のBazel(0.1.2)ではデフォルトでDEFAULT_IOS_CPU="x86_64"が指定されています。
[ソースコードを参照](https://github.com/bazelbuild/bazel/blob/dfefaa98e8e1cd315c0e6be0e7ce89368d4d4d7f/src/main/java/com/google/devtools/build/lib/rules/apple/AppleCommandLineOptions.java#L72)

これをそのまま使うと、シミュレータ用のipaファイルが出力されることになってしまうので、今回はarm64を指定しました。
もちろん、その他のCPUアーキテクチャを指定することも可能です。
実機のiPhoneで使用されているCPUアーキテクチャは[他の方の記事](http://qiita.com/takkyun/items/814aa45beee422a5f0c6)を参照してください。

また、実際にどうやってこのCPUアーキテクチャからipaの種類を変更しているかについては、ソースコードに書いてあるので参照してください。
[ソースコードを参照](https://github.com/bazelbuild/bazel/blob/49a3d10d071281c0aca4828519a685851e115019/src/tools/xcode/xcrunwrapper/xcrunwrapper.sh)


### default_ios_provisiong_profile

ここではios_applicationで指定したものと同じProvisioning Profileを指定します。
また、ここでの指定方法はLabelを使っているらしく、実際にBUILDファイル内に指定したprovisioning_profileを参照するらしく、BUILDファイル内にprovisioning_profileの指定が無いとエラーになります。

## ipaファイル

ビルドを実行すると、以下のpathにipaファイルが作成されます。

```
./bazel-bin/ios-app.ipa
```

これを[DeployGate](https://deploygate.com)にアップロードすれば、実機で動作が確認できると思います。

また、ipaファイルのアップロードには[dgコマンド](https://github.com/DeployGate/deploygate-cli)を使用すると簡単にアップロードができるので、オススメです。

```shell-session
$ dg deploy ./bazel-bin/ios-app.ipa
```

このdgコマンドについては、[他の方が書いてくれた記事](http://qiita.com/kiy0p0n/items/dacb576ecfbe8cbb1676)があるので、ご参照ください。

# おわりに

今回はiOSアプリのビルドにBazelを使用して、実際に実機で動作可能なipaファイルの作成を紹介しました。
冒頭でも述べた通り、まだBazelでのiOSビルドのメリットはあまりないと思いますが、今後の[Bazelプロジェクト](http://bazel.io/)に期待しましょう！！！！