iOSアプリのCI/CD〜Azure Pipelines編〜

## はじめに

Azure PipelinesでiOSアプリのCI/CDを実現します。
Travis CIやCircle CI、Bitriseの記事はよく見かけるのですが、Pipelinesの記事はあまり見かけないのが寂しいです。。

[前回](https://qiita.com/uhooi/items/5ed8db6aaec01425e453)は署名なしで単体テストを実行するジョブを構築しました。
今回はそれに加え、署名してAppCenterへデプロイするまでのジョブを構築します。

## 環境

Xcode：10.1 (10B61)
Swift：4.2.1
Carthage：0.32.0
CocoaPods：1.7.0.beta.1

## Pipelinesでジョブの作成

[前回](https://qiita.com/uhooi/items/5ed8db6aaec01425e453#pipelinesでジョブの作成)と同様なので省略します。

## ジョブの詳細([Tasks]タブ)

[前回](https://qiita.com/uhooi/items/5ed8db6aaec01425e453#ジョブの詳細tasksタブ)との差分のみ記載します。

- Pipeline > Name  
{プロジェクト名}-iOS-Deploy-AppCenter

## タスクの詳細

[前回](https://qiita.com/uhooi/items/5ed8db6aaec01425e453#タスクの詳細)との差分のみ記載します。

### 不要なジョブの削除

前回削除した以下のタスクは使うので削除しません。

Install an Apple certificate
Install an Apple provisioning profile
Copy Files to: …
Publish Artifact: drop
Deploy */.ipa to Visual Studio App Center

以下のタスクのみ削除します。

Test with Visual Studio App Center
∵今回はUIテストを実施しないため

### タスクの入れ替え

今回はテストとビルドでタスクを入れ替えません。
∵テストに失敗したらビルドしないようにしたいため

### タスクの追加・編集

[前回](https://qiita.com/uhooi/items/5ed8db6aaec01425e453#タスクの追加編集)との差分のみ記載します。

#### Install an Apple certificate

署名に必要な証明書をインストールするタスクです。

以下の項目を編集します。

- Certificate (P12)  
ギアのアイコンをクリック→.p12ファイルをアップロード
- Enabled  
OFF→ON
- Run this task  
Custom conditions→Only when all previous tasks have succeeded

<img width="796" alt="スクリーンショット_2019-03-05_12_13_17.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/589043b9-a8f4-13a1-2451-6245d78e35ff.jpeg">

.p12ファイルのパスワードは[Variables]タブの「P12password」に入力します。

<img width="868" alt="スクリーンショット_2019-03-05_12_15_01.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/e419bd73-a5f4-7ce4-3ab1-a78115fb6f21.jpeg">

#### Install an Apple provisioning profile

署名に必要なプロビジョニングプロファイルをインストールするタスクです。

以下の項目を編集します。

- Provisioning profile  
ギアのアイコンをクリック→.mobileprovisionファイルをアップロード
- Enabled  
OFF→ON
- Run this task  
Custom conditions→Only when all previous tasks have succeeded

<img width="800" alt="スクリーンショット_2019-03-05_12_27_49.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/f1bcec04-c1dc-697a-0927-0c07c0c06d0a.jpeg">

例ではAdHoc版のプロビジョニングプロファイルを使っています。

#### Xcode test

Xcodeでテストを実行するタスクです。

前回と異なる項目のみ記載します。

- Signing style  
Project defaultsのまま  
Do not code signでもOK

#### Xcode build

Xcodeでビルドするタスクです。

前回と異なる項目のみ記載します。

- Signing style  
__常に手動署名にしないとエラーになります__ 。  
(回避策を知っている方がいたらご教授お願いします)  
Xcodeの署名設定(製品ターゲット > Automatically manage signing)によって選択肢を変えます。  
Do not code sign→Project defaults(OFFの場合)  
→Manual signing(ONの場合)
- Signing identity(「Manual signing」のみ)  
空欄→$(APPLE_CERTIFICATE_SIGNING_IDENTITY)
- Provisioning profile UUID(「Manual signing」のみ)  
空欄→$(APPLE_PROV_PROFILE_UUID)
- Create app package  
OFF→ON
- Export options  
Automaticのまま  
失敗する場合は「Plist」を選択し、以下のような.plistファイルをリポジトリに作成します。

```xml:XcodeTaskExportOptions.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>{enterpriseなど}</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>{Bundle Identifier}</key>
        <string>{Provisioning Profile名}</string>
    </dict>
</dict>
</plist>
```
- Export options plist(Export optionsが「Plist」の場合のみ)  
作成した.plistファイルを指定します。
- Destination platform  
Defaultのまま

<img width="977" alt="スクリーンショット_2019-03-13_17_51_29.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/d8ef4218-977f-8734-e918-7c255833ff04.jpeg">

以下のようにCocoaPodsで管理しているライブラリでエラーが発生する場合、Podfileにおまじないを追加することで回避できます。

```
** BUILD FAILED **


❌  error: GoogleUtilities does not support provisioning profiles. GoogleUtilities does not support provisioning profiles, but provisioning profile {プロビジョニングプロファイル名} has been manually specified. Set the provisioning profile value to "Automatic" in the build settings editor. (in target 'GoogleUtilities')



❌  error: FirebaseCore does not support provisioning profiles. FirebaseCore does not support provisioning profiles, but provisioning profile {プロビジョニングプロファイル名} has been manually specified. Set the provisioning profile value to "Automatic" in the build settings editor. (in target 'FirebaseCore')



❌  error: nanopb does not support provisioning profiles. nanopb does not support provisioning profiles, but provisioning profile {プロビジョニングプロファイル名} has been manually specified. Set the provisioning profile value to "Automatic" in the build settings editor. (in target 'nanopb')



❌  error: Pods-{プロジェクト名} does not support provisioning profiles. Pods-{プロジェクト名} does not support provisioning profiles, but provisioning profile {プロビジョニングプロファイル名} has been manually specified. Set the provisioning profile value to "Automatic" in the build settings editor. (in target 'Pods-{プロジェクト名}')


##[error]Error: /usr/bin/xcodebuild failed with return code: 65
##[section]Finishing: Xcode build
```

```ruby:Podfile
post_install do |installer|
  installer.pods_project.build_configurations.each do |config|
    # CI/CD環境でビルドエラーを防ぐためのおまじない
    # 参考：https://github.com/fastlane/fastlane/issues/10543
    # 　　：https://github.com/fastlane/fastlane/issues/12557
    config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = ''
    config.build_settings['CODE_SIGNING_REQUIRED'] = 'NO'
    config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
  end
end
```

#### Copy Files(任意)

ファイルをコピーするタスクです。
ここではビルドした.ipaファイルをアーティファクト(後述)のステージングフォルダにコピーします。

以下の項目を編集します。

- Display name(任意)  
Copy Files to: \$(build.artifactstagingdirectory)→Copy IPA Files to: \$(build.artifactstagingdirectory)

<img width="791" alt="スクリーンショット_2019-03-05_17_23_13.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/a2b5c75d-5a87-7c1f-19da-f1415b599796.jpeg">

#### Publish Build Artifacts(任意)

指定したフォルダをダウンロードできるようにする(アーティファクトをビルドして発行する)タスクです。
ここではステージングフォルダにコピーしたファイルを「Artifact name」で指定したフォルダ名でアーティファクト化します。

後ほど使いたいファイルが存在する場合に実行します。

以下の項目を編集します。

- Run this task  
Custom conditions→Only when all previous tasks have succeeded

<img width="792" alt="スクリーンショット_2019-03-05_17_32_58.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/f9118c26-ff86-b3e7-45cb-ad8ebbf11852.jpeg">

#### App Center Distribute

バイナリファイルをApp Centerへ配布するタスクです。
ここではビルドした.ipaファイルをApp Centerにデプロイします。

以下の項目を編集します。

- App Center connection  
「AppCenter」を選択します。
- App slug  
AppCenterのURLを確認し、 `https://appcenter.ms/users/{username}/apps/{app_identifier}` の{}で括られている部分を取り出して `{username}/{app_identifier}` の形式で入力します。
- Create release notes  
「Enter Release Notes」を選択します。
- Release Notes  
リリースノートの内容を記載します。ここではテストアプリ用に様々な情報を載せます。  
`- Environment：AdHoc`  
`- Configuration：$(Configuration)`  
`- Certificate：$(APPLE_CERTIFICATE_SIGNING_IDENTITY)`  
`- Provisioning Profile：$(APPLE_PROV_PROFILE_UUID)`  
`- Branch：$(Build.SourceBranchName)`  
`- Last Commit ID：$(Build.SourceVersion)`  
`- Last Commit Comment：$(Build.SourceVersionMessage)`  
`- Release Note：$(ReleaseNote)`  
- Distribution Group ID  
特定のグループに配布したい場合に指定します。ここでは空欄とします。
- Enabled  
OFF→ON
- Run this task  
Custom conditions→Only when all previous tasks have succeeded

<img width="703" alt="スクリーンショット_2019-03-25_23_23_14.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/538322cf-bb79-9955-e9d5-46df42277a82.jpeg">
<img width="700" alt="スクリーンショット_2019-03-25_23_23_31.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/20c9e83d-7668-022d-2ecd-d7919f7aaa55.jpeg">
<img width="699" alt="スクリーンショット_2019-03-25_23_23_48.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/5c661a1b-9dd4-14f5-05ef-8c9e50535fa3.jpeg">

## ジョブの完成形

こちらがジョブの完成形です。

<img width="585" alt="スクリーンショット 2019-03-05 17.50.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/8acb6ab6-1405-d2ea-8e41-2f09abc98bf4.png">

## パイプラインの実行(手動)

[queue]ボタンをクリックすることでパイプラインを手動で実行します。
`$(ReleaseNote)` は自分で作成した変数であり、実行時に指定できるようにしています。

<img width="707" alt="スクリーンショット_2019-03-24_21_17_56.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/0aa97746-8896-29ad-ef81-1ffffbe987cc.jpeg">

## App Centerへのデプロイ結果

パイプラインの実行に成功すると、App Centerへデプロイされます。

<img width="899" alt="スクリーンショット_2019-03-24_22_24_19.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/236a5e4e-6593-932f-dd5b-2c9a5e0d315f.jpeg">

## おわりに

これで「ソースをプッシュ→単体テスト→ビルド→デプロイ」を自動化できました！

あとはUIテストを実行したり、Gitのブランチやタグを自動で切るようにしたりすれば完璧です。

## 参考リンク

- [VSTS XcodeタスクでのiOSアプリの自動署名 - スタックオーバーフロー](https://stackoverflow.com/questions/48982289/automatic-signing-for-ios-app-in-vsts-xcode-task)
- [Predefined build variables - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&viewFallbackFrom=vsts&tabs=yaml)