.NET MAUIをAzure Pipelinesで発行する

.NET MAUI の GA からまもなく半年が経とうとしています。皆様は MAUI を触っていますか? 残念ながら筆者は全く触れていません。今回は将来的に触る際の布石として MAUI のビルド自動化周りを試してみた内容を紹介します。


はじめに

.NET MAUI は2022年5月に無事 GA となり、また MAUI や .NET の一部に統合された Xamarin は2024年5月に EOS を迎えます。筆者が所属する情報システム部では、スクラッチアプリに対応する小さなモバイルアプリを Xamarin.Forms を使用して開発していますので、 Xamarin EOS までには MAUI へと作り替える必要がありそうです。

将来すぐに対応を開始するための一環として、今回は CI について、特に Android と iOS の発行周りについて試してみましたので、その内容をメモとしてこちらに残しておきます。

なお情報システム部では CI ツールとして Azure Pipelines を使用しています。 GitHub Actions でも内容は大して変わらないと思いますので、 GitHub Actions をご利用の方は適時読み替えをお願いします。

試した環境

要素 バージョン
.NET SDK 6.0.402

本記事に記載のコードは GitHub 上に置いてあります。

github.com

MAUI のビルドに必要なもの

MAUI をビルドする前に、 MAUI の構造についておさらいしましょう。以下 Microsoft Learning から MAUI のアーキテクチャーについての引用です。

.NET MAUI は、モバイル アプリとデスクトップ アプリ用の UI を構築するための単一のフレームワークを提供します。 次の図は、.NET MAUI アプリのアーキテクチャの概要を示しています。

.NET MAUI アプリでは、主に .NET MAUI API (1) と対話するコードを記述します。 その後、.NET MAUI はネイティブ プラットフォーム API を直接使用します (3)。 さらに、必要に応じて、アプリ コードでプラットフォーム API (2) を直接実行できます。

.NET MAUI アプリは、PC または Mac で記述し、ネイティブ アプリ パッケージにコンパイルできます。

  • .NET MAUI を使用してビルドされた Android アプリは、C# から中間言語 (IL) にコンパイルされ、アプリの起動時に Just-In-Time (JIT) がネイティブ アセンブリにコンパイルされます。
  • .NET MAUI を使用して構築された iOS アプリは、C# からネイティブ ARM アセンブリ コードに完全に先行コンパイル (AOT) されます。
  • .NET MAUI を使用してビルドされた macOS アプリは、Mac Catalyst を使用します。これは、UIKit で構築された iOS アプリをデスクトップに導入し、必要に応じて追加の AppKit とプラットフォーム API で拡張する Apple のソリューションです。
  • .NET MAUI を使用してビルドされた Windows アプリでは、Windows UI 3 (WinUI 3) ライブラリを使用して、Windows デスクトップを対象とするネイティブ アプリを作成します。 WinUI 3 の詳細については、「 Windows UI ライブラリ」を参照してください。

.NET MAUI とは - .NET MAUI | Microsoft Learn

MAUI を使用して開発したアプリはユーザーコードこそ共通ですが、各ネイティブ環境に対してはそれぞれの環境に合わせたビルドがなされると記載されています。つまり Android アプリをビルドするには Android 環境専用のビルドが、同様に iOS アプリをビルドするには iOS 環境専用のビルドが必要です。

また MAUI の各ネイティブ環境の機能は .NET SDK の初期状態ではインストールされていません。「ワークロード」という形で提供されていますので、ビルドするネイティブ環境に応じたワークロードのインストールも必要になります。

なおワークロードの機能は .NET SDK のメジャー・マイナーバージョンに依存します。 .NET SDK 6.0.100 および 6.0.101 であれば同等の結果が得られますが、 6.0.100 と 6.0.200 では異なる結果となります。

.NET SDK 6.0.40x では以下のワークロードが提供されています。

> dotnet workload search

