Skip to content

Conversation

grendello
Copy link
Contributor

@grendello grendello commented Sep 1, 2025

Fixes: #10455

Implements a minimal Android host for NativeAOT applications. The goal is to create
a runtime environment that is as compatible with Xamarin.Android / .NET for Android
applications using MonoVM or CoreCLR as possible. This PR implements the very basics:

  • Support for pre-loading of JNI libraries
  • Support for setting of the application environment variables (via <AndroidEnvironment>)
  • Support for the CoreCLR GC bridge

An important element that's currently missing is linking libc++ statically into the runtime, instead we include the shared libc++ from the NDK. The reason for this is that
NativeAOT build task references a compatibility C++ library which produces conflicts with
the static libc++ one when attempting to link. This issue will be fixed in a separate
PR.

@grendello grendello force-pushed the dev/grendel/naot-runtime branch 2 times, most recently from 443bfb4 to 03d77e5 Compare September 2, 2025 14:15
@grendello
Copy link
Contributor Author

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@grendello grendello force-pushed the dev/grendel/naot-runtime branch from 03d77e5 to 04ca2d9 Compare September 3, 2025 12:17
@jonpryor
Copy link
Contributor

jonpryor commented Sep 3, 2025

Requested feature addition: CoreCLR has logic to use libSystem.Security.Cryptography.Native.Android.a":

It would be handy if similar integration could be done with NativeAOT, such that AndroidCryptoNative_InitLibraryOnLoad() is invoked during app startup when crypto support is required. This would remove the need for the kludge described here:

@grendello
Copy link
Contributor Author

@jonpryor I plan to add support for this, definitely. It'll be the same mechanism as with CoreCLR, just need to implement a bit more things before that

@jkoritzinsky
Copy link
Member

@grendello @jonathanpeppers should I close #10402 if this is intended to replace it?

@grendello
Copy link
Contributor Author

@jkoritzinsky no, please keep it open. I'm not going to put everything in this PR that's in #10402. Once this one is done, you can rebase yours and apply the remaining changes + finetuning + whatnot.

I should be done with this PR next week (need to add two more things)

@grendello grendello force-pushed the dev/grendel/naot-runtime branch from bb04b1e to e6d636a Compare September 9, 2025 13:17
@grendello grendello marked this pull request as ready for review September 10, 2025 14:08
Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NativeAOT test on Windows is failing with:

       (_CollectAssembliesToCompress target) -> 
         C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009: System.InvalidOperationException: Assembly compression info not found for key '__CompressedAssembliesInfo:C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj'. Compression will not be performed. [C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj]
       C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009:    at Xamarin.Android.Tasks.CollectAssemblyFilesToCompress.GetCompressedAssemblyInfo() [C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj]
       C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009:    at Xamarin.Android.Tasks.CollectAssemblyFilesToCompress.RunTask() [C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj]
       C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009:    at Microsoft.Android.Build.Tasks.AndroidTask.Execute() [C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj]

I don't see any new RegisterTaskObject() calls.

So, is the problem specific to Windows? Or maybe the test is doing multiple RIDs?

<!-- Passes linked assemblies to outer MSBuild tasks/targets -->
<ResolvedFileToPublish Include="@(IlcCompileInput);@(_AndroidILLinkAssemblies)" RuntimeIdentifier="$(RuntimeIdentifier)" />
<!-- Include libc++ -->
<ResolvedFileToPublish Include="$(_NdkSysrootDir)libc++_shared.so" RuntimeIdentifier="$(RuntimeIdentifier)" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be removed? Are we statically linking libc++?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not right now because NativeAOT has a compatibility library that conflicts with static libc++. I want to explore a fix for this in a separate PR, since this one is too big already.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it makes sense to split that off into separate PR. I suppose you can remove the shim libc++ from the list and then add the real one instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to keep both references to libc++_shared.so in this PR as we both need to link against it (the <LinkerArg> item) and package it (the ResolvedFileToPublish item)

