Azure DevOps で iOS の Unity 自動ビルド環境の構築

#はじめに

[Pipelines の Microsoft-hosted と Self-hosted](https://qiita.com/akiojin/items/1fb2d516399967c183bf)
[Self-hosted 環境を構築する方法](https://qiita.com/akiojin/items/ef9226b430ef73806b78)
[Unity Tools for Azure DevOps の使い方](https://qiita.com/akiojin/items/cf85eee9ddd11780e209)

上記では自動ビルドを行うための準備やパーツを紹介しましたが、今回は実際に Microsoft-hosted 環境で Unity の自動ビルド環境を構築したいと思います。
すでに紹介済みの記事で大凡はカバーできていますので、まとめみたいな記事になっていますがご了承ください。

Microsoft-hosted ですので、ローカルに環境がなくても Unity のライセンスさえあればクラウド上でビルド環境が構築できます。


#ゴール

- Unity で iOS の自動ビルド環境の構築

#ソースコード

では、いきなりですがソースコードをすべて掲載します。

```yml
################################################################################
#
#  Unity Build for iOS (Microsoft-hosted)
#
################################################################################

trigger:
- master

variables:
  ProjectName: 'DevOps-Example'

  Unity.BaseDirectory: '/Applications/Unity/Hub/Editor'
  Unity.EditorDirectory: ''
  Unity.TargetBuild: 'iOS'
  Unity.InstallComponents: 'Mac,$(Unity.TargetBuild)'
  Unity.ProjectDirectory: '$(Build.SourcesDirectory)'
  Unity.OutputDirectory: '$(Build.BinariesDirectory)'
  Unity.OutputFileName: '$(ProjectName)-$(Unity.TargetBuild)'

  Configuration: 'Debug'
  SDK: 'iphoneos'
  TeamId: '<チーム ID>'

  Xcode.Scheme: 'Unity-iPhone'
  Xcode.ArchiveDirectory: '$(Build.BinariesDirectory)/Archive'
  Xcode.ExportDirectory: '$(Build.ArtifactStagingDirectory)/Export'
  Xcode.P12FileName: '<P12 ファイル名>'
  Xcode.ProvisioningProfileUuid: '<プロビジョニングプロファイルの UUID>'
  Xcode.ProvisioningProfile: '$(Xcode.ProvisioningProfileUuid).mobileprovision'

  AppCenter.Endpoint: 'AppCenter'
  AppCenter.AppSlug: '<組織名>/$(Unity.OutputFileName)'
  AppCenter.AppFilePath: '$(Xcode.ExportDirectory)/$(ProjectName).ipa'

pool:
  vmImage: 'macOS-latest'

jobs:
  - job: Unity
    displayName: 'Unity build started.'
    condition: or(not(variables['Unity.UserName']), not(variables['Unity.Password']), not(variables['Unity.SerialKey']))
    workspace:
      clean: outputs

    steps:
    # macOS を選択した場合、Unity の PackageManager が Mac のファイアーウォールで弾かれる可能性があるのでファイアーウォールを無効化
    - bash: |
        sudo defaults delete /Library/Preferences/com.apple.alf globalstate

    - task: UnityGetProjectVersionTask@1
      name: UnityGetProjectVersion
      inputs:
        unityProjectPath: '$(Unity.ProjectPath)'

    - bash: echo "##vso[task.setvariable variable=Unity.EditorDirectory]$(Unity.BaseDirectory)/$(UnityGetProjectVersion.projectVersion)"
      displayName: 'Unity エディターのパスを取得'

    - task: Cache@2
      displayName: 'Unity Install cache'
      inputs:
        key: '$(Agent.OS) | "$(UnityGetProjectVersion.projectVersion)" | $(Unity.InstallComponents)'
        path: '$(Unity.EditorDirectory)'
        cacheHitVar: 'UnityInstallCached'

    # Microsoft-hosted 環境で Unity のインストールが失敗する原因を解消する対処
    # https://github.com/microsoft/unitysetup.powershell/issues/156#issuecomment-650578187
    - powershell: |
        mkdir ~/.unitysetup
      displayName: 'mkdir ~/.unitysetup'
      condition: and(succeeded(), ne(variables['UnityInstallCached'], true))

    - powershell: |
        Install-Module -Name UnitySetup -AllowPrerelease -Force -AcceptLicense
      displayName: 'Installing Unity Setup Powershell Module.'
      condition: and(succeeded(), ne(variables['UnityInstallCached'], true))
      failOnStderr: true

    - powershell: |
        Install-UnitySetupInstance -Installers (Find-UnitySetupInstaller -Version '$(UnityGetProjectVersion.projectVersion)' -Components "$(Unity.InstallComponents)") -Verbose
      displayName: 'Installing Unity components.'
      condition: and(succeeded(), ne(variables['UnityInstallCached'], true))

    - task: UnityActivateLicenseTask@1
      displayName: 'Unity License Activations.'
      condition: succeeded()
      inputs:
        username: '$(Unity.UserName)'
        password: '$(Unity.Password)'
        serial: '$(Unity.SerialKey)'
        unityEditorsPathMode: 'unityHub'
        unityProjectPath: '$(Unity.ProjectPath)'

    - task: UnityBuildTask@3
      name: UnityBuild
      inputs:
        buildTarget: '$(Unity.TargetBuild)'
        unityProjectDirectory: '$(Unity.ProjectDirectory)'
        outputPath: '$(Unity.OutputDirectory)'
        outputFileName: '$(Unity.OutputFileName)'

    - task: PublishBuildArtifacts@1
      displayName: 'ビルドエラー時にエラーログをパブリッシュ'
      condition: failed()
      inputs:
        PathtoPublish: '$(UnityBuild.logsOutputPath)'
        ArtifactName: 'UnityLog'
        publishLocation: 'Container'

    - task: InstallAppleCertificate@2
      inputs:
        certSecureFile: '$(Xcode.P12FileName)'
        certPwd: '$(Xcode.P12Password)'
        keychain: 'temp'

    - task: InstallAppleProvisioningProfile@1
      inputs:
        provisioningProfileLocation: 'secureFiles'
        provProfileSecureFile: '$(Xcode.ProvisioningProfile)'

    # UnityBuildTask@3 で出力された MapFileParser.sh に実行権限がないため
    - bash: |
        chmod +x '$(Unity.OutputDirectory)/$(Unity.OutputFileName)/MapFileParser.sh'
      displayName: 'MapFileParser.sh に実行権限を付与'

    # Info.plist に記載されている CFBunbleVersion を置き換える
    # ここはプロジェクトごとにポリシーが違うと思いますので、削除なりしていただければよいかと
    - bash: |
        sed -i -e 's/99999/$(Build.BuildId)/g' $(Unity.OutputDirectory)/$(Unity.OutputFileName)/Info.plist
      displayName: 'CFBundleVersion の書き換え'

    - task: Xcode@5
      inputs:
        actions: 'archive'
        xcWorkspacePath: '$(Unity.OutputDirectory)/$(Unity.OutputFileName)/$(Xcode.Scheme).xcodeproj/project.xcworkspace'
        scheme: '$(Xcode.Scheme)'
        packageApp: true
        archivePath: '$(Xcode.ArchiveDirectory)'
        exportPath: '$(Xcode.ExportDirectory)'
        signingOption: 'manual'
        provisioningProfileUuid: '$(Xcode.ProvisioningProfileUuid)'
        args: 'DEVELOPMENT_TEAM=$(TeamId)'

    - task: AppCenterDistribute@3
      inputs:
        serverEndpoint: '$(AppCenter.Endpoint)'
        appSlug: '$(AppCenter.AppSlug)'
        appFile: '$(AppCenter.AppFilePath)'
        releaseNotesOption: 'input'
        releaseNotesInput: |
          - Agent Name：$(Agent.Name)
          - Agent OS：$(Agent.OS)
          - Agent Architecture：$(Agent.OSArchitecture)
          - Configuration：$(Configuration)
          - SDK：$(SDK)
          - Provisioning Profile：$(Xcode.ProvisioningProfileUuid)
          - Branch：$(Build.SourceBranchName)
          - Last Commit ID：$(Build.SourceVersion)
          - Last Commit Comment：$(Build.SourceVersionMessage)
          - BuildNumber：$(Build.BuildNumber)
          - Reason：$(Build.Reason)
          - Requester：$(Build.RequestedFor)
        isMandatory: true
        destinationType: 'groups'
```

この yml を利用するには変数を設定する必要があります。
yml エディターの右上にある『Variables』で『Unity.Password』『Unity.SerialKey』『Unity.UserName』の3つの変数を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19224/b42aa04e-b15a-34c2-1773-08cdb433d1e9.png)

それぞれ Unity から提供される『パスワード』『シリアル番号』『ユーザー名』です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19224/f2b18e04-db89-1125-fedb-395e621adf36.png)

超簡単です！
これだけで Azure DevOps の Pipelines で Unity の自動ビルドの環境ができます。
このソースコードをリポジトリに azure-pipelines.yml とかの名前で保存してインポートするだけです。

##小ネタ

今回の Pipelines では Azure Repos などの git からクローンをしてくるようになるのですが、一度でも成功するとソースコードがキャッシュされてクローンではなくプルになりますので、失敗しそうなタスクは最初はコメントアウトしておき、必ず最初に一度成功させておくのが良いです。

#おわりに

ちなみに無料アカウントでも十分自動ビルド環境の構築が可能ですので、是非試してみてください。