Azure PipelinesのYAMLでXamarin.Forms製アプリのApp Center配布パイプラインを構築する方法

「Azure PipelinesのYAMLでXamarin.Forms製アプリのCI/CD環境を構築する」は3部構成です。
記事を順番に読み進めると、Azure PipelinesでXamarin.Forms製アプリのCI/CD環境が構築できるようになります。

第1部: [CI環境の構築](https://qiita.com/uhooi/items/f830165143eb7dd9d8bd)
第2部: App Center配布パイプラインの構築　__←イマココ__
第3部: [ストア配布パイプラインの構築](https://qiita.com/uhooi/items/31f0630aa47e7980fbc3)

## はじめに

本記事は [Azure DevOps Advent Calendar 2021](https://qiita.com/advent-calendar/2021/azuredevops) の2日目の記事です。
昨日は @kkamegawa さんで [Azure DevOps ServiceでTLS 1.0/1.1が無効化されます](https://kkamegawa.hatenablog.jp/entry/2021/12/01/061354) でした。

Azure Pipelinesを使い、Xamarin.Forms製アプリをApp Centerへ配布するCDを構築します。

## 本記事で説明しないこと

- Azure Pipelinesの概要や基本的な操作方法  
[私が以前書いた記事](https://qiita.com/uhooi/items/5ed8db6aaec01425e453) が参考になると思います
- Visual Studio App Centerの概要や基本的な操作方法
- [CI環境の構築](https://qiita.com/uhooi/items/f830165143eb7dd9d8bd) で説明した内容  
以前説明した内容を説明するのは冗長なのでしません

## App Centerのコネクションを追加

[Azure PipelinesのYAMLでiOSアプリのApp Center配布パイプラインを構築する方法](https://qiita.com/uhooi/items/1245344a6add3e86cdc8#app-centerのコネクションを追加) と同様なので省略します。

## 証明書のアップロード

アプリの署名に必要なファイルをアップロードします。

### iOS: 証明書とProvisioning Profileのアップロード

[Azure PipelinesのYAMLでiOSアプリのApp Center配布パイプラインを構築する方法](https://qiita.com/uhooi/items/1245344a6add3e86cdc8#証明書とprovisioning-profileのアップロード) と同様なので省略します。

### Android: キーストアのアップロード

[Azure PipelinesのYAMLでAndroidアプリのApp Center配布パイプラインを構築する方法](https://qiita.com/uhooi/items/1bb2010f632ccad7008c#キーストアのアップロード) と同様なので省略します。

## 証明書のパスワードの追加

### iOS: P12ファイルのパスワードの追加

追加方法は [Azure PipelinesのYAMLでiOSアプリのApp Center配布パイプラインを構築する方法](https://qiita.com/uhooi/items/1245344a6add3e86cdc8#p12ファイルのパスワードの追加) と同様なので詳細は省略します。

`{アプリ名}-iOS-Signing-{証明書の種類}` でVariable groupを複数作成し、中身の変数名を統一することで、変数を簡単に切り替えられるのでオススメです。

例として以下のVariable groupを作成します。

- `Foo-iOS-Signing-Development`
- `Foo-iOS-Signing-Distribution`

変数は以下の通りです。

|Variable|説明|機密|
|:--|:--|:--|
|`P12Password`|P12ファイルのパスワード|○|

### Android: キーストアと鍵のパスワードの追加

追加方法は [Azure PipelinesのYAMLでAndroidアプリのApp Center配布パイプラインを構築する方法](https://qiita.com/uhooi/items/1bb2010f632ccad7008c#キーストアと鍵のパスワードの追加) と同様なので詳細は省略します。

iOSと同様、Variable groupを複数作成します。

- `Foo-Android-Signing-Debug`
- `Foo-Android-Signing-Release`

変数は以下の通りです。

|Variable|説明|機密|
|:--|:--|:--|
|`StorePassword`|キーストアのパスワード|○|
|`KeyAlias`|キーストアのエイリアス|×|
|`KeyPassword`|キーストアの鍵のパスワード|○|

## 設定ファイルの作成

今回は以下2つの環境でApp Centerに配布します。

- `develop`
  - iOS・Android共にデバッグビルド
- `staging`
  - iOS・Android共にリリースビルド

まずは全環境の設定ファイルを作成します。
見てわかる通りVariable groupとテンプレートのみ変えています。

ライブラリなどのバージョンを管理しやすくするため、まとめて変数として定義しています。

```yaml:distribute-appcenter-develop.yml
name: $(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)

trigger: none

jobs:
- job: distribute_appcenter_ios
  pool:
    vmImage: 'macOS-11'
  variables:
  - group: Foo-iOS-Signing-Development
  - name: XamarinSymlinkVersion
    value: '6_12_8'
  - name: XcodeVersion
    value: '13.0'
  - name: DEVELOPER_DIR
    value: '/Applications/Xcode_$(XcodeVersion).app'
  steps:
  # iOSアプリをApp Centerへ配布
  - template: templates/distribute-appcenter-debug-ios-template.yml

- job: distribute_appcenter_android
  pool:
    vmImage: 'macOS-11'
  variables:
  - group: Foo-Android-Signing-Debug
  - name: XamarinSymlinkVersion
    value: '6_12_8'
  - name: BuildConfiguration
    value: 'Debug'
  steps:
  # AndroidアプリをApp Centerへ配布
  - template: templates/distribute-appcenter-debug-android-template.yml
```

```yaml:distribute-appcenter-staging.yml
name: $(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)

trigger: none

jobs:
- job: distribute_appcenter_ios
  pool:
    vmImage: 'macOS-11'
  variables:
  - group: Foo-iOS-Signing-Distribution
  - name: XamarinSymlinkVersion
    value: '6_12_8'
  - name: XcodeVersion
    value: '13.0'
  - name: DEVELOPER_DIR
    value: '/Applications/Xcode_$(XcodeVersion).app'
  steps:
  # iOSアプリをApp Centerへ配布
  - template: templates/distribute-appcenter-adhoc-ios-template.yml

- job: distribute_appcenter_android
  pool:
    vmImage: 'macOS-11'
  variables:
  - group: Foo-Android-Signing-Release
  - name: XamarinSymlinkVersion
    value: '6_12_8'
  - name: BuildConfiguration
    value: 'release'
  steps:
  # AndroidアプリをApp Centerへ配布
  - template: templates/distribute-appcenter-release-android-template.yml
```

次にテンプレートを作成します。

```yaml:templates/distribute-appcenter-debug-ios-template.yml
steps:
# 証明書のインストール
- task: InstallAppleCertificate@2
  inputs:
    certSecureFile: 'foo_development.p12'
    certPwd: '$(P12Password)'

# Provisioning Profileのインストール
- task: InstallAppleProvisioningProfile@1
  inputs:
    provProfileSecureFile: 'foo_debug.mobileprovision'

# iOS固有のセットアップ
- template: setup/setup-ios-template.yml

# Xamarinのセットアップ
- template: setup/setup-xamarin-template.yml

# IPAファイルの生成
- task: XamariniOS@2
  displayName: 'Build an iOS app with Xamarin on macOS'
  inputs:
    solutionFile: '**/*.sln'
    configuration: 'debug'
    clean: true
    signingIdentity: 'Apple Development'
    signingProvisioningProfileID: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'

# アーティファクトのステージングへコピー
- task: CopyFiles@2
  displayName: 'Copy IPA to Build.ArtifactStagingDirectory'
  inputs:
    Contents: '**/*.ipa'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

# アーティファクトへアップロード
- task: PublishBuildArtifacts@1
  inputs:
    ArtifactName: 'debug_ios'

# App Centerへ配布
- task: AppCenterDistribute@3
  inputs:
    serverEndpoint: 'App Center'
    appSlug: '{Organization}/{App}'
    appFile: '**/*.ipa'
    releaseNotesOption: 'input'
    releaseNotesInput: |
      - Configuration: Debug
      - Certificate: $(APPLE_CERTIFICATE_SIGNING_IDENTITY)
      - Provisioning Profile: $(APPLE_PROV_PROFILE_UUID)
      - Build ID: $(Build.BuildId)
      - Build Number: $(Build.BuildNumber)
      - Reason: $(Build.Reason)
      - Requested For: $(Build.RequestedFor)
      - Source Branch: $(Build.SourceBranch)
      - Last Commit ID: $(Build.SourceVersion)
      - Last Commit Comment: $(Build.SourceVersionMessage)
      - Release Note: $(ReleaseNote)
    destinationType: 'groups'
```

```yaml:templates/distribute-appcenter-debug-android-template.yml
steps:
# Xamarinのセットアップ
- template: setup/setup-xamarin-template.yml

# APKファイルの生成
- task: XamarinAndroid@1
  displayName: 'Build an Android app with Xamarin'
  inputs:
    projectFile: '**/*.Android.csproj' 
    outputDirectory: '$(Build.BinariesDirectory)/$(BuildConfiguration)'
    configuration: '$(BuildConfiguration)'

# APKファイルの署名
- task: AndroidSigning@3
  displayName: 'Sign and align Android APK files'
  inputs:
    apkFiles: '$(Build.BinariesDirectory)/$(BuildConfiguration)/*.apk'
    apksignerKeystoreFile: 'foo_debug.keystore'
    apksignerKeystorePassword: '$(KeystorePassword)'
    apksignerKeystoreAlias: '$(KeyAlias)'
    apksignerKeyPassword: '$(KeyPassword)'

# アーティファクトのステージングへコピー
- task: CopyFiles@2
  displayName: 'Copy APK to Build.ArtifactStagingDirectory'
  inputs:
    Contents: '$(Build.BinariesDirectory)/$(BuildConfiguration)/*.apk'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

# アーティファクトへアップロード
- task: PublishBuildArtifacts@1
  inputs:
    ArtifactName: '$(BuildConfiguration)_android'

# App Centerへ配布
- task: AppCenterDistribute@3
  inputs:
    serverEndpoint: 'App Center'
    appSlug: '{Organization}/{App}'
    appFile: '**/$(BuildConfiguration)/*.apk'
    releaseNotesOption: 'input'
    releaseNotesInput: |
      - Build Configuration: $(BuildConfiguration)
      - Keystore: foo_debug.keystore
      - Build ID: $(Build.BuildId)
      - Build Number: $(Build.BuildNumber)
      - Reason: $(Build.Reason)
      - Requested For: $(Build.RequestedFor)
      - Source Branch: $(Build.SourceBranch)
      - Last Commit ID: $(Build.SourceVersion)
      - Last Commit Comment: $(Build.SourceVersionMessage)
      - Release Note: $(ReleaseNote)
    destinationType: 'groups'
```

```yaml:templates/distribute-appcenter-adhoc-ios-template.yml
steps:
# 証明書のインストール
- task: InstallAppleCertificate@2
  inputs:
    certSecureFile: 'foo_distribution.p12'
    certPwd: '$(P12Password)'

# Provisioning Profileのインストール
- task: InstallAppleProvisioningProfile@1
  inputs:
    provProfileSecureFile: 'foo_adhoc.mobileprovision'

# iOS固有のセットアップ
- template: setup/setup-ios-template.yml

# Xamarinのセットアップ
- template: setup/setup-xamarin-template.yml

# IPAファイルの生成
- task: XamariniOS@2
  displayName: 'Build an iOS app with Xamarin on macOS'
  inputs:
    solutionFile: '**/*.sln'
    configuration: 'Ad-Hoc'
    clean: true
    signingIdentity: 'Apple Distribution'
    signingProvisioningProfileID: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'

# アーティファクトのステージングへコピー
- task: CopyFiles@2
  displayName: 'Copy IPA to Build.ArtifactStagingDirectory'
  inputs:
    Contents: '**/*.ipa'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

# アーティファクトへアップロード
- task: PublishBuildArtifacts@1
  inputs:
    ArtifactName: 'adhoc_ios'

# App Centerへ配布
- task: AppCenterDistribute@3
  inputs:
    serverEndpoint: 'App Center'
    appSlug: '{Organization}/{App}'
    appFile: '**/*.ipa'
    releaseNotesOption: 'input'
    releaseNotesInput: |
      - Configuration: Ad-Hoc
      - Certificate: $(APPLE_CERTIFICATE_SIGNING_IDENTITY)
      - Provisioning Profile: $(APPLE_PROV_PROFILE_UUID)
      - Build ID: $(Build.BuildId)
      - Build Number: $(Build.BuildNumber)
      - Reason: $(Build.Reason)
      - Requested For: $(Build.RequestedFor)
      - Source Branch: $(Build.SourceBranch)
      - Last Commit ID: $(Build.SourceVersion)
      - Last Commit Comment: $(Build.SourceVersionMessage)
      - Release Note: $(ReleaseNote)
    destinationType: 'groups'
```

```yaml:templates/distribute-appcenter-release-android-template.yml
steps:
# Xamarinのセットアップ
- template: setup/setup-xamarin-template.yml

# APKファイルの生成
- task: XamarinAndroid@1
  displayName: 'Build an Android app with Xamarin'
  inputs:
    projectFile: '**/*.Android.csproj' 
    outputDirectory: '$(Build.BinariesDirectory)/$(BuildConfiguration)'
    configuration: '$(BuildConfiguration)'

# APKファイルの署名
- task: AndroidSigning@3
  displayName: 'Sign and align Android APK files'
  inputs:
    apkFiles: '$(Build.BinariesDirectory)/$(BuildConfiguration)/*.apk'
    apksignerKeystoreFile: 'foo_release.keystore'
    apksignerKeystorePassword: '$(KeystorePassword)'
    apksignerKeystoreAlias: '$(KeyAlias)'
    apksignerKeyPassword: '$(KeyPassword)'

# アーティファクトのステージングへコピー
- task: CopyFiles@2
  displayName: 'Copy APK to Build.ArtifactStagingDirectory'
  inputs:
    Contents: '$(Build.BinariesDirectory)/$(BuildConfiguration)/*.apk'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

# アーティファクトへアップロード
- task: PublishBuildArtifacts@1
  inputs:
    ArtifactName: '$(BuildConfiguration)_android'

# App Centerへ配布
- task: AppCenterDistribute@3
  inputs:
    serverEndpoint: 'App Center'
    appSlug: '{Organization}/{App}'
    appFile: '**/$(BuildConfiguration)/*.apk'
    releaseNotesOption: 'input'
    releaseNotesInput: |
      - Build Configuration: $(BuildConfiguration)
      - Keystore: foo_release.keystore
      - Build ID: $(Build.BuildId)
      - Build Number: $(Build.BuildNumber)
      - Reason: $(Build.Reason)
      - Requested For: $(Build.RequestedFor)
      - Source Branch: $(Build.SourceBranch)
      - Last Commit ID: $(Build.SourceVersion)
      - Last Commit Comment: $(Build.SourceVersionMessage)
      - Release Note: $(ReleaseNote)
    destinationType: 'groups'
```

iOS固有とXamarinのセットアップのテンプレートも作成します。

XcodeやXamarin SDKのバージョンは、以下のページを参考に指定しています。

https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#how-can-i-manually-select-versions-of-tools-on-the-hosted-macos-agent

Xamarin.iOSやXamarin.AndroidはXamarin.Monoとバージョンを揃えるべきだと思うので、macOSエージェントに用意されている `symlink` を使って指定するのが望ましいです。

`symlink` のバージョンがどのXamarin SDKに紐付いているかは、macOSエージェントのREADMEに記載されています。

https://github.com/actions/virtual-environments/blob/main/images/macos/macos-11-Readme.md#xamarin-bundles

```yaml:templates/setup/setup-ios-template.yml
steps:
# Xcodeの選択
- script: |
    echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'$(DEVELOPER_DIR);sudo xcode-select --switch $(DEVELOPER_DIR)/Contents/Developer
    xcodebuild -version
  displayName: 'Select Xcode $(XcodeVersion)'
```

```yaml:templases/setup/setup-xamarin-template.yml
steps:
# Xamarin SDKの選択
- script: |
    sudo $AGENT_HOMEDIRECTORY/scripts/select-xamarin-sdk.sh $(XamarinSymlinkVersion)
    ls -l /Library/Frameworks/Mono.framework/Versions/
    ls -l /Library/Frameworks/Xamarin.iOS.framework/Versions/
    ls -l /Library/Frameworks/Xamarin.Android.framework/Versions/
    mono --version
    msbuild -version
  displayName: 'Select Xamarin SDK $(XamarinSymlinkVersion)'

# NuGetパッケージの復元
- task: NuGetCommand@2
  displayName: 'NuGet restore'
  inputs:
    command: 'restore'
    restoreSolution: '**/*.sln'
```

他は基本的に以下と同様なので省略します。

- [Azure PipelinesのYAMLでiOSアプリのApp Center配布パイプラインを構築する方法](https://qiita.com/uhooi/items/1245344a6add3e86cdc8#各項目の紹介)
- [Azure PipelinesのYAMLでAndroidアプリのApp Center配布パイプラインを構築する方法](https://qiita.com/uhooi/items/1bb2010f632ccad7008c#設定ファイルの作成)

## おわりに

Azure PipelinesのYAMLでXamarin.Forms製アプリをApp Centerへ配布することができました！

以上 [Azure DevOps Advent Calendar 2021](https://qiita.com/advent-calendar/2021/azuredevops) の2日目の記事でした。
明日は @zt_megane さんで「Azure DevOpsでスクラムプロジェクトを始める時に設定していること」です。

## 参考リンク

- [Build Xamarin apps - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/xamarin?view=azure-devops&tabs=yaml)
- [Install Apple Certificate task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/install-apple-certificate?view=azure-devops)
- [Install Apple Provisioning Profile task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/install-apple-provisioning-profile?view=azure-devops)
- [Xamarin.iOS build and release task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/xamarin-ios?view=azure-devops)
- [Xamarin.Android build and release task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/xamarin-android?view=azure-devops)
- [Android signing build and release task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/android-signing?view=azure-devops)
- [NuGet restore, pack, and publish task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/package/nuget?view=azure-devops)