diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 7694c2bfeac..5ff64d07008 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -208,26 +208,35 @@ 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 MarshalMethodsClassifier? classifier = null; - AndroidTargetArch firstArch = AndroidTargetArch.None; - bool archAgnosticDone = false; + MarshalMethodsMirrorHelperState? mirrorHelperState = null; + bool abiAgnosticCode = false; foreach (var kvp in allAssembliesPerArch) { AndroidTargetArch arch = kvp.Key; Dictionary archAssemblies = kvp.Value; XAAssemblyResolverNew res = MakeResolver (useMarshalMethods, arch, archAssemblies); - if (!archAgnosticDone) { + if (!abiAgnosticCode) { var cache = new TypeDefinitionCache (); (List allJavaTypes, List javaTypesForJCW) = ScanForJavaTypes (res, cache, archAssemblies, userAssembliesPerArch[arch], useMarshalMethods); if (!GenerateJavaSourcesAndMaybeClassifyMarshalMethods (res, javaTypesForJCW, cache, useMarshalMethods, out classifier)) { return; } - firstArch = arch; - archAgnosticDone = true; + abiAgnosticCode = true; + + if (useMarshalMethods) { + mirrorHelperState = new MarshalMethodsMirrorHelperState (arch, archAssemblies, classifier); + } + } + + if (mirrorHelperState != null) { + mirrorHelperState.CurrentArch = arch; + mirrorHelperState.CurrentArchResolver = res; + mirrorHelperState.CurrentArchAssemblies = archAssemblies; } - RewriteMarshalMethods (classifier, firstArch, allAssembliesPerArch); + RewriteMarshalMethods (mirrorHelperState); } } @@ -252,14 +261,14 @@ void Run (bool useMarshalMethods) return (allJavaTypes, javaTypesForJCW); } - void RewriteMarshalMethods (MarshalMethodsClassifier? classifier, AndroidTargetArch classifiedArch, Dictionary> allAssembliesPerArch) + void RewriteMarshalMethods (MarshalMethodsMirrorHelperState? mirrorHelperState) { - if (classifier == null) { + if (mirrorHelperState == null) { return; } - var mirrorHelper = new MarshalMethodsMirrorHelper (classifier, classifiedArch, allAssembliesPerArch, Log); - IDictionary perArchMarshalMethods = mirrorHelper.Reflect (); + var mirrorHelper = new MarshalMethodsMirrorHelper (mirrorHelperState, Log); + ArchitectureMarshalMethods perArchMarshalMethods = mirrorHelper.Reflect (); // 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. @@ -888,7 +897,7 @@ void FindMatchingMethodInType (MarshalMethodEntry methodEntry, TypeDefinition ty } Log.LogDebugMessage ($"Found match for '{typeNativeCallbackMethod.FullName}' in {type.Module.FileName}"); - string methodKey = classifier.GetStoreMethodKey (methodEntry); + string methodKey = methodEntry.GetStoreMethodKey (classifier.TypeDefinitionCache); classifier.MarshalMethods[methodKey].Add (new MarshalMethodEntry (methodEntry, typeNativeCallbackMethod)); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs index 092a99c679a..77144121d01 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsClassifier.cs @@ -80,6 +80,13 @@ string EnsureNonEmpty (string s, string argName) return s; } + + public string GetStoreMethodKey (TypeDefinitionCache tdCache) + { + MethodDefinition registeredMethod = RegisteredMethod; + string typeName = registeredMethod.DeclaringType.FullName.Replace ('/', '+'); + return $"{typeName}, {registeredMethod.DeclaringType.GetPartialAssemblyName (tdCache)}\t{registeredMethod.Name}"; + } } class MarshalMethodsClassifier : JavaCallableMethodClassifier @@ -236,6 +243,7 @@ public bool Matches (MethodDefinition method) public ICollection Assemblies => assemblies; public ulong RejectedMethodCount => rejectedMethodCount; public ulong WrappedMethodCount => wrappedMethodCount; + public TypeDefinitionCache TypeDefinitionCache => tdCache; public MarshalMethodsClassifier (TypeDefinitionCache tdCache, IAssemblyResolver res, TaskLoggingHelper log) { @@ -688,16 +696,9 @@ FieldDefinition FindField (TypeDefinition type, string fieldName, bool lookForIn return FindField (tdCache.Resolve (type.BaseType), fieldName, lookForInherited); } - public string GetStoreMethodKey (MarshalMethodEntry methodEntry) - { - MethodDefinition registeredMethod = methodEntry.RegisteredMethod; - string typeName = registeredMethod.DeclaringType.FullName.Replace ('/', '+'); - return $"{typeName}, {registeredMethod.DeclaringType.GetPartialAssemblyName (tdCache)}\t{registeredMethod.Name}"; - } - void StoreMethod (MarshalMethodEntry entry) { - string key = GetStoreMethodKey (entry); + string key = entry.GetStoreMethodKey (tdCache); // Several classes can override the same method, we need to generate the marshal method only once, at the same time // keeping track of overloads diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsMirrorHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsMirrorHelper.cs index b243f00abd8..43bdafb837e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsMirrorHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsMirrorHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; +using Java.Interop.Tools.Cecil; using Microsoft.Android.Build.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -11,19 +12,37 @@ namespace Xamarin.Android.Tasks; sealed class ArchitectureMarshalMethods { - public readonly IDictionary> MarshalMethods; + public readonly IDictionary> Methods; public readonly ICollection Assemblies; public ArchitectureMarshalMethods (IDictionary> marshalMethods, ICollection assemblies) { - MarshalMethods = marshalMethods; + Methods = marshalMethods; Assemblies = assemblies; } public ArchitectureMarshalMethods () { - MarshalMethods = new Dictionary> (StringComparer.OrdinalIgnoreCase); - Assemblies = new List (); + Methods = new Dictionary> (StringComparer.OrdinalIgnoreCase); + Assemblies = new HashSet (); + } +} + +sealed class MarshalMethodsMirrorHelperState +{ + public readonly AndroidTargetArch TemplateArch; + public readonly Dictionary TemplateArchAssemblies; + public readonly MarshalMethodsClassifier Classifier; + + public AndroidTargetArch CurrentArch { get; set; } = AndroidTargetArch.None; + public XAAssemblyResolverNew? CurrentArchResolver { get; set; } + public Dictionary? CurrentArchAssemblies { get; set; } + + public MarshalMethodsMirrorHelperState (AndroidTargetArch classifiedArch, Dictionary classifiedArchAssemblies, MarshalMethodsClassifier classifier) + { + TemplateArch = classifiedArch; + TemplateArchAssemblies = classifiedArchAssemblies; + Classifier = classifier; } } @@ -40,75 +59,229 @@ public ArchitectureMarshalMethods () /// class MarshalMethodsMirrorHelper { - readonly MarshalMethodsClassifier classifier; - readonly AndroidTargetArch templateArch; - readonly Dictionary> allAssembliesPerArch; + readonly MarshalMethodsMirrorHelperState state; readonly TaskLoggingHelper log; - public MarshalMethodsMirrorHelper (MarshalMethodsClassifier classifier, AndroidTargetArch templateArch, Dictionary> allAssembliesPerArch, TaskLoggingHelper log) + public MarshalMethodsMirrorHelper (MarshalMethodsMirrorHelperState state, TaskLoggingHelper log) { - this.classifier = classifier; - this.templateArch = templateArch; - this.allAssembliesPerArch = allAssembliesPerArch; + this.state = state; this.log = log; } - public IDictionary Reflect () + public ArchitectureMarshalMethods Reflect () { - var ret = new Dictionary (); + if (state.CurrentArch == state.TemplateArch) { + log.LogDebugMessage ($"Not reflecting marshal methods for architecture '{state.CurrentArch}' since it's the template architecture"); + return new ArchitectureMarshalMethods (state.Classifier.MarshalMethods, state.Classifier.Assemblies); + } - foreach (var kvp in allAssembliesPerArch) { - AndroidTargetArch arch = kvp.Key; - IDictionary assemblies = kvp.Value; + var ret = new ArchitectureMarshalMethods (); + var assemblyCache = new Dictionary (StringComparer.OrdinalIgnoreCase); + var typeCache = new TypeDefinitionCache (); - if (arch == templateArch) { - ret.Add (arch, new ArchitectureMarshalMethods (classifier.MarshalMethods, classifier.Assemblies)); - continue; + log.LogDebugMessage ($"Reflecting marshal methods for architecture {state.CurrentArch}"); + foreach (var kvp in state.Classifier.MarshalMethods) { + foreach (MarshalMethodEntry templateMethod in kvp.Value) { + ReflectMethod (templateMethod, ret, assemblyCache, typeCache); } + } - ret.Add (arch, Reflect (arch, assemblies)); + if (ret.Assemblies.Count != state.TemplateArchAssemblies.Count) { + throw new InvalidOperationException ($"Internal error: expected to found {state.TemplateArchAssemblies.Count} assemblies for architecture '{state.CurrentArch}', but found {ret.Assemblies.Count} instead"); + } + + foreach (AssemblyDefinition templateAssembly in state.Classifier.Assemblies) { + bool found = false; + + foreach (AssemblyDefinition assembly in ret.Assemblies) { + if (String.Compare (templateAssembly.FullName, assembly.FullName, StringComparison.Ordinal) != 0) { + continue; + } + found = true; + break; + } + + if (!found) { + throw new InvalidOperationException ($"Internal error: assembly '{templateAssembly.FullName}' not found in assembly set for architecture '{state.CurrentArch}'"); + } } return ret; } - ArchitectureMarshalMethods Reflect (AndroidTargetArch arch, IDictionary archAssemblies) + void ReflectMethod (MarshalMethodEntry templateMethod, ArchitectureMarshalMethods archMethods, Dictionary assemblyCache, TypeDefinitionCache typeCache) { - var ret = new ArchitectureMarshalMethods (); - var cache = new Dictionary (StringComparer.OrdinalIgnoreCase); + TypeDefinition matchingType = GetMatchingType (templateMethod.NativeCallback, archMethods, assemblyCache, typeCache); + MethodDefinition nativeCallback = FindMatchingMethod (templateMethod.NativeCallback, matchingType, "native callback"); - log.LogDebugMessage ($"Reflecting marshal methods for architecture {arch}"); - foreach (var kvp in classifier.MarshalMethods) { - foreach (MarshalMethodEntry templateMethod in kvp.Value) { - ReflectMethod (arch, templateMethod, archAssemblies, ret, cache); + MarshalMethodEntry? archMethod = null; + if (templateMethod.IsSpecial) { + // All we need is the native callback in this case + archMethod = new MarshalMethodEntry ( + matchingType, + nativeCallback, + templateMethod.JniTypeName, + templateMethod.JniMethodName, + templateMethod.JniMethodSignature + ); + + AddMethod (archMethod); + return; + } + + // This marshal method must have **all** the associated methods present + matchingType = GetMatchingType (templateMethod.Connector, archMethods, assemblyCache, typeCache); + MethodDefinition connector = FindMatchingMethod (templateMethod.Connector, matchingType, "connector"); + + matchingType = GetMatchingType (templateMethod.RegisteredMethod, archMethods, assemblyCache, typeCache); + MethodDefinition registered = FindMatchingMethod (templateMethod.RegisteredMethod, matchingType, "registered"); + + matchingType = GetMatchingType (templateMethod.ImplementedMethod, archMethods, assemblyCache, typeCache); + MethodDefinition implemented = FindMatchingMethod (templateMethod.ImplementedMethod, matchingType, "implemented"); + + TypeDefinition? fieldMatchingType = GetMatchingType (templateMethod.CallbackField, archMethods, assemblyCache, typeCache); + FieldDefinition? callbackField = null; + if (fieldMatchingType != null) {// callback field is optional + callbackField = FindMatchingField (templateMethod.CallbackField, fieldMatchingType, "callback"); + } + + archMethod = new MarshalMethodEntry ( + matchingType, + nativeCallback, + connector, + registered, + implemented, + callbackField, + templateMethod.JniTypeName, + templateMethod.JniMethodName, + templateMethod.JniMethodSignature, + templateMethod.NeedsBlittableWorkaround + ); + AddMethod (archMethod); + + void AddMethod (MarshalMethodEntry method) + { + string methodKey = method.GetStoreMethodKey (typeCache); + if (!archMethods.Methods.TryGetValue (methodKey, out IList methodList)) { + methodList = new List (); + archMethods.Methods.Add (methodKey, methodList); } + + methodList.Add (method); } + } - return ret; + TypeDefinition GetMatchingType (MethodDefinition? templateMethod, ArchitectureMarshalMethods archMethods, Dictionary assemblyCache, TypeDefinitionCache typeCache) + { + if (templateMethod == null) { + throw new ArgumentNullException (nameof (templateMethod)); + } + + return GetMatchingType (templateMethod.DeclaringType, archMethods, assemblyCache, typeCache); } - void ReflectMethod (AndroidTargetArch arch, MarshalMethodEntry templateMethod, IDictionary archAssemblies, ArchitectureMarshalMethods archMarshalMethods, Dictionary cache) + TypeDefinition? GetMatchingType (FieldDefinition? templateField, ArchitectureMarshalMethods archMethods, Dictionary assemblyCache, TypeDefinitionCache typeCache) { - string? assemblyName = templateMethod.NativeCallback.DeclaringType.Module?.Assembly?.Name?.Name; + if (templateField == null) { + return null; + } + + return GetMatchingType (templateField.DeclaringType, archMethods, assemblyCache, typeCache); + } + + TypeDefinition GetMatchingType (TypeDefinition templateDeclaringType, ArchitectureMarshalMethods archMethods, Dictionary assemblyCache, TypeDefinitionCache typeCache) + { + string? assemblyName = templateDeclaringType.Module?.Assembly?.Name?.Name; if (String.IsNullOrEmpty (assemblyName)) { - throw new InvalidOperationException ($"Unable to obtain assembly name for method {templateMethod}"); + throw new InvalidOperationException ($"Unable to obtain assembly name"); + } + assemblyName = $"{assemblyName}.dll"; + + if (!assemblyCache.TryGetValue (assemblyName, out AssemblyDefinition assembly)) { + assembly = LoadAssembly (assemblyName, assemblyCache); + assemblyCache.Add (assemblyName, assembly); + } + + if (!archMethods.Assemblies.Contains (assembly)) { + archMethods.Assemblies.Add (assembly); } - if (!cache.TryGetValue (assemblyName, out AssemblyDefinition assembly)) { - assembly = LoadAssembly (arch, assemblyName, archAssemblies, cache); - cache.Add (assemblyName, assembly); + string templateTypeName = templateDeclaringType.FullName; + log.LogDebugMessage ($" looking for type '{templateTypeName}' ('{templateDeclaringType.Name}')"); + + TypeDefinition? matchingType = typeCache.Resolve (templateDeclaringType); + if (matchingType == null) { + throw new InvalidOperationException ($"Unable to find type '{templateTypeName}'"); } - throw new NotImplementedException (); + if (matchingType == null) { + throw new InvalidOperationException ($"Unable to locate type '{templateDeclaringType.FullName}' in assembly '{assembly.FullName}'"); + } + log.LogDebugMessage (" type found"); + + return matchingType; } - AssemblyDefinition LoadAssembly (AndroidTargetArch arch, string assemblyName, IDictionary archAssemblies, Dictionary cache) + MethodDefinition FindMatchingMethod (MethodDefinition? templateMethod, TypeDefinition type, string methodDescription) { - if (!archAssemblies.TryGetValue (assemblyName, out ITaskItem assemblyItem)) { - throw new InvalidOperationException ($"Internal error: assembly '{assemblyName}' not found for architecture '{arch}'"); + if (templateMethod == null) { + throw new ArgumentNullException (nameof (templateMethod)); + } + + string templateMethodName = templateMethod.FullName; + log.LogDebugMessage ($" looking for method '{templateMethodName}'"); + foreach (MethodDefinition method in type.Methods) { + if (String.Compare (method.FullName, templateMethodName, StringComparison.Ordinal) != 0) { + continue; + } + + log.LogDebugMessage (" found"); + return method; + } + + throw new InvalidOperationException ($"Unable to locate {methodDescription} method '{templateMethod.FullName}' in '{type.FullName}'"); + } + + FieldDefinition? FindMatchingField (FieldDefinition? templateField, TypeDefinition type, string fieldDescription) + { + if (templateField == null) { + return null; + } + + string templateFieldName = templateField.FullName; + log.LogDebugMessage ($" looking for field '{templateFieldName}'"); + foreach (FieldDefinition field in type.Fields) { + if (String.Compare (field.FullName, templateFieldName, StringComparison.Ordinal) != 0) { + continue; + } + + log.LogDebugMessage (" found"); + return field; + } + + return null; + } + + AssemblyDefinition LoadAssembly (string assemblyName, Dictionary cache) + { + if (state.CurrentArchResolver == null) { + throw new InvalidOperationException ($"Internal error: resolver for architecture '{state.CurrentArch}' not set"); + } + + if (state.CurrentArchResolver.TargetArch != state.CurrentArch) { + throw new InvalidOperationException ($"Internal error: resolver should target architecture '{state.CurrentArch}', but it targets '{state.CurrentArchResolver.TargetArch}' instead"); + } + + if (!state.CurrentArchAssemblies.TryGetValue (assemblyName, out ITaskItem assemblyItem)) { + throw new InvalidOperationException ($"Internal error: assembly '{assemblyName}' not found for architecture '{state.CurrentArch}'"); + } + + AssemblyDefinition? assembly = state.CurrentArchResolver.Resolve (assemblyName); + if (assembly == null) { + throw new InvalidOperationException ($"Internal error: assembly '{assemblyName}' cannot be resolved for architecture '{state.CurrentArch}'"); } - throw new NotImplementedException (); + return assembly; } void ReflectType (AndroidTargetArch arch, string fullTypeName, IList templateMethods, IDictionary archAssemblies, ArchitectureMarshalMethods archMarshalMethods) diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/XAAssemblyResolver.cs b/src/Xamarin.Android.Build.Tasks/Utilities/XAAssemblyResolver.cs index 9126016c759..f501bb3e41e 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/XAAssemblyResolver.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/XAAssemblyResolver.cs @@ -17,13 +17,14 @@ class XAAssemblyResolverNew : IAssemblyResolver TaskLoggingHelper log; bool loadDebugSymbols; ReaderParameters readerParameters; - AndroidTargetArch targetArch; + readonly AndroidTargetArch targetArch; /// /// **MUST** point to directories which contain assemblies for single ABI **only**. /// One special case is when linking isn't enabled, in which instance directories /// containing ABI-agnostic assemblies can we used as well. public ICollection SearchDirectories { get; } = new List (); + public AndroidTargetArch TargetArch => targetArch; public XAAssemblyResolverNew (AndroidTargetArch targetArch, TaskLoggingHelper log, bool loadDebugSymbols, ReaderParameters? loadReaderParameters = null) { @@ -68,7 +69,11 @@ public XAAssemblyResolverNew (AndroidTargetArch targetArch, TaskLoggingHelper lo return name; } - var file = Path.Combine (directory, $"{name}.dll"); + if (!name.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) { + name = "${name}.dll"; + } + + var file = Path.Combine (directory, name); if (File.Exists (file)) { return file; }