Azure DevOpsでHoloLensアプリをビルドする(MS-hosted編)


こんにちは。酒井(@saka_it)です。

突然ですが、皆さん、CI(継続的インテグレーション)してますか?
今どきの開発に CI 環境の構築は欠かせませんが、CI環境構築やメンテナンスは、面倒なことも多いと思います。

そこで今回は Azure DevOps の一部である Azure Pipelines の Microsoft-hosted agents を使って、ビルドマシンの用意が不要なCI環境を作ってみます。

※この記事では HoloLens(Unity)アプリのビルドを説明します。WebApps の CI/CD 環境を構築したい方はネスケラボの記事もどうぞ。

制約

  • Microsoft-hosted agents を使ったビルドには Unity Plus/Pro のライセンスが必要となります
  • 現状(2019/06/14時点)は Unity がインストールされた環境が用意されていないため、都度インストール作業を行う分、ビルド時間が余分にかかります
  • Windows SDKのバージョンの都合により、現状(2019/06/14時点)は、MRTKv2のビルドができません

Azure Pipelines と Microsoft-hosted agents とは?

Azure Pipelines とは、Microsoft が提供しているクラウド型のプロジェクト管理ツール Azure DevOps で CI/CD を提供する機能です。

その中でもビルドを実行する Agents には、下記の二種類があります。

  • Microsoft-hosted agents
    • 予め用意されているビルド環境を利用
    • ビルド環境のカスタマイズができない代わり、簡単に利用可能
    • Public Project では 10並列ジョブまで無料、Private Project は 1並列ジョブ1800分/月 まで無料
  • Self-hosted agents
    • 自前で用意したビルド環境を利用(オンプレ・クラウドどちらもOK)
    • 環境構築が必要だが、カスタマイズしたビルド環境を利用可能
    • Public Project では 無制限、Private Project は 1接続まで無料(ビルド環境の維持費は別途必要)

今回は、よりお手軽に使える前者の Microsoft-hosted agents を使用して、HoloLens アプリ用の Pipeline を作成していきます。

「Unity Tools for Azure DevOps」をインストールする

今回、Unity の実行などを行うために「Unity Tools for Azure DevOps」プラグインを使用します。
Unity Tools for Azure DevOps (Visual Studio Marketplace) よりプラグインをインストールします。

リンク先に飛びましたら、「Get it free」ボタンをクリックして、画面の指示に従い対象プロジェクトを指定してインストールします。

Unity Tools for Azure DevOps (Visual Studio Marketplace)

Pipeline を作成する

Azure DevOps の作成対象プロジェクトに入り、「Pipelines」→「Builds」を選択します。

Buidsを選択

「New」→「New build pipeline」をクリックし、新しい Build pipeline を作成します。

Build pipeline を作成

最初にソースコードのあるサービスを指定します。今回は Azure Repos Git にソースがあるため、そちらを選択します。

Azure Repos Git を選択

対象のリポジトリを選択します。

リポジトリの選択

Pipeline を YAML 形式で作成します。「Starter pipeline」を選択します。

Starter pipelineを選択

テンプレートの YAML ファイルが作成されたら、一旦、「Save and run」をクリックして Pipeline の YAML を保存します。
(一度保存しないと、この後に使用する項目が表示されません)

YAML の確認

この YAML ファイルはリポジトリのルートに「azure-pipeline.yml」としてコミットされます。必要に応じて、ここのコミットメッセージやブランチ作成の有無を設定してください。

コミットメッセージの設定

Pipeline の中身を作成する

保存が完了したら、再度「Pipelines」→「Buids」から先ほど作成した Pipeline を選択し、「Edit」ボタンをクリックして YAML の編集画面に入ります。
ここでエディタ右側の Tasks ペインをクリックすると入力フォームを元に YAML ファイルを設定していくことができます。

YAML ファイルの編集

早速、Unity Tools for Azure DevOps のマニュアルを元に、ステップを設定していくのですが、今回は完成済みの定義を使用します(〇分クッキング方式)。下記のコードをエディタ画面にコピーペーストしてください。

ペースト後に一度「Save」し、今度は Unity の認証情報を環境変数に設定します。
「Run」(or「Save」)ボタン横の3点箇所をクリックして、Variablesを選択します。

Variables を開く

ここで環境変数を設定します。下記の名称の変数を設定してください。
この時、鍵アイコンをクリックして暗号情報として設定しておきます(Valueが非表示になります)。

  • unityLicense.userName:Unity アカウントのID
  • unityLicense.password:Unity アカウントのパスワード
  • unityLicense.serial:Unity Plus/Pro のシリアルキー

完了したら、「Save & queue」をクリックして、保存しておきます。

Variables の設定

ビルド結果を確認する
「Pipelines」→「Buids」から作成した Pipeline を選択し、一番上にある最新ビルドを選択します。
(ジョブの実行履歴が表示されます。ビルドが完了していなければ、完了まで待ちましょう)

ビルドログを確認する

全て正常に完了していれば、右上の「Artifacts」から成果物一覧が確認できます。ここからビルド結果のAppPackageのダウンロードもできます。

ビルド成果物のダウンロード

※今回はデプロイ部分を構築していませんが、実際に使用する際は「Releases」で各種ストレージなどへのデプロイ設定を行うことをお勧めします。