ワークロード ID             説明
-----------------------------------------------------------------------------------------
android               .NET SDK Workload for building Android applications.
android-32            Support for Android API-32.
android-33            Support for Android API-33.
ios                   .NET SDK Workload for building iOS applications.
maccatalyst           .NET SDK Workload for building macOS applications with MacCatalyst.
macos                 .NET SDK Workload for building macOS applications.
maui                  .NET MAUI SDK for all platforms
maui-android          .NET MAUI SDK for Android
maui-desktop          .NET MAUI SDK for Desktop
maui-ios              .NET MAUI SDK for iOS
maui-maccatalyst      .NET MAUI SDK for Mac Catalyst
maui-mobile           .NET MAUI SDK for Mobile
maui-tizen            .NET MAUI SDK for Tizen
maui-windows          .NET MAUI SDK for Windows
runtimes-windows      workloads/runtimes-windows/description
tvos                  .NET SDK Workload for building tvOS applications.
wasm-tools            .NET WebAssembly ビルド ツール

MAUI を発行する

各ネイティブ向けの専用のビルドが必要で、かつ事前にワークロードのインストールが必要だと分かったところで、実際にアプリを発行してみましょう。

今回はアプリの発行のみに焦点を当てた記事となりますので、テンプレートから作成しただけの MAUI アプリを Android および iOS それぞれ発行します。対象のプロジェクトは以下のコマンドで作成したものになります。

# .NET SDKバージョンを固定化
dotnet new globaljson --sdk-version=6.0.402

# .NET向けの.gitignoreファイルを作成
dotnet new gitignore

# MAUIプロジェクト作成
dotnet new maui

Android アプリを発行する

まずは Android アプリを発行してみましょう。 Android アプリの発行にあたって必要な操作はこちらの3つです。

  1. ビルド環境の準備( SDK 本体とワークロードのインストール)
  2. 署名用の Java キーストア取得
  3. 発行(リストア、発行、成果物の転送)

面倒そうなのはビルド処理だけですね。

ジョブの冒頭部分です。 Android アプリをビルドする OS は何でも良いため、今回は Azure Pipelines で用意されている Linux 環境( ubuntu-22.04 )を利用します。

- job: PUBLISH_ANDROID_APP
  displayName: Publish MAUI Android App
  workspace:
    clean: all
  pool:
    vmImage: ubuntu-22.04

それではそれぞれの詳細を眺めていきます。

ビルド環境の準備

最初の手順である .NET SDK のインストールです。 UseDotNet タスクを使用して global.json で指定したバージョンをインストールしましょう。ワークロードのインストールは DotNetCoreCLI では実行できないため script を使用します。

# .NET SDKインストール
- task: UseDotNet@2
  displayName: Install .NET SDK
  inputs:
    packageType: sdk
    useGlobalJson: true

# MAUI Android Workloadインストール
- script: dotnet workload install maui-android
  displayName: Install .NET SDK Android Workload
署名用の Java キーストア取得

続いて Android アプリの署名に使用するキーストアを Azure Pipelines のセキュアファイルから取得します。セキュアファイルのアップロード方法は別媒体の筆者記事に記載しています。

- task: DownloadSecureFile@1
  name: keystore # ダウンロードしたセキュアファイルを別タスクで使用する際のキー
  displayName: Download keystore file
  inputs:
    secureFile: android.keystore  # セキュアファイル登録されているキーストアファイルの名前

YAML 上にコメントも残していますが name に設定した名前(今回の例では keystore )は後の発行タスクで使用します。

発行

最後に MAUI アプリを復元して発行を行います。 Android アプリを CLI でビルドするためのドキュメントはこちらです。

learn.microsoft.com

ドキュメントを眺めると Android アプリを署名して発行するには以下のプロパティを設定する必要があると分かります。

パラメーター名 説明
TargetFramework Android をビルドするなら net6.0-android
ApplicationId 発行するアプリのアプリケーション ID 。
ApplicationVersion 発行するアプリのバージョンコード。 versionName ではなく versionCode
AndroidKeyStore 発行するアプリに署名をするかどうか。
AndroidSigningKeyStore 署名に使用するキーストアファイル。
AndroidSigningStorePass 署名に使用するキーストアのパスワード。
AndroidSigningKeyAlias 署名に使用するキーエイリアス。
AndroidSigningKeyPass 署名に使用するキーストアのパスワード。

