From 8abcbfe2718a543b2f8dea386150e883f6ac87c5 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Sun, 5 Jul 2026 00:02:04 +0200 Subject: [PATCH] [TrimmableTypeMap][NativeAOT] Auto-detect runtime-only ACW assemblies Under the trimmable typemap on NativeAOT, the runtime host assembly Microsoft.Android.Runtime.NativeAOT - and its only Java Callable Wrapper type, UncaughtExceptionMarshaler - was never scanned by the typemap generator, so its JCW/typemap/acw-map entries were missing, R8 had nothing to keep, and the app crashed at startup in JavaInteropRuntime.init (setDefaultUncaughtExceptionHandler). Root cause: _GenerateTrimmableTypeMap runs in the RID-independent OUTER build over @(ReferencePath), the compile closure, which omits runtime-only assemblies (no ref-pack counterpart) that are only pulled from the RID-specific runtime pack in the per-RID inner build. Runtime-pack resolution (ResolveRuntimePackAssets) requires a single RuntimeIdentifier. Fix - discover them automatically from the SDK's own resolution (no hard-coded assembly names, no per-assembly reference-assembly plumbing): * _ResolveRuntimeOnlyAssembliesForTypeMap (AssemblyResolution.targets): a resolve-only sibling of _ComputeFilesToPublishForRuntimeIdentifiers that stops after ResolveReferences (no ComputeFilesToPublish/ILLink/ILC/AOT) and returns the Android runtime-pack managed assemblies (Microsoft.Android.Runtime.*) with no @(ReferencePath) counterpart (matched on Filename, so Mono.Android/Java.Interop are not double-scanned; the .NET/BCL runtime pack is excluded). * _ResolveRuntimeOnlyAssembliesForTrimmableTypeMap + _AddRuntimeOnlyAssembliesToTrimmableTypeMap (NativeAOT.targets): before _ResolveAssemblies spawns the per-RID ILC builds, run that target once via the MSBuild task for the first RID (managed metadata is RID-independent) and cache the result. The nested resolve is gated on Inputs/Outputs so it does not re-run on incremental no-op builds; an always-run loader reads the cached list into the generator's extra-framework input. ILC framework classification is generalized: any runtime-pack managed assembly's per-assembly typemap DLL is treated as framework (IlcReference but not an unmanaged-entrypoint root), matching Mono.Android/Java.Interop. Adds Build_WithTrimmableTypeMap_KeepsNativeAotRuntimeHostAcws, which opts into the trimmable typemap on NativeAOT and asserts classes.dex retains the UncaughtExceptionMarshaler runtime ACW. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...oft.Android.Sdk.AssemblyResolution.targets | 30 ++++++++ ...id.Sdk.TypeMap.Trimmable.NativeAOT.targets | 73 +++++++++++++++++++ ...soft.Android.Sdk.TypeMap.Trimmable.targets | 7 +- .../TrimmableTypeMapBuildTests.cs | 32 ++++++++ 4 files changed, 141 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index efb8c209f7b..c02e81bf37f 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -86,6 +86,36 @@ _ResolveAssemblies MSBuild target. + + + + <_TypeMapRuntimeOnlyAssembly Include="@(RuntimePackAsset)" + Condition=" '%(RuntimePackAsset.Extension)' == '.dll' and '%(RuntimePackAsset.AssetType)' == 'runtime' and $([System.String]::Copy('%(RuntimePackAsset.NuGetPackageId)').StartsWith('Microsoft.Android.Runtime')) " /> + + <_TypeMapRuntimeOnlyAssembly Remove="@(ReferencePath)" MatchOnMetadata="Filename" /> + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets index fb655661a24..82e46dfc521 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets @@ -27,6 +27,15 @@ <_TrimmableTypeMapFrameworkIlcAssemblyNames Include="@(PrivateSdkAssemblies->'_%(Filename).TypeMap')" /> <_TrimmableTypeMapFrameworkIlcAssemblyNames Include="@(ReferencePath->'_%(Filename).TypeMap')" Condition=" '%(ReferencePath.FrameworkAssembly)' == 'true' " /> + + <_TrimmableTypeMapFrameworkIlcAssemblyNames Include="@(RuntimePackAsset->'_%(Filename).TypeMap')" + Condition=" '%(RuntimePackAsset.Extension)' == '.dll' and '%(RuntimePackAsset.AssetType)' == 'runtime' " /> + + + <_TrimmableTypeMapResolveRid Condition=" '$(RuntimeIdentifiers)' != '' ">$([System.String]::Copy('$(RuntimeIdentifiers)').Split(';')[0]) + <_TrimmableTypeMapResolveRid Condition=" '$(_TrimmableTypeMapResolveRid)' == '' ">$(RuntimeIdentifier) + + <_TrimmableTypeMapResolveProperties>_ComputeFilesToPublishForRuntimeIdentifiers=true;SelfContained=true;DesignTimeBuild=$(DesignTimeBuild);AppendRuntimeIdentifierToOutputPath=true;ResolveAssemblyReferencesFindRelatedSatellites=false;SkipCompilerExecution=true;_OuterIntermediateAssembly=@(IntermediateAssembly);_OuterOutputPath=$(OutputPath);_OuterIntermediateOutputPath=$(IntermediateOutputPath);_AndroidNdkDirectory=$(_AndroidNdkDirectory) + + + <_TrimmableTypeMapResolveProject Include="$(MSBuildProjectFile)" + AdditionalProperties="RuntimeIdentifier=$(_TrimmableTypeMapResolveRid);$(_TrimmableTypeMapResolveProperties)" /> + + + + + + + + + + + + + + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets index 5090ba7fa6a..cf0ded016ac 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets @@ -99,7 +99,7 @@ Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' and '$(DesignTimeBuild)' != 'true' and '@(ReferencePath->Count())' != '0' and '$(_OuterIntermediateOutputPath)' == '' " AfterTargets="CoreCompile" DependsOnTargets="_GetLibraryImports" - Inputs="@(ReferencePath);@(PrivateSdkAssemblies);@(FrameworkAssemblies);@(ExtractedManifestDocuments);$(IntermediateOutputPath)$(TargetFileName);$(_AndroidManifestAbs);$(_AndroidBuildPropertiesCache)" + Inputs="@(ReferencePath);@(PrivateSdkAssemblies);@(FrameworkAssemblies);@(ExtractedManifestDocuments);@(_AndroidTrimmableTypeMapExtraFrameworkAssembly);$(IntermediateOutputPath)$(TargetFileName);$(_AndroidManifestAbs);$(_AndroidBuildPropertiesCache)" Outputs="$(_TrimmableTypeMapOutputStamp)"> @@ -108,9 +108,14 @@ <_TypeMapInputAssemblies Include="@(ResolvedFrameworkAssemblies)" /> <_TypeMapInputAssemblies Include="@(PrivateSdkAssemblies)" /> <_TypeMapInputAssemblies Include="@(FrameworkAssemblies)" /> + + <_TypeMapInputAssemblies Include="@(_AndroidTrimmableTypeMapExtraFrameworkAssembly)" /> <_TypeMapFrameworkAssemblies Include="@(ResolvedFrameworkAssemblies)" /> <_TypeMapFrameworkAssemblies Include="@(PrivateSdkAssemblies)" /> <_TypeMapFrameworkAssemblies Include="@(FrameworkAssemblies)" /> + <_TypeMapFrameworkAssemblies Include="@(_AndroidTrimmableTypeMapExtraFrameworkAssembly)" /> <_TypeMapInputAssemblies Include="$(IntermediateOutputPath)$(TargetFileName)" Condition="Exists('$(IntermediateOutputPath)$(TargetFileName)')" />