AndroidアプリをAzure Pipelinesでビルド・リリースする方法【Xamarin.Forms】
初稿:
更新:
- 8 min read -

本記事の主旨
Xamarin.Formsで作成したAndroidアプリを、AzureDevOps Pipelinesで、ビルドからGoogle Play Consoleへリリースするまでの手順をまとめる。
環境
Android開発フレームワーク
Xamarin.Forms 5.0.0.2244
ビルド・リリース環境
Azure DevOps Pipelines
リリース先
Google Play Console(Google Cloud Platformのサービスアカウントを使用)
前提条件
- ソースファイルはAzure DevOpsのRepos(Git)で管理している
- ビルドで使用するYAMLファイルは事前に作成しリポジトリに含めている
- 成果物のフォーマットはaab
- Google Play Consoleへのリリースに必要なGoogle Cloud Platformのサービスアカウントを事前に登録し鍵を取得している
- Azure DevOpsに「Google Playのプラグイン」をMarketplaceよりインストール済みである
- リリース処理については、すでに一度Google Play Consoleにアプリを公開済みであること(初回は手動で行う必要があるため)
作業詳細
ビルドはYAMLファイルを事前に作成しPipelineで行う。 Google Play Consoleへの発行はPipelinesのReleaseをブラウザで設定し行う。
Azure DevOps Pipelinesによるビルド
Library設定
Azure DevOps PipelinesのLibraryに以下を登録する。
- Variable group(Pipelineでソース上のProduct用シークレット情報を置換する際に使用)
- Secure files(Google Play Consoleへaabをリリースる際に必要なKeyStoreファイル)
Variable groupの登録手順は下記を参照
Azure DevOps の Pipelines の変数を使おう - かずきのBlog@hatena
ハードコーディングされた値は死すべし!! ということで Azure DevOps の Pipelines で変数使っていこうと思います。 docs.microsoft.com ハローワールド 何事もハローワルドから。variables で変数を定義できます。定義した変数は $(変数名) で参照できます。ということでさくっと以下のようはパイプラインの yaml を作ってみました。後々の確認のために特に必要はないのですが stage から定義しています。 trigger: - master variables: var1: Hello var2: World pool: vmImage: 'ubu…
Azure Pipelines の変数グループ - Azure Pipelines
変数グループを使用して、パイプライン間で共通の変数を共有します。
Secure fileの登録手順は下記を参照
Azure Pipelines のセキュア ファイル - Azure Pipelines
Azure Pipelines にセキュア ファイルを追加して使用する方法について説明します。
KeyStoreファイルについてはこちらを参照
作業後のLibraryはこんな感じ


