ぴーさんログ

だいたいXamarin.Formsのブログ

Slim Bindingアプローチについて

今回はSlim Bindingアプローチを題材に.NET iOS/Android, MAUIにおけるBinding Library事情についての話をします。(Slim Bindingアプローチの具体的な実装手順は割愛します)

Binding Libraryとは?

Slim Bindingアプローチは言ってしまえばBinding Libraryの作り方の一つです。ですので、まずはBinding Libraryについて簡単に説明します。

例えば、FirebaseやFacebookのSDKのようなiOS/Android向けのネイティブライブラリを使いたい時、これらをC#から呼べるようにラップしたものがBinding Libraryです。

技術的には.NET iOS/Androidが各OSのAPIを呼ぶのと同様の仕組みを利用しており、元ライブラリのAPIとC#を対応させるためにAPI定義やサポートクラスを用意する必要があります。 C#として使いやすいように工夫したり、自動生成された結果を調整することが必要で、元のライブラリの規模複雑さに比例して労力がかかります。

バインディング用のC#プロジェクト(以下、Bindingプロジェクト)を作成し、そこにAPI定義ファイルとネイティブライブラリを配置してBinding Libraryをビルドするというのが標準的な作り方です。

Slim Bindingと比較して、元のネイティブライブラリが備えるAPIを全てC#から使えるようにするアプローチは"フルバインディング"と呼ばれます。

Slim Bindingアプローチ

Slim BindingもBinding Libraryを作る方法の一つですが、API定義の作成を簡単にするために工夫されています。

まず、XcodeやAndroid Studioで目的のネイティブライブラリを使用するライブラリを作成します。そして、C#のBindingプロジェクトでは自作したネイティブライブラリをターゲットにします。

自作ネイティブライブラリで最小限のAPIを公開したり、C#に落とし込みやすい形にすることにより、Bindingプロジェクトでの作業がスムーズになります。

1つのアプリケーションがネイティブライブラリの全APIを使用することはまず無いため、このアプローチは理にかなっています。

Slim Bindingとそうでない作り方の違いはこの図のようになります。

Slim Binding作り方

Git Hub上の公式サンプルリポジトリ

Redth/DotNet.Platform.SlimBindings: Slim bindings to native libraries for use with .NET iOS/Android & .NET MAUI apps

Slim Bindingが上手くいくケース

なるべくライブラリの依存関係を浅くして、自分がコントロールできる部分を大きくしようとするとハマりやすくなります。

ネイティブライブラリのフル機能の提供するBinding Libraryとそれに依存するライブラリのエコシステムを活用するのは魅力的ですが、カバー範囲の広さ故にアップデートに時間がかかる傾向にあります。

例えば、Microsoftが提供するBinding Libraryを通してGoogle Firebaseの一部のコンポーネント(Cloud Messagingなど)を利用していた場合を考えてみてください。 元のFirebase SDKはサービスごとに細かく分かれていて、それを反映したBinding Libraryも複数に分かれています。そのため、一連のライブラリの更新作業が足並み揃えて完了するまで待たなければなりません。

もしも、Slim Bindingアプローチにより必要最小限の機能を自力でアップデートできるのであれば、ネイティブライブラリの更新をアプリケーションに素早く適用することも可能となるわけです。

Binding Libraryを取り巻く問題

特に「Binding Libraryに依存するライブラリ」を利用していたアプリケーションとって難しい状況が続きました。それにより、Slim Bindingによる自力解決を検討すべき時を迎えたと言えます。

Xamarin.iOSと.NET iOSのバイナリ非互換

.NET iOSでnintnuintなどの型がC#で正式にサポートされることになった影響で、Xamarin.iOS向けに作られたライブラリは.NET iOSで利用できないという問題が発生しました。 この件は、オーナーがモチベーションを失って更新が停止したライブラリの存在を浮き彫りにしました。

Xamarinのサポート終了における.NET(MAUI)化の際に対応が必須だったこともあり、ソースコードをフォークして自分でビルドし直して対処したプロジェクトもあるのではないでしょうか。

[Announcement] Breaking changes in .NET 6 for iOS, tvOS and macOS · Issue #13087 · xamarin/xamarin-macios

Microsoftによる保守が停止したBinding Library

Xamarin時代、Google、Facebookなど利用者の多いSDKライブラリについてはXamarin社がBinding Libraryを作ってくれていました。MicrosoftがXamarinを買収した後はMicrosoftがこれらの保守を引継ぎました。

困ったことにiOS版では2年ほど更新が停止状態にあります、Microsoftからのサポート終了宣言はありません。一方でSlim Bindingがフルバインディングより適切なアプローチであると説明されされています。つまりそういうことなのでしょう。

何故かAndroid版に関しては更新が継続されているのですが、何かしらの幸運(サポート可能な開発者が残っている、iOSよりもバインディングの負荷が低い?)に支えられた現状であると推測されます。もしこれが無くなった時にどうするべきかは一度検討しておいた方が良いでしょう。