最後に

今回はビルド環境構築が不要な Microsoft-hosted agents を使った、HoloLens アプリの CI 環境を構築しました。YAMLファイルを設定するだけで、自動で環境構築~Unityビルド/VSビルドまでやってくれるので、CI 導入が容易になったのかな、と思います。

一方、毎回Unityインストールを行う、ビルドスピードがあまり早くないなど、本格運用するには少々厳しいところもあります。
もし、このような課題を何とかしたいという場合には別途ビルド環境を用意して、Self-hosted agents を使ったビルドをお試しください。その際には、以前発表した資料を参考にしていただけると幸いです。

では、良いCIライフを。

補足:azure-piplens.ymlファイル解説

trigger:
- master

master ブランチをビルド開始トリガーに指定しています。
他のブランチをトリガーにする場合はここを変更します。

pool:
  vmImage: 'vs2017-win2016'

VMイメージとして、Visual Studio 2017 がインストールされたものを指定しています。

variables:
  unityProjectPath: '.'

変数を設定しています。プロジェクトがリポジトリのルートに無い場合はここを変更してください。
※認証情報をリポジトリにプッシュしないよう、ここではUnityライセンスの情報を設定していません。

- task: UnityGetProjectVersionTask@1
  displayName: 'Unity Get Project Version'
  name: unityGetProjectVersion
  inputs:
    unityProjectPath: '$(unityProjectPath)'

Unity Tools を使って、プロジェクトのUnityバージョンを取得しています。
nameにを指定し、後段でバージョン番号を取得できるようにしています。

- task: PowerShell@2
  displayName: 'Install UnitySetup.PowerShell module'
  inputs:
    targetType: 'inline'
    script: 'Install-Module -Name UnitySetup -AllowPrerelease -Scope CurrentUser -Force'
    failOnStderr: true

unitysetup.powershell をインストールしています。
次の Unity インストールはこのモジュールを使用しています。

- task: PowerShell@2
  displayName: 'Install Unity Editor'
  inputs:
    targetType: 'inline'
    script: 'Install-UnitySetupInstance -Installers (Find-UnitySetupInstaller -Version ''$(unitygetprojectversion.projectVersion)'' -Components Windows,UWP,UWP_IL2CPP) -Verbose'

提供されているビルド環境に Unity が含まれるものが無いため、ここでインストールしています。
今回は.NET,IL2CPPどちらでも対応できるように「-Components」オプションに両方のモジュールをインストールするようにしていますが、両対応不要であれば、片方を削ってインストール時間を節約することもできます。
逆に、UWP以外で必要なモジュールがあれば、ここで追加するようにしてください。

- task: UnityActivateLicenseTask@1
  displayName: 'Unity Activate License'
  inputs:
    username: '$(unityLicense.userName)'
    password: '$(unityLicense.password)'
    serial: '$(unityLicense.serial)'
    unityEditorsPathMode: 'unityHub'
    unityProjectPath: '$(unityProjectPath)'

Unity Tools を使って、Unity のライセンスを設定しています。
なお、ビルドジョブの最後に自動でライセンスの解放処理が入ります。

- task: UnityBuildTask@2
  displayName: 'Unity Build WindowsStoreApps'
  name: 'unityBuild'
  inputs:
    buildTarget: 'WindowsStoreApps'
    unityEditorsPathMode: 'unityHub'
    unityProjectPath: '$(unityProjectPath)'
    commandLineArgumentsMode: 'default'

Unity Tools を使って、Unity 上でのビルドを実行します。
ここでも name を指定して、後段でビルド出力先ディレクトリ名を取得できるようにしておきます。

- task: NuGetToolInstaller@1
  displayName: 'Use NuGet 4.3.0'
  inputs:
    versionSpec: '4.3.0'

- task: NuGetCommand@2
  displayName: 'NuGet restore'
  inputs:
    command: 'restore'
    restoreSolution: '$(unityBuild.buildOutputPath)\*.sln'
    feedsToUse: 'select'

この2ステップで UWP プロジェクトの NuGet 復元を行ってます。
Unity から出力されたプロジェクトであれば、特に変更は不要かと思います。

- task: VSBuild@1
  displayName: 'Build solution $(unityBuild.buildOutputPath)\*.sln'
  inputs:
    solution: '$(unityBuild.buildOutputPath)\*.sln'
    vsVersion: '15.0'
    msbuildArgs: '/p:AppxBundle=Always;AppxBundlePlatforms="x86"'
    platform: 'x86'
    configuration: 'release'

MSBuild を使った UWP プロジェクトのビルド処理です。Unity ビルドの出力先ディレクトリ内全てのソリューションを対象にしていますが、通常は1つしか出力されないため、問題ないかと思います。
ビルド設定は”release””x86″をターゲットにして、AppPackageを出力するようにしています。

- task: CopyFiles@2
  displayName: 'Copy AppPackages'
  inputs:
    SourceFolder: '$(unityBuild.buildOutputPath)'
    Contents: '**\AppPackages\**'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: drop'
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'drop'
    publishLocation: 'Container'

最後に、出力されたAppPackagesディレクトリ内のすべてのファイルを成果物として出力しています。