YAMLファイル作成
Pipelineのビルドで使用するYAMLファイルを事前に作成しておく。(ブラウザポータル上で作成することも可能)
処理の概要は以下のとおり。
- 変数の定義
- ソースコード上のシークレット情報置換(Bash)
- Android Manifestのバージョンコード置換(PowerShell)
- NugetToolインストール
- Keystoreファイルのダウンロード
- ビルド
- aabファイルの発行
シークレットはjsonやXMLであれば置換するライブラリが用意されているが、C#の定数クラスを使っているので自力でシェルスクリプトによる置換を行っている。
実際のYAMLファイルはこんな感じ。
stages:
- stage: Build
# 他の環境ではAndroid ManifestのXMLファイルを処理する際にうまく置換が行われなかったためWindowsを使用
pool:
vmImage: 'windows-latest'
jobs:
- job: GenerateAab
variables:
# Libraryに登録したVariable groupを呼び出し
- group: SecretsForRelease
# メジャー&マイナーバージョンコードを定義
- name: appVersion
value: '1.1'
- name: buildConfiguration
value: 'Release'
# Product用のシークレットを定義するクラスファイルの置換処理
# Variable groupに登録した変数を呼び出し、シェルで置換する
steps:
- task: Bash@3
displayName: 'Insert Secret Variables'
inputs:
targetType: 'inline'
script: |
echo '#######################################################'
echo ' Environment Variables data replace'
echo '#######################################################'
FilePathVariables='OneThird.Core/Constants/Variables.cs'
echo ''
echo '#######################################################'
echo ' Variables.cs'
echo ' Target file path - ' $FilePathVariables
echo '#######################################################'
echo ''
echo 'AdmobUnitIdBanner - $(AdmobUnitIdBanner)'
sed -i -e 's|\[AdmobUnitIdBanner\]|$(AdmobUnitIdBanner)|' $FilePathVariables
echo ''
echo 'AdmobUnitIdInterstitial - $(AdmobUnitIdInterstitial)'
sed -i -e 's|\[AdmobUnitIdInterstitial\]|$(AdmobUnitIdInterstitial)|' $FilePathVariables
echo ''
echo 'B2cPasswordResetPolicy - $(B2cPasswordResetPolicy)'
sed -i -e 's|\[B2cPasswordResetPolicy\]|$(B2cPasswordResetPolicy)|' $FilePathVariables
echo ''
echo 'B2cSignInPolicy - $(B2cSignInPolicy)'
sed -i -e 's|\[B2cSignInPolicy\]|$(B2cSignInPolicy)|' $FilePathVariables
echo ''
echo 'B2cTenantDomainName - $(B2cTenantDomainName)'
sed -i -e 's|\[B2cTenantDomainName\]|$(B2cTenantDomainName)|' $FilePathVariables
echo ''
echo 'B2cTenantDomainURL - $(B2cTenantDomainURL)'
sed -i -e 's|\[B2cTenantDomainURL\]|$(B2cTenantDomainURL)|' $FilePathVariables
echo ''
echo 'B2cAppMobileClientId - $(B2cAppMobileClientId)'
sed -i -e 's|\[B2cAppMobileClientId\]|$(B2cAppMobileClientId)|' $FilePathVariables
echo ''
echo '#######################################################'
echo " Show result"
echo '#######################################################'
cat $FilePathVariables
echo ''
echo ''
echo ''
FilePathCosmosDBConstants='OneThird.Infrastructure/CosmosDb/CosmosDBConstants.cs'
echo '#######################################################'
echo ' CosmosDBConstants.cs'
echo ' Target file path - ' $FilePathCosmosDBConstants
echo '#######################################################'
echo ''
echo 'CosmosdbAccountUrl - $(CosmosdbAccountUrl)'
sed -i -e 's|\[CosmosdbAccountUrl\]|$(CosmosdbAccountUrl)|' $FilePathCosmosDBConstants
echo ''
echo 'CosmosdbAccountKey - $(CosmosdbAccountKey)'
sed -i -e 's|\[CosmosdbAccountKey\]|$(CosmosdbAccountKey)|' $FilePathCosmosDBConstants
echo ''
echo '#######################################################'
echo " Show result"
echo '#######################################################'
cat $FilePathCosmosDBConstants
echo ''
echo ''
echo '#######################################################'
echo ' Finished - Environment Variables data replace'
echo '#######################################################'
echo ''
echo ''
# Android ManifestのVersionCodeおよびVersionNameを置換する
# VersionCodeは年月日時、VersionNameは最初に定義したappVersion.VersionCode
- task: PowerShell@2
displayName: 'Updating Version Code and Name in Android Manifest'
inputs:
targetType: 'inline'
script: |
[string] $sourcePath = "$(System.DefaultWorkingDirectory)\OneThird.Android\Properties\AndroidManifestRelease.xml"
[string] $appVersionName = "$(AppVersion).$(Build.BuildId)"
[string] $appVersionCode = Get-Date -Format "yyMMddHH"
[xml] $androidManifestXml = Get-Content -Path $sourcePath
Write-Host "Original Manifest:"
Get-Content $sourcePath | Write-Host
$VersionName= Select-Xml -xml $androidManifestXml -Xpath "/manifest/@android:versionName" -namespace @{android = "http://schemas.android.com/apk/res/android" }
$oldVersionName= $VersionName.Node.Value;
Write-Host " (i) Original Version Name: $oldVersionName"
$VersionName.Node.Value = $appVersionName
Write-Host " (i) New Package Name: $appVersionName"
$VersionCode= Select-Xml -xml $androidManifestXml -Xpath "/manifest/@android:versionCode" -namespace @{android = "http://schemas.android.com/apk/res/android" }
$oldVersionCode = $VersionCode.Node.Value;
Write-Host " (i) Old Version Code: $oldVersionCode"
$VersionCode.Node.Value = $appVersionCode
Write-Host " (i) New App Name: $appVersionCode "
$androidManifestXml.Save($sourcePath)
Write-Host "Final Manifest:"
Get-Content $sourcePath | Write-Host
# NugetToolインストール
- task: NuGetToolInstaller@1
displayName: 'Installing Nuget'
- task: NuGetCommand@2
displayName: 'Restoring Nugets'
inputs:
command: 'restore'
restoreSolution: '**/*.sln'
# LibraryからKeystoreファイルをダウンロード
- task: DownloadSecureFile@1
displayName: 'Download keystore'
name: keystore
inputs:
secureFile: 'upload_keystore.jks'
# ビルド
- task: XamarinAndroid@1
displayName: 'Build aab'
inputs:
projectFile: '**/OneThird.Android.csproj'
outputDirectory: '$(Build.BinariesDirectory)'
configuration: '$(BuildConfiguration)'
clean: true
msbuildVersionOption: 'latest'
msbuildArguments: '-restore -t:SignAndroidPackage -p:AndroidPackageFormat=aab -p:AndroidKeyStore=True -p:AndroidSigningKeyStore=$(keystore.secureFilePath) -p:AndroidSigningStorePass=$(KeystorePassword) -p:AndroidSigningKeyAlias=$(KeystoreAlias) -p:AndroidSigningKeyPass=$(KeystorePassword)'
jdkOption: 'JDKVersion'
# aabファイル発行
- task: PublishPipelineArtifact@1
displayName: 'Publishing aab artifacts'
inputs:
targetPath: '$(Build.BinariesDirectory)'
artifact: AndroidAabPackage
publishLocation: 'pipeline'
このYAMLファイルを対象とした新規Pipelineを作成し、実行するとRelease可能なaabファイルを作成することができる。
既存のYAMLファイルを対象とした新規Pipelineの作成手順は以下記事を参照。
【備忘録】既存のYAMLファイルを使用してPipelineを新規作成する【Azure DevOps Pipelines】
モバイルアプリ(Xamarin Forms)を個人開発している。現在、Azure DevOps上のGitリポジトリからAppCenterを経由してGoogle Play Consoleにデプロイしている。これを、Azure DevOps Pipelines(以降、Pipelines)によりビルド及び
Azure DevOps Pipelinesによるリリース
Azure DevOpsのPipelines→Releasesより、Create Releaseをクリック

