Skip to content

Commit 90b42d8

Browse files
committed
[XABT] Move scanning for ACW map JLOs to FindJavaObjectsStep.
1 parent 542cf52 commit 90b42d8

10 files changed

+404
-47
lines changed

src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs

+10-15
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,20 @@ public bool ProcessAssembly (AssemblyDefinition assembly, string destinationJLOX
4040
var initial_count = types.Count;
4141

4242
// Filter out Java types we don't care about
43-
types = types.Where (t => !t.IsInterface && !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (t, Context)).ToList ();
43+
types = types.Where (t => !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (t, Context)).ToList ();
4444

4545
Log.LogDebugMessage ($"{assembly.Name.Name} - Found {initial_count} Java types, filtered to {types.Count}");
4646

47-
var wrappers = ConvertToCallableWrappers (types);
47+
var xml = new JavaObjectsXmlFile ();
4848

49-
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
50-
XmlExporter.Export (sw, wrappers, true);
51-
Files.CopyIfStreamChanged (sw.BaseStream, destinationJLOXml);
52-
}
49+
xml.ACWMapEntries.AddRange (types.Select (t => ACWMapEntry.Create (t, Context)));
50+
xml.JavaCallableWrappers.AddRange (ConvertToCallableWrappers (types.Where (t => !t.IsInterface).ToList ()));
5351

54-
return true;
55-
}
52+
xml.Export (destinationJLOXml);
5653

57-
public static void WriteEmptyXmlFile (string destination)
58-
{
59-
XmlExporter.Export (destination, [], false);
54+
Log.LogDebugMessage ($"Wrote '{destinationJLOXml}', {xml.JavaCallableWrappers.Count} JCWs, {xml.ACWMapEntries.Count} ACWs");
55+
56+
return true;
6057
}
6158

6259
List<TypeDefinition> ScanForJavaTypes (AssemblyDefinition assembly)
@@ -88,10 +85,8 @@ List<CallableWrapperType> ConvertToCallableWrappers (List<TypeDefinition> types)
8885
if (UseMarshalMethods)
8986
reader_options.MethodClassifier = new MarshalMethodsClassifier (Context, Context.Resolver, Log);
9087

91-
foreach (var type in types) {
92-
var wrapper = CecilImporter.CreateType (type, Context, reader_options);
93-
wrappers.Add (wrapper);
94-
}
88+
foreach (var type in types)
89+
wrappers.Add (CecilImporter.CreateType (type, Context, reader_options));
9590

9691
return wrappers;
9792
}

