diff --git a/build-tools/create-packs/Microsoft.Android.Sdk.TrimmableTypeMap.targets b/build-tools/create-packs/Microsoft.Android.Sdk.TrimmableTypeMap.targets new file mode 100644 index 00000000000..2571729ab8b --- /dev/null +++ b/build-tools/create-packs/Microsoft.Android.Sdk.TrimmableTypeMap.targets @@ -0,0 +1,105 @@ + + + + + + + <_PreBuiltTypeMapPackSubDir>data\prebuilt-typemap + <_PreBuiltTypeMapOutputDir>$(MicrosoftAndroidSdkPackDir)$(_PreBuiltTypeMapPackSubDir)\ + + <_PreBuiltTypeMapIntDir>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', 'obj', 'prebuilt-typemap')) + <_PreBuiltTypeMapJavaDir>$(_PreBuiltTypeMapIntDir)java\ + <_PreBuiltTypeMapAssembly>$(_PreBuiltTypeMapOutputDir)_Mono.Android.TypeMap.dll + <_PreBuiltTypeMapJavaInteropAssembly>$(_PreBuiltTypeMapOutputDir)_Java.Interop.TypeMap.dll + <_PreBuiltTypeMapAcwMap>$(_PreBuiltTypeMapOutputDir)mono.android-acw-map.txt + + <_PreBuiltTypeMapMaxArrayRank>3 + + + + + + <_PreBuiltTypeMapInput Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.dll" /> + <_PreBuiltTypeMapInput Include="$(_MonoAndroidNETDefaultOutDir)Java.Interop.dll" /> + + <_PreBuiltTypeMapFramework Include="$(_MonoAndroidNETDefaultOutDir)*.dll" + Exclude="@(_PreBuiltTypeMapInput)" /> + + + + + + + <_PreBuiltTypeMapJcwJar>$(_PreBuiltTypeMapOutputDir)mono.android-typemaps.jar + <_PreBuiltTypeMapJcwClassesDir>$(_PreBuiltTypeMapIntDir)jcw-classes\ + <_PreBuiltTypeMapJcwSourceList>$(_PreBuiltTypeMapIntDir)jcw-sources.txt + <_PreBuiltTypeMapAndroidJar>$(AndroidSdkDirectory)\platforms\android-$(AndroidPlatformId)\android.jar + <_PreBuiltTypeMapMonoAndroidJar>$(_MonoAndroidNETDefaultOutDir)mono.android.jar + <_PreBuiltTypeMapRuntimeJar>$(MicrosoftAndroidSdkOutDir)java_runtime.jar + + + + + + <_PreBuiltTypeMapJavaFile Include="$(_PreBuiltTypeMapJavaDir)**\*.java" /> + + + + + + + + + + + <_PreBuiltTypeMapAllAssemblies Include="$(_PreBuiltTypeMapOutputDir)*.TypeMap.dll" /> + + + + + + + diff --git a/build-tools/create-packs/Microsoft.Android.Sdk.proj b/build-tools/create-packs/Microsoft.Android.Sdk.proj index de28a81047f..71b45305b7b 100644 --- a/build-tools/create-packs/Microsoft.Android.Sdk.proj +++ b/build-tools/create-packs/Microsoft.Android.Sdk.proj @@ -12,6 +12,8 @@ core workload SDK packs imported by WorkloadManifest.targets. + + $(DotNetStableTargetFramework) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs index d742d120d8c..c924746abf6 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs @@ -2,4 +2,9 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; -public readonly record struct AssemblyInput (string Name, string Path, PEReader Reader); +/// +/// When true (default) the assembly is scanned and its Java peers are emitted into a typemap. +/// When false the assembly is only indexed for base-type resolution (e.g. Mono.Android on the app +/// build, whose typemap is pre-generated at SDK build time, issue #10792) and no peers are emitted. +/// +public readonly record struct AssemblyInput (string Name, string Path, PEReader Reader, bool ScanForPeers = true); diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs index de295c8d4a9..643f563f0b4 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs @@ -80,7 +80,16 @@ public RootTypeMapAssemblyGenerator (Version systemRuntimeVersion) /// sentinels. Must match the value passed to the per-assembly generators. 0 means /// no array sentinels were emitted; the loader passes null for array maps. /// - public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, Stream stream, string? assemblyName = null, string? moduleName = null, int maxArrayRank = 0) + /// + /// Names of pre-generated framework per-assembly typemaps (e.g. _Mono.Android.TypeMap, + /// produced at SDK build time, issue #10792) that always use Java.Lang.Object as their + /// universe anchor regardless of . They are emitted as + /// [assembly: TypeMapAssemblyTarget<Java.Lang.Object>("name")] so the runtime loads + /// them into the Java.Lang.Object universe: in shared mode they merge into the single + /// app universe; in aggregate mode the loader adds the Java.Lang.Object universe alongside + /// the app's per-assembly universes. + /// + public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, Stream stream, string? assemblyName = null, string? moduleName = null, int maxArrayRank = 0, IReadOnlyList? sharedFrameworkTypeMapNames = null) { if (perAssemblyTypeMapNames is null) { throw new ArgumentNullException (nameof (perAssemblyTypeMapNames)); @@ -125,6 +134,19 @@ public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSha } EmitArrayAssemblyTargetAttributes (pe, perAssemblyTypeMapNames, maxArrayRank); + // Pre-generated framework typemaps (e.g. _Mono.Android.TypeMap) always live in the + // Java.Lang.Object universe, so reference them under that anchor regardless of mode. + bool hasSharedFrameworkUniverse = sharedFrameworkTypeMapNames is { Count: > 0 }; + if (hasSharedFrameworkUniverse) { + var javaLangObjectHandle = pe.Metadata.AddTypeReference (pe.MonoAndroidRef, + pe.Metadata.GetOrAddString ("Java.Lang"), + pe.Metadata.GetOrAddString ("Object")); + EmitSharedUniverseAssemblyTargetAttributes (pe, javaLangObjectHandle, sharedFrameworkTypeMapNames!); + // The pre-generated framework typemaps also emit __ArrayMapRank{N} array universes; the + // app-build root must reference those so framework array types resolve at runtime. + EmitArrayAssemblyTargetAttributes (pe, sharedFrameworkTypeMapNames!, maxArrayRank); + } + // Emit [assembly: IgnoresAccessChecksTo("...")] so TypeMapLoader.Initialize() can access // internal types (TrimmableTypeMap and friends in Mono.Android, and private anchors // in each per-assembly typemap DLL when aggregate universes or array maps are used). @@ -135,7 +157,7 @@ public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSha pe.EmitIgnoresAccessChecksToAttribute (accessTargets); // Emit TypeMapLoader class with Initialize() method - EmitTypeMapLoader (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse, maxArrayRank, assemblyName); + EmitTypeMapLoader (pe, anchorTypeHandle, perAssemblyTypeMapNames, useSharedTypemapUniverse, maxArrayRank, assemblyName, hasSharedFrameworkUniverse); pe.WritePE (stream); } @@ -201,7 +223,7 @@ static void EmitAssemblyTargetAttribute (PEAssemblyBuilder pe, MemberReferenceHa pe.Metadata.AddCustomAttribute (EntityHandle.AssemblyDefinition, ctorRef, blobHandle); } - static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, int maxArrayRank, string assemblyName) + static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, int maxArrayRank, string assemblyName, bool hasSharedFrameworkUniverse = false) { var metadata = pe.Metadata; @@ -240,6 +262,9 @@ static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHand var externalDictArrayTypeSpec = MakeIReadOnlyDictArrayTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true); if (useSharedTypemapUniverse) { + // Single Java.Lang.Object universe. Pre-generated framework typemaps (if any) also + // anchor on Java.Lang.Object, so GetOrCreateExternalTypeMapping() + // already merges them in via their TypeMapAssemblyTarget attributes — no extra universe. if (maxArrayRank > 0) { var initializeRef = AddInitializeSingleWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); EmitInitializeWithSingleTypeMap (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef, @@ -251,13 +276,21 @@ static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHand } else { var proxyDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false); if (maxArrayRank > 0) { + if (hasSharedFrameworkUniverse) { + // Debug + array maps (NativeAOT) combined with a pre-generated shared framework + // universe is not yet wired: the array-map aggregation is built per-assembly and + // would need an extra Java.Lang.Object array-map slot. Tracked by + // https://github.com/dotnet/android/issues/12128. The app build avoids reaching + // here by falling back to scanning the framework assemblies for this combination. + throw new NotSupportedException ("Pre-generated framework typemaps are not yet supported together with aggregate array maps (maxArrayRank > 0). See https://github.com/dotnet/android/issues/12128."); + } var initializeRef = AddInitializeAggregateWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); EmitInitializeWithAggregateTypeMap (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, initializeRef, externalDictTypeSpec, proxyDictTypeSpec, externalDictArrayTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, maxArrayRank, assemblyName); } else { var initializeRef = AddInitializeAggregateNoArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef); EmitInitializeWithAggregateTypeMapNoArrays (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef, - initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, assemblyName); + initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, assemblyName, hasSharedFrameworkUniverse); } } } @@ -348,18 +381,30 @@ static void EmitInitializeWithAggregateTypeMapNoArrays (PEAssemblyBuilder pe, MemberReferenceHandle initializeRef, TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle proxyDictTypeSpec, TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, - string assemblyName) + string assemblyName, + bool hasSharedFrameworkUniverse = false) { - var count = perAssemblyTypeMapNames.Count; + // When a pre-generated framework typemap is present (e.g. _Mono.Android.TypeMap), it lives in + // the Java.Lang.Object universe. Emit it as universe [0] (via + // GetOrCreate*TypeMapping()), followed by the app's per-assembly universes. + var count = perAssemblyTypeMapNames.Count + (hasSharedFrameworkUniverse ? 1 : 0); var getExternalSpecs = new EntityHandle [count]; var getProxySpecs = new EntityHandle [count]; - for (int i = 0; i < count; i++) { + int offset = 0; + if (hasSharedFrameworkUniverse) { + var javaLangObjectRef = pe.Metadata.AddTypeReference (pe.MonoAndroidRef, + pe.Metadata.GetOrAddString ("Java.Lang"), pe.Metadata.GetOrAddString ("Object")); + getExternalSpecs [0] = MakeGenericMethodSpec (pe, getExternalMemberRef, javaLangObjectRef); + getProxySpecs [0] = MakeGenericMethodSpec (pe, getProxyMemberRef, javaLangObjectRef); + offset = 1; + } + for (int i = 0; i < perAssemblyTypeMapNames.Count; i++) { var asmRef = pe.FindOrAddAssemblyRef (perAssemblyTypeMapNames [i]); var perAsmAnchorRef = pe.Metadata.AddTypeReference (asmRef, default, pe.Metadata.GetOrAddString ("__TypeMapAnchor")); - getExternalSpecs [i] = MakeGenericMethodSpec (pe, getExternalMemberRef, perAsmAnchorRef); - getProxySpecs [i] = MakeGenericMethodSpec (pe, getProxyMemberRef, perAsmAnchorRef); + getExternalSpecs [offset + i] = MakeGenericMethodSpec (pe, getExternalMemberRef, perAsmAnchorRef); + getProxySpecs [offset + i] = MakeGenericMethodSpec (pe, getProxyMemberRef, perAsmAnchorRef); } pe.EmitBody ("Initialize", diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs index f239f509f54..e88c234e2b4 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs @@ -173,9 +173,15 @@ public List Scan (IReadOnlyList<(string Name, PEReader Reader)> as public List Scan (IEnumerable assemblies) { + // Reference-only assemblies (ScanForPeers == false) are indexed so base-type resolution + // works, but are not scanned for peer emission (their typemap is pre-generated elsewhere). + var referenceOnlyAssemblyNames = new HashSet (StringComparer.Ordinal); foreach (var assembly in assemblies) { var index = AssemblyIndex.Create (assembly.Reader, assembly.Name, assembly.Path); assemblyCache [index.AssemblyName] = index; + if (!assembly.ScanForPeers) { + referenceOnlyAssemblyNames.Add (index.AssemblyName); + } } // Key by (managedTypeName, assemblyName) to avoid collisions when two assemblies @@ -183,6 +189,9 @@ public List Scan (IEnumerable assemblies) // Java.Interop and Mono.Android). var resultsByQualifiedName = new Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> (); foreach (var index in assemblyCache.Values) { + if (referenceOnlyAssemblyNames.Contains (index.AssemblyName)) { + continue; + } ScanAssembly (index, resultsByQualifiedName); } ForceUnconditionalCrossReferences (resultsByQualifiedName, assemblyCache); diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs index fee0811ebc4..ae0b917d0a6 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs @@ -35,7 +35,9 @@ public TrimmableTypeMapResult Execute ( XDocument? manifestTemplate = null, string? packageNamingPolicy = null, int maxArrayRank = 0, - bool generateTypeMapAssemblies = true) + bool generateTypeMapAssemblies = true, + bool generateRootAssembly = true, + IReadOnlyList? sharedFrameworkTypeMapNames = null) { _ = assemblies ?? throw new ArgumentNullException (nameof (assemblies)); _ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion)); @@ -56,7 +58,7 @@ public TrimmableTypeMapResult Execute ( PropagateCannotRegisterToDescendants (allPeers); var generatedAssemblies = generateTypeMapAssemblies - ? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse, maxArrayRank) + ? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse, maxArrayRank, generateRootAssembly, sharedFrameworkTypeMapNames) : []; var jcwPeers = allPeers.Where (ShouldGenerateJcw).ToList (); logger.LogGeneratingJcwFilesInfo (jcwPeers.Count, allPeers.Count); @@ -179,7 +181,9 @@ List GenerateTypeMapAssemblies ( List allPeers, Version systemRuntimeVersion, bool useSharedTypemapUniverse, - int maxArrayRank) + int maxArrayRank, + bool generateRootAssembly = true, + IReadOnlyList? sharedFrameworkTypeMapNames = null) { List<(string AssemblyName, List Peers)> peersByAssembly; @@ -213,12 +217,20 @@ List GenerateTypeMapAssemblies ( generatedAssemblies.Add (new GeneratedAssembly (typeMapAssemblyName, stream)); logger.LogGeneratedTypeMapAssemblyInfo (typeMapAssemblyName, peers.Count); } - var rootStream = new MemoryStream (); - var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion); - rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream, maxArrayRank: maxArrayRank); - rootStream.Position = 0; - generatedAssemblies.Add (new GeneratedAssembly ("_Microsoft.Android.TypeMaps", rootStream)); - logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count); + // The root assembly (_Microsoft.Android.TypeMaps) carries the + // [assembly: TypeMapAssemblyTarget] attributes and TypeMapLoader.Initialize() that + // bind the per-assembly typemaps into universes at runtime. When pre-generating a + // framework typemap (e.g. Mono.Android) at SDK build time, the root is intentionally + // skipped: it is emitted by the app build, which references the pre-generated per-assembly + // typemap alongside the app's own. + if (generateRootAssembly) { + var rootStream = new MemoryStream (); + var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion); + rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream, maxArrayRank: maxArrayRank, sharedFrameworkTypeMapNames: sharedFrameworkTypeMapNames); + rootStream.Position = 0; + generatedAssemblies.Add (new GeneratedAssembly ("_Microsoft.Android.TypeMaps", rootStream)); + logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count); + } logger.LogGeneratedTypeMapAssembliesInfo (generatedAssemblies.Count); return generatedAssemblies; } 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 2d1201e2cfc..808cdb7b588 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 @@ -197,14 +197,40 @@ + + + + + + + + + + + + + + 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 b66b2e0208d..46169402a2e 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 @@ -61,6 +61,32 @@ + + + <_PreGeneratedTypeMapDir>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)..\data\prebuilt-typemap')) + <_PreGeneratedMonoAndroidTypeMapAssembly>$(_PreGeneratedTypeMapDir)_Mono.Android.TypeMap.dll + <_PreGeneratedMonoAndroidTypeMapJar>$(_PreGeneratedTypeMapDir)mono.android-typemaps.jar + <_PreGeneratedMonoAndroidTypeMapAcwMap>$(_PreGeneratedTypeMapDir)mono.android-acw-map.txt + <_AndroidUsePreGeneratedMonoAndroidTypeMap Condition=" '$(_AndroidUsePreGeneratedMonoAndroidTypeMap)' == '' and Exists('$(_PreGeneratedMonoAndroidTypeMapAssembly)') ">true + <_AndroidUsePreGeneratedMonoAndroidTypeMap Condition=" '$(_AndroidUsePreGeneratedMonoAndroidTypeMap)' == '' ">false + + <_AndroidUsePreGeneratedMonoAndroidTypeMap Condition=" '$(_AndroidUsePreGeneratedMonoAndroidTypeMap)' == 'true' and '$(AndroidIncludeDebugSymbols)' == 'true' and '$(_AndroidTrimmableTypeMapMaxArrayRank)' != '0' ">false + + <_TrimmableTypeMapKeepRulesAcwMapFile>$(IntermediateOutputPath)acw-map.txt + <_TrimmableTypeMapKeepRulesAcwMapFile Condition=" '$(_AndroidUsePreGeneratedMonoAndroidTypeMap)' == 'true' ">$(IntermediateOutputPath)acw-map.prebuilt-merged.txt + + @@ -127,12 +153,23 @@ <_MergedManifestDocuments Condition=" '$(AndroidManifestMerger)' == 'legacy' " Include="@(ExtractedManifestDocuments)" /> + + + <_PreGeneratedTypeMapAssemblyInput Condition=" '$(_AndroidUsePreGeneratedMonoAndroidTypeMap)' == 'true' " + Include="@(_TypeMapInputAssemblies)" KeepDuplicates="false" /> + <_PreGeneratedTypeMapAssemblyInput Remove="@(_PreGeneratedTypeMapAssemblyInput)" + Condition=" '%(Filename)' != 'Mono.Android' and '%(Filename)' != 'Java.Interop' " /> + + + <_GeneratedTypeMapAssembliesFromList Include="$(_PreGeneratedTypeMapDir)*.TypeMap.dll" /> + + + + + +