「empty job」をクリック

Releaseジョブのstage nameを入力し右上の×で閉じる

「Add an artifact」をクリック

対象となるartifact(aabファイル)の条件を入力し、「add」をクリック

※ビルドをトリガーで自動実行するようにしたい場合は、Artifactのイナズマアイコンをクリックし、「Continuous deployment trigger」を「Enable」にする。
Stagesの「1 job, 0 task」のハイパーリンクをクリック

「Agent job」の右横にある「+」をクリック

Google Playで検索し、「Google Play - Release」の「Add」をクリック
※Google Playプラグインを追加していない場合は、MarketplaceよりAzure DevOpsにインストールする →

「Release to internal」を選択し、「Service connection」の「+New」をクリック

Google Cloud Platformに登録してあるサービスアカウントの情報を入力し「Save」をクリック
- サービスアカウントの作成手順は、「Google Play Console APIを使う方法|kosuke matsumura|note」を参照
- 「Private key」は、Google Cloud Platformのサービスアカウントに登録してあるjson形式の鍵ファイルの「private_key」の値(-----BEGIN PRIVATE KEY----- で始まる)を丸ごとコピペする
- ここで登録したService connectionは、Project Settings→Service connectionsから変更できる

「Application id (com.google.MyApp)」、「Bundle path」、「Language code」を入力し、「Save」をクリック

RunでReleaseを実行すれば完了
まとめ
Github Actions等にくらべ、情報がかなり少なく、公式ドキュメントのみでは理解しきれなかったため非常に苦労した。
しかし、この一度の苦労で今後のビルドおよびリリースを自動化できることを思えばチャレンジするだけの価値はあると思う。
間違いや、もっとこうすると良いよーや、その他ご意見ご感想などあれば、コメントや Mastodonで教えていただけるとうれしい。