Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<!--
Pre-generates the trimmable type map for Mono.Android at SDK build time (issue #10792).

App builds otherwise rescan the ~8000 Mono.Android types on every (Debug) build. Instead we
scan Mono.Android once here and ship the resulting _Mono.Android.TypeMap.dll (and, later, the
Java Callable Wrapper jar) in the SDK pack. The typemap always anchors on Java.Lang.Object
(ForceSharedTypemapUniverse=true) so a single artifact serves both app-build universe modes:
merged (Release) and aggregate (Debug). The root _Microsoft.Android.TypeMaps assembly is NOT
generated here (GenerateRootAssembly=false) — the app build emits it and references this
pre-generated typemap under Java.Lang.Object.
-->
<Project>

<UsingTask TaskName="Xamarin.Android.Tasks.GenerateTrimmableTypeMap" AssemblyFile="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Tasks.dll" />

<PropertyGroup>
<_PreBuiltTypeMapPackSubDir>data\prebuilt-typemap</_PreBuiltTypeMapPackSubDir>
<_PreBuiltTypeMapOutputDir>$(MicrosoftAndroidSdkPackDir)$(_PreBuiltTypeMapPackSubDir)\</_PreBuiltTypeMapOutputDir>
<!-- Intermediate (not packaged): generated JCW .java and compiled .class files. -->
<_PreBuiltTypeMapIntDir>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', 'obj', 'prebuilt-typemap'))</_PreBuiltTypeMapIntDir>
<_PreBuiltTypeMapJavaDir>$(_PreBuiltTypeMapIntDir)java\</_PreBuiltTypeMapJavaDir>
<_PreBuiltTypeMapAssembly>$(_PreBuiltTypeMapOutputDir)_Mono.Android.TypeMap.dll</_PreBuiltTypeMapAssembly>
<_PreBuiltTypeMapJavaInteropAssembly>$(_PreBuiltTypeMapOutputDir)_Java.Interop.TypeMap.dll</_PreBuiltTypeMapJavaInteropAssembly>
<_PreBuiltTypeMapAcwMap>$(_PreBuiltTypeMapOutputDir)mono.android-acw-map.txt</_PreBuiltTypeMapAcwMap>
<!--
Array-map rank baked into the pre-generated framework typemaps. This MUST match the app
build's effective MaxArrayRank when it consumes these artifacts, otherwise array entries land
in different universes (main vs __ArrayMapRank{N}) and the runtime throws BadImageFormatException.
The app-build value comes from _AndroidTrimmableTypeMapMaxArrayRank (see
Microsoft.Android.Sdk.TypeMap.Trimmable.targets), whose NativeAOT default is 3; pre-generation
only runs for that shape, so we hardcode the same value here.
-->
<_PreBuiltTypeMapMaxArrayRank>3</_PreBuiltTypeMapMaxArrayRank>
</PropertyGroup>

<Target Name="_GeneratePreBuiltMonoAndroidTypeMap"
BeforeTargets="ConfigureLocalWorkload;_GenerateXASdkContent"
Inputs="$(_MonoAndroidNETDefaultOutDir)Mono.Android.dll;$(_MonoAndroidNETDefaultOutDir)Java.Interop.dll;$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Build.Tasks.dll"
Outputs="$(_PreBuiltTypeMapAssembly);$(_PreBuiltTypeMapJavaInteropAssembly)">
<ItemGroup>
<!-- Scan Mono.Android and Java.Interop TOGETHER so their cross-assembly aliases (e.g.
java/lang/Object mapped by both Java.Lang.Object and Java.Interop.JavaObject) are
coordinated into a single primary + aliases, instead of colliding as duplicate keys in
the merged Java.Lang.Object universe at runtime. -->
<_PreBuiltTypeMapInput Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.dll" />
<_PreBuiltTypeMapInput Include="$(_MonoAndroidNETDefaultOutDir)Java.Interop.dll" />
<!-- Reference-only framework assemblies needed to resolve base types.
These are NOT scanned for peers; only @(_PreBuiltTypeMapInput) is. -->
<_PreBuiltTypeMapFramework Include="$(_MonoAndroidNETDefaultOutDir)*.dll"
Exclude="@(_PreBuiltTypeMapInput)" />
</ItemGroup>
<MakeDir Directories="$(_PreBuiltTypeMapOutputDir);$(_PreBuiltTypeMapJavaDir)" />
<GenerateTrimmableTypeMap
ResolvedAssemblies="@(_PreBuiltTypeMapInput)"
ResolvedFrameworkAssemblies="@(_PreBuiltTypeMapFramework)"
FrameworkAssemblyNames="@(_PreBuiltTypeMapFramework->'%(Filename)')"
OutputDirectory="$(_PreBuiltTypeMapOutputDir)"
JavaSourceOutputDirectory="$(_PreBuiltTypeMapJavaDir)"
AcwMapOutputFile="$(_PreBuiltTypeMapAcwMap)"
TargetFrameworkVersion="v$(DotNetTargetFrameworkVersion)"
ForceSharedTypemapUniverse="true"
MaxArrayRank="$(_PreBuiltTypeMapMaxArrayRank)"
GenerateRootAssembly="false" />
</Target>

<PropertyGroup>
<_PreBuiltTypeMapJcwJar>$(_PreBuiltTypeMapOutputDir)mono.android-typemaps.jar</_PreBuiltTypeMapJcwJar>
<_PreBuiltTypeMapJcwClassesDir>$(_PreBuiltTypeMapIntDir)jcw-classes\</_PreBuiltTypeMapJcwClassesDir>
<_PreBuiltTypeMapJcwSourceList>$(_PreBuiltTypeMapIntDir)jcw-sources.txt</_PreBuiltTypeMapJcwSourceList>
<_PreBuiltTypeMapAndroidJar>$(AndroidSdkDirectory)\platforms\android-$(AndroidPlatformId)\android.jar</_PreBuiltTypeMapAndroidJar>
<_PreBuiltTypeMapMonoAndroidJar>$(_MonoAndroidNETDefaultOutDir)mono.android.jar</_PreBuiltTypeMapMonoAndroidJar>
<_PreBuiltTypeMapRuntimeJar>$(MicrosoftAndroidSdkOutDir)java_runtime.jar</_PreBuiltTypeMapRuntimeJar>
</PropertyGroup>

<!-- Compile the pre-generated Java Callable Wrappers into a jar shipped in the pack. The app
build adds this jar to its dex inputs instead of re-generating and re-compiling the
Mono.Android JCWs. Classpath mirrors build-tools/scripts/JavaCallableWrappers.targets:
android.jar + mono.android.jar + java_runtime.jar. -->
<Target Name="_CompilePreBuiltMonoAndroidTypeMapJcwJar"
BeforeTargets="ConfigureLocalWorkload;_GenerateXASdkContent"
DependsOnTargets="_GeneratePreBuiltMonoAndroidTypeMap"
Inputs="$(_PreBuiltTypeMapAssembly)"
Outputs="$(_PreBuiltTypeMapJcwJar)">
<ItemGroup>
<_PreBuiltTypeMapJavaFile Include="$(_PreBuiltTypeMapJavaDir)**\*.java" />
</ItemGroup>
<RemoveDir Directories="$(_PreBuiltTypeMapJcwClassesDir)" />
<MakeDir Directories="$(_PreBuiltTypeMapJcwClassesDir)" />
<WriteLinesToFile File="$(_PreBuiltTypeMapJcwSourceList)" Lines="@(_PreBuiltTypeMapJavaFile)" Overwrite="true" />
<Exec Command="&quot;$(JavaCPath)&quot; -source $(JavacSourceVersion) -target $(JavacTargetVersion) -nowarn -d &quot;$(_PreBuiltTypeMapJcwClassesDir.TrimEnd('\'))&quot; -classpath &quot;$(_PreBuiltTypeMapAndroidJar)&quot;$(PathSeparator)&quot;$(_PreBuiltTypeMapMonoAndroidJar)&quot;$(PathSeparator)&quot;$(_PreBuiltTypeMapRuntimeJar)&quot; @&quot;$(_PreBuiltTypeMapJcwSourceList)&quot;" />
<Exec Command="&quot;$(JarPath)&quot; cf &quot;$(_PreBuiltTypeMapJcwJar)&quot; -C &quot;$(_PreBuiltTypeMapJcwClassesDir.TrimEnd('\'))&quot; ." />
</Target>

<Target Name="_AddPreBuiltMonoAndroidTypeMapToPackage"
BeforeTargets="_GenerateXASdkContent"
DependsOnTargets="_GeneratePreBuiltMonoAndroidTypeMap;_CompilePreBuiltMonoAndroidTypeMapJcwJar">
<ItemGroup>
<_PreBuiltTypeMapAllAssemblies Include="$(_PreBuiltTypeMapOutputDir)*.TypeMap.dll" />
<FilesToPackage Include="@(_PreBuiltTypeMapAllAssemblies)" TargetPath="$(_PreBuiltTypeMapPackSubDir)" />
<FilesToPackage Include="$(_PreBuiltTypeMapJcwJar)" TargetPath="$(_PreBuiltTypeMapPackSubDir)" />
<FilesToPackage Include="$(_PreBuiltTypeMapAcwMap)" TargetPath="$(_PreBuiltTypeMapPackSubDir)" />
</ItemGroup>
</Target>

</Project>
2 changes: 2 additions & 0 deletions build-tools/create-packs/Microsoft.Android.Sdk.proj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ core workload SDK packs imported by WorkloadManifest.targets.
<Sdk Name="Microsoft.DotNet.SharedFramework.Sdk" Version="$(MicrosoftDotNetBuildTasksFeedPackageVersionNet10)" />
<UsingTask AssemblyFile="$(BootstrapTasksAssembly)" TaskName="Xamarin.Android.Tools.BootstrapTasks.GenerateUnixFilePermissions" TaskFactory="TaskHostFactory" Runtime="NET" />

<Import Project="Microsoft.Android.Sdk.TrimmableTypeMap.targets" />

<PropertyGroup>
<!-- Use stable TFM since CI only has the stable .NET SDK installed -->
<TargetFramework>$(DotNetStableTargetFramework)</TargetFramework>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

public readonly record struct AssemblyInput (string Name, string Path, PEReader Reader);
/// <param name="ScanForPeers">
/// 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.
/// </param>
public readonly record struct AssemblyInput (string Name, string Path, PEReader Reader, bool ScanForPeers = true);
Original file line number Diff line number Diff line change
Expand Up @@ -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 <c>null</c> for array maps.
/// </param>
public void Generate (IReadOnlyList<string> perAssemblyTypeMapNames, bool useSharedTypemapUniverse, Stream stream, string? assemblyName = null, string? moduleName = null, int maxArrayRank = 0)
/// <param name="sharedFrameworkTypeMapNames">
/// Names of pre-generated framework per-assembly typemaps (e.g. <c>_Mono.Android.TypeMap</c>,
/// produced at SDK build time, issue #10792) that always use <c>Java.Lang.Object</c> as their
/// universe anchor regardless of <paramref name="useSharedTypemapUniverse"/>. They are emitted as
/// <c>[assembly: TypeMapAssemblyTarget&lt;Java.Lang.Object&gt;("name")]</c> so the runtime loads
/// them into the <c>Java.Lang.Object</c> universe: in shared mode they merge into the single
/// app universe; in aggregate mode the loader adds the <c>Java.Lang.Object</c> universe alongside
/// the app's per-assembly universes.
/// </param>
public void Generate (IReadOnlyList<string> perAssemblyTypeMapNames, bool useSharedTypemapUniverse, Stream stream, string? assemblyName = null, string? moduleName = null, int maxArrayRank = 0, IReadOnlyList<string>? sharedFrameworkTypeMapNames = null)
{
if (perAssemblyTypeMapNames is null) {
throw new ArgumentNullException (nameof (perAssemblyTypeMapNames));
Expand Down Expand Up @@ -125,6 +134,19 @@ public void Generate (IReadOnlyList<string> 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).
Expand All @@ -135,7 +157,7 @@ public void Generate (IReadOnlyList<string> 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);
}
Expand Down Expand Up @@ -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<string> perAssemblyTypeMapNames, bool useSharedTypemapUniverse, int maxArrayRank, string assemblyName)
static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle, IReadOnlyList<string> perAssemblyTypeMapNames, bool useSharedTypemapUniverse, int maxArrayRank, string assemblyName, bool hasSharedFrameworkUniverse = false)
{
var metadata = pe.Metadata;

Expand Down Expand Up @@ -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<Java.Lang.Object>()
// 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,
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -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<Java.Lang.Object>()), 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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,25 @@ public List<JavaPeerInfo> Scan (IReadOnlyList<(string Name, PEReader Reader)> as

public List<JavaPeerInfo> Scan (IEnumerable<AssemblyInput> 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<string> (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
// define a type with the same managed name (e.g. Java.Lang.Throwable in both
// 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);
Expand Down
Loading
Loading