TargetFramework を除いて MAUI アプリのプロジェクト上に記載しておくことは可能ですが、セキュアな内容が含まれるためそれは適切では無いでしょう。今回は全て CLI コマンドの引数で指定します。また AndroidSigningKeyStore には先のタスクで取得したセキュアファイルの name を使用します。

# リストア
- task: DotNetCoreCLI@2
  displayName: Restore MAUI Android App
  inputs:
    command: restore
    projects: '**/*.csproj'
    restoreArguments: '-p:TargetFramework=net6.0-android'
    feedsToUse: select

# ビルド、発行
- task: DotNetCoreCLI@2
  displayName: Publish MAUI Android App
  inputs:
    command: publish
    publishWebProjects: false
    projects: MauiPublishSampleApp.csproj
    arguments: >-
      -c Release
      -f net6.0-android
      -o $(Build.ArtifactStagingDirectory)
      --no-restore
      -p:ApplicationId=$(ANDROID_APPLICATION_ID)
      -p:AndroidKeyStore=True
      -p:AndroidSigningKeyStore=$(keystore.secureFilePath)
      -p:AndroidSigningStorePass=$(ANDROID_KEYSTORE_PASS)
      -p:AndroidSigningKeyAlias=$(ANDROID_KEYSTORE_ALIAS)
      -p:AndroidSigningKeyPass=$(ANDROID_KEYSTORE_KEY_PASS)
    zipAfterPublish: false
    modifyOutputPath: false

# 成果物の転送
- task: PublishBuildArtifacts@1
  displayName: Publish MAUI Android App Artifacts
  inputs:
    PathtoPublish: $(Build.ArtifactStagingDirectory)
    ArtifactName: android
    publishLocation: Container

これで Android アプリが署名された状態で発行されます。成果物として署名された aab と apk ファイル、署名されていない aab ファイルの合計3ファイルが出力されます。 aab と apk の両方が出力されるのは嬉しいですね。

iOS アプリを発行する

続いて iOS アプリを発行してみましょう。 iOS アプリの発行にあたって必要な操作はこちらの3つです。

  1. ビルド環境の準備( SDK 本体とワークロードのインストール、 Xcode バージョン固定)
  2. 署名用の証明書と Provisioning Profile インストール
  3. 発行(リストア、発行、成果物の転送)

Android と基本的な流れは一緒ですね。

ジョブの冒頭部分です。 iOS アプリをビルドするには macOS でなければなりません。今回は Azure Pipelines で用意されている macOS 環境( macOS-12 )を利用します。

- job: PUBLISH_IOS_APP
  displayName: Publish MAUI iOS App
  workspace:
    clean: all
  pool:
    vmImage: macOS-12

それではそれぞれの詳細を眺めていきます。

ビルド環境の準備

ビルド環境の準備で実施することは基本的に Android と同じです。異なるのはビルドや署名に使用する Xcode のバージョンを固定する作業が iOS アプリでは必要です。
Xcode バージョン固定は別媒体の筆者記事に詳細を記載しています。

# .NET SDKインストール
- task: UseDotNet@2
  displayName: Install .NET SDK
  inputs:
    packageType: sdk
    useGlobalJson: true

# MAUI iOS Workloadインストール
- script: dotnet workload install maui-ios
  displayName: Install .NET SDK iOS Workload

# Xcodeバージョン固定
- script: echo '##vso[task.setvariable variable=MD_APPLE_SDK_ROOT;]'/Applications/Xcode_14.0.1.app;sudo xcode-select --switch /Applications/Xcode_14.0.1.app/Contents/Developer
  displayName: 'Select the Xcode Version'
署名用の証明書と Provisioning Profile インストール

続いて iOS アプリの署名に使用する証明書と Provisioning Profile のインストールです。両者ともセキュアファイルに保存したファイルを直接タスクから使用できます。タスクの詳細は別媒体の筆者記事に記載しています。組み込みエージェント使用時など共有環境を使用する場合、一時領域にインストールするオプションの指定を忘れないようにしましょう。

# Provisioning Profileインストール
- task: InstallAppleProvisioningProfile@1
  displayName: Install ProvisioningProfile
  inputs:
    provisioningProfileLocation: secureFiles
    provProfileSecureFile: $(IOS_PROVISIONING_PROFILE_NAME) # セキュアファイルのProvisioning Profileの名前
    removeProfile: true

