ぴーさんログ

だいたいXamarin.Formsのブログ

【Xamarin → .NET 6+(MAUI)移行】 csprojファイルに書くと良いかも知れない設定 3選

Xamarin.FormsアプリをMAUIへ移行する際にプロジェクトファイル(csproj)を直接編集して設定変更することが度々ありました。 今回はその中で利用機会のありそう3点をご紹介します。

  • プラットフォーム固有のソースファイル指定
  • (MAUI向け) XamlC強制
  • HttpHandler設定

プラットフォーム固有のソースファイル指定

1つのプロジェクトで複数プラットフォーム向けのアプリをビルドする場合におすすめの設定です。 MAUIで特に有効ですが、SDKスタイルのプロジェクト全般で利用できるためマルチプラットフォーム向けライブラリを作る際にも役立ちます。

csprojファイルを編集して<Project>要素の直下に追加します。

<!-- ターゲットがAndroidの場合だけ .Android.cs をコンパイルするようにする -->
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'android'">
    <Compile Remove="**\**\*.Android.cs" />
    <None Include="**\**\*.Android.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>
<!-- ターゲットがiOSの場合だけ .iOS.cs をコンパイルするようにする -->
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'ios'">
    <Compile Remove="**\**\*.iOS.cs" />
    <None Include="**\**\*.iOS.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
</ItemGroup>

説明

ファイルの命名規則を利用して、特定のプラットフォームの場合にのみコンパイルするファイルを制御できるようにします。 これにより、ソースコードが #if だらけで読みにくくなることを防ぐことができます。

特にMAUIでカスタムViewやカスタムHandlerを書く場合、ネイティブコントロールを扱うためのプラットフォーム固有のコードが大きくなるため、ファイル分割の恩恵も大きくなります。 例えば、このようにファイル分割します。 *.cs に共通なプロパティ定義などを書き、 *.<プラットフォーム名>.cs でネイティブコントロールのプロパティに伝播させるイメージです。

  • CustomViewHandler.cs
  • CustomViewHandler.Android.cs
  • CustomViewHandler.iOS.cs

余談

実は指定内容が「"◯◯以外"の場合、".◯◯.cs"を除外する」というややこしい物になっています。 これはデフォルト設定が「".cs"を全て含める」と指定しているのを打ち消すためです。

(MAUI向け) XamlC強制

MAUIプロジェクトの場合にのみ有効な設定です。 Xamarin.Formsの頃と比較してデバッグ実行時の画面遷移が遅くて困っている場合にお試しください。

csprojファイルを編集して<Project>要素の直下に追加します。

<PropertyGroup>
    <!-- MAUIのデフォルト設定を変更して、Debugビルドでも事前コンパイル済みのXAML読み込みを利用するようにしてViewの初期化を高速化します。 -->
    <!-- 代わりにコンパイル時間が伸びるため、繰り返しビルドする場合や実行時の速度が重要でない場合はコメントアウトしてください。 -->
    <MauiXamlCValidateOnly>False</MauiXamlCValidateOnly>
    <_MauiForceXamlCForDebug>True</_MauiForceXamlCForDebug>
</PropertyGroup>

説明

Xamarin.Formsには、XAMLの定義内容を事前コンパイルして実行時の初期化処理を速くする機能があります。 この機能はMAUIにも引き継がれましたがデバッグ時の挙動が変わっており、実行時にはXAMLの解析しながら初期化するため遅くなり、事前コンパイルはエラー検証のために使うようになりました。 この変更は、XAMLコンパイル結果をアプリに組み込む工程を省略することで、繰り返しビルドを速くするためのようです。

画面の初期化が遅すぎてデバッグ時の動作検証に支障がある場合、上の設定を使ってみてください。

HttpHandler設定

Xamarinで HttpClientHandler を使っていたプロジェクト向けの設定です。必要な場合のみ利用してください。

csprojファイルを編集して<Project>要素の直下に追加します。

<PropertyGroup>
    <UseNativeHttpHandler>false</UseNativeHttpHandler>
<PropertyGroup>

説明

Android/iOSネイティブのHTTP通信実装ではなく.NET由来のHTTP通信実装を使いたい場合、Xamarinでは HttpClientHandler を利用すると実現できました。 一方、.NET 6以降ではUseNativeHttpHandlerの設定によって HttpClientHandler の挙動が変わるため同じようにはいきません。