AppleのPrivacy Manifest対応

Binding Library界隈で当たりがひときわ大きかった事件です。

Appleの新しいプライバシー要件でiOSアプリやライブラリにPrivacy Manifestという情報を含めることが必要となりました。2024年5月1日以降はこれに対応していないとアプリの新規登録やアップデートがリジェクトされる(かもしれない)という重大要件です。

Plugin.xxxのようなクロスプラットフォームライブラリに依存していた場合、Privacy Manifest対応には次のような作業が必要となります。

  1. ネイティブライブラリがPrivacy Manifest対応版をリリースする。
  2. 新しいネイティブライブラリを使って、新しいBinding Libraryをリリースする。
  3. 新しいBinding Libraryを使って、新しいクロスプラットフォームライブラリをリリース。
  4. 新しいクロスプラットフォームライブラリを使って.NET(MAUI)アプリをリリースする。

Binding Libraryが対応してくれないと後続が動き出せない訳ですが、先述の通り半公式と思われていたMicrosoftのBinding Libraryが更新停止状態だったのでissueが悲惨な状況でした。

(実は今のところライブラリのPrivacy Manifestは特に何も言われないおかげで何とかなっているアプリもあるのでは)

つまりどうするべきか?

身も蓋も無い言い方をすると、Microsoftが半公式的なBinding Libraryの維持に限界を迎え、Slim Bindingによる自力解決を提案している状況です。

まずはライブラリの依存関係を把握しましょう。

アプリが依存しているライブラリ、ライブラリが依存しているライブラリを調査します。Binding Libraryが含まれていなければひとまず気にすべきことはありません。

直接的にBinding Libraryを利用していた場合

ライブラリの保守状況を確認します。 更新が望めず代替ライブラリも無い場合はSlim Bindingで対処する必要があります。

間接的にBinding Libraryを利用していた場合

Plugin.xxxのようなライブラリがBinding Libraryに依存していて、Binding Libraryの代わりにSlim Bindingで対処する場合の想定です。

プラグインで利用していた機能を自力で実装できるならそれが一番シンプルな解決法でしょう。 プラグイン全体が必要な場合、ソースコードを取り込んだ上でBinding Libraryに依存していた部分をSlim Bindingで作るライブラリが提供するように変更する方法が考えられます。今のところ試す機会はありませんでしたが、自分ならそうすると思います。(それが許されるかどうかはライセンスによるので確認してください)

関連情報

Slim Bindingアプローチのサンプル

Redth/DotNet.Platform.SlimBindings: Slim bindings to native libraries for use with .NET iOS/Android & .NET MAUI apps

Xamarin.Firebase.iOS.*のサポートに関する議論

Is Microsoft going to support Firebase.IOS and other bindings for iOS? · dotnet/maui · Discussion #20359

Xamarin.Firebase.iOS.*ライブラリ群を引き継いでフォーク版を公開している方がいます。

AdamEssenmacher/GoogleApisForiOSComponents: A community-supported fork of the abandoned Xamarin.iOS.* binding libraries from Microsoft

Binding Library関連ドキュメント(iOS)

Binding Library関連ドキュメント(Android)

【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でそれぞれリポジトリを調べてみてください。

なぜ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の方までご連絡ください。

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構成のプロパティを全て継承する」みたいな指定方法ありませんかね?

Xamarin.Macのコントリビュータになりました

Xamarin.Macなアプリは作ってないんですけどね。

この時のPRが修正を受けつつマージされました。

同様に戻り値がNSObjectになってしまっているAPI1がたまにあるらしいので、見つけた人はxamarin-maciosのリポジトリにissue登録してあげてください。NSObjectになっている箇所と、正しい型が載っているAppleのドキュメントのリンクを載せてissue登録すれば、バージョンアップのタイミングで対応してくれると思います。

なんだったらTwitterで@ticktackmobileに教えてくれるのでも良いです。

参考までにPRコメントのやりとり(超意訳)

ワイ「NSObjectから正しい型に変更するPR作ったやで」
中の人A「これじゃあ破壊的変更になるから取り込めないやで」
中の人B「API互換性を維持するために#if XAMCORE_4_0みたいなバージョン分岐作るとええで」
中の人C「ブランチ修正しといたで、これでいこか」
中の人D「ちょっとまって、古いバージョンでも正しい型で受け取れるメソッド追加するのはどうやろ」
中の人C「悪くないやん、ええんちゃう」
GitHub「マージしたやで」
ワイ「やったぜ」

#if XAMCORE_4_0の部分は次のバージョン番号によって変わると思うので、新しめのコミットから探して真似すればPRもすんなり通るんじゃないかな。


  1. たぶんヘッダーファイルではid型で定義されているのだと思う。