src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,11 @@ protected virtual void CreateRunState (RunState runState, MSBuildLinkContext con
147147

148148
protected virtual void RunPipeline (ITaskItem source, ITaskItem destination, RunState runState, WriterParameters writerParameters)
149149
{
150-
var destinationJLOXml = Path.ChangeExtension (destination.ItemSpec, ".jlo.xml");
150+
var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (destination.ItemSpec);
151151

152152
if (!TryScanForJavaObjects (source, destination, runState, writerParameters)) {
153153
// Even if we didn't scan for Java objects, we still write an empty .xml file for later steps
154-
FindJavaObjectsStep.WriteEmptyXmlFile (destinationJLOXml);
154+
JavaObjectsXmlFile.WriteEmptyFile (destinationJLOXml, Log);
155155
}
156156
}
157157

@@ -160,7 +160,7 @@ bool TryScanForJavaObjects (ITaskItem source, ITaskItem destination, RunState ru
160160
if (!ShouldScanAssembly (source))
161161
return false;
162162

163-
var destinationJLOXml = Path.ChangeExtension (destination.ItemSpec, ".jlo.xml");
163+
var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (destination.ItemSpec);
164164
var assemblyDefinition = runState.resolver!.GetAssembly (source.ItemSpec);
165165

166166
var scanned = runState.findJavaObjectsStep!.ProcessAssembly (assemblyDefinition, destinationJLOXml);
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#nullable enable
22
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.IO;
35
using System.Linq;
46
using Microsoft.Android.Build.Tasks;
57
using Microsoft.Build.Framework;
@@ -17,29 +19,68 @@ public class GenerateACWMap : AndroidTask
1719
[Required]
1820
public string IntermediateOutputDirectory { get; set; } = "";
1921

22+
[Required]
23+
public ITaskItem [] ResolvedAssemblies { get; set; } = [];
24+
25+
// This property is temporary and is used to ensure that the new "linker step"
26+
// JLO scanning produces the same results as the old process. It will be removed
27+
// once the process is complete.
28+
public bool RunCheckedBuild { get; set; }
29+
30+
[Required]
31+
public string [] SupportedAbis { get; set; } = [];
32+
2033
public override bool RunTask ()
2134
{
22-
// Retrieve the stored NativeCodeGenState
23-
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>> (
24-
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
25-
RegisteredTaskObjectLifetime.Build
26-
);
35+
// Temporarily used to ensure we still generate the same as the old code
36+
if (RunCheckedBuild) {
37+
// Retrieve the stored NativeCodeGenState
38+
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>> (
39+
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
40+
RegisteredTaskObjectLifetime.Build
41+
);
2742

28-
// We only need the first architecture, since this task is architecture-agnostic
29-
var templateCodeGenState = nativeCodeGenStates.First ().Value;
43+
// We only need the first architecture, since this task is architecture-agnostic
44+
var templateCodeGenState = nativeCodeGenStates.First ().Value;
3045

31-
var acwMapGen = new ACWMapGenerator (Log);
46+
var acwMapGen = new ACWMapGenerator (Log);
3247

33-
if (!acwMapGen.Generate (templateCodeGenState, AcwMapFile)) {
34-
Log.LogDebugMessage ("ACW map generation failed");
35-
}
48+
if (!acwMapGen.Generate (templateCodeGenState, AcwMapFile)) {
49+
Log.LogDebugMessage ("ACW map generation failed");
50+
}
3651

37-
if (Log.HasLoggedErrors) {
38-
// Ensure that on a rebuild, we don't *skip* the `_GenerateJavaStubs` target,
39-
// by ensuring that the target outputs have been deleted.
40-
Files.DeleteFile (AcwMapFile, Log);
52+
return !Log.HasLoggedErrors;
4153
}
4254

55+
GenerateMap ();
56+
4357
return !Log.HasLoggedErrors;
4458
}
59+
60+
void GenerateMap ()
61+
{
62+
// Get the set of assemblies for the "first" ABI. The ACW map is
63+
// not ABI-specific, so we can use any ABI to generate the wrappers.
64+
var allAssembliesPerArch = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, SupportedAbis, validate: true);
65+
var singleArchAssemblies = allAssembliesPerArch.First ().Value.Values.ToList ();
66+
67+
var entries = new List<ACWMapEntry> ();
68+
69+
foreach (var assembly in singleArchAssemblies) {
70+
var wrappersPath = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (assembly.ItemSpec);
71+
72+
if (!File.Exists (wrappersPath)) {
73+
Log.LogError ($"'{wrappersPath}' not found.");
74+
return;
75+
}
76+
77+
var xml = JavaObjectsXmlFile.Import (wrappersPath, JavaObjectsXmlFileReadType.ACW);
78+
79+
entries.AddRange (xml.ACWMapEntries);
80+
}
81+
82+
var acwMapGen = new ACWMapGenerator (Log);
83+
84+
acwMapGen.Generate (entries, AcwMapFile);
85+
}
4586
}

src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaCallableWrappers.cs

+9-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.IO;
66
using System.Linq;
77
using Java.Interop.Tools.JavaCallableWrappers;
8-
using Java.Interop.Tools.JavaCallableWrappers.Adapters;
98
using Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers;
109
using Java.Interop.Tools.TypeNameMappings;
1110
using Microsoft.Android.Build.Tasks;
@@ -63,16 +62,21 @@ void GenerateWrappers (List<ITaskItem> assemblies)
6362
var sw = Stopwatch.StartNew ();
6463

6564
foreach (var assembly in assemblies) {
66-
var assemblyPath = assembly.ItemSpec;
67-
var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath);
68-
var wrappersPath = Path.Combine (Path.GetDirectoryName (assemblyPath), $"{assemblyName}.jlo.xml");
65+
var wrappersPath = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (assembly.ItemSpec);
6966

7067
if (!File.Exists (wrappersPath)) {
7168
Log.LogError ($"'{wrappersPath}' not found.");
7269
return;
7370
}
7471

75-
wrappers.AddRange (XmlImporter.Import (wrappersPath, out var _));
72+
var xml = JavaObjectsXmlFile.Import (wrappersPath, JavaObjectsXmlFileReadType.JCW);
73+
74+
if (xml.JavaCallableWrappers.Count == 0) {
75+
Log.LogDebugMessage ($"'{wrappersPath}' is empty, skipping.");
76+
continue;
77+
}
78+
79+
wrappers.AddRange (xml.JavaCallableWrappers);
7680
}
7781