true(デフォルト)の場合、内部でネイティブのHTTP通信実装が使われてしまうため、Xamarinの挙動に近づけるためにfalseに変更する必要があります。 falseの場合でも、Xamarin(Mono)と.NETではHttpClientHandlerの実装が異なるため、期待と異なる挙動をする可能性があります。 その場合はGitHubでそれぞれリポジトリを調べてみてください。

.NET 6+, MAUIで使えるデバイステストランナー

今回は.NET 6以降、MAUI世代のユニットテストに使えるデバイステストランナーについて紹介します。

動機

Xamarin.Forms世代の頃には、古い内容のままでしたが一応ユニットテスト用のプロジェクトテンプレートが存在していました。しかしこれは.NET 6以降のiOS, Androidアプリに対応していないため乗り換える必要がある、というお話です。 xUnit向け、NUnit向けと1つずつ紹介します。

デバイステストテストランナーとは?

ここではiOS, Androidアプリ上で実行するテストを"デバイステスト"(Device Tests)と呼びます。 テストランナーについては、xUnitやNUnitを使って書かれたテストコードを"収集して順番に実行する"存在を指します。 今回紹介するデバイステストランナーはiOS, Androidアプリとしてテストコードを実行するためのライブラリです。

なぜ必要なのか?

Visaul Studio組み込みで動くユニットテストランナーもありますが、あれらでは.NETコンソールアプリケーションとして実行するため、.NET for iOS/Androidのプログラムをテストできません。 iOS, Android固有の機能やクラスを利用するコードをテストするには、iOS, Androidアプリとして実行する必要があります。

[xUnit向け] Shiny.Xunit.Runners.Maui

GitHubのdotnet/mauiリポジトリの中にあるテストツールを切り出したものです。 元のテストツールはMAUIを使ってMAUIをテストするような作りになっているところを、ライブラリとして利用できるように工夫されています。

