diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs
index 2c1382c5458..63e6f2fc41f 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs
@@ -29,13 +29,13 @@ protected override void ProcessAssembly (AssemblyDefinition assembly)
}
#if !ILLINK
- public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
+ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
// Only run this step on user Android assemblies
if (!context.IsAndroidUserAssembly)
- return false;
+ return;
- return AddKeepAlives (assembly);
+ context.IsAssemblyModified |= AddKeepAlives (assembly);
}
#endif // !ILLINK
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs
index 5adc2e27582..c5a44ec3b07 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs
@@ -29,7 +29,7 @@ public class FindJavaObjectsStep : BaseStep, IAssemblyModifierPipelineStep
public FindJavaObjectsStep (TaskLoggingHelper log) => Log = log;
- public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
+ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (context.Destination.ItemSpec);
var scanned = ScanAssembly (assembly, context, destinationJLOXml);
@@ -37,11 +37,7 @@ public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
if (!scanned) {
// We didn't scan for Java objects, so write an empty .xml file for later steps
JavaObjectsXmlFile.WriteEmptyFile (destinationJLOXml, Log);
- return false;
}
-
- // This step does not change the assembly
- return false;
}
public bool ScanAssembly (AssemblyDefinition assembly, StepContext context, string destinationJLOXml)
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs
new file mode 100644
index 00000000000..bfc242ea63c
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs
@@ -0,0 +1,73 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Utilities;
+using Mono.Cecil;
+using Mono.Linker.Steps;
+using Xamarin.Android.Tasks;
+
+namespace MonoDroid.Tuner;
+
+///
+/// Scans an assembly for JLOs that need to be in the typemap and writes them to an XML file.
+///
+public class FindTypeMapObjectsStep : BaseStep, IAssemblyModifierPipelineStep
+{
+ public bool Debug { get; set; }
+
+ public bool ErrorOnCustomJavaObject { get; set; }
+
+ public TaskLoggingHelper Log { get; set; }
+
+ public FindTypeMapObjectsStep (TaskLoggingHelper log) => Log = log;
+
+ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
+ {
+ var destinationTypeMapXml = TypeMapObjectsXmlFile.GetTypeMapObjectsXmlFilePath (context.Destination.ItemSpec);
+
+ // We only care about assemblies that can contains JLOs
+ if (!context.IsAndroidAssembly) {
+ Log.LogDebugMessage ($"Skipping assembly '{assembly.Name.Name}' because it is not an Android assembly");
+ TypeMapObjectsXmlFile.WriteEmptyFile (destinationTypeMapXml, Log);
+ return;
+ }
+
+ var types = ScanForJavaTypes (assembly);
+
+ var xml = new TypeMapObjectsXmlFile {
+ AssemblyName = assembly.Name.Name,
+ };
+
+ if (Debug) {
+ var (javaToManaged, managedToJava, foundJniNativeRegistration) = TypeMapCecilAdapter.GetDebugNativeEntries (types, Context);
+
+ xml.JavaToManagedDebugEntries.AddRange (javaToManaged);
+ xml.ManagedToJavaDebugEntries.AddRange (managedToJava);
+ xml.FoundJniNativeRegistration = foundJniNativeRegistration;
+ } else {
+ var genState = TypeMapCecilAdapter.GetReleaseGenerationState (types, Context, out var foundJniNativeRegistration);
+ xml.ModuleReleaseData = genState.TempModules.SingleOrDefault ().Value;
+ }
+
+ xml.Export (destinationTypeMapXml, Log);
+ }
+
+ List ScanForJavaTypes (AssemblyDefinition assembly)
+ {
+ var types = new List ();
+
+ var scanner = new XAJavaTypeScanner (Xamarin.Android.Tools.AndroidTargetArch.None, Log, Context) {
+ ErrorOnCustomJavaObject = ErrorOnCustomJavaObject
+ };
+
+ foreach (ModuleDefinition md in assembly.Modules) {
+ foreach (TypeDefinition td in md.Types) {
+ scanner.AddJavaType (td, types);
+ }
+ }
+
+ return types;
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs
index 0f979714bf0..82f1777de4b 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs
@@ -83,13 +83,13 @@ internal bool FixAbstractMethods (AssemblyDefinition assembly)
}
#if !ILLINK
- public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
+ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
// Only run this step on non-main user Android assemblies
if (context.IsMainAssembly || !context.IsAndroidUserAssembly)
- return false;
+ return;
- return FixAbstractMethods (assembly);
+ context.IsAssemblyModified |= FixAbstractMethods (assembly);
}
#endif // !ILLINK
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
index 1cfc19f2d9a..b4225d159da 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
@@ -71,13 +71,13 @@ protected override void LoadDesigner ()
}
#if !ILLINK
- public bool ProcessAssembly (AssemblyDefinition assembly, Xamarin.Android.Tasks.StepContext context)
+ public void ProcessAssembly (AssemblyDefinition assembly, Xamarin.Android.Tasks.StepContext context)
{
// Only run this step on non-main user Android assemblies
if (context.IsMainAssembly || !context.IsAndroidUserAssembly)
- return false;
+ return;
- return ProcessAssemblyDesigner (assembly);
+ context.IsAssemblyModified |= ProcessAssemblyDesigner (assembly);
}
#endif // !ILLINK
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
index 18935bf8d77..143a3931604 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
@@ -8,6 +8,7 @@
using Java.Interop.Tools.TypeNameMappings;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
using Mono.Cecil;
using MonoDroid.Tuner;
using Xamarin.Android.Tools;
@@ -78,10 +79,6 @@ public override bool RunTask ()
ReadSymbols = ReadSymbols,
};
- var writerParameters = new WriterParameters {
- DeterministicMvid = Deterministic,
- };
-
Dictionary> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, Array.Empty (), validate: false);
AssemblyPipeline? pipeline = null;
@@ -122,7 +119,7 @@ public override bool RunTask ()
Directory.CreateDirectory (Path.GetDirectoryName (destination.ItemSpec));
- RunPipeline (pipeline!, source, destination, writerParameters);
+ RunPipeline (pipeline!, source, destination);
}
pipeline?.Dispose ();
@@ -141,9 +138,26 @@ protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCont
findJavaObjectsStep.Initialize (context);
pipeline.Steps.Add (findJavaObjectsStep);
+
+ // SaveChangedAssemblyStep
+ var writerParameters = new WriterParameters {
+ DeterministicMvid = Deterministic,
+ };
+
+ var saveChangedAssemblyStep = new SaveChangedAssemblyStep (Log, writerParameters);
+ pipeline.Steps.Add (saveChangedAssemblyStep);
+
+ // FindTypeMapObjectsStep - this must be run after the assembly has been saved, as saving changes the MVID
+ var findTypeMapObjectsStep = new FindTypeMapObjectsStep (Log) {
+ ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
+ Debug = Debug,
+ };
+
+ findTypeMapObjectsStep.Initialize (context);
+ pipeline.Steps.Add (findTypeMapObjectsStep);
}
- void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination, WriterParameters writerParameters)
+ void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination)
{
var assembly = pipeline.Resolver.GetAssembly (source.ItemSpec);
@@ -157,17 +171,36 @@ void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destina
IsUserAssembly = ResolvedUserAssemblies.Any (a => a.ItemSpec == source.ItemSpec),
};
- var changed = pipeline.Run (assembly, context);
+ pipeline.Run (assembly, context);
+ }
+}
+
+class SaveChangedAssemblyStep : IAssemblyModifierPipelineStep
+{
+ public TaskLoggingHelper Log { get; set; }
- if (changed) {
- Log.LogDebugMessage ($"Saving modified assembly: {destination.ItemSpec}");
- Directory.CreateDirectory (Path.GetDirectoryName (destination.ItemSpec));
- writerParameters.WriteSymbols = assembly.MainModule.HasSymbols;
- assembly.Write (destination.ItemSpec, writerParameters);
+ public WriterParameters WriterParameters { get; set; }
+
+ public SaveChangedAssemblyStep (TaskLoggingHelper log, WriterParameters writerParameters)
+ {
+ Log = log;
+ WriterParameters = writerParameters;
+ }
+
+ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
+ {
+ if (context.IsAssemblyModified) {
+ Log.LogDebugMessage ($"Saving modified assembly: {context.Destination.ItemSpec}");
+ Directory.CreateDirectory (Path.GetDirectoryName (context.Destination.ItemSpec));
+ WriterParameters.WriteSymbols = assembly.MainModule.HasSymbols;
+ assembly.Write (context.Destination.ItemSpec, WriterParameters);
} else {
// If we didn't write a modified file, copy the original to the destination
- CopyIfChanged (source, destination);
+ CopyIfChanged (context.Source, context.Destination);
}
+
+ // We just saved the assembly, so it is no longer modified
+ context.IsAssemblyModified = false;
}
void CopyIfChanged (ITaskItem source, ITaskItem destination)
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
index c148a1446c8..70a6a60f5a9 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
@@ -3,6 +3,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using Java.Interop.Tools.Cecil;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
@@ -11,6 +12,8 @@
namespace Xamarin.Android.Tasks;
+// Note: If/When this is converted to an incremental task, every build still needs to set:
+// NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent
public class GenerateTypeMappings : AndroidTask
{
public override string TaskPrefix => "GTM";
@@ -20,25 +23,91 @@ public class GenerateTypeMappings : AndroidTask
public bool Debug { get; set; }
+ public bool EnableMarshalMethods { get;set; }
+
+ [Output]
+ public ITaskItem [] GeneratedBinaryTypeMaps { get; set; } = [];
+
[Required]
public string IntermediateOutputDirectory { get; set; } = "";
public bool SkipJniAddNativeMethodRegistrationAttributeScan { get; set; }
[Required]
- public string TypemapOutputDirectory { get; set; } = "";
+ public ITaskItem [] ResolvedAssemblies { get; set; } = [];
- [Output]
- public ITaskItem [] GeneratedBinaryTypeMaps { get; set; } = [];
+ // This property is temporary and is used to ensure that the new "linker step"
+ // JLO scanning produces the same results as the old process. It will be removed
+ // once the process is complete.
+ public bool RunCheckedBuild { get; set; }
+
+ [Required]
+ public string [] SupportedAbis { get; set; } = [];
public string TypemapImplementation { get; set; } = "llvm-ir";
+ [Required]
+ public string TypemapOutputDirectory { get; set; } = "";
+
AndroidRuntime androidRuntime;
public override bool RunTask ()
{
+ var useMarshalMethods = !Debug && EnableMarshalMethods;
+
androidRuntime = MonoAndroidHelper.ParseAndroidRuntime (AndroidRuntime);
+ if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
+ // NativeAOT typemaps are generated in `Microsoft.Android.Sdk.ILLink.TypeMappingStep`
+ Log.LogDebugMessage ("Skipping type maps for NativeAOT.");
+ return !Log.HasLoggedErrors;
+ }
+
+ // If using marshal methods, we cannot use the .typemap.xml files currently because
+ // the type token ids were changed by the marshal method rewriter after we wrote the .xml files.
+ if (!useMarshalMethods)
+ GenerateAllTypeMappings ();
+
+ // Generate typemaps from the native code generator state (produced by the marshal method rewriter)
+ if (RunCheckedBuild || useMarshalMethods)
+ GenerateAllTypeMappingsFromNativeState (useMarshalMethods);
+ return !Log.HasLoggedErrors;
+ }
+
+ void GenerateAllTypeMappings ()
+ {
+ var allAssembliesPerArch = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, SupportedAbis, validate: true);
+
+ foreach (var set in allAssembliesPerArch)
+ GenerateTypeMap (set.Key, set.Value.Values.ToList ());
+ }
+
+ void GenerateTypeMap (AndroidTargetArch arch, List assemblies)
+ {
+ Log.LogDebugMessage ($"Generating type maps for architecture '{arch}'");
+
+ var state = TypeMapObjectsFileAdapter.Create (arch, assemblies, Log);
+
+ // An error was already logged to Log.LogError
+ if (state is null)
+ return;
+
+ if (TypemapImplementation != "llvm-ir") {
+ Log.LogDebugMessage ($"TypemapImplementation='{TypemapImplementation}' will write an empty native typemap.");
+ state.XmlFiles.Clear ();
+ }
+
+ var tmg = new TypeMapGenerator (Log, state, androidRuntime);
+ tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, TypemapOutputDirectory);
+
+ // Set for use by task later
+ NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = state.JniAddNativeMethodRegistrationAttributePresent;
+
+ AddOutputTypeMaps (tmg, state.TargetArch);
+ }
+
+ void GenerateAllTypeMappingsFromNativeState (bool useMarshalMethods)
+ {
// Retrieve the stored NativeCodeGenState
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal> (
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
@@ -50,37 +119,42 @@ public override bool RunTask ()
foreach (var kvp in nativeCodeGenStates) {
NativeCodeGenState state = kvp.Value;
templateCodeGenState = state;
- WriteTypeMappings (state);
+ GenerateTypeMapFromNativeState (state, useMarshalMethods);
}
if (templateCodeGenState is null)
throw new InvalidOperationException ($"Internal error: no native code generator state defined");
- // Set for use by task later
- NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = templateCodeGenState.JniAddNativeMethodRegistrationAttributePresent;
-
- return !Log.HasLoggedErrors;
+ // Set for use by task later
+ if (useMarshalMethods)
+ NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = templateCodeGenState.JniAddNativeMethodRegistrationAttributePresent;
}
- void WriteTypeMappings (NativeCodeGenState state)
+ void GenerateTypeMapFromNativeState (NativeCodeGenState state, bool useMarshalMethods)
{
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
// NativeAOT typemaps are generated in `Microsoft.Android.Sdk.ILLink.TypeMappingStep`
Log.LogDebugMessage ("Skipping type maps for NativeAOT.");
return;
}
- Log.LogDebugMessage ($"Generating type maps for architecture '{state.TargetArch}'");
+ Log.LogDebugMessage ($"Generating type maps from native state for architecture '{state.TargetArch}' (RunCheckedBuild = {RunCheckedBuild})");
if (TypemapImplementation != "llvm-ir") {
Log.LogDebugMessage ($"TypemapImplementation='{TypemapImplementation}' will write an empty native typemap.");
state = new NativeCodeGenState (state.TargetArch, new TypeDefinitionCache (), state.Resolver, [], [], state.Classifier);
}
- var tmg = new TypeMapGenerator (Log, state, androidRuntime);
+ var tmg = new TypeMapGenerator (Log, new NativeCodeGenStateAdapter (state), androidRuntime) { RunCheckedBuild = RunCheckedBuild && !useMarshalMethods };
tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, TypemapOutputDirectory);
- string abi = MonoAndroidHelper.ArchToAbi (state.TargetArch);
+ AddOutputTypeMaps (tmg, state.TargetArch);
+ }
+
+ void AddOutputTypeMaps (TypeMapGenerator tmg, AndroidTargetArch arch)
+ {
+ string abi = MonoAndroidHelper.ArchToAbi (arch);
var items = new List ();
+
foreach (string file in tmg.GeneratedBinaryTypeMaps) {
var item = new TaskItem (file);
string fileName = Path.GetFileName (file);
@@ -90,6 +164,6 @@ void WriteTypeMappings (NativeCodeGenState state)
items.Add (item);
}
- GeneratedBinaryTypeMaps = items.ToArray ();
+ GeneratedBinaryTypeMaps = GeneratedBinaryTypeMaps.Concat (items).ToArray ();
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPipeline.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPipeline.cs
index 5cf54ca78af..eba39ee60ed 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPipeline.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPipeline.cs
@@ -20,14 +20,10 @@ public AssemblyPipeline (DirectoryAssemblyResolver resolver)
Resolver = resolver;
}
- public bool Run (AssemblyDefinition assembly, StepContext context)
+ public void Run (AssemblyDefinition assembly, StepContext context)
{
- var changed = false;
-
foreach (var step in Steps)
- changed |= step.ProcessAssembly (assembly, context);
-
- return changed;
+ step.ProcessAssembly (assembly, context);
}
protected virtual void Dispose (bool disposing)
@@ -51,7 +47,7 @@ public void Dispose ()
public interface IAssemblyModifierPipelineStep
{
- bool ProcessAssembly (AssemblyDefinition assembly, StepContext context);
+ void ProcessAssembly (AssemblyDefinition assembly, StepContext context);
}
public class StepContext
@@ -60,6 +56,7 @@ public class StepContext
public ITaskItem Destination { get; }
public bool EnableMarshalMethods { get; set; }
public bool IsAndroidAssembly { get; set; }
+ public bool IsAssemblyModified { get; set; }
public bool IsDebug { get; set; }
public bool IsFrameworkAssembly { get; set; }
public bool IsMainAssembly { get; set; }
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs
index 197948048ac..7ac2f437ddc 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs
@@ -16,15 +16,25 @@ class TypeMapCecilAdapter
{
public static (List javaToManaged, List managedToJava) GetDebugNativeEntries (NativeCodeGenState state)
{
+ var (javaToManaged, managedToJava, foundJniNativeRegistration) = GetDebugNativeEntries (state.AllJavaTypes, state.TypeCache);
+
+ state.JniAddNativeMethodRegistrationAttributePresent = foundJniNativeRegistration;
+
+ return (javaToManaged, managedToJava);
+ }
+
+ public static (List javaToManaged, List managedToJava, bool foundJniNativeRegistration) GetDebugNativeEntries (List types, TypeDefinitionCache cache)
+ {
+ var javaDuplicates = new Dictionary> (StringComparer.Ordinal);
var javaToManaged = new List ();
var managedToJava = new List ();
+ var foundJniNativeRegistration = false;
- var javaDuplicates = new Dictionary> (StringComparer.Ordinal);
- foreach (TypeDefinition td in state.AllJavaTypes) {
- UpdateApplicationConfig (state, td);
+ foreach (var td in types) {
+ foundJniNativeRegistration = JniAddNativeMethodRegistrationAttributeFound (foundJniNativeRegistration, td);
- TypeMapDebugEntry entry = GetDebugEntry (td, state.TypeCache);
- HandleDebugDuplicates (javaDuplicates, entry, td, state.TypeCache);
+ TypeMapDebugEntry entry = GetDebugEntry (td, cache);
+ HandleDebugDuplicates (javaDuplicates, entry, td, cache);
javaToManaged.Add (entry);
managedToJava.Add (entry);
@@ -32,7 +42,7 @@ public static (List javaToManaged, List ma
SyncDebugDuplicates (javaDuplicates);
- return (javaToManaged, managedToJava);
+ return (javaToManaged, managedToJava, foundJniNativeRegistration);
}
public static ReleaseGenerationState GetReleaseGenerationState (NativeCodeGenState state)
@@ -40,17 +50,28 @@ public static ReleaseGenerationState GetReleaseGenerationState (NativeCodeGenSta
var genState = new ReleaseGenerationState ();
foreach (TypeDefinition td in state.AllJavaTypes) {
- ProcessReleaseType (state, genState, td);
+ UpdateApplicationConfig (state, td);
+ ProcessReleaseType (state.TypeCache, genState, td);
}
return genState;
}
- static void ProcessReleaseType (NativeCodeGenState state, ReleaseGenerationState genState, TypeDefinition td)
+ public static ReleaseGenerationState GetReleaseGenerationState (List types, TypeDefinitionCache cache, out bool foundJniNativeRegistration)
{
- UpdateApplicationConfig (state, td);
- genState.AddKnownAssembly (GetAssemblyName (td));
+ var genState = new ReleaseGenerationState ();
+ foundJniNativeRegistration = false;
+ foreach (TypeDefinition td in types) {
+ foundJniNativeRegistration = JniAddNativeMethodRegistrationAttributeFound (foundJniNativeRegistration, td);
+ ProcessReleaseType (cache, genState, td);
+ }
+
+ return genState;
+ }
+
+ static void ProcessReleaseType (TypeDefinitionCache cache, ReleaseGenerationState genState, TypeDefinition td)
+ {
// We must NOT use Guid here! The reason is that Guid sort order is different than its corresponding
// byte array representation and on the runtime we need the latter in order to be able to binary search
// through the module array.
@@ -65,7 +86,6 @@ static void ProcessReleaseType (NativeCodeGenState state, ReleaseGenerationState
moduleData = new ModuleReleaseData {
Mvid = td.Module.Mvid,
MvidBytes = moduleUUID,
- //Assembly = td.Module.Assembly,
AssemblyName = td.Module.Assembly.Name.Name,
TypesScratch = new Dictionary (StringComparer.Ordinal),
DuplicateTypes = new List (),
@@ -74,7 +94,7 @@ static void ProcessReleaseType (NativeCodeGenState state, ReleaseGenerationState
tempModules.Add (moduleUUID, moduleData);
}
- string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td, state.TypeCache);
+ string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td, cache);
// We will ignore generic types and interfaces when generating the Java to Managed map, but we must not
// omit them from the table we output - we need the same number of entries in both java-to-managed and
// managed-to-java tables. `SkipInJavaToManaged` set to `true` will cause the native assembly generator
@@ -98,8 +118,6 @@ static void ProcessReleaseType (NativeCodeGenState state, ReleaseGenerationState
}
}
- static string GetAssemblyName (TypeDefinition td) => td.Module.Assembly.FullName;
-
static TypeMapDebugEntry GetDebugEntry (TypeDefinition td, TypeDefinitionCache cache)
{
return new TypeMapDebugEntry {
@@ -130,7 +148,6 @@ static string GetManagedTypeName (TypeDefinition td)
return $"{managedTypeName}, {td.Module.Assembly.Name.Name}";
}
-
static void HandleDebugDuplicates (Dictionary> javaDuplicates, TypeMapDebugEntry entry, TypeDefinition td, TypeDefinitionCache cache)
{
List duplicates;
@@ -147,6 +164,7 @@ static void HandleDebugDuplicates (Dictionary> j
// Fix things up so the abstract type is first, and the `Invoker` is considered a duplicate.
duplicates.Insert (0, entry);
oldEntry.SkipInJavaToManaged = false;
+ oldEntry.IsInvoker = true;
} else {
// ¯\_(ツ)_/¯
duplicates.Add (entry);
@@ -179,15 +197,20 @@ static void SyncDebugDuplicates (Dictionary> jav
static void UpdateApplicationConfig (NativeCodeGenState state, TypeDefinition javaType)
{
- if (state.JniAddNativeMethodRegistrationAttributePresent || !javaType.HasCustomAttributes) {
- return;
- }
+ state.JniAddNativeMethodRegistrationAttributePresent = JniAddNativeMethodRegistrationAttributeFound (state.JniAddNativeMethodRegistrationAttributePresent, javaType);
+ }
+ static bool JniAddNativeMethodRegistrationAttributeFound (bool alreadyFound, TypeDefinition javaType)
+ {
+ if (alreadyFound || !javaType.HasCustomAttributes) {
+ return alreadyFound;
+ }
+
foreach (CustomAttribute ca in javaType.CustomAttributes) {
- if (!state.JniAddNativeMethodRegistrationAttributePresent && String.Compare ("JniAddNativeMethodRegistrationAttribute", ca.AttributeType.Name, StringComparison.Ordinal) == 0) {
- state.JniAddNativeMethodRegistrationAttributePresent = true;
- break;
+ if (string.Equals ("JniAddNativeMethodRegistrationAttribute", ca.AttributeType.Name, StringComparison.Ordinal)) {
+ return true;
}
}
+ return false;
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs
index cada872317d..00a0ede03ad 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs
@@ -3,13 +3,18 @@
using System.IO;
using System.Linq;
using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.Cecil;
+using Xamarin.Android.Tools;
+using static Xamarin.Android.Tasks.TypeMapGenerator;
namespace Xamarin.Android.Tasks
{
class TypeMapGenerator
{
+ public bool RunCheckedBuild { get; set; }
+
internal sealed class ModuleUUIDArrayComparer : IComparer
{
int Compare (byte[] left, byte[] right)
@@ -62,6 +67,14 @@ internal sealed class TypeMapDebugEntry
// It is not used to create the typemap.
public TypeDefinition TypeDefinition;
+ // These fields are only used by the XML adapter for temp storage while reading.
+ // It is not used to create the typemap.
+ public string? DuplicateForJavaToManagedKey { get; set; }
+ public bool IsInvoker { get; set; }
+ public bool IsMonoAndroid { get; set; } // Types in Mono.Android take precedence over other assemblies
+
+ public string Key => $"{JavaName}|{ManagedName}";
+
public override string ToString ()
{
return $"TypeMapDebugEntry{{JavaName={JavaName}, ManagedName={ManagedName}, SkipInJavaToManaged={SkipInJavaToManaged}, DuplicateForJavaToManaged={DuplicateForJavaToManaged}}}";
@@ -78,36 +91,26 @@ internal sealed class ModuleDebugData
internal sealed class ReleaseGenerationState
{
- int assemblyId = 0;
+ // This field is only used by the Cecil adapter for temp storage while reading.
+ // It is not used to create the typemap.
+ public readonly Dictionary MvidCache;
- public readonly Dictionary KnownAssemblies;
- public readonly Dictionary MvidCache;
- public readonly Dictionary TempModules;
+ public readonly Dictionary TempModules;
public ReleaseGenerationState ()
{
- KnownAssemblies = new Dictionary (StringComparer.Ordinal);
- MvidCache = new Dictionary ();
- TempModules = new Dictionary ();
- }
-
- public void AddKnownAssembly (string assemblyName)
- {
- if (KnownAssemblies.ContainsKey (assemblyName)) {
- return;
- }
-
- KnownAssemblies.Add (assemblyName, ++assemblyId);
+ MvidCache = new Dictionary ();
+ TempModules = new Dictionary ();
}
}
readonly TaskLoggingHelper log;
- readonly NativeCodeGenState state;
+ readonly ITypeMapGeneratorAdapter state;
readonly AndroidRuntime runtime;
public IList GeneratedBinaryTypeMaps { get; } = new List ();
- public TypeMapGenerator (TaskLoggingHelper log, NativeCodeGenState state, AndroidRuntime runtime)
+ public TypeMapGenerator (TaskLoggingHelper log, ITypeMapGeneratorAdapter state, AndroidRuntime runtime)
{
this.log = log ?? throw new ArgumentNullException (nameof (log));
this.state = state ?? throw new ArgumentNullException (nameof (state));
@@ -133,7 +136,7 @@ public void Generate (bool debugBuild, bool skipJniAddNativeMethodRegistrationAt
void GenerateDebugNativeAssembly (string outputDirectory)
{
- (var javaToManaged, var managedToJava) = TypeMapCecilAdapter.GetDebugNativeEntries (state);
+ (var javaToManaged, var managedToJava) = state.GetDebugNativeEntries ();
var data = new ModuleDebugData {
EntryCount = (uint)javaToManaged.Count,
@@ -150,7 +153,7 @@ void GenerateDebugNativeAssembly (string outputDirectory)
void GenerateRelease (string outputDirectory)
{
- var genState = TypeMapCecilAdapter.GetReleaseGenerationState (state);
+ var genState = state.GetReleaseGenerationState ();
ModuleReleaseData [] modules = genState.TempModules.Values.ToArray ();
Array.Sort (modules, new ModuleUUIDArrayComparer ());
@@ -188,9 +191,135 @@ void GenerateNativeAssembly (LLVMIR.LlvmIrComposer composer, LLVMIR.LlvmIrModule
throw;
} finally {
sw.Flush ();
- Files.CopyIfStreamChanged (sw.BaseStream, outputFile);
+
+ if (RunCheckedBuild) {
+ if (Files.HasStreamChanged (sw.BaseStream, outputFile)) {
+ Files.CopyIfStreamChanged (sw.BaseStream, outputFile + "2");
+ log.LogError ("Output file changed");
+ } else {
+ log.LogMessage ($"RunCheckedBuild: Output file '{outputFile}' unchanged");
+ }
+ } else {
+ Files.CopyIfStreamChanged (sw.BaseStream, outputFile);
+ }
+ }
+ }
+ }
+ }
+
+ // This abstraction is temporary to facilitate the transition from the old
+ // typemap generator to the new one. It will be removed once the transition
+ // is complete.
+ interface ITypeMapGeneratorAdapter
+ {
+ AndroidTargetArch TargetArch { get; }
+ bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
+ (List javaToManaged, List managedToJava) GetDebugNativeEntries ();
+ ReleaseGenerationState GetReleaseGenerationState ();
+ }
+
+ class NativeCodeGenStateAdapter : ITypeMapGeneratorAdapter
+ {
+ readonly NativeCodeGenState state;
+
+ public NativeCodeGenStateAdapter (NativeCodeGenState state)
+ {
+ this.state = state ?? throw new ArgumentNullException (nameof (state));
+ }
+
+ public AndroidTargetArch TargetArch => state.TargetArch;
+
+ public bool JniAddNativeMethodRegistrationAttributePresent {
+ get => state.JniAddNativeMethodRegistrationAttributePresent;
+ set => state.JniAddNativeMethodRegistrationAttributePresent = value;
+ }
+
+ public (List javaToManaged, List managedToJava) GetDebugNativeEntries ()
+ {
+ return TypeMapCecilAdapter.GetDebugNativeEntries (state);
+ }
+
+ public ReleaseGenerationState GetReleaseGenerationState ()
+ {
+ return TypeMapCecilAdapter.GetReleaseGenerationState (state);
+ }
+ }
+
+ class TypeMapObjectsFileAdapter : ITypeMapGeneratorAdapter
+ {
+ public List XmlFiles { get; } = [];
+ public AndroidTargetArch TargetArch { get; }
+
+ public TypeMapObjectsFileAdapter (AndroidTargetArch targetArch)
+ {
+ TargetArch = targetArch;
+ }
+
+ public bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
+
+ public (List javaToManaged, List managedToJava) GetDebugNativeEntries ()
+ {
+ var javaToManaged = new List ();
+ var managedToJava = new List ();
+
+ foreach (var xml in XmlFiles) {
+ javaToManaged.AddRange (xml.JavaToManagedDebugEntries);
+ managedToJava.AddRange (xml.ManagedToJavaDebugEntries);
+ }
+
+ // Handle entries with duplicate JavaNames
+ GroupDuplicateDebugEntries (javaToManaged);
+ GroupDuplicateDebugEntries (managedToJava);
+
+ return (javaToManaged, managedToJava);
+ }
+
+ void GroupDuplicateDebugEntries (List debugEntries)
+ {
+ foreach (var group in debugEntries.GroupBy (ent => ent.JavaName).Where (g => g.Count () > 1)) {
+ // We need to sort:
+ // - Types in Mono.Android come first
+ // - Types that are not invokers come first
+ var entries = group
+ .OrderBy (e => e.IsMonoAndroid ? 0 : 1)
+ .ThenBy (e => e.IsInvoker ? 1 : 0)
+ .ToList ();
+
+ for (var i = 1; i < entries.Count; i++)
+ entries [i].DuplicateForJavaToManaged = entries [0];
+ }
+ }
+
+ public ReleaseGenerationState GetReleaseGenerationState ()
+ {
+ var state = new ReleaseGenerationState ();
+
+ foreach (var xml in XmlFiles)
+ if (xml.HasReleaseEntries)
+ state.TempModules.Add (xml.ModuleReleaseData.MvidBytes, xml.ModuleReleaseData);
+
+ return state;
+ }
+
+ public static TypeMapObjectsFileAdapter? Create (AndroidTargetArch targetArch, List assemblies, TaskLoggingHelper log)
+ {
+ var adapter = new TypeMapObjectsFileAdapter (targetArch);
+
+ foreach (var assembly in assemblies) {
+ var typeMapPath = TypeMapObjectsXmlFile.GetTypeMapObjectsXmlFilePath (assembly.ItemSpec);
+
+ if (!File.Exists (typeMapPath)) {
+ log.LogError ($"'{typeMapPath}' not found.");
+ return null;
}
+
+ var xml = TypeMapObjectsXmlFile.Import (typeMapPath);
+
+ if (xml.WasScanned)
+ adapter.XmlFiles.Add (xml);
}
+
+ return adapter;
}
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs
new file mode 100644
index 00000000000..13cd476cc25
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs
@@ -0,0 +1,278 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Utilities;
+using NuGet.Packaging;
+
+using ModuleReleaseData = Xamarin.Android.Tasks.TypeMapGenerator.ModuleReleaseData;
+using TypeMapDebugEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugEntry;
+using TypeMapReleaseEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapReleaseEntry;
+
+namespace Xamarin.Android.Tasks;
+
+class TypeMapObjectsXmlFile
+{
+ static readonly XmlWriterSettings settings = new XmlWriterSettings {
+ Indent = true,
+ NewLineOnAttributes = false,
+ OmitXmlDeclaration = true,
+ };
+
+ static readonly TypeMapObjectsXmlFile unscanned = new TypeMapObjectsXmlFile { WasScanned = false };
+
+ public string? AssemblyName { get; set; }
+ public bool FoundJniNativeRegistration { get; set; }
+ public List JavaToManagedDebugEntries { get; } = [];
+ public List ManagedToJavaDebugEntries { get; } = [];
+ public ModuleReleaseData? ModuleReleaseData { get; set; }
+ public bool HasDebugEntries => JavaToManagedDebugEntries.Count > 0 || ManagedToJavaDebugEntries.Count > 0;
+ public bool HasReleaseEntries => ModuleReleaseData is not null;
+
+ public bool WasScanned { get; private set; }
+
+ public void Export (string filename, TaskLoggingHelper log)
+ {
+ if (!HasDebugEntries && ModuleReleaseData == null) {
+ WriteEmptyFile (filename, log);
+ return;
+ }
+
+ using var sw = MemoryStreamPool.Shared.CreateStreamWriter ();
+
+ using (var xml = XmlWriter.Create (sw, settings))
+ Export (xml);
+
+ sw.Flush ();
+
+ Files.CopyIfStreamChanged (sw.BaseStream, filename);
+
+ log.LogDebugMessage ($"Wrote '{filename}', {JavaToManagedDebugEntries.Count} JavaToManagedDebugEntries, {ManagedToJavaDebugEntries.Count} ManagedToJavaDebugEntries, FoundJniNativeRegistration: {FoundJniNativeRegistration}");
+ }
+
+ void Export (XmlWriter xml)
+ {
+ xml.WriteStartElement ("api");
+ xml.WriteAttributeString ("type", HasDebugEntries ? "debug" : "release");
+ xml.WriteAttributeStringIfNotDefault ("assembly-name", AssemblyName);
+ xml.WriteAttributeStringIfNotDefault ("found-jni-native-registration", FoundJniNativeRegistration);
+
+ if (HasDebugEntries)
+ ExportDebugData (xml);
+ else if (HasReleaseEntries)
+ ExportReleaseData (xml);
+
+ xml.WriteEndElement ();
+ }
+
+ void ExportDebugData (XmlWriter xml)
+ {
+ if (JavaToManagedDebugEntries.Count > 0) {
+ xml.WriteStartElement ("java-to-managed");
+
+ foreach (var entry in JavaToManagedDebugEntries)
+ WriteTypeMapDebugEntry (xml, entry);
+
+ xml.WriteEndElement ();
+ }
+
+ if (ManagedToJavaDebugEntries.Count > 0) {
+ xml.WriteStartElement ("managed-to-java");
+
+ foreach (var entry in ManagedToJavaDebugEntries)
+ WriteTypeMapDebugEntry (xml, entry);
+
+ xml.WriteEndElement ();
+ }
+ }
+
+ void WriteTypeMapDebugEntry (XmlWriter xml, TypeMapDebugEntry entry)
+ {
+ xml.WriteStartElement ("entry");
+ xml.WriteAttributeStringIfNotDefault ("java-name", entry.JavaName);
+ xml.WriteAttributeStringIfNotDefault ("managed-name", entry.ManagedName);
+ xml.WriteAttributeStringIfNotDefault ("skip-in-java-to-managed", entry.SkipInJavaToManaged);
+ xml.WriteAttributeStringIfNotDefault ("is-invoker", entry.IsInvoker);
+ xml.WriteEndElement ();
+ }
+
+ void ExportReleaseData (XmlWriter xml)
+ {
+ if (ModuleReleaseData is null)
+ return;
+
+ xml.WriteStartElement ("module");
+
+ xml.WriteAttributeStringIfNotDefault ("assembly-name", ModuleReleaseData.AssemblyName);
+ xml.WriteAttributeStringIfNotDefault ("mvid", ModuleReleaseData.Mvid.ToString ("N"));
+ xml.WriteAttributeStringIfNotDefault ("mvid-bytes", Convert.ToBase64String (ModuleReleaseData.MvidBytes));
+
+ if (ModuleReleaseData.Types?.Length > 0) {
+ xml.WriteStartElement ("types");
+
+ foreach (var entry in ModuleReleaseData.DuplicateTypes)
+ ExportTypeMapReleaseEntry (xml, entry, null);
+
+ xml.WriteEndElement ();
+ }
+
+ if (ModuleReleaseData.DuplicateTypes?.Count > 0) {
+ xml.WriteStartElement ("duplicates");
+
+ foreach (var entry in ModuleReleaseData.DuplicateTypes)
+ ExportTypeMapReleaseEntry (xml, entry, null);
+
+ xml.WriteEndElement ();
+ }
+
+ if (ModuleReleaseData.TypesScratch?.Count > 0) {
+ xml.WriteStartElement ("types-scratch");
+
+ foreach (var kvp in ModuleReleaseData.TypesScratch)
+ ExportTypeMapReleaseEntry (xml, kvp.Value, kvp.Key);
+
+ xml.WriteEndElement ();
+ }
+
+ xml.WriteEndElement ();
+ }
+
+ void ExportTypeMapReleaseEntry (XmlWriter xml, TypeMapReleaseEntry entry, string? key)
+ {
+ xml.WriteStartElement ("entry");
+
+ xml.WriteAttributeStringIfNotDefault ("key", key);
+ xml.WriteAttributeStringIfNotDefault ("java-name", entry.JavaName);
+ xml.WriteAttributeStringIfNotDefault ("managed-type-name", entry.ManagedTypeName);
+ xml.WriteAttributeStringIfNotDefault ("token", entry.Token.ToString ());
+ xml.WriteAttributeStringIfNotDefault ("skip-in-java-to-managed", entry.SkipInJavaToManaged);
+
+ xml.WriteEndElement ();
+ }
+
+ ///
+ /// Given an assembly path, return the path to the ".typemap.xml" file that should be next to it.
+ ///
+ public static string GetTypeMapObjectsXmlFilePath (string assemblyPath)
+ => Path.ChangeExtension (assemblyPath, ".typemap.xml");
+
+ public static TypeMapObjectsXmlFile Import (string filename)
+ {
+ // If the file has zero length, then the assembly wasn't scanned because it couldn't contain JLOs.
+ // This check is much faster than loading and parsing an empty XML file.
+ var fi = new FileInfo (filename);
+
+ if (fi.Length == 0)
+ return unscanned;
+
+ var xml = XDocument.Load (filename);
+ var root = xml.Root ?? throw new InvalidOperationException ($"Invalid XML file '{filename}'");
+
+ var type = root.GetRequiredAttribute ("type");
+ var assemblyName = root.GetAttributeOrDefault ("assembly-name", (string?)null);
+ var foundJniNativeRegistration = root.GetAttributeOrDefault ("found-jni-native-registration", false);
+
+ var file = new TypeMapObjectsXmlFile {
+ WasScanned = true,
+ AssemblyName = assemblyName,
+ FoundJniNativeRegistration = foundJniNativeRegistration,
+ };
+
+ if (type == "debug")
+ ImportDebugData (root, file);
+ else if (type == "release")
+ ImportReleaseData (root, file);
+
+ return file;
+ }
+
+ static void ImportDebugData (XElement root, TypeMapObjectsXmlFile file)
+ {
+ var isMonoAndroid = root.GetAttributeOrDefault ("assembly-name", string.Empty) == "Mono.Android";
+ var javaToManaged = root.Element ("java-to-managed");
+
+ if (javaToManaged is not null) {
+ foreach (var entry in javaToManaged.Elements ("entry"))
+ file.JavaToManagedDebugEntries.Add (FromDebugEntryXml (entry, isMonoAndroid));
+ }
+
+ var managedToJava = root.Element ("managed-to-java");
+
+ if (managedToJava is not null) {
+ foreach (var entry in managedToJava.Elements ("entry"))
+ file.ManagedToJavaDebugEntries.Add (FromDebugEntryXml (entry, isMonoAndroid));
+ }
+ }
+
+ static void ImportReleaseData (XElement root, TypeMapObjectsXmlFile file)
+ {
+ var module = root.Element ("module");
+
+ if (module is null)
+ return;
+
+ file.ModuleReleaseData = new ModuleReleaseData {
+ AssemblyName = module.GetAttributeOrDefault ("assembly-name", string.Empty),
+ Mvid = Guid.Parse (module.GetAttributeOrDefault ("mvid", Guid.Empty.ToString ())),
+ MvidBytes = Convert.FromBase64String (module.GetAttributeOrDefault ("mvid-bytes", string.Empty)),
+ TypesScratch = new Dictionary (StringComparer.Ordinal),
+ DuplicateTypes = new List (),
+ };
+
+ if (module.Element ("types") is XElement types)
+ file.ModuleReleaseData.Types = types.Elements ("entry")
+ .Select (FromReleaseEntryXml)
+ .ToArray ();
+
+ if (module.Element ("duplicates") is XElement duplicates)
+ file.ModuleReleaseData.DuplicateTypes.AddRange (duplicates.Elements ("entry")
+ .Select (FromReleaseEntryXml));
+
+ if (module.Element ("types-scratch") is XElement typesScratch)
+ file.ModuleReleaseData.TypesScratch.AddRange (typesScratch.Elements ("entry")
+ .Select (elem => new KeyValuePair (elem.GetAttributeOrDefault ("key", string.Empty), FromReleaseEntryXml (elem))));
+ }
+
+ public static void WriteEmptyFile (string destination, TaskLoggingHelper log)
+ {
+ log.LogDebugMessage ($"Writing empty file '{destination}'");
+
+ // We write a zero byte file to indicate the file couldn't have JLO types and wasn't scanned
+ File.Create (destination).Dispose ();
+ }
+
+ static TypeMapDebugEntry FromDebugEntryXml (XElement entry, bool isMonoAndroid)
+ {
+ var javaName = entry.GetAttributeOrDefault ("java-name", string.Empty);
+ var managedName = entry.GetAttributeOrDefault ("managed-name", string.Empty);
+ var skipInJavaToManaged = entry.GetAttributeOrDefault ("skip-in-java-to-managed", false);
+ var isInvoker = entry.GetAttributeOrDefault ("is-invoker", false);
+
+ return new TypeMapDebugEntry {
+ JavaName = javaName,
+ ManagedName = managedName,
+ SkipInJavaToManaged = skipInJavaToManaged,
+ IsInvoker = isInvoker,
+ IsMonoAndroid = isMonoAndroid,
+ };
+ }
+
+ static TypeMapReleaseEntry FromReleaseEntryXml (XElement entry)
+ {
+ var javaName = entry.GetAttributeOrDefault ("java-name", string.Empty);
+ var managedTypeName = entry.GetAttributeOrDefault ("managed-type-name", string.Empty);
+ var token = entry.GetAttributeOrDefault ("token", 0u);
+ var skipInJavaToManaged = entry.GetAttributeOrDefault ("skip-in-java-to-managed", false);
+
+ return new TypeMapReleaseEntry {
+ JavaName = javaName,
+ ManagedTypeName = managedTypeName,
+ Token = token,
+ SkipInJavaToManaged = skipInJavaToManaged,
+ };
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
index a15756ff32d..f3a0b70801b 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
@@ -47,6 +47,7 @@
+
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index c04712421c4..2f70668561d 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -1613,8 +1613,12 @@ because xbuild doesn't support framework reference assemblies.