From 5aab9322ca90f35b27ebb5cfd5150f59da49cb00 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Sun, 24 May 2026 23:39:15 +0200 Subject: [PATCH] Package ReadyToRun trimmable typemaps Add generated trimmable typemap assemblies to ResolvedFileToPublish so the .NET SDK ReadyToRun pipeline sees them, then replace linked typemap assemblies with the R2R outputs before Android packaging. Extend the CoreCLR trimmable typemap packaging test to assert the packaged entry assembly is ReadyToRun and that typemap payloads are unique. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...roid.Sdk.TypeMap.Trimmable.CoreCLR.targets | 85 +++++++------------ .../TrimmableTypeMapBuildTests.cs | 57 +++++++++++++ 2 files changed, 86 insertions(+), 56 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets index d0ca9742e30..706a73ae31e 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets @@ -25,66 +25,39 @@ - - + + - <_TrimmableTypeMapAbi Include="@(_BuildTargetAbis)" /> - <_TrimmableTypeMapAbi Update="@(_TrimmableTypeMapAbi)" Condition=" '%(_TrimmableTypeMapAbi.Identity)' == 'arm64-v8a' "> - android-arm64 - - <_TrimmableTypeMapAbi Update="@(_TrimmableTypeMapAbi)" Condition=" '%(_TrimmableTypeMapAbi.Identity)' == 'armeabi-v7a' "> - android-arm - - <_TrimmableTypeMapAbi Update="@(_TrimmableTypeMapAbi)" Condition=" '%(_TrimmableTypeMapAbi.Identity)' == 'x86_64' "> - android-x64 - - <_TrimmableTypeMapAbi Update="@(_TrimmableTypeMapAbi)" Condition=" '%(_TrimmableTypeMapAbi.Identity)' == 'x86' "> - android-x86 - + <_TrimmableTypeMapLinkedAssemblies Include="$(IntermediateOutputPath)linked\_*.TypeMap.dll;$(IntermediateOutputPath)linked\_Microsoft.Android.TypeMap*.dll" /> + <_TrimmableTypeMapAssembliesToPublish Include="@(_TrimmableTypeMapLinkedAssemblies)" /> + <_TrimmableTypeMapAssembliesToPublish Include="@(_GeneratedTypeMapAssembliesFromList)" Condition=" '@(_TrimmableTypeMapLinkedAssemblies->Count())' == '0' " /> + + %(Filename)%(Extension) + PreserveNewest + true + $(RuntimeIdentifier) + + - - - <_LinkedTypeMapDlls Include="$(IntermediateOutputPath)%(_TrimmableTypeMapAbi.RuntimeIdentifier)/linked/_*.TypeMap.dll;$(IntermediateOutputPath)%(_TrimmableTypeMapAbi.RuntimeIdentifier)/linked/_Microsoft.Android.TypeMap*.dll"> - %(_TrimmableTypeMapAbi.Identity) - %(_TrimmableTypeMapAbi.RuntimeIdentifier) - %(_TrimmableTypeMapAbi.Identity)/%(_LinkedTypeMapDlls.Filename)%(_LinkedTypeMapDlls.Extension) - %(_TrimmableTypeMapAbi.Identity)/ - - - - <_BuildApkResolvedUserAssemblies Include="@(_LinkedTypeMapDlls)"> - %(_LinkedTypeMapDlls.Abi) - %(_LinkedTypeMapDlls.RuntimeIdentifier) - %(_LinkedTypeMapDlls.DestinationSubPath) - %(_LinkedTypeMapDlls.DestinationSubDirectory) - - - - - <_TypeMapDlls Include="$(_TypeMapOutputDirectory)*.dll"> - %(_TrimmableTypeMapAbi.Identity) - %(_TrimmableTypeMapAbi.RuntimeIdentifier) - %(_TrimmableTypeMapAbi.Identity)/%(_TypeMapDlls.Filename)%(_TypeMapDlls.Extension) - %(_TrimmableTypeMapAbi.Identity)/ - - - - <_BuildApkResolvedUserAssemblies Include="@(_TypeMapDlls)"> - %(_TypeMapDlls.Abi) - %(_TypeMapDlls.RuntimeIdentifier) - %(_TypeMapDlls.DestinationSubPath) - %(_TypeMapDlls.DestinationSubDirectory) - - + - <_LinkedTypeMapDlls Remove="@(_LinkedTypeMapDlls)" /> - <_TypeMapDlls Remove="@(_TypeMapDlls)" /> - <_TrimmableTypeMapAbi Remove="@(_TrimmableTypeMapAbi)" /> + <_TrimmableTypeMapLinkedAssemblies Include="$(IntermediateOutputPath)linked\_*.TypeMap.dll;$(IntermediateOutputPath)linked\_Microsoft.Android.TypeMap*.dll" /> + <_TrimmableTypeMapReadyToRunAssemblies Include="$(IntermediateOutputPath)R2R\_*.TypeMap.dll;$(IntermediateOutputPath)R2R\_Microsoft.Android.TypeMap*.dll" /> + + + + %(Filename)%(Extension) + PreserveNewest + $(RuntimeIdentifier) + diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs index 93195ece435..838a6290bb6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using NUnit.Framework; using Xamarin.Android.Tasks; +using Xamarin.Android.Tools; using Xamarin.ProjectTools; namespace Xamarin.Android.Build.Tests { @@ -197,6 +198,62 @@ public void CoreClrTrimmableTypeMap_PackagesJavaProxyThrowable () $"`{dexFile}` should include `android.runtime.JavaProxyThrowable`."); } + [Test] + public void CoreClrTrimmableTypeMap_PackagesReadyToRunTypeMap () + { + if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: true)) { + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.CoreCLR); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + proj.SetProperty ("RuntimeIdentifier", "android-arm64"); + proj.SetProperty ("AndroidEnableAssemblyCompression", "false"); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); + + var r2rTypeMap = builder.Output.GetIntermediaryPath (Path.Combine ("android-arm64", "R2R", "_Microsoft.Android.TypeMaps.dll")); + FileAssert.Exists (r2rTypeMap, "ReadyToRun should compile the generated TypeMap entry assembly."); + using (var r2rStream = File.OpenRead (r2rTypeMap)) { + using var r2rReader = new System.Reflection.PortableExecutable.PEReader (r2rStream); + Assert.IsTrue ( + r2rReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory.Size > 0, + "ReadyToRun output for _Microsoft.Android.TypeMaps.dll should have a managed native header."); + } + + var apk = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath, "android-arm64", $"{proj.PackageName}-Signed.apk"); + FileAssert.Exists (apk); + + var helper = new ArchiveAssemblyHelper (apk, useAssemblyStores: true); + var packagedTypeMapEntries = helper.ListArchiveContents ("lib/", arch: AndroidTargetArch.Arm64) + .Where (entry => entry.StartsWith ("lib/arm64-v8a/lib__", StringComparison.Ordinal) && + entry.EndsWith (".dll.so", StringComparison.Ordinal) && + !entry.EndsWith (".ni.dll.so", StringComparison.Ordinal) && + entry.Contains ("TypeMap", StringComparison.Ordinal)) + .ToArray (); + Assert.AreEqual ( + packagedTypeMapEntries.Distinct ().Count (), + packagedTypeMapEntries.Length, + "TypeMap assemblies should be packaged only once; do not include both linked IL and ReadyToRun copies."); + Assert.AreEqual ( + 1, + packagedTypeMapEntries.Count (entry => entry == "lib/arm64-v8a/lib__Microsoft.Android.TypeMaps.dll.so"), + "_Microsoft.Android.TypeMaps.dll should be packaged only once."); + + Assert.IsTrue (helper.Exists ("assemblies/arm64-v8a/_Microsoft.Android.TypeMaps.dll"), "_Microsoft.Android.TypeMaps.dll should exist in the APK."); + using (var packagedTypeMap = helper.ReadEntry ("assemblies/arm64-v8a/_Microsoft.Android.TypeMaps.dll", AndroidTargetArch.Arm64)) { + Assert.IsNotNull (packagedTypeMap, "_Microsoft.Android.TypeMaps.dll should be readable from the APK."); + using var packagedReader = new System.Reflection.PortableExecutable.PEReader (packagedTypeMap); + Assert.IsTrue ( + packagedReader.PEHeaders.CorHeader.ManagedNativeHeaderDirectory.Size > 0, + "Packaged _Microsoft.Android.TypeMaps.dll should be the ReadyToRun image, not the linked IL image."); + } + } + [Test] public void TrimmableTypeMap_PreserveLists_ArePackagedInSdk () {