余談: MAUIプロジェクトではxharnessという実験的な内製ツールを使ってHeadless Testをしているようです。興味がある人は覗いてみてください。(dotnet/xharness: C# command line tool for running tests on Android / iOS / tvOS devices and simulators)

使い方

MAUIの新規アプリを作成して CreateMauiApp() の部分に .ConfigureTests() と .UseVisualRunner() を追加します。 TestOptions.AssembliesにxNuitのテストが定義されているAssemblyを指定すると、自動的にテストを収集して実行してくれます。

テストランナー画面

必要最低限の機能を備えているという感じ。テキスト入力によるフィルタリングがありがたいです。

[NUnit向け] NUnit.Maui.Runner

Xamarin.Forms版のNUnitテストランナーをForkしてMAUI仕様に手直ししたライブラリのようです。

使い方

まずConfigクラスを用意します。ProvideAssemblies()でNNuitのテストが定義されているAssemblyを返すようにしてください。

MAUIの新規アプリを作成して CreateMauiApp() の部分で .UseMauiApp<T>() に渡すApplicationクラスの型をテストランナーが用意したAppクラスに差し替えます。そして builder.Services に上で用意したConfigを登録します。

テストランナー画面

こちらも必要最低限の機能を備えているという感じ。ただテキスト入力フィルタは付いていません。

おわり

アプリの規模やプロジェクトごとの判断によるところではありますが、共通な部分は標準のテストランナーで.NETコンソールアプリとしてユニットテストを実行して、iOS, Android固有な部分は実アプリでテストすると割り切る判断もアリだと思います。公式ドキュメントも割とそんな世界観のように見えます。

とはいえ、MAUIのプロジェクトテンプレートにも何かしらユニットテストプロジェクトを用意してほしいところですね。

なぜXamarinから.NET 6+, MAUIへ移行しなければならないのか

Xamarin、およびXamarin.Formsのサポート終了期日(2024年5月1日)まで半年を切りました。

今回はなぜ移行が必要なのか、どんな作業が発生するのかといった事について書いていきます。

なお、本記事では.NET 6とそれ移行のバージョンについて.NET 6+と表記します。

移行のモチベーション

なぜXamarinから.NET 6+へ移行が必要なの?

(ここはXamarin.Formsを使っていないアプリにも共通するお話です)

サポートが切れてもXamarinを使い続ければ良いのでは?

最大の動機は ストアにアプリを公開し続けるため です。これにつきます。

iOS, Androidどちらのアプリストアも提出要件に対象OS、SDKの最低バージョンを設けており、徐々にこれを引き上げています。 一方でXamarinのサポートポリシーでは「Android API 34 と Xcode 15 SDK (iOS および iPadOS 17、macOS 14)」を最後の対象バージョンと定めています。(2023年11月12日時点 Xamarin 公式サポートポリシー | .NET)

Xamarin SDKで開発を続けた場合、ストアの提出要件がXamarinのサポート範囲を超えた時に アプリの新規公開や、アップデートができなくなってしまいます。 だから新しいSDKを使ってストアにアプリを公開し続けるのために Xamarinから.NET 6+への移行が必要 なのです。

Xamarin.Formsから.NET MAUIへの移行は必須なの?

.NET 6+の新規プロジェクトに既存のXamarin.Formsアプリを移植すれば最低限の工数で対応できるのでは?

残念なことにこの試みは失敗します。

Xamarin.iOS向けのライブラリは.NET 6+とバイナリ互換がなくなってしまったため利用できません。 AndroidではXamarin.FormsのNuGetパッケージはインストールできるものの、ビルドツールが対応できないようでやはりビルド失敗となりました。

そのため、Xamarin.Formsアプリは.NET MAUIへの移行が必須となります。

.NET MAUIへの移行はどのくらい大変?

アプリの規模や作りによって変わってきますが、たいてい1か月以上かかる作業と考えてもらって良いと思います。 必然的に既存のXamarin.Forms版と並行開発する期間が発生しますので運用についても工夫しましょう。

いくつかMAUIへの移行に取り組んだ感触として、大きく次のような段階に分けられると思います。

  • 新規MAUIプロジェクトへ既存Xamarin.Formsアプリのソースコードを移植、ビルドできるように修正する
  • 動かしてみて実行時エラーを出しながら修正する
  • レイアウト崩れ、破綻を修正する

特にライブラリ移行の影響が大きく、どうしてもMAUIで利用できない場合は代わりを自作する、もしくは仕様の方を変更して調整する必要が出てきます。 移行作業を始める前に調査して目処をつけておくことをオススメします。

新規MAUIプロジェクトへ既存Xamarin.Formsアプリのソースコードを移植、ビルドできるように修正する

移植したソースコードのエラーをひたすら解消しつつ、MAUIアプリとしてビルドできるようにする作業です。 なんだかんだ、これらの作業で2〜4週間くらいかかると思います。

  • MAUI方式のプロジェクトへ組み換え
  • MAUIで名前空間やクラスが変更された箇所への対応
  • 今まで利用していたライブラリの互換性調査、移行

MAUI方式のプロジェクトへ組み換え

MAUIは1つのプロジェクト(csproj)から複数の成果物をビルドするSingle Projectというスタイルを採用しています。 Xamarin.Formsでは最低3プロジェクト構成が基本なので、1つにまとめる際にディレクトリ構造をどうまとめるかなど、検討ポイントがあります。 csprojで使用する設定も変わっているため、何をいじれば良いのか把握するのも大変だと思います。

MAUIで名前空間やクラスが変更された箇所への対応

主にXamarin.Forms, Xamarin.EssentialsからMAUI, MAUI Essentialsへの移行作業です。 公式ドキュメントに情報があって比較的取り組みやすい部分です。

今まで利用していたライブラリの互換性調査、移行

今まで利用していたライブラリのMAUI, .NET 6+対応版が出ているか調査して、あれば乗り換えます。 初期化方法MAUI向けになっていたり使い方が変わっていることが多いです。

後継ライブラリが存在しない場合、独自に同じ機能を実装できそうか、他に乗り換えられるライブラリがないか検討する必要があります。 あまりに労力が大きすぎる場合、MAUIへの移行そのものに影を落とすかもしれません。

一見MAUI対応版が出ていても一部の機能が削除されているケースもありますので根気よく取り組みましょう。

動かしてみて実行時エラーを出しながら修正する

ここまで全く動作確認できずにコードを修正しているので、ちょっと動かすとすぐに問題が出ます。 主要なシナリオや移行時に変更した箇所を重点的にチェックしましょう。

レイアウト崩れ、破綻を修正する

MAUIでレイアウト仕様に見直しが入っているため、Xamarin.Formsで問題ないXAML記述でもUIが崩れる場合があるため、最終的には全画面を確認したいです。 軽微なものは余白の大きさが変わっている程度ですが、Viewが重なってボタンが押せなくなってしまう場合もあります。

終わり

つまるところアプリをリリースし続けるならば.NET 6+, MAUIへ移行は避けられないというお話でした。 会社のお仕事でXamarin.FormsからMAUIへの移行サポートをやっていますので、ご入用でしたらいったん@ticktackmobileの方までご連絡ください。

MAUI移行時のDependencyServiceの対処

Xamarin.FormsアプリをMAUIへ移行する際に遭遇した問題から一つ。

問題
DependencyService.Get<T>()でインタンスを取得できなくなる。

解決方法
型の登録方法を変更し、DependentyAttributeで自動登録する方法から、明示的な手動登録に変更する。

解説

MAUIへ移行すると DependencyService.Get<T>() した際にインスタンスを取得できなくなると思います。

型の自動登録は、DependencyService.Get<T>()した際にDependencyServiceが未初期化なら、AssemblyからDepenedency属性のついた収集して初期化するという実装になっています。

しかし、MAUIではブートストラップ処理中にDependencyServiceを初期化済みとマークするため自動登録が働くなっています。 このため、従来の処理を移植すると未登録の型を要求することになりインスタンスが取得できなくなります。

おそらくMAUI標準のDependency Injectionを利用して欲しいという意図とパフォーマンス上の理由でこのような扱いになっているのだと思います。 (依存関係の挿入 | Microsoft Learn)

対策としては、こんな感じの型登録処理を用意して MauiProgram.CreateMauiApp() の際に呼ぶと良いでしょう。

static void SetupDependencyService()
{
#if ANDROID
    DependencyService.Register<IService, MyApp.AndroidServiceImpl>();
#elif IOS
    DependencyService.Register<IService, MyApp.iOSServiceImpl>();
#endif
}

余談
理屈としては UseMauiApp<T>() より先に DependencyService.Get<T>() すると型の自動登録が走るので、そういう解決方法もアリかも?

参考

DependencyService初期化済みとする部分

maui/src/Controls/src/Xaml/Hosting/AppHostBuilderExtensions.cs at f04c5aff095525ab99417d68796849962a4c4903 · dotnet/maui

DependencyAttributeから型を自動登録する部分

maui/src/Controls/src/Core/DependencyService.cs at f04c5aff095525ab99417d68796849962a4c4903 · dotnet/maui

HttpClient周りの仕様が変わってました

Xamarinから.NET 6+(MAUI含む)へ移行する際に注意すべきHTTPスタックのお話です。

HttpClientの挙動に影響するので、.NET 6+へ移行したらHTTP通信周りに異常が無いかテストしましょう!

HTTP Handlerの設定

.NET 6+になってHTTP Handler周りの仕様がXamarin.iOS / Androidの頃と変わりました。

Xamarin時代ではプロジェクト設定の↓こういうところにいたやつです。

Xamarinの場合

アプリプロジェクトでHttpClientが規定で使用するHttpMessageHandlerの実装を指定します。 Android/iOSのHTTPスタックのいずれか、Managed(.NET由来の実装)から選択可能です。

実装的には HttpClient を引数無しで初期化する場合に影響します。

.NET 6+の場合

アプリプロジェクトの UseNativeHttpHandler という設定が新しくできました。

  • true: Android/iOS 用の HttpMessageHandler を使用します。
  • false: Managed(.NET由来の実装)を使用します。

実装的には HttpClientHandler を使用する場合に影響します。 HttpClientHandler は.NET版とネイティブ版両方のHTTPスタックのラッパーのような立ち位置になり、UseNativeHttpHandlerの値によって挙動を切り替えます。 HttpClient を引数無しで初期化した場合は HttpClientHandler が使用されるようになっているため、こちらにも影響します。

HttpClient初期化方法による違い

Xamarinの場合

引数で任意のHttpMessageHandler実装を与えればHTTPスタックをコントロールできます。

.NET 6+の場合

UseNativeHttpHandlerがtrueの場合、基本的に.NETのHTTPスタックを使う道が無くなります。

ライブラリを作る場合の注意

UseNativeHttpHandler はアプリビルド時の設定なのでライブラリでは制御する事が出来ません。

.NET版のHttpMessageHandler実装を期待して HttpClientHandler を使用していた場合、ネイティブHttp Handlerが使われても動作するように修正するか、独自に.NET版のHttpMessageHandler実装を用意する必要があります。

例えば、iOSでUseNativeHttpHandlerがtrueの場合、HttpClientHandler.Proxyのセッターを呼び出すと例外が発生します。

実行時にUseNativeHttpHandlerの値を取得する

次のようなコードでアプリビルド時に設定されたUseNativeHttpHandlerの値を取得する事ができます。

System.AppContext.TryGetSwitch("System.Net.Http.UseNativeHttpHandler", out bool isNativeHttpHandlerEnabled);

参考

iOS, Androidで動作する場合のHttpClientHandlerクラスのソースコード - https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs - https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.InvokeNativeHandler.cs

iOSで使用されるネイティブ版HttpHandlerのソースコード - https://github.com/xamarin/xamarin-macios/blob/main/src/Foundation/NSUrlSessionHandler.cs

Androidで使用されるネイティブ版HttpHandlerのソースコード - https://github.com/xamarin/xamarin-android/blob/main/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs

.NET for iOS / AndroidのSDKバージョンを固定する

.NET for iOS / Android、およびMAUI SDKのバージョンを制御する方法というテーマで調べた事をつらつら書いていきます。

問題

  • .NET 6以降のiOS/Android SDK管理は、基本的に最新へ上げ続ける事を要求する。
  • ついでに、Xamarin.Formsが.NET MAUIになってバージョンを下げられなくなった。

特に.NET for iOS SDKのバージョンが上がると、Xcodeのバージョンを上げないとビルドすらできなくなる場合もあり大変困ります。

主な動機

  • ビルドパイプラインのために毎回同じ環境を一から構築できるようにする。
  • セットアップ時期の違うマシンで開発環境を揃える。
  • バグのあるバージョンを避けてSDKをダウングレードする。

SDK? Workload?

さて、今回扱うSDKは、XamarinでいうとXamarin.iOS / Android SDKに当たるものです。 つまり、C#のコードをiOS/Androidアプリにするためのビルドツールや、ネイティブAPIC#ラッパーの事です。 ネイティブのAndroid SDKXcodeは含みません。(簡単のためC#前提で進めます)

.NET 6以降でこれらのSDKWorkload という仕組みでインストールされます。 Visual StudioSDKの更新サイクルを分離して、個別にアップデート出来るようにするのが大きな目的です。

dotnet workload コマンドが用意されており、例えばMAUI SDKをインストールする場合は dotnet workload install maui を実行します。 dpotnet install では常に最新のWorkloadがインストールされ、 dpotnet update でインストール済みのWorkloadを全て最新に更新します。バージョン指定するオプションはありません。

いくら何でも強気すぎますね!?

ついでに、Xamarin.FormsはただのライブラリだったのでNuGetで任意のバージョンをインストールできましたが、後継の.NET MAUIは.NET SDKの一部になってしまい開発環境で特定のバージョンを使うことになりました。 (複数のプロジェクトで違うバージョンで開発したい場合に困りますね)

アンドキュメントな方法でバージョンを指定する

実は workload installworkload update には --from-rollback-file という隠しパラメータが存在していて、所定のフォーマットのjsonファイルを与えるとバージョン指定が出来ます。

ロールバックファイルの中身はこんな感じ。

{
  "microsoft.net.sdk.maui": "7.0.52",
}

コマンド実行する時はこんな感じになります。

workload install maui --from-rollback-file rollback.json

先行事例に倣って"microsoft.net.sdk.maui"と指定しましたが、実験したところ代わりにWorkload IDの"maui"を使っても大丈夫でした。

参考

ドキュメント化しないと明言されているissue

Add workload install --from-rollback-file documentation · Issue #28226 · dotnet/docs

MAUIのバージョンを固定したいと相談しているissue

How do I Pin a Maui App to a specific version? · Issue #8985 · dotnet/maui

ロールバックファイルで指定する内容

ロールバックファイルで指定するバージョンをいくつにしたら良いか?

大抵の場合は安定環境にインストールされているメモして、それを利用するのが良さそうです。

WindowsVisual Studioをインストールした環境で dotnet workload list を実行すると以下のような結果となりました。

インストール済みワークロードの ID      マニフェストのバージョン           インストール ソース
----------------------------------------------------------------
maui-android            7.0.52/7.0.100         VS 17.4.33213.308
android                 33.0.4/7.0.100         VS 17.4.33213.308
maui-windows            7.0.52/7.0.100         VS 17.4.33213.308
maui-maccatalyst        7.0.52/7.0.100         VS 17.4.33213.308
maccatalyst             16.1.1477/7.0.100      VS 17.4.33213.308
maui-ios                7.0.52/7.0.100         VS 17.4.33213.308
ios                     16.1.1477/7.0.100      VS 17.4.33213.308

消せないWorkload

WindowsVisual Studioをインストールしている環境では関連するWorkloadをアンインストールできなくなるようです。

「インストールソース」で頭に"VS"が付いてるものをアンインストールしようとすると、見つからない(実際にはインストールされている)扱いとなるので特別扱いされます。

"ロールバックファイルで指定する内容"の状態からmauiiosのWorkloadをインストールするとこのようになります。

インストール済みワークロードの ID      マニフェストのバージョン           インストール ソース
-----------------------------------------------------------------------------
ios                     16.2.1024/7.0.100      SDK 7.0.100, VS 17.4.33213.308
maui                    7.0.59/7.0.100         SDK 7.0.100

この後、mauiiosのWorkloadをアンインストールできますが、mauiのそのままリストから消えるのに対し、iosの方は「インストールソース」の"SDK 7.0.100"部分だけ消えてWorkload自体は残ります。

CIなどを考える場合、WindowsではVisual Studioをインストールせずに.NET SDK、Workloadだけを直接インストールしたビルド専用マシンにするのが良さそうです。

Workload Manifest

WorkloadのIDやバージョンはWorkload Manifestに定義されています。 ManifestファイルはNuGetで配布されていて、dotnetコマンドをこれを自動的に取得して有効なIDか、新しいバージョンがあるかなどを判別しているようです。

nuget.orgで"Microsoft.NET.Sdk"と検索するとそれらしいのが沢山ヒットします。

命名規則は次のようになっているようで……

Microsoft.NET.Sdk.<プラットフォーム>.Manifest-<.NET SDKバージョン>

.NET 7.0.1xx系で利用可能なiOS開発用SDKの場合は Microsoft.NET.Sdk.iOS.Manifest-7.0.100 という具合ですね。

中身には WorkloadManifest.json という名前でマニフェストファイルの実体が入っています。

おわり

  • ビルド専用: ロールバックファイルを利用すれば固定SDKバージョンで再現性のある環境構築が出来そう。
  • 開発用(Windows): Visual StudioのせいでSDKバージョンをコントロール出来ないので、最新に追従し続ける事になりそう。
  • 開発用(Mac): ロールバックファイルでバージョン指定できるが、VS for Macが利用しているXamarin SDKとバージョンが乖離していくと問題が発生しそう。(どうやらデバッグ実行でiOSシミュレータ起動したりはXamarin SDK側の機能っぽい)
  • Xamarin用の開発環境と.NET 6+用の開発環境は分けた方が安全そう。

csprojファイル内で使用されるMSBuildプロパティのデフォルト値を調べる

前置き

.NET Coreと同じくらいの時期に登場したSDKスタイルと呼ばれるcsproj形式では、 明示的に記述しなくても多くのMSBuildプロパティに初期値が設定されるようになっています。

ここで Release ビルドの設定を少し変更した Release_Sandbox のようなビルド構成を 作る場合を考えます。

Configurationプロパティ(ビルド構成)が DebugReleaseかによって設定される初期値が変わってきますが、 独自のビルド構成ではこの条件に引っかかりません。

そのためRelease構成のバリエーションを作るためには Releaseの場合に設定される初期値を調べて再現する必要が出てきます。

方法

MSBuildコマンドの-pp(-preprocess)を使って、ビルド時にcsprojにインポートされる全てのファイルが展開された結果を出力します。

次のようなコマンドを実行します。(MSBuildの部分はdotnet buildでもOK)

MSBuild NetAndroidApp.csproj -pp:_NetAndroidApp.csproj.xml

実行すると画像左のcsprojから右のファイルが出力されます。

ここから '$(Configuration)' == 'Release' の条件が含まれる部分を調べていけば良さそうです。

最適化や……

Assemblyのトリミング設定に影響している事が分かりますね。

おわり

という訳でXamarin.Androidプロジェクトを.NET 6+に移行する際に調べたメモでした。

CIやコマンドラインビルドの場合はRelease構成指定にプロパティを上書きで問題ありませんが、 VSのGUI上ではビルド構成を追加しないと使い分けにくいんですよね。

どこかに「Release構成のプロパティを全て継承する」みたいな指定方法ありませんかね?