JenkinsでiOSのビルドを行う時にハマるポイントとその解決法・2


とても便利なJenkinsですが、 **たまによくしっかり** ハマるので、その辺のポイントと私なりの解決法を共有します。

ちょっと設定を変えたipaのビルドをjenkinsでたくさん作りたい
----------------------------------------------------------------------------------

今回は「 **開発中のアプリでAPIサーバのHOST名だけを変えたビルドをいくつか作り、それをテスターに配布したい** 」というシナリオを想定して話を進めていきたいと思います。
基本的にアプリ側では ifdef や define を使って APIサーバのBASE URLを決定する仕組みがあって、
コンパイル時に `-DRAW_API_HOST=<API_HOST>` と与えることで異なるビルドが作れるようなイメージで進めます。

### マルチ構成プロジェクトでホスト名の一覧を定義する

まず、Jenkinsのプロジェクトで「 **マルチ構成プロジェクトのビルド** 」というのがあり、微妙に値を変えたビルドを量産するときに便利です。JenkinsのJOBプロジェクトはこれで作成します。

すると、「 **マトリクスの設定** 」というメニューがありますので「ユーザ定義」から

* 名前: **API_HOST**
* 値: ホスト名を改行区切りで入力

というものを追加しておきます。
これでXCodeプラグイン実行時に 環境変数 **API_HOST** という名で毎回異なるホスト名を受け取ることができるようになります。

### 罠：マルチ構成プロジェクトで実行するノードを制限する

Slaveノードにビルド用Macをぶら下げている場合、実行するノードを制限することが必要になりますが、マルチ構成プロジェクトではどうも通常の「 **実行するノードを制限** 」が効かないようです。

そこで、
**NodeLabel Parameter Plugin**
https://wiki.jenkins-ci.org/display/JENKINS/NodeLabel+Parameter+Plugin
というものを使うと、「ビルドのパラメータ化」という項目が増え、そこにチェックを入れ、
「パラメータの追加」→「Label」 を選択し、

* Name: LabelSelect
* Default Value: <制限したいノードのLabel> など

とかすることで実行するノードを制限できるようになります。
もっと良い方法がありそうですが、とりあえずこれを使っています。


### コンパイラに RAW_API_HOST を渡す

XCode Pluginの設定で、**Custom xcodebuild arguments** があるので、そこに

	GCC_PREPROCESSOR_DEFINITIONS="RAW_API_HOST=$API_HOST"

と書きます。これで、コンパイル時に `-DRAW_API_HOST=<API_HOST>` が渡されるようになります。

### 罠：アプリで外部のDefinitionを受け取る

ここももう一山ひっかかるポイントなのですが、
最終的にソースコード内部では、

	#define API_HOST     @"myapi.example.com"

という様に、 @"HOST名" という define 文にしたいとします。
まあ、こういう使い方はよくあると思いますがここの @ と "" をプリプロセッサにつけさせるのがちょっと工夫が必要です。

試行錯誤は省きますが、以下の様に定義しておくと API_HOST に `@"<RAW_API_HOST>"` が入ります。

	#if defined(RAW_API_HOST)  // "#"演算子はマクロ展開を二段に重ねないと、引数のマクロの中身が展開されない?
	#  define MY_IOS_STRINGIZE_I(x) @#x
	#  define MY_IOS_STRINGIZE(x) MY_IOS_STRINGIZE_I(x)
	#  define API_HOST  MY_IOS_STRINGIZE(RAW_API_HOST)
	#elif !defined(API_HOST)
	#  define API_HOST @"api.example.com"
	#endif

**MY_IOS_STRINGIZE_I** のマクロが一見冗長ですが、これがないと 

	#define API_HOST  @"RAW_API_HOST"

のようになってしまいます(RAW_API_HOSTのマクロが展開されない)。

### 最後の罠： HTMLの中のURL Escape

上手く行けばビルドも複数通り、ipaファイルやHTMLファイルも生成([その１](http://qiita.com/mokemokechicken/items/5ea39c87512a515071f5)を参照）されていると思いますが、
私の場合、Download用のHTMLのリンクをいくらiPhoneでクリックしてもInstallが始まりませんでした。

問題は、HTMLでの app Install用の書き方の下記の部分で、 **url=** の部分がEscapeされてないことでした。

```HTML
<a href="itms-services://?action=download-manifest&url=${DIST_URL_ESC}/${PLIST}">
Install ${PACKAGE_NAME} b$BUILD_NUMBER
</a>
```

マルチ構成プロジェクトにすると、その一つ一つが子プロジェクトのようになって成果物ページのURLが下記のようになる

`http://jenkins.example.com:8080/job/myapp-multi-build/API_HOST=api1.example.com/`

のですが、そこに KEY=VALUE と **=** が含まれているので、これをうっかりそのまま url= に記述するとInstallが始まらないという現象になります。エラーも何も表示されないのでなかなか気がつきませんでした(--;

まあ、普通にURL Escapeすれば大丈夫で、その１で紹介したスクリプトでは = だけはEscapeしています（他にも場合によっては必要になるとは思いますが、適宜追加してください）。


この方法の課題
-----------------

ホスト名一箇所を変更したビルドを作る場合はこのような方法で大丈夫でしたが、他にも複数値を変えたい場合にこの方法は少し無理がありそうです。マルチ構成のプロジェクトは値の全組み合わせを実行しようとするので、フィルタで制限できるようですが、なかなかつらそうだからです(慣れれば良いのかな…)。

発展としては、

* 設定ファイル名をアプリに -D で渡して、アプリ実行時にその設定ファイル(JSONなど？）を読み込むようにする
	* プロジェクトにファイルを追加するだけで、設定を増やせる
* Jenkinsのフィルタでがんばる
	* ソースコードのリポジトリに手を加えなくて済む
* 何か良いJenkinsプラグインを見つける
	* 何もがんばらなくて済むｗ

という感じでしょうか。
何か良い方法があれば是非誰か教えてください！