7882
Log.LogDebugMessage ($"Deserialized {wrappers.Count} Java callable wrappers in {sw.ElapsedMilliseconds}ms");

src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -277,15 +277,14 @@ void CompareScannedAssemblies ()
277277
// Find every assembly that was scanned by the linker by looking at the .jlo.xml files
278278
foreach (var assembly in ResolvedAssemblies) {
279279
var assemblyPath = assembly.ItemSpec;
280-
var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath);
281-
var wrappersPath = Path.Combine (Path.GetDirectoryName (assemblyPath), $"{assemblyName}.jlo.xml");
280+
var wrappersPath = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (assembly.ItemSpec);
282281

283282
if (!File.Exists (wrappersPath))
284283
Log.LogError ($"'{wrappersPath}' not found.");
285284

286-
XmlImporter.Import (wrappersPath, out var wasScanned);
285+
var xml = JavaObjectsXmlFile.Import (wrappersPath, JavaObjectsXmlFileReadType.None);
287286

288-
if (wasScanned) {
287+
if (xml.WasScanned) {
289288
Log.LogDebugMessage ($"CompareScannedAssemblies: Found scanned assembly .jlo.xml '{assemblyPath}'");
290289
linker_scanned_assemblies.Add (assembly.ItemSpec);
291290
}

src/Xamarin.Android.Build.Tasks/Utilities/ACWMapGenerator.cs

+134-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3-
3+
using System.IO;
4+
using System.Linq;
5+
using System.Xml.Linq;
46
using Java.Interop.Tools.Cecil;
57
using Java.Interop.Tools.TypeNameMappings;
68
using Microsoft.Android.Build.Tasks;
@@ -33,7 +35,7 @@ public bool Generate (NativeCodeGenState codeGenState, string acwMapFile)
3335
bool success = true;
3436

3537
using var acw_map = MemoryStreamPool.Shared.CreateStreamWriter ();
36-
foreach (TypeDefinition type in javaTypes) {
38+
foreach (TypeDefinition type in javaTypes.OrderBy (t => t.FullName.Replace ('/', '.'))) {
3739
string managedKey = type.FullName.Replace ('/', '.');
3840
string javaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.');
3941

@@ -79,7 +81,19 @@ public bool Generate (NativeCodeGenState codeGenState, string acwMapFile)
7981
}
8082

8183
acw_map.Flush ();
82-
Files.CopyIfStreamChanged (acw_map.BaseStream, acwMapFile);
84+
85+
// If there's conflicts, the "new way" file never got written, and will show up as
86+
// "changed" in our comparison test, so skip it.
87+
if (javaConflicts.Count > 0) {
88+
return false;
89+
}
90+
91+
if (Files.HasStreamChanged (acw_map.BaseStream, acwMapFile)) {
92+
log.LogError ($"ACW map file '{acwMapFile}' changed");
93+
Files.CopyIfStreamChanged (acw_map.BaseStream, acwMapFile + "2");
94+
} else {
95+
log.LogDebugMessage ($"ACW map file '{acwMapFile}' unchanged");
96+
}
8397

8498
foreach (var kvp in managedConflicts) {
8599
log.LogCodedWarning ("XA4214", Properties.Resources.XA4214, kvp.Key, string.Join (", ", kvp.Value));
@@ -99,4 +113,121 @@ public bool Generate (NativeCodeGenState codeGenState, string acwMapFile)
99113

100114
return success;
101115
}
116+
117+
public void Generate (List<ACWMapEntry> javaTypes, string acwMapFile)
118+
{
119+
// We need to save a map of .NET type -> ACW type for resource file fixups
120+
var managed = new Dictionary<string, ACWMapEntry> (javaTypes.Count, StringComparer.Ordinal);
121+
var java = new Dictionary<string, ACWMapEntry> (javaTypes.Count, StringComparer.Ordinal);
122+
123+
var managedConflicts = new Dictionary<string, List<string>> (0, StringComparer.Ordinal);
124+
var javaConflicts = new Dictionary<string, List<string>> (0, StringComparer.Ordinal);
125+
126+
using var acw_map = MemoryStreamPool.Shared.CreateStreamWriter ();
127+
128+
foreach (var type in javaTypes.OrderBy (t => t.ManagedKey)) {
129+
string managedKey = type.ManagedKey;
130+
string javaKey = type.JavaKey;
131+
132+
acw_map.Write (type.PartialAssemblyQualifiedName);
133+
acw_map.Write (';');
134+
acw_map.Write (javaKey);
135+
acw_map.WriteLine ();
136+
137+
ACWMapEntry conflict;
138+
bool hasConflict = false;
139+
140+
if (managed.TryGetValue (managedKey, out conflict)) {
141+
if (!conflict.ModuleName.Equals (type.ModuleName)) {
142+
if (!managedConflicts.TryGetValue (managedKey, out var list))
143+
managedConflicts.Add (managedKey, list = new List<string> { conflict.PartialAssemblyName });
144+
list.Add (type.PartialAssemblyName);
145+
}
146+
hasConflict = true;
147+
}
148+
149+
if (java.TryGetValue (javaKey, out conflict)) {
150+
if (!conflict.ModuleName.Equals(type.ModuleName)) {
151+
if (!javaConflicts.TryGetValue (javaKey, out var list))
152+
javaConflicts.Add (javaKey, list = new List<string> { conflict.AssemblyQualifiedName });
153+
list.Add (type.AssemblyQualifiedName);
154+
}
155+
hasConflict = true;
156+
}
157+
158+
if (!hasConflict) {
159+
managed.Add (managedKey, type);
160+
java.Add (javaKey, type);
161+
162+
acw_map.Write (managedKey);
163+
acw_map.Write (';');
164+
acw_map.Write (javaKey);
165+
acw_map.WriteLine ();
166+
167+
acw_map.Write (type.CompatJniName);
168+
acw_map.Write (';');
169+
acw_map.Write (javaKey);
170+
acw_map.WriteLine ();
171+
}
172+
}
173+
174+
acw_map.Flush ();
175+
176+
foreach (var kvp in managedConflicts) {
177+
log.LogCodedWarning ("XA4214", Properties.Resources.XA4214, kvp.Key, string.Join (", ", kvp.Value));
178+
log.LogCodedWarning ("XA4214", Properties.Resources.XA4214_Result, kvp.Key, kvp.Value [0]);
179+
}
180+
181+
foreach (var kvp in javaConflicts) {
182+
log.LogCodedError ("XA4215", Properties.Resources.XA4215, kvp.Key);
183+
184+
foreach (var typeName in kvp.Value) {
185+
log.LogCodedError ("XA4215", Properties.Resources.XA4215_Details, kvp.Key, typeName);
186+
}
187+
}
188+
189+
// Don't write the output file if there are any errors so that
190+
// future incremental builds will try again.
191+
if (javaConflicts.Count > 0)
192+
return;
193+
194+
Files.CopyIfStreamChanged (acw_map.BaseStream, acwMapFile);
195+
}
196+
}
197+
198+
class ACWMapEntry
199+
{
200+
public string AssemblyQualifiedName { get; set; }
201+
public string CompatJniName { get; set; }
202+
public string JavaKey { get; set; }
203+
public string ManagedKey { get; set; }
204+
public string ModuleName { get; set; }
205+
public string PartialAssemblyName { get; set; }
206+
public string PartialAssemblyQualifiedName { get; set; }
207+
208+
public static ACWMapEntry Create (TypeDefinition type, TypeDefinitionCache cache)
209+
{
210+
return new ACWMapEntry {
211+
AssemblyQualifiedName = type.GetAssemblyQualifiedName (cache),
212+
CompatJniName = JavaNativeTypeManager.ToCompatJniName (type, cache).Replace ('/', '.'),
213+
JavaKey = JavaNativeTypeManager.ToJniName (type, cache).Replace ('/', '.'),
214+
ManagedKey = type.FullName.Replace ('/', '.'),
215+
ModuleName = type.Module.Name,
216+
PartialAssemblyName = type.GetPartialAssemblyName (cache),
217+
PartialAssemblyQualifiedName = type.GetPartialAssemblyQualifiedName (cache),
218+
};
219+
}
220+
221+
public static ACWMapEntry Create (XElement type, string partialAssemblyName, string moduleName)
222+
{
223+
return new ACWMapEntry {
224+
AssemblyQualifiedName = type.GetAttributeOrDefault ("assembly-qualified-name", string.Empty),
225+
CompatJniName = type.GetAttributeOrDefault ("compat-jni-name", string.Empty),
226+
JavaKey = type.GetAttributeOrDefault ("java-key", string.Empty),
227+
ManagedKey = type.GetAttributeOrDefault ("managed-key", string.Empty),
228+
ModuleName = moduleName,
229+
PartialAssemblyName = partialAssemblyName,
230+
PartialAssemblyQualifiedName = type.GetAttributeOrDefault ("partial-assembly-qualified-name", string.Empty),
231+
};
232+
}
102233
}

0 commit comments

Comments
 (0)