diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RewriteMarshalMethodsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RewriteMarshalMethodsStep.cs
new file mode 100644
index 00000000000..e85ef7af3e7
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RewriteMarshalMethodsStep.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using Java.Interop.Tools.JavaCallableWrappers;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Utilities;
+using Mono.Cecil;
+using Mono.Linker;
+using Mono.Linker.Steps;
+using Xamarin.Android.Tasks;
+using Xamarin.Android.Tools;
+
+namespace MonoDroid.Tuner;
+
+///
+/// Scans an assembly for marshal methods and converts them to LLVM marshal methods.
+///
+public class RewriteMarshalMethodsStep : BaseStep, IAssemblyModifierPipelineStep
+{
+ public TaskLoggingHelper Log { get; set; }
+
+ bool? brokenExceptionTransitionsEnabled;
+
+ public RewriteMarshalMethodsStep (TaskLoggingHelper log)
+ {
+ Log = log;
+ }
+
+ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
+ {
+ if (!context.IsAndroidAssembly)
+ return;
+
+ var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;
+
+ if (action == AssemblyAction.Delete)
+ return;
+
+ // We need to parse the environment files supplied by the user to see if they want to use broken exception transitions. This information is needed
+ // in order to properly generate wrapper methods in the marshal methods assembly rewriter.
+ // We don't care about those generated by us, since they won't contain the `XA_BROKEN_EXCEPTION_TRANSITIONS` variable we look for.
+ if (!brokenExceptionTransitionsEnabled.HasValue) {
+ var environmentParser = new EnvironmentFilesParser ();
+ brokenExceptionTransitionsEnabled = environmentParser.AreBrokenExceptionTransitionsEnabled (context.Environments);
+ }
+
+ var collection = MarshalMethodsCollection.FromAssembly (context.Architecture, assembly, Context.Resolver, Log);
+
+ var state = new NativeCodeGenState (context.Architecture, Context, Context.Resolver, [], [], collection);
+ Run (state, context);
+
+ var stateObject = MarshalMethodCecilAdapter.CreateNativeCodeGenState (context.Architecture, state);
+ var destinationMarshalMethodsXml = MarshalMethodsXmlFile.GetMarshalMethodsXmlFilePath (context.Destination.ItemSpec);
+
+ MarshalMethodsXmlFile.Export (destinationMarshalMethodsXml, context.Architecture, stateObject, Log);
+
+ // TODO: Only return true if we actually modified the assembly
+ context.IsAssemblyModified = true;
+ }
+
+ void Run (NativeCodeGenState state, StepContext context)
+ {
+ if (state.Classifier is null) {
+ Log.LogError ("state.Classifier cannot be null if marshal methods are enabled");
+ return;
+ }
+
+ if (!context.EnableManagedMarshalMethodsLookup) {
+ RewriteMethods (state, brokenExceptionTransitionsEnabled.GetValueOrDefault ());
+ state.Classifier.AddSpecialCaseMethods ();
+ } else {
+ // We need to run `AddSpecialCaseMethods` before `RewriteMarshalMethods` so that we can see the special case
+ // methods (such as TypeManager.n_Activate_mm) when generating the managed lookup tables.
+ state.Classifier.AddSpecialCaseMethods ();
+ state.ManagedMarshalMethodsLookupInfo = new ManagedMarshalMethodsLookupInfo (Log);
+ RewriteMethods (state, brokenExceptionTransitionsEnabled.GetValueOrDefault ());
+ }
+
+ Log.LogDebugMessage ($"[{state.TargetArch}] Number of generated marshal methods: {state.Classifier.MarshalMethods.Count}");
+ if (state.Classifier.DynamicallyRegisteredMarshalMethods.Count > 0) {
+ Log.LogWarning ($"[{state.TargetArch}] Number of methods in the project that will be registered dynamically: {state.Classifier.DynamicallyRegisteredMarshalMethods.Count}");
+ }
+
+ var wrappedCount = state.Classifier.MarshalMethods.Sum (m => m.Value.Count (m2 => m2.NeedsBlittableWorkaround));
+
+ if (wrappedCount > 0) {
+ // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers
+ Log.LogDebugMessage ($"[{state.TargetArch}] Number of methods in the project that need marshal method wrappers: {wrappedCount}");
+ }
+ }
+
+ void RewriteMethods (NativeCodeGenState state, bool brokenExceptionTransitionsEnabled)
+ {
+ if (state.Classifier == null) {
+ return;
+ }
+
+ var rewriter = new MarshalMethodsAssemblyRewriter (Log, state.TargetArch, state.Classifier, state.Resolver, state.ManagedMarshalMethodsLookupInfo);
+ rewriter.Rewrite (brokenExceptionTransitionsEnabled);
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
index 4086bde3e99..6c19f9f623c 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
@@ -24,6 +24,8 @@ public class AssemblyModifierPipeline : AndroidTask
{
public override string TaskPrefix => "AMP";
+ public string AndroidSdkPlatform { get; set; } = "";
+
public string ApplicationJavaClass { get; set; } = "";
public string CodeGenerationTarget { get; set; } = "";
@@ -37,8 +39,15 @@ public class AssemblyModifierPipeline : AndroidTask
public bool EnableMarshalMethods { get; set; }
+ public bool EnableManagedMarshalMethodsLookup { get; set; }
+
+ public ITaskItem [] Environments { get; set; } = [];
+
public bool ErrorOnCustomJavaObject { get; set; }
+ // If we're using ILLink, this process modifies the linked assemblies in place
+ protected virtual bool ModifiesAssembliesInPlace => true;
+
public string? PackageNamingPolicy { get; set; }
///
@@ -64,7 +73,7 @@ public class AssemblyModifierPipeline : AndroidTask
[Required]
public string TargetName { get; set; } = "";
- protected JavaPeerStyle codeGenerationTarget;
+ JavaPeerStyle codeGenerationTarget;
public override bool RunTask ()
{
@@ -76,6 +85,8 @@ public override bool RunTask ()
var readerParameters = new ReaderParameters {
ReadSymbols = ReadSymbols,
+ ReadWrite = ModifiesAssembliesInPlace || EnableMarshalMethods,
+ InMemory = ModifiesAssembliesInPlace || EnableMarshalMethods,
};
Dictionary> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, Array.Empty (), validate: false);
@@ -118,7 +129,7 @@ public override bool RunTask ()
Directory.CreateDirectory (Path.GetDirectoryName (destination.ItemSpec));
- RunPipeline (pipeline!, source, destination);
+ RunPipeline (pipeline!, source, destination, perArchAssemblies [sourceArch].Values.ToArray ());
}
pipeline?.Dispose ();
@@ -138,6 +149,13 @@ protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCont
findJavaObjectsStep.Initialize (context);
pipeline.Steps.Add (findJavaObjectsStep);
+ // RewriteMarshalMethodsStep
+ if (EnableMarshalMethods && !Debug) {
+ var rewriteMarshalMethodsStep = new RewriteMarshalMethodsStep (Log);
+ rewriteMarshalMethodsStep.Initialize (context);
+ pipeline.Steps.Add (rewriteMarshalMethodsStep);
+ }
+
// SaveChangedAssemblyStep
var writerParameters = new WriterParameters {
DeterministicMvid = Deterministic,
@@ -156,13 +174,15 @@ protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCont
pipeline.Steps.Add (findTypeMapObjectsStep);
}
- void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination)
+ void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination, ITaskItem [] archAssemblies)
{
var assembly = pipeline.Resolver.GetAssembly (source.ItemSpec);
- var context = new StepContext (source, destination) {
+ var context = new StepContext (source, destination, AndroidSdkPlatform, Environments, archAssemblies) {
+ Architecture = MonoAndroidHelper.GetRequiredValidArchitecture (source),
CodeGenerationTarget = codeGenerationTarget,
EnableMarshalMethods = EnableMarshalMethods,
+ EnableManagedMarshalMethodsLookup = EnableManagedMarshalMethodsLookup,
IsAndroidAssembly = MonoAndroidHelper.IsAndroidAssembly (source),
IsDebug = Debug,
IsFrameworkAssembly = MonoAndroidHelper.IsFrameworkAssembly (source),
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
index 31818e8cafc..36cd5c529bd 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
@@ -155,6 +155,7 @@ void Run (bool useMarshalMethods)
// Now that "never" never happened, we can proceed knowing that at least the assembly sets are the same for each architecture
var nativeCodeGenStates = new ConcurrentDictionary ();
+ var nativeCodeGenStateObjects = new ConcurrentDictionary ();
NativeCodeGenState? templateCodeGenState = null;
var firstArch = allAssembliesPerArch.First ().Key;
@@ -169,7 +170,7 @@ void Run (bool useMarshalMethods)
// Pick the "first" one as the one to generate Java code for
var generateJavaCode = arch == firstArch;
- (bool success, NativeCodeGenState? state) = GenerateJavaSourcesAndMaybeClassifyMarshalMethods (arch, archAssemblies, MaybeGetArchAssemblies (userAssembliesPerArch, arch), useMarshalMethods, generateJavaCode);
+ (bool success, NativeCodeGenState? state, NativeCodeGenStateObject? stateObject) = GenerateJavaSourcesAndMaybeClassifyMarshalMethods (arch, archAssemblies, MaybeGetArchAssemblies (userAssembliesPerArch, arch), useMarshalMethods, generateJavaCode);
if (!success) {
generateSucceeded = false;
@@ -181,6 +182,7 @@ void Run (bool useMarshalMethods)
}
nativeCodeGenStates.TryAdd (arch, state);
+ nativeCodeGenStateObjects.TryAdd (arch, stateObject);
});
// If we hit an error generating the Java code, we should bail out now
@@ -198,6 +200,21 @@ void Run (bool useMarshalMethods)
// Save NativeCodeGenState for later tasks
Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenState)} to {nameof (NativeCodeGenStateRegisterTaskKey)}");
BuildEngine4.RegisterTaskObjectAssemblyLocal (MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory), nativeCodeGenStates, RegisteredTaskObjectLifetime.Build);
+
+ // If we still need the NativeCodeGenState in the task because we're using marshal methods,
+ // we're going to transfer it to a new object that doesn't require holding open Cecil AssemblyDefinitions.
+ if (useMarshalMethods) {
+ //var nativeCodeGenStateObject = MarshalMethodCecilAdapter.GetNativeCodeGenStateCollection (Log, nativeCodeGenStates);
+ var nativeCodeGenStateCollection = new NativeCodeGenStateCollection ();
+
+ foreach (var kvp in nativeCodeGenStateObjects) {
+ nativeCodeGenStateCollection.States.Add (kvp.Key, kvp.Value);
+ Log.LogDebugMessage ($"Added NativeCodeGenStateObject for arch: {kvp.Key}, containing {kvp.Value.MarshalMethods.Count} marshal methods");
+ }
+
+ Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenStateObject)} to {nameof (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey)}");
+ BuildEngine4.RegisterTaskObjectAssemblyLocal (MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory), nativeCodeGenStateCollection, RegisteredTaskObjectLifetime.Build);
+ }
}
internal static Dictionary MaybeGetArchAssemblies (Dictionary> dict, AndroidTargetArch arch)
@@ -209,7 +226,7 @@ internal static Dictionary MaybeGetArchAssemblies (Dictionary
return archDict;
}
- (bool success, NativeCodeGenState? stubsState) GenerateJavaSourcesAndMaybeClassifyMarshalMethods (AndroidTargetArch arch, Dictionary assemblies, Dictionary userAssemblies, bool useMarshalMethods, bool generateJavaCode)
+ (bool success, NativeCodeGenState? stubsState, NativeCodeGenStateObject? stateObject) GenerateJavaSourcesAndMaybeClassifyMarshalMethods (AndroidTargetArch arch, Dictionary assemblies, Dictionary userAssemblies, bool useMarshalMethods, bool generateJavaCode)
{
XAAssemblyResolver resolver = MakeResolver (useMarshalMethods, arch, assemblies);
var tdCache = new TypeDefinitionCache ();
@@ -227,15 +244,62 @@ internal static Dictionary MaybeGetArchAssemblies (Dictionary
}
if (!success) {
- return (false, null);
+ return (false, null, null);
}
MarshalMethodsCollection? marshalMethodsCollection = null;
+ NativeCodeGenStateObject? stateObject = null;
+
+ if (useMarshalMethods) {
+ stateObject = new NativeCodeGenStateObject ();
+
+ foreach (var assembly in assemblies.Values) {
+ var marshalMethodXmlFile = MarshalMethodsXmlFile.GetMarshalMethodsXmlFilePath (assembly.ItemSpec);
+
+ if (!File.Exists (marshalMethodXmlFile))
+ continue;
+
+ var xml = MarshalMethodsXmlFile.Import (marshalMethodXmlFile);
+
+ if (xml is null || xml?.Value.MarshalMethods.Count == 0) {
+ Log.LogDebugMessage ($"'{marshalMethodXmlFile}' is empty, skipping.");
+ continue;
+ }
+
+ foreach (var kvp in xml.Value.Value.MarshalMethods) {
+ if (!stateObject.MarshalMethods.TryGetValue (kvp.Key, out var methods)) {
+ methods = new List ();
+ stateObject.MarshalMethods.Add (kvp.Key, methods);
+ }
+
+ foreach (var method in kvp.Value) {
+ // We don't need to add the special case method multiple times
+ if (methods.Count > 0 && method.IsSpecial)
+ continue;
+ methods.Add (method);
+ }
+ }
+ }
+
+ //marshalMethodsCollection = MarshalMethodsCollection.FromAssemblies (arch, assemblies.Values.ToList (), resolver, Log);
+ //marshalMethodsCollection.AddSpecialCaseMethods ();
+
+ marshalMethodsCollection = new EmptyMarshalMethodsCollection ();
+ }
+
+ var state = new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, marshalMethodsCollection);
- if (useMarshalMethods)
- marshalMethodsCollection = MarshalMethodsCollection.FromAssemblies (arch, assemblies.Values.ToList (), resolver, Log);
+ //if (useMarshalMethods) {
+ // var info = new ManagedMarshalMethodsLookupInfo (Log);
- return (true, new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, marshalMethodsCollection));
+ // foreach (var kvp in marshalMethodsCollection.MarshalMethods)
+ // foreach (var method in kvp.Value)
+ // info.AddNativeCallbackWrapper (method.NativeCallback);
+
+ // state.ManagedMarshalMethodsLookupInfo = info;
+ //}
+
+ return (true, state, stateObject);
}
(List allJavaTypes, List javaTypesForJCW) ScanForJavaTypes (XAAssemblyResolver res, TypeDefinitionCache cache, Dictionary assemblies, Dictionary userAssemblies, bool useMarshalMethods)
@@ -343,4 +407,43 @@ void CompareLists (string name, List list1, List list2)
Log.LogDebugMessage ($"No differences");
}
}
+
+ class EmptyMarshalMethodsCollection : MarshalMethodsCollection
+ {
+ public override HashSet AssembliesWithMarshalMethods => throw new NotSupportedException ();
+
+ ///
+ /// Marshal methods that have already been rewritten as LLVM marshal methods.
+ ///
+ public override IDictionary> ConvertedMarshalMethods => throw new NotSupportedException ();
+
+ ///
+ /// Marshal methods that cannot be rewritten and must be registered dynamically.
+ ///
+ public override List DynamicallyRegisteredMarshalMethods => throw new NotSupportedException ();
+
+ ///
+ /// Marshal methods that can be rewritten as LLVM marshal methods.
+ ///
+ public override IDictionary> MarshalMethods => throw new NotSupportedException ();
+
+ public EmptyMarshalMethodsCollection ()
+ {
+ }
+
+ public override void AddSpecialCaseMethods ()
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, MethodDefinition registeredMethod, MethodDefinition implementedMethod, CustomAttribute registerAttribute)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override bool TypeHasDynamicallyRegisteredMethods (TypeDefinition type)
+ {
+ throw new NotImplementedException ();
+ }
+ }
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs
index 1b9195d85c7..c04c0ea72b7 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs
@@ -28,7 +28,6 @@ public class GenerateMainAndroidManifest : AndroidTask
public string CodeGenerationTarget { get; set; } = "";
public bool Debug { get; set; }
public bool EmbedAssemblies { get; set; }
- public bool EnableMarshalMethods { get; set; }
[Required]
public string IntermediateOutputDirectory { get; set; } = "";
public string []? ManifestPlaceholders { get; set; }
@@ -52,8 +51,6 @@ public class GenerateMainAndroidManifest : AndroidTask
AndroidRuntime androidRuntime;
JavaPeerStyle codeGenerationTarget;
- bool UseMarshalMethods => !Debug && EnableMarshalMethods;
-
public override bool RunTask ()
{
// Retrieve the stored NativeCodeGenState (and remove it from the cache)
@@ -74,16 +71,6 @@ public override bool RunTask ()
var additionalProviders = MergeManifest (templateCodeGenState, GenerateJavaStubs.MaybeGetArchAssemblies (userAssembliesPerArch, templateCodeGenState.TargetArch));
GenerateAdditionalProviderSources (templateCodeGenState, additionalProviders);
-
- // If we still need the NativeCodeGenState in the task because we're using marshal methods,
- // we're going to transfer it to a new object that doesn't require holding open Cecil AssemblyDefinitions.
- if (UseMarshalMethods) {
- var nativeCodeGenStateObject = MarshalMethodCecilAdapter.GetNativeCodeGenStateCollection (Log, nativeCodeGenStates);
-
- Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenStateObject)} to {nameof (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey)}");
- BuildEngine4.RegisterTaskObjectAssemblyLocal (MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory), nativeCodeGenStateObject, RegisteredTaskObjectLifetime.Build);
- }
-
// Dispose the Cecil resolvers so the assemblies are closed.
Log.LogDebugMessage ($"Disposing all {nameof (NativeCodeGenState)}.{nameof (NativeCodeGenState.Resolver)}");
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
index a811405e481..dc8faf555a2 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
@@ -63,12 +63,12 @@ public override bool RunTask ()
// 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)
+ //if (!useMarshalMethods)
GenerateAllTypeMappings ();
// Generate typemaps from the native code generator state (produced by the marshal method rewriter)
- if (RunCheckedBuild || useMarshalMethods)
- GenerateAllTypeMappingsFromNativeState (useMarshalMethods);
+ //if (RunCheckedBuild || useMarshalMethods)
+ // GenerateAllTypeMappingsFromNativeState (useMarshalMethods);
return !Log.HasLoggedErrors;
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
index d3e061b1f65..4339c0cd53e 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
@@ -14,6 +14,9 @@ public class LinkAssembliesNoShrink : AssemblyModifierPipeline
public bool AddKeepAlives { get; set; }
+ // If we're running instead of ILLink, this process writes copies of the assemblies to a new location
+ protected override bool ModifiesAssembliesInPlace => false;
+
public bool UseDesignerAssembly { get; set; }
protected override void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkContext context)
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPipeline.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPipeline.cs
index 703b08d19d1..67b58dc872b 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPipeline.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPipeline.cs
@@ -4,6 +4,7 @@
using Java.Interop.Tools.JavaCallableWrappers;
using Microsoft.Build.Framework;
using Mono.Cecil;
+using Xamarin.Android.Tools;
namespace Xamarin.Android.Tasks;
@@ -51,22 +52,31 @@ public interface IAssemblyModifierPipelineStep
public class StepContext
{
+ public AndroidTargetArch Architecture { get; set; }
+ public string AndroidSdkPlatform { get; set; }
public JavaPeerStyle CodeGenerationTarget { get; set; }
public ITaskItem Destination { get; }
public bool EnableMarshalMethods { get; set; }
+ public bool EnableManagedMarshalMethodsLookup { get; set; }
+ public ITaskItem [] Environments { 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; }
public bool IsUserAssembly { get; set; }
+ // This only contains the resolved assemblies for *this* architecture
+ public ITaskItem [] ResolvedAssemblies { get; set; }
public ITaskItem Source { get; }
public bool IsAndroidUserAssembly => IsAndroidAssembly && IsUserAssembly;
- public StepContext (ITaskItem source, ITaskItem destination)
+ public StepContext (ITaskItem source, ITaskItem destination, string androidSdkPlatform, ITaskItem [] environments, ITaskItem [] resolvedAssemblies)
{
- Source = source;
+ AndroidSdkPlatform = androidSdkPlatform;
Destination = destination;
+ Environments = environments;
+ ResolvedAssemblies = resolvedAssemblies;
+ Source = source;
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs
index cb446607eea..d44e1e31c90 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs
@@ -25,11 +25,11 @@ class JCWGeneratorContext
{
public AndroidTargetArch Arch { get; }
public TypeDefinitionCache TypeDefinitionCache { get; }
- public XAAssemblyResolver Resolver { get; }
+ public IAssemblyResolver Resolver { get; }
public IList JavaTypes { get; }
public ICollection ResolvedAssemblies { get; }
- public JCWGeneratorContext (AndroidTargetArch arch, XAAssemblyResolver res, ICollection resolvedAssemblies, List javaTypesForJCW, TypeDefinitionCache tdCache)
+ public JCWGeneratorContext (AndroidTargetArch arch, IAssemblyResolver res, ICollection resolvedAssemblies, List javaTypesForJCW, TypeDefinitionCache tdCache)
{
Arch = arch;
Resolver = res;
@@ -174,7 +174,7 @@ public static void EnsureAllArchitecturesAreIdentical (TaskLoggingHelper logger,
}
EnsureIdenticalCollections (logger, templateState, state);
- EnsureClassifiersMatch (logger, templateState, state);
+ //EnsureClassifiersMatch (logger, templateState, state);
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
index 02f3d1927b8..6b2fabf1360 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
@@ -11,6 +11,7 @@ namespace Xamarin.Android.Tasks;
class MarshalMethodCecilAdapter
{
+ [return: NotNullIfNotNull (nameof (nativeCodeGenStates))]
public static NativeCodeGenStateCollection? GetNativeCodeGenStateCollection (TaskLoggingHelper log, ConcurrentDictionary? nativeCodeGenStates)
{
if (nativeCodeGenStates is null) {
@@ -31,7 +32,7 @@ class MarshalMethodCecilAdapter
return collection;
}
- static NativeCodeGenStateObject CreateNativeCodeGenState (AndroidTargetArch arch, NativeCodeGenState state)
+ public static NativeCodeGenStateObject CreateNativeCodeGenState (AndroidTargetArch arch, NativeCodeGenState state)
{
var obj = new NativeCodeGenStateObject ();
@@ -54,6 +55,9 @@ static NativeCodeGenStateObject CreateNativeCodeGenState (AndroidTargetArch arch
static MarshalMethodEntryObject CreateEntry (MarshalMethodEntry entry, ManagedMarshalMethodsLookupInfo? info)
{
+ var nativeCallback = entry.NativeCallback;
+ //var nativeCallback = entry is ConvertedMarshalMethodEntry cmm ? cmm.ConvertedNativeCallback : entry.NativeCallback;
+
var obj = new MarshalMethodEntryObject (
declaringType: CreateDeclaringType (entry.DeclaringType),
implementedMethod: CreateMethod (entry.ImplementedMethod),
@@ -61,12 +65,12 @@ static MarshalMethodEntryObject CreateEntry (MarshalMethodEntry entry, ManagedMa
jniTypeName: entry.JniTypeName,
jniMethodName: entry.JniMethodName,
jniMethodSignature: entry.JniMethodSignature,
- nativeCallback: CreateMethod (entry.NativeCallback),
+ nativeCallback: CreateMethod (nativeCallback),
registeredMethod: CreateMethodBase (entry.RegisteredMethod)
);
if (info is not null) {
- (uint assemblyIndex, uint classIndex, uint methodIndex) = info.GetIndex (entry.NativeCallback);
+ (uint assemblyIndex, uint classIndex, uint methodIndex) = info.GetIndex (nativeCallback);
obj.NativeCallback.AssemblyIndex = assemblyIndex;
obj.NativeCallback.ClassIndex = classIndex;
@@ -142,7 +146,7 @@ class NativeCodeGenStateObject
public Dictionary> MarshalMethods { get; } = [];
}
-class MarshalMethodEntryObject
+public class MarshalMethodEntryObject
{
public MarshalMethodEntryTypeObject DeclaringType { get; }
public MarshalMethodEntryMethodObject? ImplementedMethod { get; }
@@ -166,7 +170,7 @@ public MarshalMethodEntryObject (MarshalMethodEntryTypeObject declaringType, Mar
}
}
-class MarshalMethodEntryAssemblyObject
+public class MarshalMethodEntryAssemblyObject
{
public string FullName { get; }
public string NameFullName { get; } // Cecil's Assembly.Name.FullName
@@ -182,7 +186,7 @@ public MarshalMethodEntryAssemblyObject (string fullName, string nameFullName, s
}
}
-class MarshalMethodEntryModuleObject
+public class MarshalMethodEntryModuleObject
{
public MarshalMethodEntryAssemblyObject Assembly { get; }
@@ -192,7 +196,7 @@ public MarshalMethodEntryModuleObject (MarshalMethodEntryAssemblyObject assembly
}
}
-class MarshalMethodEntryTypeObject
+public class MarshalMethodEntryTypeObject
{
public string FullName { get; }
public uint MetadataToken { get; }
@@ -206,7 +210,7 @@ public MarshalMethodEntryTypeObject (string fullName, uint metadataToken, Marsha
}
}
-class MarshalMethodEntryMethodBaseObject
+public class MarshalMethodEntryMethodBaseObject
{
public string FullName { get; }
@@ -216,7 +220,7 @@ public MarshalMethodEntryMethodBaseObject (string fullName)
}
}
-class MarshalMethodEntryMethodObject : MarshalMethodEntryMethodBaseObject
+public class MarshalMethodEntryMethodObject : MarshalMethodEntryMethodBaseObject
{
public string Name { get; }
public MarshalMethodEntryTypeObject DeclaringType { get; }
@@ -239,7 +243,7 @@ public MarshalMethodEntryMethodObject (string name, string fullName, MarshalMeth
}
}
-class MarshalMethodEntryMethodParameterObject
+public class MarshalMethodEntryMethodParameterObject
{
public string Name { get; }
public string ParameterTypeName { get; } // Cecil's ParameterDefinition.ParameterType.Name
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
index 47bb70fd95b..9bbee5c309a 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
@@ -26,11 +26,11 @@ sealed class AssemblyImports
readonly TaskLoggingHelper log;
readonly MarshalMethodsCollection classifier;
- readonly XAAssemblyResolver resolver;
+ readonly IAssemblyResolver resolver;
readonly AndroidTargetArch targetArch;
readonly ManagedMarshalMethodsLookupInfo? managedMarshalMethodsLookupInfo;
- public MarshalMethodsAssemblyRewriter (TaskLoggingHelper log, AndroidTargetArch targetArch, MarshalMethodsCollection classifier, XAAssemblyResolver resolver, ManagedMarshalMethodsLookupInfo? managedMarshalMethodsLookupInfo)
+ public MarshalMethodsAssemblyRewriter (TaskLoggingHelper log, AndroidTargetArch targetArch, MarshalMethodsCollection classifier, IAssemblyResolver resolver, ManagedMarshalMethodsLookupInfo? managedMarshalMethodsLookupInfo)
{
this.log = log ?? throw new ArgumentNullException (nameof (log));
this.targetArch = targetArch;
@@ -123,78 +123,78 @@ public void Rewrite (bool brokenExceptionTransitions)
managedMarshalMethodLookupGenerator.Generate (classifier.MarshalMethods.Values);
}
- foreach (AssemblyDefinition asm in classifier.AssembliesWithMarshalMethods) {
- string? path = asm.MainModule.FileName;
- if (String.IsNullOrEmpty (path)) {
- throw new InvalidOperationException ($"[{targetArch}] Internal error: assembly '{asm}' does not specify path to its file");
- }
-
- string pathPdb = Path.ChangeExtension (path, ".pdb");
- bool havePdb = File.Exists (pathPdb);
-
- var writerParams = new WriterParameters {
- WriteSymbols = havePdb,
- };
-
- string directory = Path.Combine (Path.GetDirectoryName (path), "new");
- Directory.CreateDirectory (directory);
- string output = Path.Combine (directory, Path.GetFileName (path));
- log.LogDebugMessage ($"[{targetArch}] Writing new version of '{path}' assembly: {output}");
-
- // TODO: this should be used eventually, but it requires that all the types are reloaded from the assemblies before typemaps are generated
- // since Cecil doesn't update the MVID in the already loaded types
- //asm.MainModule.Mvid = Guid.NewGuid ();
- asm.Write (output, writerParams);
-
- CopyFile (output, path);
- RemoveFile (output);
-
- if (havePdb) {
- string outputPdb = Path.ChangeExtension (output, ".pdb");
- if (File.Exists (outputPdb)) {
- CopyFile (outputPdb, pathPdb);
- }
- RemoveFile (outputPdb);
- }
- }
-
- void CopyFile (string source, string target)
- {
- log.LogDebugMessage ($"[{targetArch}] Copying rewritten assembly: {source} -> {target}");
-
- string targetBackup = $"{target}.bak";
- if (File.Exists (target)) {
- // Try to avoid sharing violations by first renaming the target
- File.Move (target, targetBackup);
- }
-
- File.Copy (source, target, true);
-
- if (File.Exists (targetBackup)) {
- try {
- File.Delete (targetBackup);
- } catch (Exception ex) {
- // On Windows the deletion may fail, depending on lock state of the original `target` file before the move.
- log.LogDebugMessage ($"[{targetArch}] While trying to delete '{targetBackup}', exception was thrown: {ex}");
- log.LogDebugMessage ($"[{targetArch}] Failed to delete backup file '{targetBackup}', ignoring.");
- }
- }
- }
-
- void RemoveFile (string? path)
- {
- if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
- return;
- }
-
- try {
- log.LogDebugMessage ($"[{targetArch}] Deleting: {path}");
- File.Delete (path);
- } catch (Exception ex) {
- log.LogWarning ($"[{targetArch}] Unable to delete source file '{path}'");
- log.LogDebugMessage ($"[{targetArch}] {ex.ToString ()}");
- }
- }
+ //foreach (AssemblyDefinition asm in classifier.Assemblies) {
+ // string? path = asm.MainModule.FileName;
+ // if (String.IsNullOrEmpty (path)) {
+ // throw new InvalidOperationException ($"[{targetArch}] Internal error: assembly '{asm}' does not specify path to its file");
+ // }
+
+ // string pathPdb = Path.ChangeExtension (path, ".pdb");
+ // bool havePdb = File.Exists (pathPdb);
+
+ // var writerParams = new WriterParameters {
+ // WriteSymbols = havePdb,
+ // };
+
+ // string directory = Path.Combine (Path.GetDirectoryName (path), "new");
+ // Directory.CreateDirectory (directory);
+ // string output = Path.Combine (directory, Path.GetFileName (path));
+ // log.LogDebugMessage ($"[{targetArch}] Writing new version of '{path}' assembly: {output}");
+
+ // // TODO: this should be used eventually, but it requires that all the types are reloaded from the assemblies before typemaps are generated
+ // // since Cecil doesn't update the MVID in the already loaded types
+ // //asm.MainModule.Mvid = Guid.NewGuid ();
+ // asm.Write (output, writerParams);
+
+ // CopyFile (output, path);
+ // RemoveFile (output);
+
+ // if (havePdb) {
+ // string outputPdb = Path.ChangeExtension (output, ".pdb");
+ // if (File.Exists (outputPdb)) {
+ // CopyFile (outputPdb, pathPdb);
+ // }
+ // RemoveFile (outputPdb);
+ // }
+ //}
+
+ //void CopyFile (string source, string target)
+ //{
+ // log.LogDebugMessage ($"[{targetArch}] Copying rewritten assembly: {source} -> {target}");
+
+ // string targetBackup = $"{target}.bak";
+ // if (File.Exists (target)) {
+ // // Try to avoid sharing violations by first renaming the target
+ // File.Move (target, targetBackup);
+ // }
+
+ // File.Copy (source, target, true);
+
+ // if (File.Exists (targetBackup)) {
+ // try {
+ // File.Delete (targetBackup);
+ // } catch (Exception ex) {
+ // // On Windows the deletion may fail, depending on lock state of the original `target` file before the move.
+ // log.LogDebugMessage ($"[{targetArch}] While trying to delete '{targetBackup}', exception was thrown: {ex}");
+ // log.LogDebugMessage ($"[{targetArch}] Failed to delete backup file '{targetBackup}', ignoring.");
+ // }
+ // }
+ //}
+
+ //void RemoveFile (string? path)
+ //{
+ // if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
+ // return;
+ // }
+
+ // try {
+ // log.LogDebugMessage ($"[{targetArch}] Deleting: {path}");
+ // File.Delete (path);
+ // } catch (Exception ex) {
+ // log.LogWarning ($"[{targetArch}] Unable to delete source file '{path}'");
+ // log.LogDebugMessage ($"[{targetArch}] {ex.ToString ()}");
+ // }
+ //}
static bool HasUnmanagedCallersOnlyAttribute (MethodDefinition method)
{
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs
index a801d0c5355..0f5e8d66fe3 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs
@@ -111,6 +111,12 @@ public ConvertedMarshalMethodEntry (TypeDefinition declaringType, MethodDefiniti
{
ConvertedNativeCallback = convertedNativeCallback ?? throw new ArgumentNullException (nameof (convertedNativeCallback));
}
+
+ public ConvertedMarshalMethodEntry (TypeDefinition declaringType, MethodDefinition nativeCallback, string jniTypeName, string jniName, string jniSignature)
+ : base (declaringType, nativeCallback, jniTypeName, jniName, jniSignature)
+ {
+ ConvertedNativeCallback = nativeCallback;
+ }
}
sealed class DynamicallyRegisteredMarshalMethodEntry : MethodEntry
@@ -274,6 +280,7 @@ public bool Matches (MethodDefinition method)
// we need to accept them as equivalent
static readonly (string Source, string Replacement)[] equivalent_types = [
(Source: "System.Boolean", Replacement: "System.SByte"),
+ (Source: "System.Boolean", Replacement: "System.Byte"),
(Source: "System.Char", Replacement: "System.UInt16"),
];
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsCollection.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsCollection.cs
index 44e21810574..198056f6631 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsCollection.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsCollection.cs
@@ -1,4 +1,3 @@
-#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -21,22 +20,22 @@ class MarshalMethodsCollection : JavaCallableMethodClassifier
/// Assemblies that contain convertible marshal methods. These assemblies need to
/// have the proper assembly references added to them.
///
- public HashSet AssembliesWithMarshalMethods { get; } = [];
+ public virtual HashSet AssembliesWithMarshalMethods { get; } = [];
///
/// Marshal methods that have already been rewritten as LLVM marshal methods.
///
- public IDictionary> ConvertedMarshalMethods { get; } = new Dictionary> (StringComparer.Ordinal);
+ public virtual IDictionary> ConvertedMarshalMethods { get; } = new Dictionary> (StringComparer.Ordinal);
///
/// Marshal methods that cannot be rewritten and must be registered dynamically.
///
- public List DynamicallyRegisteredMarshalMethods { get; } = [];
+ public virtual List DynamicallyRegisteredMarshalMethods { get; } = [];
///
/// Marshal methods that can be rewritten as LLVM marshal methods.
///
- public IDictionary> MarshalMethods { get; } = new Dictionary> (StringComparer.Ordinal);
+ public virtual IDictionary> MarshalMethods { get; } = new Dictionary> (StringComparer.Ordinal);
readonly MarshalMethodsClassifier classifier;
readonly TaskLoggingHelper log;
@@ -50,11 +49,18 @@ public MarshalMethodsCollection (MarshalMethodsClassifier classifier)
resolver = classifier.Resolver;
}
+ public MarshalMethodsCollection ()
+ {
+ classifier = null!;
+ log = null!;
+ resolver = null!;
+ }
+
///
/// Adds MarshalMethodEntry for each method that won't be returned by the JavaInterop type scanner, mostly
/// used for hand-written methods (e.g. Java.Interop.TypeManager+JavaTypeManager::n_Activate)
///
- public void AddSpecialCaseMethods ()
+ public virtual void AddSpecialCaseMethods ()
{
AddTypeManagerSpecialCaseMethods ();
}
@@ -89,7 +95,26 @@ public static MarshalMethodsCollection FromAssemblies (AndroidTargetArch arch, L
return collection;
}
- static void ScanTypeForMarshalMethods (TypeDefinition type, MarshalMethodsCollection collection, XAAssemblyResolver resolver, TypeDefinitionCache cache, TaskLoggingHelper log, MarshalMethodsClassifier classifier)
+ public static MarshalMethodsCollection FromAssembly (AndroidTargetArch arch, AssemblyDefinition assembly, IAssemblyResolver resolver, TaskLoggingHelper log)
+ {
+ var cache = new TypeDefinitionCache ();
+ var classifier = new MarshalMethodsClassifier (cache, resolver, log);
+ var collection = new MarshalMethodsCollection (classifier);
+ var scanner = new XAJavaTypeScanner (arch, log, cache);
+
+ var javaTypes = scanner.GetJavaTypes (assembly);
+
+ foreach (var type in javaTypes) {
+ if (type.IsInterface || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (type, cache))
+ continue;
+
+ ScanTypeForMarshalMethods (type, collection, resolver, cache, log, classifier);
+ }
+
+ return collection;
+ }
+
+ static void ScanTypeForMarshalMethods (TypeDefinition type, MarshalMethodsCollection collection, IAssemblyResolver resolver, TypeDefinitionCache cache, TaskLoggingHelper log, MarshalMethodsClassifier classifier)
{
// Methods
foreach (var minfo in type.Methods.Where (m => !m.IsConstructor)) {
@@ -130,7 +155,7 @@ public override bool ShouldBeDynamicallyRegistered (TypeDefinition topType, Meth
return method is DynamicallyRegisteredMarshalMethodEntry;
}
- public bool TypeHasDynamicallyRegisteredMethods (TypeDefinition type)
+ public virtual bool TypeHasDynamicallyRegisteredMethods (TypeDefinition type)
{
return typesWithDynamicallyRegisteredMarshalMethods.Contains (type);
}
@@ -146,18 +171,20 @@ MethodEntry AddMethod (TypeDefinition topType, MethodDefinition registeredMethod
return dynamicMethod;
}
- if (marshalMethod is ConvertedMarshalMethodEntry convertedMethod) {
- var key = convertedMethod.GetStoreMethodKey (classifier.TypeDefinitionCache);
+ //if (marshalMethod is ConvertedMarshalMethodEntry convertedMethod) {
+ // var key = convertedMethod.GetStoreMethodKey (classifier.TypeDefinitionCache);
- if (!ConvertedMarshalMethods.TryGetValue (key, out var list)) {
- list = new List ();
- ConvertedMarshalMethods.Add (key, list);
- }
+ // if (!ConvertedMarshalMethods.TryGetValue (key, out var list)) {
+ // list = new List ();
+ // ConvertedMarshalMethods.Add (key, list);
+ // }
- list.Add (convertedMethod);
+ // list.Add (convertedMethod);
- return convertedMethod;
- }
+ // AssembliesWithMarshalMethods.Add (convertedMethod.NativeCallback.Module.Assembly);
+
+ // return convertedMethod;
+ //}
if (marshalMethod is MarshalMethodEntry marshalMethodEntry) {
var key = marshalMethodEntry.GetStoreMethodKey (classifier.TypeDefinitionCache);
@@ -273,7 +300,7 @@ void AddTypeManagerSpecialCaseMethods ()
}
var entry = new MarshalMethodEntry (javaTypeManager, nActivate_mm, jniTypeName!, jniMethodName!, jniSignature!); // NRT- Guarded above
- MarshalMethods.Add (".:!SpEcIaL:Java.Interop.TypeManager+JavaTypeManager::n_Activate_mm", new List { entry });
+ MarshalMethods.Add (".:!SpEcIaL:Java.Interop.TypeManager+JavaTypeManager::n_Activate_mm", [entry]);
[DoesNotReturn]
void ThrowMissingMethod (string name)
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
index 1420a25494d..3721071fc1c 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
@@ -342,7 +342,7 @@ void Init ()
foreach (MarshalMethodInfo method in allMethods) {
if (seenNativeSymbols.Contains (method.NativeSymbolName)) {
- Log.LogDebugMessage ($"Removed MM duplicate '{method.NativeSymbolName}' (implemented: {method.Method.ImplementedMethod.FullName}; registered: {method.Method.RegisteredMethod.FullName}");
+ Log.LogDebugMessage ($"Removed MM duplicate '{method.NativeSymbolName}' (implemented: {method.Method.ImplementedMethod?.FullName}; registered: {method.Method.RegisteredMethod?.FullName}");
continue;
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsXmlFile.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsXmlFile.cs
new file mode 100644
index 00000000000..b05b0836688
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsXmlFile.cs
@@ -0,0 +1,311 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.Serialization;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+class MarshalMethodsXmlFile
+{
+ static readonly XmlWriterSettings settings = new XmlWriterSettings {
+ Indent = true,
+ NewLineOnAttributes = false,
+ OmitXmlDeclaration = true,
+ };
+
+ public static void Export (string filename, AndroidTargetArch arch, NativeCodeGenStateObject state, TaskLoggingHelper log)
+ {
+ if (state.MarshalMethods.Count == 0) {
+ WriteEmptyFile (filename, log);
+ return;
+ }
+
+ using var sw = MemoryStreamPool.Shared.CreateStreamWriter ();
+ using (var xml = XmlWriter.Create (sw, settings))
+ Export (xml, arch, state);
+
+ sw.Flush ();
+
+ Files.CopyIfStreamChanged (sw.BaseStream, filename);
+
+ log.LogDebugMessage ($"Wrote '{filename}'");
+ }
+
+ public static KeyValuePair? Import (string filename)
+ {
+ // If the file has zero length, then the assembly doesn't have marshal methods.
+ // This check is much faster than loading and parsing an empty XML file.
+ var fi = new FileInfo (filename);
+
+ if (fi.Length == 0)
+ return null;
+
+ var xml = XDocument.Load (filename);
+ var root = xml.Root ?? throw new InvalidOperationException ($"Invalid XML file '{filename}'");
+
+ var arch = (AndroidTargetArch) Enum.Parse (typeof (AndroidTargetArch), root.GetRequiredAttribute ("arch"));
+ var state = new KeyValuePair (arch, new NativeCodeGenStateObject ());
+
+ var marshalMethods = root.Element ("marshal-methods");
+
+ if (marshalMethods is not null)
+ ImportMethods (marshalMethods, state);
+
+ return state;
+ }
+
+ static void ImportMethods (XElement element, KeyValuePair state)
+ {
+ foreach (var mm in element.Elements ("key")) {
+ var name = mm.GetRequiredAttribute ("name");
+ var methods = new List ();
+
+ foreach (var method in mm.Elements ("method")) {
+
+ var isSpecial = method.GetAttributeOrDefault ("is-special", false);
+ var jniTypeName = method.GetRequiredAttribute ("jni-type-name");
+ var jniMethodName = method.GetRequiredAttribute ("jni-method-name");
+ var jniMethodSignature = method.GetRequiredAttribute ("jni-method-signature");
+ var declaringType = ImportDeclaringType (method.Element ("declaring-type"));
+ var implementedMethod = ImportMethod (method.Element ("implemented-method"));
+ var nativeCallback = ImportMethod (method.Element ("native-callback"));
+ var registeredMethod = ImportMethodBase (method.Element ("registered-method"));
+
+ var obj = new MarshalMethodEntryObject (
+ declaringType: declaringType,
+ implementedMethod: implementedMethod,
+ isSpecial: isSpecial,
+ jniTypeName: jniTypeName,
+ jniMethodName: jniMethodName,
+ jniMethodSignature: jniMethodSignature,
+ nativeCallback: nativeCallback!,
+ registeredMethod: registeredMethod
+ );
+
+ methods.Add (obj);
+ }
+ state.Value.MarshalMethods.Add (name, methods);
+ }
+ }
+
+ static MarshalMethodEntryMethodObject? ImportMethod (XElement element)
+ {
+ if (element is null)
+ return null;
+
+ var name = element.GetRequiredAttribute ("name");
+ var fullName = element.GetRequiredAttribute ("full-name");
+ var metadataToken = uint.Parse (element.GetRequiredAttribute ("metadata-token"));
+ var assemblyIndex = element.GetUIntAttributeOrDefault ("assembly-index", null);
+ var classIndex = element.GetUIntAttributeOrDefault ("class-index", null);
+ var methodIndex = element.GetUIntAttributeOrDefault ("method-index", null);
+ var declaringType = ImportDeclaringType (element.Element ("declaring-type"));
+ var parameters = ImportParameters (element.Element ("parameters"));
+
+ var method = new MarshalMethodEntryMethodObject (
+ name: name,
+ fullName: fullName,
+ declaringType: declaringType,
+ metadataToken: metadataToken,
+ parameters: parameters
+ );
+
+ method.AssemblyIndex = assemblyIndex;
+ method.ClassIndex = classIndex;
+ method.MethodIndex = methodIndex;
+
+ return method;
+ }
+
+ static List ImportParameters (XElement element)
+ {
+ var parameters = new List ();
+
+ if (element is null)
+ return parameters;
+
+ foreach (var parameter in element.Elements ("parameter")) {
+ var name = parameter.GetAttributeOrDefault ("name", "");
+ var parameterTypeName = parameter.GetRequiredAttribute ("parameter-type-name");
+ var parameterObject = new MarshalMethodEntryMethodParameterObject (name, parameterTypeName);
+ parameters.Add (parameterObject);
+ }
+
+ return parameters;
+ }
+
+ static MarshalMethodEntryMethodBaseObject? ImportMethodBase (XElement element)
+ {
+ if (element is null)
+ return null;
+
+ var fullName = element.GetRequiredAttribute ("full-name");
+
+ return new MarshalMethodEntryMethodBaseObject (
+ fullName: fullName
+ );
+ }
+
+ static MarshalMethodEntryTypeObject ImportDeclaringType (XElement element)
+ {
+ var fullName = element.GetRequiredAttribute ("full-name");
+ var metadataToken = uint.Parse (element.GetRequiredAttribute ("metadata-token"));
+ var module = element.Element ("module");
+
+ return new MarshalMethodEntryTypeObject (
+ fullName: fullName,
+ metadataToken: metadataToken,
+ module: ImportModule (module)
+ );
+ }
+
+ static MarshalMethodEntryModuleObject ImportModule (XElement element)
+ {
+ var assembly = element.Element ("assembly");
+
+ return new MarshalMethodEntryModuleObject (
+ assembly: ImportAssembly (assembly)
+ );
+ }
+
+ static MarshalMethodEntryAssemblyObject ImportAssembly (XElement element)
+ {
+ var fullName = element.GetRequiredAttribute ("full-name");
+ var nameFullName = element.GetRequiredAttribute ("name-full-name");
+ var mainModuleFileName = element.GetAttributeOrDefault ("main-module-file-name", "");
+ var nameName = element.GetRequiredAttribute ("name-name");
+
+ return new MarshalMethodEntryAssemblyObject (
+ fullName: fullName,
+ nameFullName: nameFullName,
+ mainModuleFileName: mainModuleFileName,
+ nameName: nameName
+ );
+ }
+
+ static void Export (XmlWriter xml, AndroidTargetArch arch, NativeCodeGenStateObject state)
+ {
+ xml.WriteStartElement ("api");
+ xml.WriteAttributeString ("arch", arch.ToString ());
+
+ xml.WriteStartElement ("marshal-methods");
+
+ foreach (var kvp in state.MarshalMethods)
+ ExportMethods (xml, kvp);
+
+ xml.WriteEndElement ();
+ xml.WriteEndElement ();
+ }
+
+ static void ExportMethods (XmlWriter xml, KeyValuePair> kvp)
+ {
+ xml.WriteStartElement ("key");
+ xml.WriteAttributeString ("name", kvp.Key);
+
+ foreach (var method in kvp.Value) {
+ xml.WriteStartElement ("method");
+ xml.WriteAttributeString ("is-special", method.IsSpecial.ToString ());
+ xml.WriteAttributeString ("jni-type-name", method.JniTypeName);
+ xml.WriteAttributeString ("jni-method-name", method.JniMethodName);
+ xml.WriteAttributeString ("jni-method-signature", method.JniMethodSignature);
+ ExportDeclaringType (xml, method.DeclaringType);
+ ExportMethod (xml, "implemented-method", method.ImplementedMethod);
+ ExportMethod (xml, "native-callback", method.NativeCallback);
+ ExportRegisteredMethod (xml, method.RegisteredMethod);
+ xml.WriteEndElement ();
+ }
+
+ xml.WriteEndElement ();
+ }
+
+ static void ExportDeclaringType (XmlWriter xml, MarshalMethodEntryTypeObject type)
+ {
+ xml.WriteStartElement ("declaring-type");
+ xml.WriteAttributeString ("full-name", type.FullName);
+ xml.WriteAttributeString ("metadata-token", type.MetadataToken.ToString ());
+ ExportModule (xml, type.Module);
+ xml.WriteEndElement ();
+ }
+
+ static void ExportModule (XmlWriter xml, MarshalMethodEntryModuleObject module)
+ {
+ xml.WriteStartElement ("module");
+ ExportAssembly (xml, module.Assembly);
+ xml.WriteEndElement ();
+ }
+
+ static void ExportAssembly (XmlWriter xml, MarshalMethodEntryAssemblyObject assembly)
+ {
+ xml.WriteStartElement ("assembly");
+ xml.WriteAttributeString ("full-name", assembly.FullName);
+ xml.WriteAttributeString ("name-full-name", assembly.NameFullName);
+ xml.WriteAttributeString ("main-module-file-name", assembly.MainModuleFileName);
+ xml.WriteAttributeString ("name-name", assembly.NameName);
+ xml.WriteEndElement ();
+ }
+
+ static void ExportRegisteredMethod (XmlWriter xml, MarshalMethodEntryMethodBaseObject? method)
+ {
+ if (method is null)
+ return;
+
+ xml.WriteStartElement ("registered-method");
+ xml.WriteAttributeString ("full-name", method.FullName);
+ xml.WriteEndElement ();
+ }
+
+ static void ExportMethod (XmlWriter xml, string elementName, MarshalMethodEntryMethodObject? method)
+ {
+ if (method is null)
+ return;
+
+ xml.WriteStartElement (elementName);
+ xml.WriteAttributeString ("name", method.Name);
+ xml.WriteAttributeString ("full-name", method.FullName);
+ xml.WriteAttributeString ("metadata-token", method.MetadataToken.ToString ());
+ xml.WriteAttributeStringIfNotDefault ("assembly-index", method.AssemblyIndex.ToString ());
+ xml.WriteAttributeStringIfNotDefault ("class-index", method.ClassIndex.ToString ());
+ xml.WriteAttributeStringIfNotDefault ("method-index", method.MethodIndex.ToString ());
+
+ ExportDeclaringType (xml, method.DeclaringType);
+
+ if (method.HasParameters) {
+ xml.WriteStartElement ("parameters");
+ foreach (var parameter in method.Parameters)
+ ExportParameter (xml, parameter);
+ xml.WriteEndElement ();
+ }
+
+ xml.WriteEndElement ();
+ }
+
+ static void ExportParameter (XmlWriter xml, MarshalMethodEntryMethodParameterObject parameter)
+ {
+ xml.WriteStartElement ("parameter");
+ xml.WriteAttributeString ("name", parameter.Name);
+ xml.WriteAttributeString ("parameter-type-name", parameter.ParameterTypeName);
+ xml.WriteEndElement ();
+ }
+
+ ///
+ /// Given an assembly path, return the path to the ".mm.xml" file that should be next to it.
+ ///
+ public static string GetMarshalMethodsXmlFilePath (string assemblyPath)
+ => Path.ChangeExtension (assemblyPath, ".mm.xml");
+
+ 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 ();
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs
index 6a0785dfd94..bf77c68e802 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeCodeGenState.cs
@@ -31,13 +31,13 @@ class NativeCodeGenState
public List AllJavaTypes { get; }
public List JavaTypesForJCW { get; }
- public XAAssemblyResolver Resolver { get; }
+ public IAssemblyResolver Resolver { get; }
public TypeDefinitionCache TypeCache { get; }
public bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
public ManagedMarshalMethodsLookupInfo? ManagedMarshalMethodsLookupInfo { get; set; }
- public NativeCodeGenState (AndroidTargetArch arch, TypeDefinitionCache tdCache, XAAssemblyResolver resolver, List allJavaTypes, List javaTypesForJCW, MarshalMethodsCollection? classifier)
+ public NativeCodeGenState (AndroidTargetArch arch, TypeDefinitionCache tdCache, IAssemblyResolver resolver, List allJavaTypes, List javaTypesForJCW, MarshalMethodsCollection? classifier)
{
TargetArch = arch;
TypeCache = tdCache;
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/UtilityExtensions.cs b/src/Xamarin.Android.Build.Tasks/Utilities/UtilityExtensions.cs
index 5ed1a1da988..02d9c8a46b3 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/UtilityExtensions.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/UtilityExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Xml;
@@ -56,6 +57,20 @@ public static T GetAttributeOrDefault (this XElement xml, string name, T defa
return (T) Convert.ChangeType (value, typeof (T));
}
+ [return: NotNullIfNotNull (nameof (defaultValue))]
+ public static uint? GetUIntAttributeOrDefault (this XElement xml, string name, uint? defaultValue)
+ {
+ var value = xml.Attribute (name)?.Value;
+
+ if (string.IsNullOrWhiteSpace (value))
+ return defaultValue;
+
+ if (uint.TryParse (value, out var result))
+ return result;
+
+ return defaultValue;
+ }
+
public static string GetRequiredAttribute (this XElement xml, string name)
{
var value = xml.Attribute (name)?.Value;
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 cddbe0d63f2..9bae33d59a7 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
@@ -48,6 +48,7 @@
+
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index b48988a65e7..7657044b73d 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -1471,10 +1471,13 @@ because xbuild doesn't support framework reference assemblies.
Inputs="@(ResolvedAssemblies);$(_AndroidBuildPropertiesCache)"
Outputs="@(ResolvedAssemblies->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')">
-
-
+ -->