# 証明書インストール
- task: InstallAppleCertificate@2
  displayName: Install Certificate
  inputs:
    keychain: temp
    certSecureFile: $(IOS_CERTIFICATE_NAME) # セキュアファイルの証明書の名前
    certPwd: $(IOS_CERTIFICATE_PASS) # 証明書のパスワード

証明書および Provisioning Profile インストールタスクの実行結果は環境変数に出力されます。それぞれ APPLE_CERTIFICATE_SIGNING_IDENTITYAPPLE_PROV_PROFILE_UUID です。両方とも後述の発行タスクで使用します。

発行

最後に MAUI アプリを復元して発行を行います。 iOS アプリを CLI でビルドするためのドキュメントはこちらです。

learn.microsoft.com

ドキュメントを眺めると iOS アプリを署名して発行するには以下のプロパティを設定する必要があると分かります。

パラメーター名 説明
TargetFramework iOS をビルドするなら net6.0-ios
RuntimeIdentifier ios-arm64 固定。
ApplicationId 発行するアプリのアプリケーション ID 。
ApplicationVersion 発行するアプリのバージョン。
ArchiveOnBuild アプリパッケージをビルドするかどうか。 True 固定。
CodesignKey 署名に使用する証明書。
CodesignProvision 署名に使用する Provisioning Profile 。
ServerAddress リモートビルドを実行する際の macOS のホスト名や IP アドレス。
TcpPort リモートビルドを実行する際の macOS と接続する TCP ポート番号。
ServerUser リモートビルドを実行する際の macOS にログインするユーザー名。
ServerPassword リモートビルドを実行する際の macOS にログインするユーザーのパスワード。

Azure Pipelines の組み込み macOS エージェントで発行する限りリモートビルド用の設定は不要です。 iOS アプリの発行においても必要なパラメーターは Android アプリ同様、全て CLI コマンドの引数で指定します。また CodesignKey および CodesignProvision には先のタスクで取得された結果の環境変数を使用します。

# リストア
- task: DotNetCoreCLI@2
  displayName: Restore MAUI iOS App
  inputs:
    command: restore
    projects: '**/*.csproj'
    restoreArguments: >-
      -p:TargetFramework=net6.0-ios
      -p:RuntimeIdentifier=ios-arm64
    feedsToUse: select

# ビルド、発行
- task: DotNetCoreCLI@2
  displayName: Publish MAUI iOS App
  inputs:
    command: publish
    publishWebProjects: false
    projects: MauiPublishSampleApp.csproj
    arguments: >-
      -c Release
      -f net6.0-ios
      -o $(Build.ArtifactStagingDirectory)
      --no-restore
      -p:ApplicationId=$(IOS_APPLICATION_ID)
      -p:RuntimeIdentifier=ios-arm64
      -p:ArchiveOnBuild=True
      -p:CodesignKey="$(APPLE_CERTIFICATE_SIGNING_IDENTITY)"
      -p:CodesignProvision=$(APPLE_PROV_PROFILE_UUID)
    zipAfterPublish: false
    modifyOutputPath: false

# 成果物の転送
- task: PublishBuildArtifacts@1
  displayName: Publish MAUI iOS App Artifacts
  inputs:
    PathtoPublish: $(Build.ArtifactStagingDirectory)
    ArtifactName: ios
    publishLocation: Container

これで署名された iOS アプリが発行されます。 iOS アプリの成果物は ipa ファイル1つのみです。

おわりに

MAUI アプリを CI で署名して発行する方法についての簡単なメモ書きでした。

今回は発行のサンプルのためアプリプロジェクトが1つだけ含まれる場合で試していますが、実際には複数のプロジェクトが含まれるソリューションをビルドすることになるでしょう。その場合、今回の例みたいにはすんなりいかないはずですので、近いうちに試して記事に残したいと思います。

執筆担当者プロフィール
中谷 大造

中谷 大造(日本ビジネスシステムズ株式会社)

情報システム部の中谷です。社内用スクラッチアプリの開発をしています。

担当記事一覧