Comment on lines +86 to +88
if (gref_gc_threshold != int.MaxValue) {
gref_gc_threshold = checked((gref_gc_threshold * 9) / 10);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this taking 90% of this number for all runtimes? Do we need this change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other runtimes set gref_gc_threshold through different code path but it's still the same value (90% of the max GREF count).

@filipnavara
Copy link
Member

This mostly seems to work, except few build issues and excessive logging.

When doing dotnet publish the build configuration is automatically set to Release and that in turn sets EmbedAssembliesIntoApk=true, which later leads to this build error:

    D:\dotnet-android\android\bin\Debug\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009:
      System.InvalidOperationException: Assembly compression info not found for key '__CompressedAssembliesInfo:D:\emcli
      ent\MailClient.Mobile\MailClient.Mobile.Android\MailClient.Mobile.Android.csproj'. Compression will not be perform
      ed.
         at Xamarin.Android.Tasks.CollectAssemblyFilesToCompress.GetCompressedAssemblyInfo()
         at Xamarin.Android.Tasks.CollectAssemblyFilesToCompress.RunTask()
         at Microsoft.Android.Build.Tasks.AndroidTask.Execute()

Since NativeAOT doesn't operate with assemblies it seems safe to workaround it with -p:EmbedAssembliesIntoApk=false.

When doing multi-RID builds (again, default) I constantly got a variation of this error (for either RID at random):

  MailClient.Mobile.Android failed with 2 error(s) (35.1s)
    ILLink : error IL1011: Failed to write 'obj\Release\net10.0-android\android-x64\linked\Java.Interop.dll'.
    C:\Users\filip\.nuget\packages\microsoft.net.illink.tasks\10.0.0-rc.2.25427.104\build\Microsoft.NET.ILLink.targets(103,5): error NETSDK1144: Optimizing assemblies for size failed.

To get past that I used -r android-x64 and the app was built successfully.

The startup took well over a minute and the whole log was spammed with the following:

09-10 22:00:00.319  5559  5576 W monodroid-assembly: virtual bool BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference(JNIEnv *, jobject, jobject)
09-10 22:00:00.319  5559  5576 W monodroid-assembly: virtual bool BridgeProcessing::maybe_call_gc_user_peerable_add_managed_reference(JNIEnv *, jobject, jobject)
...
09-10 22:00:00.340  5559  5576 W monodroid-assembly: virtual bool BridgeProcessing::maybe_call_gc_user_peerable_clear_managed_references(JNIEnv *, jobject)
09-10 22:00:00.340  5559  5576 W monodroid-assembly: virtual bool BridgeProcessing::maybe_call_gc_user_peerable_clear_managed_references(JNIEnv *, jobject)

Presumably the slow startup was caused just by the logging but it needs to be verified. Experiments with the previous PR showed similar behavior due to incorrectly set gref_gc_threshold which caused excessive GC triggers, but this seems to be handled correctly in this PR.

@grendello grendello force-pushed the dev/grendel/naot-runtime branch from 493cbd8 to 96113be Compare September 11, 2025 09:17
@grendello
Copy link
Contributor Author

The NativeAOT test on Windows is failing with:

       (_CollectAssembliesToCompress target) -> 
         C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009: System.InvalidOperationException: Assembly compression info not found for key '__CompressedAssembliesInfo:C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj'. Compression will not be performed. [C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj]
       C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009:    at Xamarin.Android.Tasks.CollectAssemblyFilesToCompress.GetCompressedAssemblyInfo() [C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj]
       C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009:    at Xamarin.Android.Tasks.CollectAssemblyFilesToCompress.RunTask() [C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj]
       C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009:    at Microsoft.Android.Build.Tasks.AndroidTask.Execute() [C:\a\_work\1\a\TestRelease\09-10_14.27.54\temp\NativeAOT\Hello.csproj]

I don't see any new RegisterTaskObject() calls.

So, is the problem specific to Windows? Or maybe the test is doing multiple RIDs?

The problem was Debug vs Release. I tested only in the former and disabled only part of compressed assembly support. It should work now for Release too.

@filipnavara
Copy link
Member

filipnavara commented Sep 11, 2025

The GREF threshold initialization still seems to be wrong:

...
09-11 15:18:56.370 10555 10555 I NativeAotFromAndroid: APPLICATION Initialize END
09-11 15:18:56.372 10555 10555 D monodroid-gc: 83 outstanding GREFs. Performing a full GC!
09-11 15:18:56.376 10555 10617 D monodroid-gc: 84 outstanding GREFs. Performing a full GC!
09-11 15:18:56.392 10555 10555 D monodroid-gc: 59 outstanding GREFs. Performing a full GC!
09-11 15:18:56.400 10555 10617 D monodroid-gc: 60 outstanding GREFs. Performing a full GC!
09-11 15:18:56.416 10555 10573 I ient.mailclient: Explicit concurrent mark compact GC freed 1132KB AllocSpace bytes, 0(0B) LOS objects, 49% free, 2752KB/5504KB, paused 2.622ms,1.462ms total 35.554ms
09-11 15:18:56.419 10555 10555 D monodroid-gc: 86 outstanding GREFs. Performing a full GC!
09-11 15:18:56.419 10555 10617 D monodroid-gc: 86 outstanding GREFs. Performing a full GC!
09-11 15:18:56.448 10555 10573 I ient.mailclient: Explicit concurrent mark compact GC freed 152KB AllocSpace bytes, 0(0B) LOS objects, 49% free, 2728KB/5456KB, paused 1.917ms,2.356ms total 23.981ms
09-11 15:18:56.455 10555 10555 D monodroid-gc: 87 outstanding GREFs. Performing a full GC!
09-11 15:18:56.482 10555 10573 I ient.mailclient: Explicit concurrent mark compact GC freed 160KB AllocSpace bytes, 0(0B) LOS objects, 49% free, 2696KB/5392KB, paused 1.136ms,1.627ms total 21.812ms
09-11 15:18:56.488 10555 10555 D monodroid-gc: 88 outstanding GREFs. Performing a full GC!
09-11 15:18:56.515 10555 10573 I ient.mailclient: Explicit concurrent mark compact GC freed 96KB AllocSpace bytes, 0(0B) LOS objects, 49% free, 2696KB/5392KB, paused 730us,1.376ms total 22.020ms
09-11 15:18:56.517 10555 10555 D monodroid-gc: 89 outstanding GREFs. Performing a full GC!

Essentially every allocation of bridge object triggers a GC.

Probably a missing call to AndroidSystem::init_max_gref_count (); somewhere.

jonpryor added a commit to unoplatform/uno that referenced this pull request Sep 11, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time, we've decided to introduce a new
stage to build and run these unit tests within an Android+NativeAOT
environment.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Sep 11, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Sep 12, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
Builds, installs, does nothing yet.
Linking seems to work, not tested yet
NativeAOT sample now links without errors about missing symbols (added
linker options for it to fail if missing symbols are detected)
Disable attempts to collect assemblies for compression to avoid
this error:

    D:\dotnet-android\android\bin\Debug\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.dev-grendel-naot-runtime.323\tools\Xamarin.Android.Common.targets(2258,3): error XACAF7009:
      System.InvalidOperationException: Assembly compression info not found for key '__CompressedAssembliesInfo:D:\emcli
      ent\MailClient.Mobile\MailClient.Mobile.Android\MailClient.Mobile.Android.csproj'. Compression will not be perform
      ed.
         at Xamarin.Android.Tasks.CollectAssemblyFilesToCompress.GetCompressedAssemblyInfo()
         at Xamarin.Android.Tasks.CollectAssemblyFilesToCompress.RunTask()
         at Microsoft.Android.Build.Tasks.AndroidTask.Execute()
@grendello grendello force-pushed the dev/grendel/naot-runtime branch from 251297f to 68b9a88 Compare September 12, 2025 07:57
Comment on lines +105 to +106
// TODO: this shouldn't be public
public class AndroidObjectReferenceManager : JniRuntime.JniObjectReferenceManager {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only thing we'll need to fix ASAP next week.

@grendello grendello merged commit 869b0e0 into main Sep 12, 2025
59 checks passed
@grendello grendello deleted the dev/grendel/naot-runtime branch September 12, 2025 14:14
jonpryor added a commit to unoplatform/uno that referenced this pull request Sep 15, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
grendello added a commit that referenced this pull request Sep 18, 2025
Context: #10461

A follow up to #10461 which adds some more compatibility with the "standard"
MonoVM and CoreCLR runtime hosts, namely:

  * Support for setting environment variables via the environment file
    (`<AndroidEnvironment>` MSBuild item)
  * Support for setting **local** (that is, application level, not system
    level) system properties

In addition to the above, it makes NativeAOT use static libc++ instead of the
shared one, which not only shrinks the package size (`libc++_shared.so` is
around 9mb), but it also makes startup slightly faster.
jonpryor added a commit to unoplatform/uno that referenced this pull request Sep 25, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Sep 25, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Sep 25, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Sep 30, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 2, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

Finally, .NET Crypto support isn't propertly initialized in
Android+NativeAOT apps in .NET 10 RC1; see dotnet/android#10463.
This may be fixed in dotnet/android#10461, but in the meantime we can
manually call `AndroidCryptoNative_InitLibraryOnLoad()` so that
methods such as `SHA1.Create()` won't throw.

Note: Do *not* make Release-config apps [Debuggable][2] under
.NET 10 Preview 7 or earlier; you will run into
[dotnet/java-interop@90ac202e][3].

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 3, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Update `UnitTestsControl.cs` to provide better error messages when
tests fail in certain scenarios.  Previously, many (many!) tests
would fail:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

The problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist* (it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`).
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

Exclude tests which don't pass under NativeAOT.  These fall into two
major categories:

 1. Failures that don't make sense, failing due to the above two
    mentioned exceptions.

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 3, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Update `UnitTestsControl.cs` to provide better error messages when
tests fail in certain scenarios.  Previously, many (many!) tests
would fail:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

The problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist* (it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`).
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

Exclude tests which don't pass under NativeAOT.  These fall into two
major categories:

 1. Failures that don't make sense, failing due to the above two
    mentioned exceptions.

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 3, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Update `UnitTestsControl.cs` to provide better error messages when
tests fail in certain scenarios.  Previously, many (many!) tests
would fail:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

The problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist* (it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`).
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

Exclude tests which don't pass under NativeAOT.  These fall into two
major categories:

 1. Failures that don't make sense, failing due to the above two
    mentioned exceptions.

    These are ignored via:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 3, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Update `UnitTestsControl.cs` to provide better error messages when
tests fail in certain scenarios.  Previously, many (many!) tests
would fail:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

The problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist* (it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`).
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

Exclude tests which don't pass under NativeAOT.  These fall into two
major categories:

 1. Failures that don't make sense, failing due to the above two
    mentioned exceptions.

    These are ignored via:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 4, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Update `UnitTestsControl.cs` to provide better error messages when
tests fail in certain scenarios.  Previously, many (many!) tests
would fail:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

The problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist* (it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`).
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

Exclude tests which don't pass under NativeAOT.  These fall into two
major categories:

 1. Failures that don't make sense, failing due to the above two
    mentioned exceptions.

    These are ignored via:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 6, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Exclude tests which don't pass under NativeAOT.  These fall into two
major categories:

 1. Failures that don't make sense, failing due to
    `TargetParameterCountException` or `InvalidOperationException`
	described below.

    These are ignored via:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

Update `UnitTestsControl.cs` to try to work around NativeAOT
"weirdness" and provide better error messages when
tests fail in certain scenarios.  Previously, many (many!) tests
would fail with one of:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

A problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist*; it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`.
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

An intermediate form of this commit would wrap the
`TargetParameterCountException` in an `InvalidOperationException`,
resulting in messages such as:

	System.InvalidOperationException: Exception thrown while invoking Uno.UI.Tests.Windows_Globalization.Given_NumeralSystemTranslator.When_NumeralSystemIsMtei(System.String, System.String) with arguments { System.Object[]{ 1 as System.String, ꯱ as System.String } }.
	---> System.Reflection.TargetParameterCountException: Parameter count mismatch.

Note that `When_NumeralSystemIsMtei(System.String, System.String)`
takes two arguments, but it's given *one* argument with value
`{{ System.Object[]{ 1 as System.String, ꯱ as System.String } }}`,
i.e. *the values are there*, just "wrapped" in an extra array.

Update `UnitTestsControl.InvokeTestMethod()` to always call
`ExpandArgumentsWithDefaultValues()` as part of test method
invocation, so that we have a centralized place to look for such
"extra array wrapping".  Update `ExpandArgumentsWithDefaultValues()`
so that if it encounters an array for a parameter type which isn't
an array, the array is expanded as additional arguments.

Additionally, if a `TargetInvocationException` is thrown when the
the method arguments count and parameters count match, throw an
`AssertInconclusiveException`.

Log various "weird" scenarios via `Console.WriteLine()` to make it
easier to get a "complete" listing of such failing methods by using
`adb logcat` output.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 6, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Exclude tests which don't pass under NativeAOT.  These fall into two
major categories:

 1. Failures that don't make sense, failing due to
    `TargetParameterCountException` or `InvalidOperationException`
	described below.

    These are ignored via:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

Update `UnitTestsControl.cs` to try to work around NativeAOT
"weirdness" and provide better error messages when
tests fail in certain scenarios.  Previously, many (many!) tests
would fail with one of:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

A problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist*; it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`.
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

An intermediate form of this commit would wrap the
`TargetParameterCountException` in an `InvalidOperationException`,
resulting in messages such as:

	System.InvalidOperationException: Exception thrown while invoking Uno.UI.Tests.Windows_Globalization.Given_NumeralSystemTranslator.When_NumeralSystemIsMtei(System.String, System.String) with arguments { System.Object[]{ 1 as System.String, ꯱ as System.String } }.
	---> System.Reflection.TargetParameterCountException: Parameter count mismatch.

Note that `When_NumeralSystemIsMtei(System.String, System.String)`
takes two arguments, but it's given *one* argument with value
`{{ System.Object[]{ 1 as System.String, ꯱ as System.String } }}`,
i.e. *the values are there*, just "wrapped" in an extra array.

Update `UnitTestsControl.InvokeTestMethod()` to always call
`ExpandArgumentsWithDefaultValues()` as part of test method
invocation, so that we have a centralized place to look for such
"extra array wrapping".  Update `ExpandArgumentsWithDefaultValues()`
so that if it encounters an array for a parameter type which isn't
an array, the array is expanded as additional arguments.

Additionally, if a `TargetInvocationException` is thrown when the
the method arguments count and parameters count match, throw an
`AssertInconclusiveException`.

Log various "weird" scenarios via `Console.WriteLine()` to make it
easier to get a "complete" listing of such failing methods by using
`adb logcat` output.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 6, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Exclude tests which don't pass under NativeAOT.  These fall into
three major categories:

 1. Failures that don't make sense, likely due to unknown bugs within
    the NativeAOT toolchain itself, failing due to
    `TargetParameterCountException` or `InvalidOperationException`
    described below.  Some of these are worked around (see below),
    while others are ignored via e.g.:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

 3. Other limitations within the NativeAOT environment.  For example,
    Android+NativeAOT does not yet have a GC bridge, meaning every
    `Java.Lang.Object` subclass instance is *never* collected by the
    GC unless explicitly `.Dispose()`d

Update `UnitTestsControl.cs` to try to work around some NativeAOT
"weirdness" and provide better error messages when tests fail in
certain scenarios.  Previously, many tests would fail with one of:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

A problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist*; it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`.
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

An intermediate form of this commit would wrap the
`TargetParameterCountException` in an `InvalidOperationException`,
resulting in messages such as:

	System.InvalidOperationException: Exception thrown while invoking Uno.UI.Tests.Windows_Globalization.Given_NumeralSystemTranslator.When_NumeralSystemIsMtei(System.String, System.String) with arguments { System.Object[]{ 1 as System.String, ꯱ as System.String } }.
	---> System.Reflection.TargetParameterCountException: Parameter count mismatch.

Note that `When_NumeralSystemIsMtei(System.String, System.String)`
takes two arguments, but it's given *one* argument with value
`{{ System.Object[]{ 1 as System.String, ꯱ as System.String } }}`,
i.e. *the values are there*, just "wrapped" in an extra array.

Update `UnitTestsControl.InvokeTestMethod()` to always call
`ExpandArgumentsWithDefaultValues()` as part of test method
invocation, so that we have a centralized place to look for such
"extra array wrapping".  Update `ExpandArgumentsWithDefaultValues()`
so that if it encounters an array for a parameter type which isn't
an array, the array is expanded as additional arguments.

Log various "weird" scenarios via `Console.WriteLine()` to make it
easier to get a "complete" listing of such failing methods by using
`adb logcat` output.

TODO? the migration to AwesomeAssertions in 80c0705 results in some
"bizarre" failure messages, e.g.

	Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Expected value to be less than or equal to 81 because
	TextBox/SingleTextBox;Microsoft.UI.Xaml.VisualStateManager;Microsoft.UI.Xaml.Controls.Grid;…;ElementStub/HorizontalScrollBar;…

which is *truncated* at *4000* characters.  *Something* is wonky there.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 7, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Exclude tests which don't pass under NativeAOT.  These fall into
three major categories:

 1. Failures that don't make sense, likely due to unknown bugs within
    the NativeAOT toolchain itself, failing due to
    `TargetParameterCountException` or `InvalidOperationException`
    described below.  Some of these are worked around (see below),
    while others are ignored via e.g.:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

 3. Other limitations within the NativeAOT environment.  For example,
    Android+NativeAOT does not yet have a GC bridge, meaning every
    `Java.Lang.Object` subclass instance is *never* collected by the
    GC unless explicitly `.Dispose()`d

Update `UnitTestsControl.cs` to try to work around some NativeAOT
"weirdness" and provide better error messages when tests fail in
certain scenarios.  Previously, many tests would fail with one of:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

A problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist*; it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`.
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

An intermediate form of this commit would wrap the
`TargetParameterCountException` in an `InvalidOperationException`,
resulting in messages such as:

	System.InvalidOperationException: Exception thrown while invoking Uno.UI.Tests.Windows_Globalization.Given_NumeralSystemTranslator.When_NumeralSystemIsMtei(System.String, System.String) with arguments { System.Object[]{ 1 as System.String, ꯱ as System.String } }.
	---> System.Reflection.TargetParameterCountException: Parameter count mismatch.

Note that `When_NumeralSystemIsMtei(System.String, System.String)`
takes two arguments, but it's given *one* argument with value
`{{ System.Object[]{ 1 as System.String, ꯱ as System.String } }}`,
i.e. *the values are there*, just "wrapped" in an extra array.

Update `UnitTestsControl.InvokeTestMethod()` to always call
`ExpandArgumentsWithDefaultValues()` as part of test method
invocation, so that we have a centralized place to look for such
"extra array wrapping".  Update `ExpandArgumentsWithDefaultValues()`
so that if it encounters an array for a parameter type which isn't
an array, the array is expanded as additional arguments.

Log various "weird" scenarios via `Console.WriteLine()` to make it
easier to get a "complete" listing of such failing methods by using
`adb logcat` output.

TODO? the migration to AwesomeAssertions in 80c0705 results in some
"bizarre" failure messages, e.g.

	Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Expected value to be less than or equal to 81 because
	TextBox/SingleTextBox;Microsoft.UI.Xaml.VisualStateManager;Microsoft.UI.Xaml.Controls.Grid;…;ElementStub/HorizontalScrollBar;…

which is *truncated* at *4000* characters.  *Something* is wonky there.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 10, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`, which fails:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Exclude tests which don't pass under NativeAOT.  These fall into
three major categories:

 1. Failures that don't make sense, likely due to unknown bugs within
    the NativeAOT toolchain itself, failing due to
    `TargetParameterCountException` or `InvalidOperationException`
    described below.  Some of these are worked around (see below),
    while others are ignored via e.g.:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

 3. Other limitations within the NativeAOT environment.  For example,
    Android+NativeAOT does not yet have a GC bridge, meaning every
    `Java.Lang.Object` subclass instance is *never* collected by the
    GC unless explicitly `.Dispose()`d

Update `UnitTestsControl.cs` to try to work around some NativeAOT
"weirdness" and provide better error messages when tests fail in
certain scenarios.  Previously, many tests would fail with one of:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

or:

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

A problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist*; it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`.
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

An intermediate form of this commit would wrap the
`TargetParameterCountException` in an `InvalidOperationException`,
resulting in messages such as:

	System.InvalidOperationException: Exception thrown while invoking Uno.UI.Tests.Windows_Globalization.Given_NumeralSystemTranslator.When_NumeralSystemIsMtei(System.String, System.String) with arguments { System.Object[]{ 1 as System.String, ꯱ as System.String } }.
	---> System.Reflection.TargetParameterCountException: Parameter count mismatch.

Note that `When_NumeralSystemIsMtei(System.String, System.String)`
takes two arguments, but it's given *one* argument with value
`{{ System.Object[]{ 1 as System.String, ꯱ as System.String } }}`,
i.e. *the values are there*, just "wrapped" in an extra array.

Update `UnitTestsControl.InvokeTestMethod()` to always call
`ExpandArgumentsWithDefaultValues()` as part of test method
invocation, so that we have a centralized place to look for such
"extra array wrapping".  Update `ExpandArgumentsWithDefaultValues()`
so that if it encounters an array for a parameter type which isn't
an array, the array is expanded as additional arguments.

Log various "weird" scenarios via `Console.WriteLine()` to make it
easier to get a "complete" listing of such failing methods by using
`adb logcat` output.

TODO? the migration to AwesomeAssertions in 80c0705 results in some
"bizarre" failure messages, e.g.

	Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Expected value to be less than or equal to 81 because
	TextBox/SingleTextBox;Microsoft.UI.Xaml.VisualStateManager;Microsoft.UI.Xaml.Controls.Grid;…;ElementStub/HorizontalScrollBar;…

which is *truncated* at *4000* characters.  *Something* is wonky there.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 10, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`, which fails:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Exclude tests which don't pass under NativeAOT.  These fall into
three major categories:

 1. Failures that don't make sense, likely due to unknown bugs within
    the NativeAOT toolchain itself, failing due to
    `TargetParameterCountException` or `InvalidOperationException`
    described below.  Some of these are worked around (see below),
    while others are ignored via e.g.:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

 3. Other limitations within the NativeAOT environment.  For example,
    Android+NativeAOT does not yet have a GC bridge, meaning every
    `Java.Lang.Object` subclass instance is *never* collected by the
    GC unless explicitly `.Dispose()`d

Update `UnitTestsControl.cs` to try to work around some NativeAOT
"weirdness" and provide better error messages when tests fail in
certain scenarios.  Previously, many tests would fail with one of:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

or:

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

A problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist*; it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`.
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

An intermediate form of this commit would wrap the
`TargetParameterCountException` in an `InvalidOperationException`,
resulting in messages such as:

	System.InvalidOperationException: Exception thrown while invoking Uno.UI.Tests.Windows_Globalization.Given_NumeralSystemTranslator.When_NumeralSystemIsMtei(System.String, System.String) with arguments { System.Object[]{ 1 as System.String, ꯱ as System.String } }.
	---> System.Reflection.TargetParameterCountException: Parameter count mismatch.

Note that `When_NumeralSystemIsMtei(System.String, System.String)`
takes two arguments, but it's given *one* argument with value
`{{ System.Object[]{ 1 as System.String, ꯱ as System.String } }}`,
i.e. *the values are there*, just "wrapped" in an extra array.

Update `UnitTestsControl.InvokeTestMethod()` to always call
`ExpandArgumentsWithDefaultValues()` as part of test method
invocation, so that we have a centralized place to look for such
"extra array wrapping".  Update `ExpandArgumentsWithDefaultValues()`
so that if it encounters an array for a parameter type which isn't
an array, the array is expanded as additional arguments.

Log various "weird" scenarios via `Console.WriteLine()` to make it
easier to get a "complete" listing of such failing methods by using
`adb logcat` output.

TODO? the migration to AwesomeAssertions in 80c0705 results in some
"bizarre" failure messages, e.g.

	Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Expected value to be less than or equal to 81 because
	TextBox/SingleTextBox;Microsoft.UI.Xaml.VisualStateManager;Microsoft.UI.Xaml.Controls.Grid;…;ElementStub/HorizontalScrollBar;…

which is *truncated* at *4000* characters.  *Something* is wonky there.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 10, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`, which fails:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Exclude tests which don't pass under NativeAOT.  These fall into
three major categories:

 1. Failures that don't make sense, likely due to unknown bugs within
    the NativeAOT toolchain itself, failing due to
    `TargetParameterCountException` or `InvalidOperationException`
    described below.  Some of these are worked around (see below),
    while others are ignored via e.g.:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

 3. Other limitations within the NativeAOT environment.  For example,
    Android+NativeAOT does not yet have a GC bridge, meaning every
    `Java.Lang.Object` subclass instance is *never* collected by the
    GC unless explicitly `.Dispose()`d

Update `UnitTestsControl.cs` to try to work around some NativeAOT
"weirdness" and provide better error messages when tests fail in
certain scenarios.  Previously, many tests would fail with one of:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

or:

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

A problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist*; it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`.
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

An intermediate form of this commit would wrap the
`TargetParameterCountException` in an `InvalidOperationException`,
resulting in messages such as:

	System.InvalidOperationException: Exception thrown while invoking Uno.UI.Tests.Windows_Globalization.Given_NumeralSystemTranslator.When_NumeralSystemIsMtei(System.String, System.String) with arguments { System.Object[]{ 1 as System.String, ꯱ as System.String } }.
	---> System.Reflection.TargetParameterCountException: Parameter count mismatch.

Note that `When_NumeralSystemIsMtei(System.String, System.String)`
takes two arguments, but it's given *one* argument with value
`{{ System.Object[]{ 1 as System.String, ꯱ as System.String } }}`,
i.e. *the values are there*, just "wrapped" in an extra array.

Update `UnitTestsControl.InvokeTestMethod()` to always call
`ExpandArgumentsWithDefaultValues()` as part of test method
invocation, so that we have a centralized place to look for such
"extra array wrapping".  Update `ExpandArgumentsWithDefaultValues()`
so that if it encounters an array for a parameter type which isn't
an array, the array is expanded as additional arguments.

Log various "weird" scenarios via `Console.WriteLine()` to make it
easier to get a "complete" listing of such failing methods by using
`adb logcat` output.

TODO? the migration to AwesomeAssertions in 80c0705 results in some
"bizarre" failure messages, e.g.

	Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Expected value to be less than or equal to 81 because
	TextBox/SingleTextBox;Microsoft.UI.Xaml.VisualStateManager;Microsoft.UI.Xaml.Controls.Grid;…;ElementStub/HorizontalScrollBar;…

which is *truncated* at *4000* characters.  *Something* is wonky there.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
jonpryor added a commit to unoplatform/uno that referenced this pull request Oct 10, 2025
…droid

Context: #21256 / 52a6f3e
Context: dotnet/android#10461
Context: dotnet/android#10457
Context: dotnet/android#10463
Context: AwesomeAssertions/AwesomeAssertions#290

#21256 added support for building with .NET 10, and
one of the new features in .NET 10 is that Android has (very!)
preliminary preview support for [NativeAOT][0].

As building `SamplesApp.Skia.netcoremobile.csproj` with NativeAOT
takes a significant amount of time and disk space, we've decided to
introduce a new `Tests - Android+NativeAOT Skia` stage to build and
run these unit tests within an Android+NativeAOT environment.
To help reduce disk usage, after building the `.apk` we delete the
`obj` directory.

Update `android-run-skia-runtime-tests.sh` to always create
the `$(build.sourcesdirectory)/build/uitests-failure-results` path
before existing with an error, as failure to do so means that the
`PublishBuildArtifacts@1` YAML task fails:

	##[error]Publishing build artifacts failed with an error: Not found PathtoPublish: /agent/_work/1/s/build/uitests-failure-results

which in turn means subsequent `DownloadBuildArtifacts@0` /
**Download previous test runs failed tests** steps *also* always fail:

	##[error]Artifact uitests-android-nativeaot-failure-results not found for build 174635. Please ensure you have published artifacts in any previous phases of the current build.

Update `ApplicationData.GetRoamingFolder()` to explicitly create
`Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)`.
(Apparently this isn't created by default under NativeAOT?)
This avoids a startup assertion:

	I NativeAotFromAndroid: App failed to initialize: System.IO.DirectoryNotFoundException: IO_PathNotFound_Path, /data/user/0/uno.platform.samplesapp.skia/files/.config/b63c4306-f361-42d0-bc1d-5be385a95c78.txt
	I NativeAotFromAndroid:    at System.IO.FileSystem.DeleteFile(String) + 0xe7
	I NativeAotFromAndroid:    at SamplesApp.App.<AssertApplicationData>g__AssertCanCreateFile|32_3(StorageFolder) + 0x10c
	I NativeAotFromAndroid:    at SamplesApp.App.AssertApplicationData() + 0xa5
	I NativeAotFromAndroid:    at SamplesApp.App..ctor() + 0x2a3
	I NativeAotFromAndroid:    at SamplesApp.Droid.Application.<>c.<.ctor>b__0_0() + 0x18
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.NativeApplication.<OnActivityStarted>b__7_0() + 0x12
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.<Run>g__CreateApp|2_10(ApplicationInitializationCallbackParams _) + 0xf
	I NativeAotFromAndroid:    at Microsoft.UI.Xaml.Application.StartPartial(ApplicationInitializationCallback callback) + 0xb5
	I NativeAotFromAndroid:    at Uno.UI.Runtime.Skia.Android.AndroidHost.Run() + 0x306

Enable NativeAOT builds by updating
`SamplesApp.Skia.netcoremobile.csproj` to set the [`$(PublishAot)`][1]
property to the `$(SkiaPublishAot)` MSBuild property.  This allows
us to build a single project for NativeAOT --
`SamplesApp.Skia.netcoremobile.csproj` -- *without* trying to build
every referenced project for NativeAOT, which is what happens if you
instead try `dotnet publish -p:PublishAot=true …`, which fails:

	% dotnet build -c Release -p:PublishAot=true src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj -bl
	…
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(121,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework.

	% dotnet publish src/SamplesApp/SamplesApp.Skia.netcoremobile/SamplesApp.Skia.netcoremobile.csproj \
	  -c Release -r android-x64 -f net10.0-android -p:UnoTargetFrameworkOverride=net10.0-android -p:SkiaPublishAot=true -bl
	# succeeds… after 15 minutes…

This also requires updating `$(NoWarn)` to ignore the hundreds of
IL trimmer warnings.  We're just trying to see where things stand for now.

Additionally, we need to publish with `-r android-x64` in order to
avoid the build error:

	…/Microsoft.NETCore.Native.Publish.targets(92,5): error MSB3030: Could not copy the file "bin/Release/net10.0-android/native/SamplesApp.so" because it was not found.

Because it's `bin/Release/net10.0-android/android-{arm64,x64}/native/SamplesApp.so`!

Oddly, using `-r android-x64` still results in *both* ABIs being
included, which appears to be a unoplatform/uno "bug":

	% unzip -l src/SamplesApp/SamplesApp.Skia.netcoremobile/bin/Release/net10.0-android/android-x64/publish/uno.platform.samplesapp.skia-Signed.apk | grep SamplesApp.so
	763563120  08-28-2025 19:47   lib/x86_64/libSamplesApp.so
	757982224  08-28-2025 19:48   lib/arm64-v8a/libSamplesApp.so

The need for `-r android-x64` may be related to dotnet/android#10457.

.NET Crypto support isn't propertly initialized in Android+NativeAOT
apps in .NET 10 RC1; see dotnet/android#10463.  This may be fixed in
dotnet/android#10461, but in the meantime we can manually call
`AndroidCryptoNative_InitLibraryOnLoad()` so that methods such as
`SHA1.Create()` won't throw.

Exclude tests which don't pass under NativeAOT.  These fall into
three major categories:

 1. Failures that don't make sense, likely due to unknown bugs within
    the NativeAOT toolchain itself, failing due to
    `TargetParameterCountException` or `InvalidOperationException`
    described below.  Some of these are worked around (see below),
    while others are ignored via e.g.:

        [Ignore("DataRowAttribute.GetData() wraps data in an extra array under NativeAOT; not yet understood why.")]

 2. Failures which are due to `AwesomeAssertions` using
    `MethodInfo.MakeGenericMethod()`; see
    AwesomeAssertions/AwesomeAssertions#290:

        Unhandled exception. System.NotSupportedException: 'AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.HandleImpl[System.Int32](AwesomeAssertions.Equivalency.Steps.EnumerableEquivalencyValidator,System.Object[],System.Collections.Generic.IEnumerable`1[System.Int32])' is missing native code. MethodInfo.MakeGenericMethod() is not compatible with AOT compilation. Inspect and fix AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
           at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x2a
           at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x3f
           at AwesomeAssertions.Equivalency.Steps.GenericEnumerableEquivalencyStep.Handle(Comparands, IEquivalencyValidationContext, IValidateChildNodeEquivalency) + 0x21a
           at AwesomeAssertions.Equivalency.EquivalencyValidator.TryToProveNodesAreEquivalent(Comparands, IEquivalencyValidationContext) + 0x129
           at AwesomeAssertions.Equivalency.EquivalencyValidator.AssertEquality(Comparands, EquivalencyValidationContext) + 0x43
           at AwesomeAssertions.Collections.GenericCollectionAssertions`3.BeEquivalentTo[TExpectation](IEnumerable`1, Func`2, String, Object[]) + 0x1f9

    Basically, the `.BeBeEquivalentTo()` extension method cannot
    currently work in NativeAOT environments.

    These are ignored via:

        [Ignore(".BeEquivalentTo() unsupported under NativeAOT; see: AwesomeAssertions/AwesomeAssertions#290")]

 3. Other limitations within the NativeAOT environment.  For example,
    Android+NativeAOT does not yet have a GC bridge, meaning every
    `Java.Lang.Object` subclass instance is *never* collected by the
    GC unless explicitly `.Dispose()`d

Update `UnitTestsControl.cs` to try to work around some NativeAOT
"weirdness" and provide better error messages when tests fail in
certain scenarios.  Previously, many tests would fail with one of:

	System.Reflection.TargetParameterCountException: Arg_ParmCnt
	    at System.Reflection.DynamicInvokeInfo.ThrowForArgCountMismatch() + 0x83
	    at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0x136
	    at Internal.Reflection.Execution.MethodInvokers.InstanceMethodInvoker.Invoke(Object, Object[], BinderBundle, Boolean) + 0x4f
	    at Internal.Reflection.Core.Execution.MethodBaseInvoker.Invoke(Object, Object[], Binder, BindingFlags, CultureInfo) + 0x48
	    at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.Invoke(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x70
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x4b9

or:

	System.InvalidOperationException: Missing parameter does not have default value
	    at Uno.UI.Samples.Tests.UnitTestsControl.ExpandArgumentsWithDefaultValues(Object[] methodArguments, ParameterInfo[] methodParameters) + 0x138
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_4.<<ExecuteTestsForInstance>b__12>d.MoveNext() + 0x92
	--- End of stack trace from previous location ---
	    at Uno.UI.Samples.Tests.UnitTestsControl.<>c__DisplayClass67_2.<<ExecuteTestsForInstance>g__InvokeTestMethod|5>d.MoveNext() + 0x549

A problem is that the reported test that was failing would be
reported as e.g. `When_Add_Remove(System.Object[])`, which is a test
method which *does not exist*; it's actually
`When_Add_Remove(object, int, LeakTestStyles, RuntimeTestPlatforms)`.
Additionally, which parameter doesn't have a default value?
Or how does the parameter count not match? Or…

An intermediate form of this commit would wrap the
`TargetParameterCountException` in an `InvalidOperationException`,
resulting in messages such as:

	System.InvalidOperationException: Exception thrown while invoking Uno.UI.Tests.Windows_Globalization.Given_NumeralSystemTranslator.When_NumeralSystemIsMtei(System.String, System.String) with arguments { System.Object[]{ 1 as System.String, ꯱ as System.String } }.
	---> System.Reflection.TargetParameterCountException: Parameter count mismatch.

Note that `When_NumeralSystemIsMtei(System.String, System.String)`
takes two arguments, but it's given *one* argument with value
`{{ System.Object[]{ 1 as System.String, ꯱ as System.String } }}`,
i.e. *the values are there*, just "wrapped" in an extra array.

Update `UnitTestsControl.InvokeTestMethod()` to always call
`ExpandArgumentsWithDefaultValues()` as part of test method
invocation, so that we have a centralized place to look for such
"extra array wrapping".  Update `ExpandArgumentsWithDefaultValues()`
so that if it encounters an array for a parameter type which isn't
an array, the array is expanded as additional arguments.

Log various "weird" scenarios via `Console.WriteLine()` to make it
easier to get a "complete" listing of such failing methods by using
`adb logcat` output.

TODO? the migration to AwesomeAssertions in 80c0705 results in some
"bizarre" failure messages, e.g.

	Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Expected value to be less than or equal to 81 because
	TextBox/SingleTextBox;Microsoft.UI.Xaml.VisualStateManager;Microsoft.UI.Xaml.Controls.Grid;…;ElementStub/HorizontalScrollBar;…

which is *truncated* at *4000* characters.  *Something* is wonky there.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#publish-native-aot-using-the-cli
[2]: https://developer.android.com/guide/topics/manifest/application-element#debug
[3]: dotnet/java-interop@90ac202
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create our initial native library for NativeAOT

5 participants