.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 上に置いてあります。
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 ライブラリ」を参照してください。
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つです。
- ビルド環境の準備( SDK 本体とワークロードのインストール)
- 署名用の Java キーストア取得
- 発行(リストア、発行、成果物の転送)
面倒そうなのはビルド処理だけですね。
ジョブの冒頭部分です。 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 でビルドするためのドキュメントはこちらです。
ドキュメントを眺めると 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つです。
- ビルド環境の準備( SDK 本体とワークロードのインストール、 Xcode バージョン固定)
- 署名用の証明書と Provisioning Profile インストール
- 発行(リストア、発行、成果物の転送)
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_IDENTITY
と APPLE_PROV_PROFILE_UUID
です。両方とも後述の発行タスクで使用します。
発行
最後に MAUI アプリを復元して発行を行います。 iOS アプリを CLI でビルドするためのドキュメントはこちらです。
ドキュメントを眺めると 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つだけ含まれる場合で試していますが、実際には複数のプロジェクトが含まれるソリューションをビルドすることになるでしょう。その場合、今回の例みたいにはすんなりいかないはずですので、近いうちに試して記事に残したいと思います。