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..3a3432bf141
--- /dev/null
+++ b/build-tools/create-packs/Microsoft.Android.Sdk.TrimmableTypeMap.targets
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+ <_PreBuiltTypeMapPackSubDir>data\prebuilt-typemap
+ <_PreBuiltTypeMapOutputDir>$(MicrosoftAndroidSdkPackDir)$(_PreBuiltTypeMapPackSubDir)\
+
+ $(NoWarn);NU5100
+
+ <_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
+ <_PreBuiltTypeMapProperties>$(_PreBuiltTypeMapOutputDir)framework.props
+ <_PreBuiltTypeMapJavaFilesList>$(_PreBuiltTypeMapIntDir)java-files.txt
+ <_PreBuiltTypeMapGenerationStamp>$(_PreBuiltTypeMapIntDir)_GeneratePreBuiltMonoAndroidTypeMap.stamp
+
+
+
+
+ <_PreBuiltTypeMapInput Remove="@(_PreBuiltTypeMapInput)" />
+ <_PreBuiltTypeMapFramework Remove="@(_PreBuiltTypeMapFramework)" />
+ <_PreBuiltTypeMapInput Include="$(_MonoAndroidNETDefaultOutDir)Mono.Android.dll" />
+ <_PreBuiltTypeMapInput Include="$(_MonoAndroidNETDefaultOutDir)Java.Interop.dll" />
+ <_PreBuiltTypeMapFramework Include="$(_MonoAndroidNETDefaultOutDir)*.dll"
+ Exclude="@(_PreBuiltTypeMapInput)" />
+
+
+
+
+
+
+ <_ExpectedPreBuiltTypeMapJavaFile Remove="@(_ExpectedPreBuiltTypeMapJavaFile)" />
+ <_ActualPreBuiltTypeMapJavaFile Remove="@(_ActualPreBuiltTypeMapJavaFile)" />
+ <_MissingPreBuiltTypeMapJavaFile Remove="@(_MissingPreBuiltTypeMapJavaFile)" />
+ <_UnexpectedPreBuiltTypeMapJavaFile Remove="@(_UnexpectedPreBuiltTypeMapJavaFile)" />
+
+
+
+
+
+ <_ActualPreBuiltTypeMapJavaFile Include="$(_PreBuiltTypeMapJavaDir)**\*.java" />
+ <_MissingPreBuiltTypeMapJavaFile Include="@(_ExpectedPreBuiltTypeMapJavaFile)"
+ Condition="!Exists('%(_ExpectedPreBuiltTypeMapJavaFile.Identity)')" />
+ <_UnexpectedPreBuiltTypeMapJavaFile Include="@(_ActualPreBuiltTypeMapJavaFile->'%(FullPath)')"
+ Exclude="@(_ExpectedPreBuiltTypeMapJavaFile)" />
+
+
+
+ <_ExpectedPreBuiltTypeMapJavaFile Remove="@(_ExpectedPreBuiltTypeMapJavaFile)" />
+ <_ActualPreBuiltTypeMapJavaFile Remove="@(_ActualPreBuiltTypeMapJavaFile)" />
+ <_MissingPreBuiltTypeMapJavaFile Remove="@(_MissingPreBuiltTypeMapJavaFile)" />
+ <_UnexpectedPreBuiltTypeMapJavaFile Remove="@(_UnexpectedPreBuiltTypeMapJavaFile)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_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 Remove="@(_PreBuiltTypeMapJavaFile)" />
+
+
+
+
+
+
+
+
+
+
+ <_PreBuiltTypeMapJavaFile>
+ <_JavacResponseFilePath>$([System.String]::Copy('%(Identity)').Replace('\', '\\'))
+
+
+
+
+
+
+
+
+
+ <_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 6f00fb8f438..b1514ce65bd 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.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Microsoft.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
index 9ca1b1e0dd5..8211a2fbb50 100644
--- a/src/Microsoft.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
+++ b/src/Microsoft.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
@@ -131,6 +132,38 @@ public void LogCustomJavaObjectWarning (string managedTypeName) =>
public string? ApplicationJavaClass { get; set; }
public bool GenerateTypeMapAssemblies { get; set; } = true;
public bool IncludeBuiltInValueTypeUniverses { get; set; }
+
+ // When false, the per-assembly typemap DLLs (and JCWs) are emitted but the root
+ // _Microsoft.Android.TypeMaps assembly is not. Used for SDK-build-time pre-generation of
+ // framework typemaps (e.g. Mono.Android, issue #10792); the app build emits the root, which
+ // references the pre-generated per-assembly typemaps alongside the app's own.
+ public bool GenerateRootAssembly { get; set; } = true;
+
+ // Framework assemblies whose typemap is pre-generated at SDK build time (issue #10792), e.g.
+ // Mono.Android. On the app build these are indexed for base-type resolution but NOT scanned for
+ // peers; instead their pre-generated per-assembly typemap (_.TypeMap) is referenced by the
+ // generated root assembly through its private __TypeMapAnchor.
+ public ITaskItem [] PreGeneratedTypeMapAssemblies { get; set; } = [];
+
+ // Assemblies that are indexed for type resolution but are not scanned for Java peers.
+ public ITaskItem [] ReferenceOnlyAssemblies { get; set; } = [];
+
+ // Pre-compiled framework JCWs whose Java names must not collide with app-generated JCWs.
+ public string? PreGeneratedJcwJar { get; set; }
+
+ // ACW map for pre-generated framework peers. Linked builds use this lightweight index to root
+ // framework types referenced only by application manifests or layouts without rescanning them.
+ public string? PreGeneratedFrameworkAcwMap { get; set; }
+
+ // Linker descriptor containing application-specific roots selected from PreGeneratedFrameworkAcwMap.
+ public string? PreGeneratedFrameworkRootDescriptorFile { get; set; }
+
+ ///
+ /// When false, application-specific manifest and layout roots do not mutate framework peers,
+ /// allowing maps generated without app inputs to match the SDK's pre-generated framework maps.
+ ///
+ public bool RootFrameworkPeersFromApplication { get; set; } = true;
+
public bool CleanJavaSourceOutputDirectory { get; set; }
///
@@ -155,11 +188,25 @@ public override bool RunTask ()
var frameworkAssemblyPaths = new HashSet (
ResolvedFrameworkAssemblies.Select (i => Path.GetFullPath (i.ItemSpec)),
StringComparer.OrdinalIgnoreCase);
+ // Assemblies whose typemap is pre-generated: index them for resolution but don't scan for
+ // peers, and reference their pre-generated _.TypeMap through its private anchor.
+ var preGeneratedAssemblyPaths = new HashSet (
+ PreGeneratedTypeMapAssemblies.Select (i => Path.GetFullPath (i.ItemSpec)),
+ StringComparer.OrdinalIgnoreCase);
+ var referenceOnlyAssemblyPaths = new HashSet (
+ ReferenceOnlyAssemblies.Select (i => Path.GetFullPath (i.ItemSpec)),
+ StringComparer.OrdinalIgnoreCase);
+ referenceOnlyAssemblyPaths.UnionWith (preGeneratedAssemblyPaths);
+ var preGeneratedTypeMapNames = PreGeneratedTypeMapAssemblies
+ .Select (i => $"_{Path.GetFileNameWithoutExtension (i.ItemSpec)}.TypeMap")
+ .Distinct (StringComparer.Ordinal)
+ .ToList ();
var assemblyInputs = ResolvedAssemblies
.GroupBy (i => Path.GetFullPath (i.ItemSpec), StringComparer.OrdinalIgnoreCase)
.Select (g => (
Path: g.Key,
- IsFrameworkAssembly: frameworkAssemblyPaths.Contains (g.Key) || g.Any (IsFrameworkAssemblyItem)))
+ IsFrameworkAssembly: frameworkAssemblyPaths.Contains (g.Key) || g.Any (IsFrameworkAssemblyItem),
+ ScanForPeers: !referenceOnlyAssemblyPaths.Contains (g.Key)))
.ToList ();
var frameworkAssemblyNames = new HashSet (DefaultFrameworkAssemblyNames, StringComparer.OrdinalIgnoreCase);
foreach (var assemblyName in FrameworkAssemblyNames) {
@@ -197,7 +244,7 @@ public override bool RunTask ()
var priorTypeMapFingerprints = ReadTypeMapFingerprints ();
TrimmableTypeMapResult? result = null;
try {
- foreach (var (path, isFrameworkAssembly) in assemblyInputs) {
+ foreach (var (path, isFrameworkAssembly, scanForPeers) in assemblyInputs) {
var peReader = new PEReader (File.OpenRead (path));
peReaders.Add (peReader);
if (!peReader.HasMetadata) {
@@ -208,7 +255,7 @@ public override bool RunTask ()
continue;
}
var assemblyName = mdReader.GetString (mdReader.GetAssemblyDefinition ().Name);
- assemblies.Add (new AssemblyInput (assemblyName, path, peReader));
+ assemblies.Add (new AssemblyInput (assemblyName, path, peReader, scanForPeers));
if (isFrameworkAssembly) {
frameworkAssemblyNames.Add (assemblyName);
}
@@ -241,6 +288,8 @@ public override bool RunTask ()
IReadOnlyCollection? customViewTypeNames = CustomViewMapFile.IsNullOrEmpty ()
? null
: LoadCustomViewTypeNames (CustomViewMapFile);
+ IReadOnlyCollection? preGeneratedJcwNames = LoadPreGeneratedJcwNames ();
+ IReadOnlyList? preGeneratedFrameworkPeers = LoadPreGeneratedFrameworkPeers ();
result = generator.Execute (
assemblies,
@@ -251,8 +300,14 @@ public override bool RunTask ()
manifestTemplate: manifestTemplate,
packageNamingPolicy: PackageNamingPolicy,
generateTypeMapAssemblies: GenerateTypeMapAssemblies,
+ generateRootAssembly: GenerateRootAssembly,
+ preGeneratedTypeMapNames: preGeneratedTypeMapNames,
+ preGeneratedFrameworkPeers: preGeneratedFrameworkPeers,
errorOnCustomJavaObject: ErrorOnCustomJavaObject,
customViewTypeNames: customViewTypeNames,
+ preGeneratedJcwNames: preGeneratedJcwNames,
+ preGeneratedJcwSource: PreGeneratedJcwJar,
+ rootFrameworkPeersFromApplication: RootFrameworkPeersFromApplication,
collectMarshalMethodsForNonAcw: false,
includeBuiltInValueTypeUniverses: IncludeBuiltInValueTypeUniverses,
shouldGenerateTypeMapAssembly: TypeMapFingerprintsFile.IsNullOrEmpty () ? null : ShouldGenerateTypeMapAssembly);
@@ -311,6 +366,8 @@ public override bool RunTask ()
Files.CopyIfStringChanged (GenerateApplicationRegistrationJava (result.ApplicationRegistrationTypes), ApplicationRegistrationOutputFile);
Log.LogDebugMessage ($"Generated ApplicationRegistration.java with {result.ApplicationRegistrationTypes.Count} deferred registration(s).");
}
+
+ WritePreGeneratedFrameworkRootDescriptor (preGeneratedFrameworkPeers);
} finally {
if (result is not null) {
foreach (var assembly in result.GeneratedAssemblies) {
@@ -561,4 +618,99 @@ static string GenerateApplicationRegistrationJava (IReadOnlyList registr
sb.AppendLine ("}");
return sb.ToString ();
}
+
+ IReadOnlyCollection? LoadPreGeneratedJcwNames ()
+ {
+ if (PreGeneratedTypeMapAssemblies.Length == 0 || PreGeneratedJcwJar.IsNullOrEmpty ()) {
+ return null;
+ }
+
+ var names = new HashSet (StringComparer.Ordinal);
+ using var stream = File.OpenRead (PreGeneratedJcwJar);
+ using var jar = new ZipArchive (stream, ZipArchiveMode.Read);
+ foreach (var entry in jar.Entries) {
+ if (entry.Name.Length > 0 && entry.FullName.EndsWith (".class", StringComparison.Ordinal)) {
+ names.Add (entry.FullName.Substring (0, entry.FullName.Length - ".class".Length));
+ }
+ }
+ return names;
+ }
+
+ IReadOnlyList? LoadPreGeneratedFrameworkPeers ()
+ {
+ if (!RootFrameworkPeersFromApplication || PreGeneratedFrameworkAcwMap.IsNullOrEmpty ()) {
+ return null;
+ }
+
+ var peers = new List ();
+ int lineNumber = 0;
+ foreach (var line in File.ReadLines (PreGeneratedFrameworkAcwMap)) {
+ lineNumber++;
+ if (line.Length == 0) {
+ continue;
+ }
+ int separator = line.IndexOf (';');
+ if (separator <= 0 || separator == line.Length - 1) {
+ throw new InvalidDataException ($"Invalid pre-generated framework ACW map entry in '{PreGeneratedFrameworkAcwMap}' at line {lineNumber}: '{line}'.");
+ }
+
+ string managedName = line.Substring (0, separator);
+ int assemblySeparator = managedName.LastIndexOf (", ", StringComparison.Ordinal);
+ // Each type also has managed-name and Java-name alias lines without an assembly name.
+ if (assemblySeparator <= 0) {
+ continue;
+ }
+
+ string managedTypeName = managedName.Substring (0, assemblySeparator);
+ string assemblyName = managedName.Substring (assemblySeparator + 2);
+ string javaName = line.Substring (separator + 1).Replace ('.', '/');
+ int namespaceSeparator = managedTypeName.LastIndexOf ('.');
+ peers.Add (new JavaPeerInfo {
+ JavaName = javaName,
+ // The ACW map does not carry a distinct compatibility JNI name. Framework manifest
+ // and layout references use the actual Java binary name represented here.
+ CompatJniName = javaName,
+ ManagedTypeName = managedTypeName,
+ ManagedTypeNamespace = namespaceSeparator < 0 ? "" : managedTypeName.Substring (0, namespaceSeparator),
+ ManagedTypeShortName = managedTypeName.Substring (namespaceSeparator + 1),
+ AssemblyName = assemblyName,
+ IsFrameworkAssembly = true,
+ DoNotGenerateAcw = true,
+ });
+ }
+ return peers;
+ }
+
+ void WritePreGeneratedFrameworkRootDescriptor (IReadOnlyList? frameworkPeers)
+ {
+ if (PreGeneratedFrameworkRootDescriptorFile.IsNullOrEmpty ()) {
+ return;
+ }
+
+ var roots = frameworkPeers?
+ .Where (peer => peer.IsUnconditional)
+ .Select (peer => (peer.AssemblyName, peer.ManagedTypeName))
+ .Distinct ()
+ .OrderBy (root => root.AssemblyName, StringComparer.Ordinal)
+ .ThenBy (root => root.ManagedTypeName, StringComparer.Ordinal)
+ .ToList () ?? [];
+ var directory = Path.GetDirectoryName (PreGeneratedFrameworkRootDescriptorFile);
+ if (!directory.IsNullOrEmpty ()) {
+ Directory.CreateDirectory (directory);
+ }
+
+ var document = new XDocument (
+ new XElement ("linker",
+ roots.GroupBy (root => root.AssemblyName, StringComparer.Ordinal)
+ .Select (assembly => new XElement ("assembly",
+ new XAttribute ("fullname", assembly.Key),
+ assembly.Select (root => new XElement ("type",
+ new XAttribute ("fullname", root.ManagedTypeName.Replace ('+', '/')),
+ new XAttribute ("preserve", "nothing")))))));
+ using var stream = new MemoryStream ();
+ document.Save (stream);
+ stream.Position = 0;
+ Files.CopyIfStreamChanged (stream, PreGeneratedFrameworkRootDescriptorFile);
+ Log.LogDebugMessage ($"Generated linker roots for {roots.Count} pre-generated framework type(s).");
+ }
}
diff --git a/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateTrimmableTypeMapTests.cs b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateTrimmableTypeMapTests.cs
index 283dc2cf301..84c09248b5e 100644
--- a/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateTrimmableTypeMapTests.cs
+++ b/src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/GenerateTrimmableTypeMapTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using Microsoft.Android.Tasks;
using Microsoft.Build.Framework;
@@ -198,6 +199,88 @@ public void ReadTypeMapFingerprints_InvalidCache_Regenerates ()
Assert.IsEmpty (task.ReadTypeMapFingerprints (), "An invalid incremental cache should regenerate every typemap assembly.");
}
+ [Test]
+ public void Execute_SwitchToPreGeneratedFrameworkTypeMap_PreservesRoot ()
+ {
+ var path = Path.Combine ("temp", TestName);
+ var outputDir = Path.Combine (Root, path, "typemap");
+ var javaDir = Path.Combine (Root, path, "java");
+
+ var monoAndroidItem = FindMonoAndroidDll ();
+ if (monoAndroidItem is null) {
+ Assert.Ignore ("Mono.Android.dll not found; skipping.");
+ return;
+ }
+
+ var assemblies = new [] { monoAndroidItem };
+ var firstTask = CreateTask (assemblies, outputDir, javaDir);
+ firstTask.Debug = true;
+ Assert.IsTrue (firstTask.Execute (), "Initial local typemap generation should succeed.");
+
+ var rootPath = Path.Combine (outputDir, "_Microsoft.Android.TypeMaps.dll");
+ var initialRoot = File.ReadAllBytes (rootPath);
+
+ var secondTask = CreateTask (assemblies, outputDir, javaDir);
+ secondTask.Debug = true;
+ secondTask.PreGeneratedTypeMapAssemblies = assemblies;
+ Assert.IsTrue (secondTask.Execute (), "Switching to the pre-generated framework typemap should succeed.");
+
+ var preGeneratedRoot = File.ReadAllBytes (rootPath);
+ CollectionAssert.AreEqual (
+ initialRoot,
+ preGeneratedRoot,
+ "The root should reference a per-assembly map identically whether that map was generated locally or pre-generated.");
+ CollectionAssert.AreEqual (
+ new [] { rootPath },
+ secondTask.GeneratedAssemblies.Select (item => item.ItemSpec).ToArray (),
+ "Only the existing root should be reported when every scanned assembly uses a pre-generated typemap.");
+ }
+
+ [Test]
+ public void Execute_ReadsPreGeneratedJcwNamesFromStandardZipArchive ()
+ {
+ var path = Path.Combine (Root, "temp", TestName);
+ var outputDir = Path.Combine (path, "typemap");
+ var javaDir = Path.Combine (path, "java");
+ var jcwJar = Path.Combine (path, "framework-jcws.jar");
+ Directory.CreateDirectory (path);
+ using (var stream = File.Create (jcwJar))
+ using (var archive = new ZipArchive (stream, ZipArchiveMode.Create)) {
+ archive.CreateEntry ("android/app/Activity.class");
+ archive.CreateEntry ("META-INF/");
+ }
+
+ var task = CreateTask ([], outputDir, javaDir);
+ task.Debug = true;
+ task.PreGeneratedTypeMapAssemblies = [new TaskItem ("Mono.Android.dll")];
+ task.PreGeneratedJcwJar = jcwJar;
+
+ Assert.IsTrue (task.Execute (), "The task should read pre-generated JCW names without external ZIP dependencies.");
+ CollectionAssert.AreEqual (
+ new [] { Path.Combine (outputDir, "_Microsoft.Android.TypeMaps.dll") },
+ task.GeneratedAssemblies.Select (item => item.ItemSpec).ToArray ());
+ }
+
+ [Test]
+ public void Execute_InvalidPreGeneratedFrameworkAcwMap_ReportsFileAndLine ()
+ {
+ var path = Path.Combine ("temp", TestName);
+ var outputDir = Path.Combine (Root, path, "typemap");
+ var javaDir = Path.Combine (Root, path, "java");
+ var acwMap = Path.Combine (Root, path, "framework-acw-map.txt");
+ Directory.CreateDirectory (Path.GetDirectoryName (acwMap));
+ File.WriteAllText (acwMap, $"{Environment.NewLine}invalid");
+
+ var errors = new List ();
+ var task = CreateTask ([], outputDir, javaDir, errors: errors);
+ task.PreGeneratedFrameworkAcwMap = acwMap;
+
+ Assert.IsFalse (task.Execute (), "A malformed pre-generated framework ACW map should fail the task.");
+ Assert.IsTrue (
+ errors.Any (error => error.Message?.Contains ($"'{acwMap}' at line 2: 'invalid'", StringComparison.Ordinal) == true),
+ "The error should identify the malformed ACW map and line number.");
+ }
+
[Test]
public void Execute_MissingJavaSource_DoesNotPruneExistingOutput ()
{
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/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
index 428b3e989e4..259976f734a 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -252,6 +252,9 @@ 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);
if (assemblyCache.TryGetValue (index.AssemblyName, out var existingIndex)) {
@@ -278,6 +281,9 @@ public List Scan (IEnumerable assemblies)
assemblyCache [index.AssemblyName] = index;
resolvabilityCache [index.AssemblyName] = new ResolvabilityResult? [index.Reader.TypeDefinitions.Count + 1];
AnyUnmanagedCallersOnlyAssembly |= index.UsesUnmanagedCallersOnlyCallbacks;
+ if (!assembly.ScanForPeers) {
+ referenceOnlyAssemblyNames.Add (index.AssemblyName);
+ }
}
// Key by (managedTypeName, assemblyName) to avoid collisions when two assemblies
@@ -285,6 +291,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 2a7309ed4df..c964860a7a3 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
@@ -45,11 +45,17 @@ public TrimmableTypeMapResult Execute (
XDocument? manifestTemplate = null,
string? packageNamingPolicy = null,
bool generateTypeMapAssemblies = true,
+ bool generateRootAssembly = true,
bool errorOnCustomJavaObject = true,
+ IReadOnlyList? preGeneratedTypeMapNames = null,
IReadOnlyCollection? customViewTypeNames = null,
+ IReadOnlyCollection? preGeneratedJcwNames = null,
+ string? preGeneratedJcwSource = null,
+ bool rootFrameworkPeersFromApplication = true,
bool collectMarshalMethodsForNonAcw = true,
bool includeBuiltInValueTypeUniverses = false,
- Func? shouldGenerateTypeMapAssembly = null)
+ Func? shouldGenerateTypeMapAssembly = null,
+ IReadOnlyList? preGeneratedFrameworkPeers = null)
{
_ = assemblies ?? throw new ArgumentNullException (nameof (assemblies));
_ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
@@ -60,32 +66,45 @@ public TrimmableTypeMapResult Execute (
frameworkAssemblyNames,
errorOnCustomJavaObject,
collectMarshalMethodsForNonAcw);
- var manifestForRooting = PrepareManifestForRooting (manifestTemplate, manifestConfig);
- if (allPeers.Count == 0) {
- if (ValidateJavaNames (allPeers, manifestConfig?.ApplicationJavaClass, manifestForRooting)) {
- logger.LogNoJavaPeerTypesFound ();
- }
- return new TrimmableTypeMapResult ([], [], allPeers);
- }
+ bool hasPeers = allPeers.Count > 0;
MarkFrameworkAssemblyPeers (allPeers, frameworkAssemblyNames);
bool validConstructors = ValidateConstructors (allPeers);
+ var manifestForRooting = PrepareManifestForRooting (manifestTemplate, manifestConfig);
- RootCustomViewTypes (allPeers, customViewTypeNames);
- RootManifestReferencedTypes (allPeers, manifestForRooting, manifestConfig?.ApplicationJavaClass);
+ var peersForApplicationRooting = allPeers;
+ if (preGeneratedFrameworkPeers is { Count: > 0 }) {
+ peersForApplicationRooting = new List (allPeers.Count + preGeneratedFrameworkPeers.Count);
+ peersForApplicationRooting.AddRange (allPeers);
+ peersForApplicationRooting.AddRange (preGeneratedFrameworkPeers);
+ }
+ RootCustomViewTypes (peersForApplicationRooting, customViewTypeNames, rootFrameworkPeersFromApplication);
+ RootManifestReferencedTypes (
+ peersForApplicationRooting,
+ manifestForRooting,
+ manifestConfig?.ApplicationJavaClass,
+ rootFrameworkPeersFromApplication);
PropagateDeferredRegistrationToBaseClasses (allPeers);
PropagateCannotRegisterToDescendants (allPeers);
- bool validJavaNames = ValidateJavaNames (allPeers, manifestConfig?.ApplicationJavaClass, manifestForRooting);
+ bool validJavaNames = ValidateJavaNames (allPeers, manifestConfig?.ApplicationJavaClass, manifestForRooting, preGeneratedJcwNames, preGeneratedJcwSource);
if (!validConstructors || !validJavaNames) {
return new TrimmableTypeMapResult ([], [], allPeers);
}
+ if (!hasPeers) {
+ logger.LogNoJavaPeerTypesFound ();
+ }
- var generatedAssemblies = generateTypeMapAssemblies
+ bool needsRootForPreGeneratedTypeMaps =
+ generateRootAssembly &&
+ preGeneratedTypeMapNames is { Count: > 0 };
+ var generatedAssemblies = generateTypeMapAssemblies && (hasPeers || needsRootForPreGeneratedTypeMaps)
? GenerateTypeMapAssemblies (
allPeers,
systemRuntimeVersion,
useSharedTypemapUniverse,
shouldGenerateTypeMapAssembly,
- includeBuiltInValueTypeUniverses)
+ includeBuiltInValueTypeUniverses,
+ generateRootAssembly,
+ preGeneratedTypeMapNames)
: [];
var jcwPeers = allPeers.Where (ShouldGenerateJcw).ToList ();
logger.LogGeneratingJcwFilesInfo (jcwPeers.Count, allPeers.Count);
@@ -103,7 +122,10 @@ public TrimmableTypeMapResult Execute (
return new TrimmableTypeMapResult (generatedAssemblies, generatedJavaSources, allPeers, manifest, appRegTypes);
}
- internal static void RootCustomViewTypes (List allPeers, IReadOnlyCollection? customViewTypeNames)
+ internal static void RootCustomViewTypes (
+ List allPeers,
+ IReadOnlyCollection? customViewTypeNames,
+ bool rootFrameworkPeers = true)
{
if (customViewTypeNames is null || customViewTypeNames.Count == 0) {
return;
@@ -111,6 +133,9 @@ internal static void RootCustomViewTypes (List allPeers, IReadOnly
var names = new HashSet (customViewTypeNames, StringComparer.Ordinal);
foreach (var peer in allPeers) {
+ if (!rootFrameworkPeers && peer.IsFrameworkAssembly) {
+ continue;
+ }
if (names.Contains (peer.ManagedTypeName) ||
names.Contains (JniSignatureHelper.JniNameToJavaBinaryName (peer.JavaName)) ||
names.Contains (JniSignatureHelper.JniNameToJavaBinaryName (peer.CompatJniName))) {
@@ -152,7 +177,9 @@ internal bool ValidateConstructors (IReadOnlyList peers)
internal bool ValidateJavaNames (
IReadOnlyList peers,
string? applicationJavaClass = null,
- XDocument? manifest = null)
+ XDocument? manifest = null,
+ IReadOnlyCollection? preGeneratedJcwNames = null,
+ string? preGeneratedJcwSource = null)
{
bool valid = true;
var reportedNames = new HashSet (StringComparer.Ordinal);
@@ -161,7 +188,10 @@ internal bool ValidateJavaNames (
.GroupBy (peer => peer.JavaName, StringComparer.Ordinal)
.OrderBy (group => group.Key, StringComparer.Ordinal)) {
var firstAssemblyName = group.First ().AssemblyName;
- if (group.All (peer => string.Equals (peer.AssemblyName, firstAssemblyName, StringComparison.Ordinal))) {
+ bool generatedByMultipleAssemblies = !group.All (
+ peer => string.Equals (peer.AssemblyName, firstAssemblyName, StringComparison.Ordinal));
+ bool conflictsWithPreGeneratedJcw = preGeneratedJcwNames?.Contains (group.Key) == true;
+ if (!generatedByMultipleAssemblies && !conflictsWithPreGeneratedJcw) {
continue;
}
@@ -172,6 +202,9 @@ internal bool ValidateJavaNames (
.ThenBy (peer => peer.AssemblyName, StringComparer.Ordinal)) {
logger.LogDuplicateJavaTypeDetailsError (javaName, $"{peer.ManagedTypeName}, {peer.AssemblyName}");
}
+ if (conflictsWithPreGeneratedJcw && preGeneratedJcwSource is { Length: > 0 }) {
+ logger.LogDuplicateJavaTypeDetailsError (javaName, preGeneratedJcwSource);
+ }
valid = false;
}
if (applicationJavaClass is not null &&
@@ -413,8 +446,13 @@ internal List GenerateTypeMapAssemblies (
Version systemRuntimeVersion,
bool useSharedTypemapUniverse,
Func? shouldGenerateTypeMapAssembly = null,
- bool includeBuiltInValueTypeUniverses = false)
+ bool includeBuiltInValueTypeUniverses = false,
+ bool generateRootAssembly = true,
+ IReadOnlyList? preGeneratedTypeMapNames = null)
{
+ if (useSharedTypemapUniverse && preGeneratedTypeMapNames is { Count: > 0 }) {
+ throw new ArgumentException ("Pre-generated per-assembly type maps cannot be used in a shared universe.", nameof (preGeneratedTypeMapNames));
+ }
List<(string AssemblyName, List Peers)> peersByAssembly;
if (useSharedTypemapUniverse) {
@@ -456,24 +494,37 @@ internal List GenerateTypeMapAssemblies (
generatedAssemblies.Add (new GeneratedAssembly (typeMapAssemblyName, stream));
logger.LogGeneratedTypeMapAssemblyInfo (typeMapAssemblyName, peers.Count);
}
- const string rootAssemblyName = "_Microsoft.Android.TypeMaps";
- bool generateRoot = true;
- if (shouldGenerateTypeMapAssembly is not null) {
- var rootFingerprint = MetadataHelper.ComputeRootIncrementalFingerprint (
- perAssemblyNames,
- systemRuntimeVersion,
- useSharedTypemapUniverse,
- includeBuiltInValueTypeUniverses);
- generateRoot = shouldGenerateTypeMapAssembly (rootAssemblyName, rootFingerprint);
- }
- if (generateRoot) {
- var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
- var rootStream = rootGenerator.GenerateToStream (
- perAssemblyNames,
- useSharedTypemapUniverse,
- includeBuiltInValueTypeUniverses);
- generatedAssemblies.Add (new GeneratedAssembly (rootAssemblyName, 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 rootTypeMapNames = new List (perAssemblyNames);
+ if (preGeneratedTypeMapNames is not null) {
+ rootTypeMapNames.AddRange (preGeneratedTypeMapNames);
+ }
+ rootTypeMapNames.Sort (StringComparer.Ordinal);
+ const string rootAssemblyName = "_Microsoft.Android.TypeMaps";
+ bool generateRoot = true;
+ if (shouldGenerateTypeMapAssembly is not null) {
+ var rootFingerprint = MetadataHelper.ComputeRootIncrementalFingerprint (
+ rootTypeMapNames,
+ systemRuntimeVersion,
+ useSharedTypemapUniverse,
+ includeBuiltInValueTypeUniverses);
+ generateRoot = shouldGenerateTypeMapAssembly (rootAssemblyName, rootFingerprint);
+ }
+ if (generateRoot) {
+ var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
+ var rootStream = rootGenerator.GenerateToStream (
+ rootTypeMapNames,
+ useSharedTypemapUniverse,
+ includeBuiltInValueTypeUniverses);
+ generatedAssemblies.Add (new GeneratedAssembly (rootAssemblyName, rootStream));
+ logger.LogGeneratedRootTypeMapInfo (rootTypeMapNames.Count);
+ }
}
logger.LogGeneratedTypeMapAssembliesInfo (generatedAssemblies.Count);
return generatedAssemblies;
@@ -564,7 +615,11 @@ List GenerateJcwJavaSources (List allPeers, s
return sources.ToList ();
}
- internal void RootManifestReferencedTypes (List allPeers, XDocument? doc, string? applicationJavaClass = null)
+ internal void RootManifestReferencedTypes (
+ List allPeers,
+ XDocument? doc,
+ string? applicationJavaClass = null,
+ bool rootFrameworkPeers = true)
{
if (doc?.Root is not { } root) {
return;
@@ -620,6 +675,9 @@ internal void RootManifestReferencedTypes (List allPeers, XDocumen
foreach (var name in componentNames) {
if (peersByDotName.TryGetValue (name, out var peers)) {
foreach (var peer in peers) {
+ if (!rootFrameworkPeers && peer.IsFrameworkAssembly) {
+ continue;
+ }
if (deferredRegistrationNames.Contains (name)) {
peer.CannotRegisterInStaticConstructor = true;
}
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 d10125e8890..db2ed8f5a2e 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
@@ -221,6 +221,7 @@ _ResolveAssemblies MSBuild target.
;_OuterOutputPath=$(OutputPath)
;_OuterIntermediateOutputPath=$(IntermediateOutputPath)
;_OuterCustomViewMapFile=$(_CustomViewMapFile)
+ ;_OuterPreGeneratedFrameworkTypeMapRootDescriptor=$(_PreGeneratedFrameworkTypeMapRootDescriptor)
;_AndroidNdkDirectory=$(_AndroidNdkDirectory)
<_AndroidBuildRuntimeIdentifiersInParallel Condition=" '$(_AndroidBuildRuntimeIdentifiersInParallel)' == '' ">true
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 c10ae389cea..b0c4f409101 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
@@ -202,6 +202,10 @@
out-of-date and run it on every build. -->
<_PostTrimTrimmableTypeMapInputAssemblies Include="@(ResolvedFileToPublish)"
Condition=" '%(Extension)' == '.dll' and Exists('%(FullPath)') and ('%(RuntimeIdentifier)' == '' or '$(_PostTrimTypeMapFirstRuntimeIdentifier)' == '' or '%(RuntimeIdentifier)' == '$(_PostTrimTypeMapFirstRuntimeIdentifier)') " />
+ <_PostTrimPreGeneratedTypeMapAssemblyInput Condition=" '$(_AndroidPreGeneratedTypeMapEnabled)' == 'true' "
+ Include="@(_PostTrimTrimmableTypeMapInputAssemblies)" KeepDuplicates="false" />
+ <_PostTrimPreGeneratedTypeMapAssemblyInput Remove="@(_PostTrimPreGeneratedTypeMapAssemblyInput)"
+ Condition=" '%(Filename)' != 'Mono.Android' and '%(Filename)' != 'Java.Interop' " />
@@ -243,11 +247,13 @@
DependsOnTargets="_ComputePostTrimTrimmableTypeMapInputs;_InvalidatePostTrimTrimmableTypeMapStampIfOutputsMissing"
AfterTargets="_ResolveAssemblies"
BeforeTargets="_GenerateJavaStubs;_CompileJava;_CompileToDalvik"
- Inputs="@(_PostTrimTrimmableTypeMapInputAssemblies);$(_ResolvedUserAssembliesHashFile);$(_AndroidBuildPropertiesCache);@(_AndroidMSBuildAllProjects)"
+ Inputs="@(_PostTrimTrimmableTypeMapInputAssemblies);$(_ResolvedUserAssembliesHashFile);$(_AndroidBuildPropertiesCache);@(_AndroidMSBuildAllProjects);$(_PreGeneratedJcwValidationInput)"
Outputs="$(_PostTrimTrimmableTypeMapJavaStamp)">
<_PostTrimTrimmableTypeMapInputAssemblies Remove="@(_PostTrimTrimmableTypeMapInputAssemblies)" />
+ <_PostTrimPreGeneratedTypeMapAssemblyInput Remove="@(_PostTrimPreGeneratedTypeMapAssemblyInput)" />
<_PostTrimGeneratedJavaFiles Remove="@(_PostTrimGeneratedJavaFiles)" />
<_PostTrimDeletedJavaFiles Remove="@(_PostTrimDeletedJavaFiles)" />
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 1117cd1dcc9..877d055fba6 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
@@ -84,6 +84,51 @@
<_AdditionalJavaStubDirectory Include="$([MSBuild]::EnsureTrailingSlash('$(_TypeMapJavaStubsSourceDirectory)'))" />
+
+
+ <_PreGeneratedTypeMapDir>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)..\data\prebuilt-typemap'))
+ <_PreGeneratedMonoAndroidTypeMapAssembly>$(_PreGeneratedTypeMapDir)_Mono.Android.TypeMap.dll
+ <_PreGeneratedJavaInteropTypeMapAssembly>$(_PreGeneratedTypeMapDir)_Java.Interop.TypeMap.dll
+ <_PreGeneratedMonoAndroidTypeMapJar>$(_PreGeneratedTypeMapDir)mono.android-typemaps.jar
+ <_PreGeneratedMonoAndroidTypeMapAcwMap>$(_PreGeneratedTypeMapDir)mono.android-acw-map.txt
+
+
+
+
+ <_AndroidPreGeneratedTypeMapMatchesApi>false
+ <_AndroidPreGeneratedTypeMapMatchesApi Condition=" '$(_AndroidPreGeneratedTypeMapApiLevel)' != '' and '$(TargetPlatformVersion)' != '' ">$([MSBuild]::VersionEquals('$(_AndroidPreGeneratedTypeMapApiLevel)', '$(TargetPlatformVersion)'))
+ <_AndroidPreGeneratedTypeMapEnabled>false
+ <_AndroidPreGeneratedTypeMapEnabled Condition=" '$(_AndroidUsePreGeneratedMonoAndroidTypeMap)' != 'false' and '$(AndroidIncludeDebugSymbols)' == 'true' and '$(_AndroidPreGeneratedTypeMapMatchesApi)' == 'true' and Exists('$(_PreGeneratedMonoAndroidTypeMapAssembly)') and Exists('$(_PreGeneratedJavaInteropTypeMapAssembly)') and Exists('$(_PreGeneratedMonoAndroidTypeMapJar)') and Exists('$(_PreGeneratedMonoAndroidTypeMapAcwMap)') ">true
+ <_PreGeneratedJcwValidationInput Condition=" '$(_AndroidPreGeneratedTypeMapEnabled)' == 'true' ">$(_PreGeneratedMonoAndroidTypeMapJar)
+ <_RootFrameworkTypeMapPeersFromApplication Condition=" '$(AndroidLinkMode)' != 'None' or '$(PublishTrimmed)' == 'true' or '$(PublishAot)' == 'true' ">true
+ <_RootFrameworkTypeMapPeersFromApplication Condition=" '$(_RootFrameworkTypeMapPeersFromApplication)' == '' ">false
+ <_CompileToDalvikDependsOnTargets>$(_CompileToDalvikDependsOnTargets);_ResolveTrimmableTypeMapKeepRulesAcwMap;_MergePreGeneratedMonoAndroidAcwMapForKeepRules
+
+
+
+
+ <_PreGeneratedFrameworkTypeMapRootDescriptor
+ Condition=" '$(_AndroidPreGeneratedTypeMapEnabled)' == 'true' and '$(_RootFrameworkTypeMapPeersFromApplication)' == 'true' ">$(IntermediateOutputPath)prebuilt-typemap-roots.xml
+ <_PreGeneratedFrameworkTypeMapAcwInput
+ Condition=" '$(_PreGeneratedFrameworkTypeMapRootDescriptor)' != '' ">$(_PreGeneratedMonoAndroidTypeMapAcwMap)
+
+
+
+
+
+
@@ -99,6 +144,8 @@
Condition=" '$(_AndroidRuntime)' == 'CoreCLR' " />
+
<_PropertyCacheItems Include="TrimmableTypeMapRuntimeIdentifiers=$(_TrimmableTypeMapEffectiveRuntimeIdentifiers)" />
+ <_PropertyCacheItems Include="UsePreGeneratedFrameworkTypeMaps=$(_AndroidPreGeneratedTypeMapEnabled)" />
@@ -195,8 +243,8 @@
@@ -205,9 +253,10 @@
+
<_TypeMapInputAssemblies Include="@(ReferencePath)" />
- <_TypeMapInputAssemblies Include="@(ResolvedAssemblies)" />
- <_TypeMapInputAssemblies Include="@(ResolvedFrameworkAssemblies)" />
<_TypeMapInputAssemblies Include="@(PrivateSdkAssemblies)" />
<_TypeMapInputAssemblies Include="@(FrameworkAssemblies)" />
+ <_PreGeneratedTypeMapAssemblyInput Condition=" '$(_AndroidPreGeneratedTypeMapEnabled)' == 'true' "
+ Include="@(_TypeMapInputAssemblies)" KeepDuplicates="false" />
+ <_PreGeneratedTypeMapAssemblyInput Remove="@(_PreGeneratedTypeMapAssemblyInput)"
+ Condition=" '%(Filename)' != 'Mono.Android' and '%(Filename)' != 'Java.Interop' " />
+
+
@@ -310,6 +373,51 @@
+
+
+ <_GeneratedTypeMapAssembliesFromList Include="$(_PreGeneratedTypeMapDir)*.TypeMap.dll" />
+
+
+
+
+
+
+
+
+
+
+ <_TrimmableTypeMapKeepRulesAcwMapFile>$(_AcwMapFile)
+ <_TrimmableTypeMapKeepRulesAcwMapFile Condition=" '$(_AndroidPreGeneratedTypeMapEnabled)' == 'true' ">$(IntermediateOutputPath)acw-map.prebuilt-merged.txt
+
+
+
+
+
+
+ <_MergedKeepRulesAcwMapLines Remove="@(_MergedKeepRulesAcwMapLines)" />
+
+
+
+
+
+
+
+
+
+
+
+ Condition=" '$(_OuterIntermediateOutputPath)' == '' and Exists('$(_TypeMapAssembliesListFile)') "
+ DependsOnTargets="_ResolveTrimmableTypeMapKeepRulesAcwMap;_ResolvePreGeneratedFrameworkTypeMapRoots">
<_GeneratedTypeMapAssembliesForFileWrites Remove="@(_GeneratedTypeMapAssembliesForFileWrites)" />
<_TypeMapJavaFilesForFileWrites Remove="@(_TypeMapJavaFilesForFileWrites)" />
@@ -364,7 +473,9 @@
-
+
+
+
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 9b6dfff193f..40bd477eb03 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
@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
using System.Security;
using System.Security.Cryptography;
using System.Text;
@@ -115,13 +117,240 @@ public void Build_WithTrimmableTypeMap_Succeeds (
Assert.IsTrue (builder.Build (proj, parameters: parameters), "Build should use the trimmable type map regardless of the obsolete property.");
var intermediateDir = builder.Output.GetIntermediaryPath ("typemap");
- AssertTrimmableTypeMapOutputs (intermediateDir);
+ AssertTrimmableTypeMapOutputs (intermediateDir, usePreGeneratedFrameworkTypeMaps: !isRelease);
if (!isRelease) {
- var frameworkJcw = Path.Combine (intermediateDir, "java", "android", "app", "ActivityTracker.java");
- FileAssert.Exists (frameworkJcw);
- var source = File.ReadAllText (frameworkJcw);
- StringAssert.Contains ("mono.android.Runtime.registerNatives (ActivityTracker.class)", source);
- StringAssert.DoesNotContain ("mono.android.TypeManager.Activate", source);
+ var dexFile = builder.Output.GetIntermediaryPath (Path.Combine ("android", "bin", "classes.dex"));
+ FileAssert.Exists (dexFile);
+ Assert.IsTrue (
+ DexUtils.ContainsClassWithMethod ("Lmono/android/view/View_OnClickListenerImplementor;", "", "()V", dexFile, AndroidSdkPath),
+ $"`{dexFile}` should include the pre-generated framework listener implementors.");
+ }
+ }
+
+ [TestCase (AndroidRuntime.CoreCLR)]
+ [TestCase (AndroidRuntime.NativeAOT)]
+ public void R8WithPreGeneratedTypeMapKeepsFrameworkJcws (AndroidRuntime runtime)
+ {
+ bool isRelease = runtime == AndroidRuntime.NativeAOT;
+ if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) {
+ return;
+ }
+
+ var proj = new XamarinAndroidApplicationProject {
+ IsRelease = isRelease,
+ };
+ proj.SetRuntime (runtime);
+ proj.SetProperty (KnownProperties.AndroidLinkTool, "r8");
+ if (runtime == AndroidRuntime.CoreCLR) {
+ proj.SetProperty ("PublishTrimmed", "false");
+ } else {
+ proj.SetProperty ("DebugSymbols", "true");
+ proj.SetProperty ("DebugType", "portable");
+ proj.SetProperty ("EmbedAssembliesIntoApk", "false");
+ proj.SetProperty ("_AndroidEnableTypemapR8Trimming", "false");
+ }
+
+ using var builder = CreateApkBuilder ();
+ Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
+
+ var mergedAcwMap = builder.Output.GetIntermediaryPath ("acw-map.prebuilt-merged.txt");
+ var intermediateDir = Path.Combine (Root, builder.ProjectDirectory, proj.IntermediateOutputPath);
+ var proguardConfigurations = runtime == AndroidRuntime.NativeAOT
+ ? Directory.GetFiles (intermediateDir, "proguard_project_references.cfg", SearchOption.AllDirectories)
+ : [builder.Output.GetIntermediaryPath (Path.Combine ("proguard", "proguard_project_primary.cfg"))];
+ var dexFile = builder.Output.GetIntermediaryPath (Path.Combine ("android", "bin", "classes.dex"));
+ const string frameworkJcw = "mono.android.view.View_OnClickListenerImplementor";
+
+ AssertFileContains (mergedAcwMap, frameworkJcw, expected: true, "merged framework ACW map");
+ Assert.IsNotEmpty (proguardConfigurations, "R8 configuration should exist.");
+ foreach (var proguardConfiguration in proguardConfigurations) {
+ AssertFileContains (proguardConfiguration, frameworkJcw, expected: true, "R8 configuration");
+ }
+ Assert.IsTrue (
+ DexUtils.ContainsClassWithMethod ($"L{frameworkJcw.Replace ('.', '/')};", "", "()V", dexFile, AndroidSdkPath),
+ $"`{dexFile}` should retain the pre-generated framework listener implementor when R8 shrinking is enabled.");
+
+ Assert.IsTrue (builder.Build (proj), "Incremental build should have succeeded.");
+ Assert.IsTrue (builder.Output.IsTargetSkipped ("_CompileToDalvik"), "_CompileToDalvik should be up to date.");
+ FileAssert.Exists (mergedAcwMap, "IncrementalClean must preserve the merged framework ACW map.");
+ }
+
+ [Test]
+ public void LowercaseCrc64UsesPreGeneratedFrameworkTypeMaps ()
+ {
+ if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: false)) {
+ return;
+ }
+
+ var proj = new XamarinAndroidApplicationProject ();
+ proj.SetRuntime (AndroidRuntime.CoreCLR);
+ proj.SetProperty ("AndroidPackageNamingPolicy", "LowercaseCrc64");
+
+ using var builder = CreateApkBuilder ();
+ Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
+
+ var typemapDir = builder.Output.GetIntermediaryPath ("typemap");
+ FileAssert.DoesNotExist (
+ Path.Combine (typemapDir, "_Mono.Android.TypeMap.dll"),
+ "Mono.Android's pre-generated map has no package-policy-dependent peer names.");
+ FileAssert.DoesNotExist (
+ Path.Combine (typemapDir, "_Java.Interop.TypeMap.dll"),
+ "Java.Interop's pre-generated map has no package-policy-dependent peer names.");
+ }
+
+ [TestCase ("Crc64")]
+ [TestCase ("LowercaseCrc64")]
+ public void PreGeneratedFrameworkTypeMapsMatchAppGeneratedMaps (string packageNamingPolicy)
+ {
+ if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: false)) {
+ return;
+ }
+
+ var proj = new XamarinAndroidApplicationProject ();
+ proj.SetRuntime (AndroidRuntime.CoreCLR);
+ proj.SetProperty ("AndroidPackageNamingPolicy", packageNamingPolicy);
+ proj.SetProperty ("_AndroidUsePreGeneratedMonoAndroidTypeMap", "false");
+
+ using var builder = CreateApkBuilder ();
+ Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
+
+ var generatedTypeMapDir = builder.Output.GetIntermediaryPath ("typemap");
+ var frameworkJcw = Path.Combine (generatedTypeMapDir, "java", "android", "app", "ActivityTracker.java");
+ FileAssert.Exists (frameworkJcw);
+ var source = File.ReadAllText (frameworkJcw);
+ StringAssert.Contains ("mono.android.Runtime.registerNatives (ActivityTracker.class)", source);
+ StringAssert.DoesNotContain ("mono.android.TypeManager.Activate", source);
+ var preGeneratedTypeMapDir = Path.Combine (
+ TestEnvironment.DotNetPreviewAndroidSdkDirectory,
+ "data",
+ "prebuilt-typemap");
+ foreach (var fileName in new [] {
+ "_Mono.Android.TypeMap.dll",
+ "_Java.Interop.TypeMap.dll",
+ }) {
+ var preGenerated = Path.Combine (preGeneratedTypeMapDir, fileName);
+ var generated = Path.Combine (generatedTypeMapDir, fileName);
+ FileAssert.Exists (preGenerated);
+ FileAssert.Exists (generated);
+ AssertTypeMapContentsEqual (generated, preGenerated, fileName);
+ }
+ }
+
+ [TestCase ("37.1")]
+ [TestCase ("37.2")]
+ public void DifferentApiPackGeneratesFrameworkTypeMaps (string apiLevel)
+ {
+ if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: false)) {
+ return;
+ }
+
+ var proj = new XamarinAndroidApplicationProject {
+ TargetFramework = $"{XABuildConfig.LatestDotNetTargetFramework}-android{apiLevel}",
+ };
+ proj.SetRuntime (AndroidRuntime.CoreCLR);
+
+ using var builder = CreateApkBuilder ();
+ Assert.IsTrue (builder.Build (proj), "A different API pack should fall back to framework typemap generation.");
+ AssertTrimmableTypeMapOutputs (builder.Output.GetIntermediaryPath ("typemap"), usePreGeneratedFrameworkTypeMaps: false);
+ FileAssert.DoesNotExist (builder.Output.GetIntermediaryPath ("acw-map.prebuilt-merged.txt"),
+ "An incompatible API pack must not consume the SDK's framework wrappers.");
+ }
+
+ [Test]
+ public void SwitchingPreGeneratedFrameworkTypeMapsInvalidatesGeneration ()
+ {
+ if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: false)) {
+ return;
+ }
+
+ var proj = new XamarinAndroidApplicationProject ();
+ proj.SetRuntime (AndroidRuntime.CoreCLR);
+
+ using var builder = CreateApkBuilder ();
+ Assert.IsTrue (builder.Build (proj), "Initial prebuilt typemap build should succeed.");
+ var typemapDir = builder.Output.GetIntermediaryPath ("typemap");
+ AssertTrimmableTypeMapOutputs (typemapDir, usePreGeneratedFrameworkTypeMaps: true);
+
+ proj.SetProperty ("_AndroidUsePreGeneratedMonoAndroidTypeMap", "false");
+ Assert.IsTrue (builder.Build (proj), "Switching to app-generated framework maps should succeed.");
+ Assert.IsFalse (builder.Output.IsTargetSkipped ("_GenerateTrimmableTypeMap"),
+ "Changing reuse eligibility must invalidate the typemap generation stamp.");
+ AssertTrimmableTypeMapOutputs (typemapDir, usePreGeneratedFrameworkTypeMaps: false);
+ }
+
+ [Test]
+ public void TrimmedBuildWithDebugSymbolsUsesPreGeneratedTypeMap ()
+ {
+ if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: true)) {
+ return;
+ }
+
+ var proj = new XamarinAndroidApplicationProject {
+ IsRelease = true,
+ };
+ proj.SetRuntime (AndroidRuntime.CoreCLR);
+ proj.SetProperty ("DebugSymbols", "true");
+ proj.SetProperty ("DebugType", "portable");
+ proj.SetProperty ("Optimize", "false");
+ proj.SetProperty ("AndroidLinkMode", "Full");
+ proj.SetProperty ("PublishTrimmed", "true");
+ proj.AndroidManifest = proj.AndroidManifest.Replace (
+ "",
+ "");
+
+ using var builder = CreateApkBuilder ();
+ Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
+
+ var typemapDir = builder.Output.GetIntermediaryPath ("typemap");
+ AssertTrimmableTypeMapOutputs (typemapDir, usePreGeneratedFrameworkTypeMaps: true);
+ FileAssert.Exists (
+ builder.Output.GetIntermediaryPath ("acw-map.prebuilt-merged.txt"),
+ "Per-assembly universes should consume the pre-generated framework artifacts even when trimming.");
+ using var monoAndroid = AssemblyDefinition.ReadAssembly (BuildTest.GetLinkedPath (builder, true, "Mono.Android.dll"));
+ Assert.IsNotNull (
+ monoAndroid.MainModule.GetType ("Android.App.NativeActivity"),
+ "Framework types referenced only from AndroidManifest.xml must remain available to conditional pre-generated typemap entries.");
+ using var monoAndroidTypeMap = AssemblyDefinition.ReadAssembly (BuildTest.GetLinkedPath (builder, true, "_Mono.Android.TypeMap.dll"));
+ Assert.IsTrue (
+ monoAndroidTypeMap.CustomAttributes.Any (attribute =>
+ attribute.AttributeType.Namespace == "System.Runtime.InteropServices" &&
+ attribute.AttributeType.Name.StartsWith ("TypeMapAttribute", StringComparison.Ordinal) &&
+ attribute.ConstructorArguments.Count > 0 &&
+ attribute.ConstructorArguments [0].Value as string == "android/app/NativeActivity"),
+ "The conditional pre-generated typemap entry for the manifest-rooted framework type must survive trimming.");
+
+ Assert.IsTrue (builder.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "No-op build should have succeeded.");
+ Assert.IsTrue (
+ builder.Output.IsTargetSkipped ("_GenerateTrimmableTypeMap"),
+ "Per-RID inner builds must not invalidate the outer typemap generation stamp.");
+ }
+
+ [Test]
+ public void DebugTrimmableTypeMapLibrary_DoesNotPackageFrameworkJavaWrappers ()
+ {
+ if (IgnoreUnsupportedConfiguration (AndroidRuntime.CoreCLR, release: false)) {
+ return;
+ }
+
+ var proj = new XamarinAndroidLibraryProject ();
+ proj.SetRuntime (AndroidRuntime.CoreCLR);
+
+ using var builder = CreateDllBuilder ();
+ Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
+
+ var aarPath = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath, $"{proj.ProjectName}.aar");
+ FileAssert.Exists (aarPath);
+ using var aar = ZipHelper.OpenZip (aarPath);
+ foreach (var entry in aar.Entries.Where (entry => entry.FullName.StartsWith ("libs/", StringComparison.Ordinal) && entry.FullName.EndsWith (".jar", StringComparison.Ordinal))) {
+ using var stream = new MemoryStream ();
+ using (var source = entry.Open ()) {
+ source.CopyTo (stream);
+ }
+ stream.Position = 0;
+ using var jar = new System.IO.Compression.ZipArchive (stream, System.IO.Compression.ZipArchiveMode.Read);
+ Assert.IsNull (
+ jar.GetEntry ("android/runtime/JavaProxyThrowable.class"),
+ $"{aarPath}!/{entry.FullName} should not redistribute the SDK's pre-generated framework wrappers.");
}
}
@@ -152,7 +381,7 @@ public class CollectionExport : Java.Lang.Object
Assert.IsTrue (builder.Build (proj), "Build with an IList export should have succeeded.");
var intermediateDir = builder.Output.GetIntermediaryPath ("typemap");
- AssertTrimmableTypeMapOutputs (intermediateDir);
+ AssertTrimmableTypeMapOutputs (intermediateDir, usePreGeneratedFrameworkTypeMaps: !isRelease);
}
[Test]
@@ -1543,12 +1772,19 @@ public void Build_PublishAotProject_UsesTrimmableTypeMapForCoreClrDebug ()
};
proj.SetRuntime (AndroidRuntime.CoreCLR);
proj.SetProperty (KnownProperties.PublishAot, "true");
+ proj.AndroidManifest = proj.AndroidManifest.Replace (
+ "",
+ "");
using var builder = CreateApkBuilder ();
Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
var intermediateDir = builder.Output.GetIntermediaryPath ("typemap");
- AssertTrimmableTypeMapOutputs (intermediateDir);
+ AssertTrimmableTypeMapOutputs (intermediateDir, usePreGeneratedFrameworkTypeMaps: true);
+ StringAssert.Contains (
+ "Android.App.NativeActivity",
+ File.ReadAllText (builder.Output.GetIntermediaryPath ("prebuilt-typemap-roots.xml")),
+ "PublishAot must root manifest-referenced framework types even when AndroidLinkMode is None.");
}
[Test]
@@ -1570,7 +1806,7 @@ public void Build_WithTrimmableTypeMap_IncrementalBuild ([ValueSource (typeof (B
Assert.IsTrue (builder.Build (proj), "First build should have succeeded.");
var intermediateDir = builder.Output.GetIntermediaryPath ("typemap");
- AssertTrimmableTypeMapOutputs (intermediateDir);
+ AssertTrimmableTypeMapOutputs (intermediateDir, usePreGeneratedFrameworkTypeMaps: !isRelease);
var typemapDlls = Directory.GetFiles (intermediateDir, "*.dll");
Assert.IsNotEmpty (typemapDlls, "First build should have generated typemap DLL(s).");
var typemapFingerprints = Path.Combine (intermediateDir, "typemap-fingerprints.txt");
@@ -2198,16 +2434,17 @@ public void NativeAotTrimmableTypeMap_DoesNotExportFrameworkTypeMaps ()
StringAssert.Contains ($"--generateunmanagedentrypoints:_{proj.ProjectName}.TypeMap", rspText);
}
- [Test]
- public void TrimmableTypeMap_PackagesAndroidJavaProxyThrowable (
- [Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime)
+ [TestCase (false, AndroidRuntime.CoreCLR)]
+ [TestCase (true, AndroidRuntime.CoreCLR)]
+ [TestCase (true, AndroidRuntime.NativeAOT)]
+ public void TrimmableTypeMap_PackagesAndroidJavaProxyThrowable (bool isRelease, AndroidRuntime runtime)
{
- if (IgnoreUnsupportedConfiguration (runtime, release: true)) {
+ if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) {
return;
}
var proj = new XamarinAndroidApplicationProject {
- IsRelease = true,
+ IsRelease = isRelease,
};
proj.SetRuntime (runtime);
@@ -3071,11 +3308,29 @@ static void AssertNoUnicodeOutputs (ProjectBuilder builder, string memberName)
);
}
- static void AssertTrimmableTypeMapOutputs (string typemapDir)
+ static void AssertTrimmableTypeMapOutputs (string typemapDir, bool usePreGeneratedFrameworkTypeMaps)
{
DirectoryAssert.Exists (typemapDir);
FileAssert.Exists (Path.Combine (typemapDir, "_Microsoft.Android.TypeMaps.dll"));
- FileAssert.Exists (Path.Combine (typemapDir, "_Mono.Android.TypeMap.dll"));
+
+ var generatedAssemblies = File.ReadAllLines (Path.Combine (typemapDir, "typemap-assemblies.txt"))
+ .Select (Path.GetFileName)
+ .ToArray ();
+ if (!usePreGeneratedFrameworkTypeMaps) {
+ FileAssert.Exists (Path.Combine (typemapDir, "_Mono.Android.TypeMap.dll"),
+ "Builds that cannot use the pre-generated framework maps must generate Mono.Android's typemap.");
+ FileAssert.Exists (Path.Combine (typemapDir, "_Java.Interop.TypeMap.dll"),
+ "Builds that cannot use the pre-generated framework maps must generate Java.Interop's typemap.");
+ CollectionAssert.Contains (generatedAssemblies, "_Mono.Android.TypeMap.dll");
+ CollectionAssert.Contains (generatedAssemblies, "_Java.Interop.TypeMap.dll");
+ } else {
+ FileAssert.DoesNotExist (Path.Combine (typemapDir, "_Mono.Android.TypeMap.dll"),
+ "Per-assembly universe builds should use the typemap pre-generated in the SDK pack.");
+ FileAssert.DoesNotExist (Path.Combine (typemapDir, "_Java.Interop.TypeMap.dll"),
+ "Per-assembly universe builds should use the typemap pre-generated in the SDK pack.");
+ CollectionAssert.DoesNotContain (generatedAssemblies, "_Mono.Android.TypeMap.dll");
+ CollectionAssert.DoesNotContain (generatedAssemblies, "_Java.Interop.TypeMap.dll");
+ }
var javaDir = Path.Combine (typemapDir, "java");
DirectoryAssert.Exists (javaDir, "Trimmable JCW Java output directory should exist.");
@@ -3094,6 +3349,48 @@ static bool AssemblyContainsType (string assemblyPath, string typeFullName)
return ContainsType (assembly.MainModule.Types, typeFullName);
}
+ static byte [] NormalizeModuleIdentity (string assemblyPath)
+ {
+ var content = File.ReadAllBytes (assemblyPath);
+ using var pe = new PEReader (new MemoryStream (content, writable: false));
+ var reader = System.Reflection.Metadata.PEReaderExtensions.GetMetadataReader (pe);
+ var mvidIndex = MetadataTokens.GetHeapOffset (reader.GetModuleDefinition ().Mvid);
+ var mvidOffset = pe.PEHeaders.MetadataStartOffset +
+ reader.GetHeapMetadataOffset (HeapIndex.Guid) +
+ (mvidIndex - 1) * 16;
+ Array.Clear (content, mvidOffset, 16);
+ Array.Clear (content, pe.PEHeaders.CoffHeaderStartOffset + 4, 4);
+ return content;
+ }
+
+ static void AssertTypeMapContentsEqual (string generatedPath, string preGeneratedPath, string fileName)
+ {
+ var generated = NormalizeModuleIdentity (generatedPath);
+ var preGenerated = NormalizeModuleIdentity (preGeneratedPath);
+ var commonLength = Math.Min (generated.Length, preGenerated.Length);
+ int firstDifference = 0;
+ while (firstDifference < commonLength && generated [firstDifference] == preGenerated [firstDifference]) {
+ firstDifference++;
+ }
+ if (firstDifference == generated.Length && firstDifference == preGenerated.Length) {
+ return;
+ }
+
+ Assert.Fail (
+ $"{fileName} should have identical metadata and IL whether generated by the SDK or the app build. " +
+ $"First difference at 0x{firstDifference:X}; generated length: {generated.Length}, " +
+ $"pre-generated length: {preGenerated.Length}; generated: {FormatHexWindow (generated, firstDifference)}; " +
+ $"pre-generated: {FormatHexWindow (preGenerated, firstDifference)}.");
+ }
+
+ static string FormatHexWindow (byte [] content, int offset)
+ {
+ const int radius = 8;
+ var start = Math.Max (0, offset - radius);
+ var count = Math.Min (content.Length - start, radius * 2 + 1);
+ return count == 0 ? "" : $"{start:X}: {BitConverter.ToString (content, start, count)}";
+ }
+
static bool ContainsType (IEnumerable types, string typeFullName)
{
foreach (var type in types) {
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targets
index ed2846846a1..33b8ebec834 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targets
@@ -17,9 +17,17 @@ Copyright (C) 2018 Xamarin. All rights reserved.
+
+
+ <_R8AcwMapFile>$(_AcwMapFile)
+ <_R8AcwMapFile Condition=" '$(_AndroidPreGeneratedTypeMapEnabled)' == 'true' ">$(_TrimmableTypeMapKeepRulesAcwMapFile)
+ <_R8AcwMapInput Condition=" '$(_AndroidPreGeneratedTypeMapEnabled)' == 'true' ">$(_R8AcwMapFile)
+
+
+
@@ -72,7 +80,7 @@ Copyright (C) 2018 Xamarin. All rights reserved.
JavaLibrariesToReference="@(AndroidExternalJavaLibrary)"
EnableDesugar="$(AndroidEnableDesugar)"
AndroidSdkBuildToolsPath="$(AndroidSdkBuildToolsPath)"
- AcwMapFile="$(_AcwMapFile)"
+ AcwMapFile="$(_R8AcwMapFile)"
JavaSourceFiles="@(_R8KeepJavaSource)"
ProguardCommonXamarinConfiguration="$([System.IO.Path]::Combine('$(IntermediateOutputPath)', 'proguard', 'proguard_xamarin.cfg'))"
ProguardGeneratedReferenceConfiguration="$(_ProguardProjectConfiguration)"
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
index 82eca1db65f..a53fbabaf70 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
@@ -65,7 +65,18 @@ public void DotNetRun (bool isRelease, AndroidRuntime runtime)
var dotnet = new DotNetCLI (Path.Combine (Root, builder.ProjectDirectory, proj.ProjectFilePath));
Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed");
- Assert.IsTrue (dotnet.Run (), "`dotnet run --no-build` should succeed");
+ var manifestPath = Path.Combine (Root, builder.ProjectDirectory, proj.IntermediateOutputPath, "android", "AndroidManifest.xml");
+ var manifest = File.ReadAllText (manifestPath);
+ string [] runParameters = [];
+ var device = Environment.GetEnvironmentVariable ("ANDROID_SERIAL");
+ if (!string.IsNullOrEmpty (device)) {
+ runParameters = ["--device", device];
+ }
+ Assert.IsTrue (dotnet.Run (parameters: runParameters), "`dotnet run --no-build` should succeed");
+ Assert.AreEqual (manifest, File.ReadAllText (manifestPath),
+ "`dotnet run --no-build` must preserve the generated manifest, including the launchable activity.");
+ Assert.IsFalse (File.ReadLines (dotnet.ProcessLogFile).Any (line => line.Contains ("XA4257")),
+ "`dotnet run --no-build` must not skip Java peers by scanning trimmed assemblies as pre-trim inputs.");
bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity",
Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), ActivityStartTimeoutInSeconds);