Skip to content

Commit f03662c

Browse files
authored
[XABT] Move JLO scanning needed for typemap generation to a linker step (#10015)
Context: ea399ed Context: 787a2a6 Context: b54ec05 COntext: b11471b Move the process of scanning for `Java.Lang.Object` (JLO) and `Java.Lang.Throwable` subclasses needed for the typemap generation task to a new `FindTypeMapObjectsStep` "linker step". The types are serialized to new `<assembly>.typemap.xml` files that sit beside the source assembly. A new file was used rather than adding more sections to the `.jlo.xml` file because this file contains a lot more types and will likely change more often on incremental builds, and we don't want to trigger ie: creating new Java stubs when not needed. This file is then read in the existing `<GenerateTypeMappings/>` task to generate the final typemap file. The existing method of JLO scanning for typemaps is still used when using LLVM marshal methods, as the marshal method rewriter runs after the linker steps, and when it changes the assemblies the MVID and type tokens no longer match the values saved in the `.typemap.xml` files. Like ea399ed, this temporarily leaves the old typemap generation code in place, guarded behind the `$(_AndroidJLOCheckedBuild)` flag. This flag generates the typemap both the new and old way, and errors out if there are differences. Note that b54ec05 updated our "pipeline" to save assemblies after *all* steps have been run. However, looking for JLOs needed for typemap generation has to be run *after* assemblies are saved. This is because Release typemaps rely on assembly MVID and type tokens which change when the assemblies are modified. Update the pipeline's "modified assembly saving" logic to itself be a pipeline step, so we can insert it *before* the new `FindTypeMapObjectsStep` step. *Note*: previously, we scanned for all JLO subclasses from *all* assemblies in a single loop, so [duplicate detection logic][0] finds duplicates even if they are in different assemblies. Consider `java.lang.Object`, which is bound as: * `Java.Lang.Object, Mono.Android` * `Java.Interop.JavaObject, Java.Interop` * *and also* several other unit test types as "aliases" (b11471b) When processing `Java.Interop.dll`, the `TypeMapDebugEntry` fields would be: - JavaName: "java/lang/Object" - ManagedName: "Java.Interop.JavaObject, Java.Interop" - SkipInJavaToManaged: "False" - DuplicateForJavaToManaged - JavaName: "java/lang/Object" - ManagedName: "Java.Lang.Object, Mono.Android" In particular, `TypeMapDebugEntry.DuplicateForJavaToManaged` mentions `Java.Lang.Object, Mono.Android` as the "main" typemap. *Now* scanning is performed *per-assembly*; it will find duplicates in the same assembly, but not duplicates that may exist in other assemblies. The `TypeMapDebugEntry` fields when processing `Java.Interop.dll` are instead: - JavaName: "java/lang/Object" - ManagedName: "Java.Interop.JavaObject, Java.Interop" - SkipInJavaToManaged: "False" - DuplicateForJavaToManaged: null Note that `TypeMapDebugEntry.DuplicateForJavaToManaged` is null. We have two ordering semantics we have to preserve in replicating the original logic: - A declared type should take precedence over an "invoker" type. - Types in `Mono.Android` have precedence over other assemblies. Aside from these two cases, which type is considered the "primary" and which type(s) are considered the "duplicates" seems to be luck of the draw. Note that "luck" only enters the picture when using the non-generic `JniRuntime.JniValueManager.GetPeer(JniObjectReference, Type?)` API and providing `null` for the `Type?` parameter. Whenever a `Type` *is provided*, or a generic wrapper such as `Java.Lang.Object.GetObject<T>()` is used, then value returned is *constrained* to the specified type, reducing the likelihood of obtaining an unexpected type. [0]: https://github.com/dotnet/android/blob/8c741263550cc57bafc30193fa61f39b7df019bc/src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs#L141-L152
1 parent b946ae1 commit f03662c

13 files changed

+697
-89
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ protected override void ProcessAssembly (AssemblyDefinition assembly)
2929
}
3030

3131
#if !ILLINK
32-
public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
32+
public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
3333
{
3434
// Only run this step on user Android assemblies
3535
if (!context.IsAndroidUserAssembly)
36-
return false;
36+
return;
3737

38-
return AddKeepAlives (assembly);
38+
context.IsAssemblyModified |= AddKeepAlives (assembly);
3939
}
4040
#endif // !ILLINK
4141

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,15 @@ public class FindJavaObjectsStep : BaseStep, IAssemblyModifierPipelineStep
2929

3030
public FindJavaObjectsStep (TaskLoggingHelper log) => Log = log;
3131

32-
public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
32+
public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
3333
{
3434
var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (context.Destination.ItemSpec);
3535
var scanned = ScanAssembly (assembly, context, destinationJLOXml);
3636

3737
if (!scanned) {
3838
// We didn't scan for Java objects, so write an empty .xml file for later steps
3939
JavaObjectsXmlFile.WriteEmptyFile (destinationJLOXml, Log);
40-
return false;
4140
}
42-
43-
// This step does not change the assembly
44-
return false;
4541
}
4642

4743
public bool ScanAssembly (AssemblyDefinition assembly, StepContext context, string destinationJLOXml)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using Microsoft.Android.Build.Tasks;
6+
using Microsoft.Build.Utilities;
7+
using Mono.Cecil;
8+
using Mono.Linker.Steps;
9+
using Xamarin.Android.Tasks;
10+
11+
namespace MonoDroid.Tuner;
12+
13+
/// <summary>
14+
/// Scans an assembly for JLOs that need to be in the typemap and writes them to an XML file.
15+
/// </summary>
16+
public class FindTypeMapObjectsStep : BaseStep, IAssemblyModifierPipelineStep
17+
{
18+
public bool Debug { get; set; }
19+
20+
public bool ErrorOnCustomJavaObject { get; set; }
21+
22+
public TaskLoggingHelper Log { get; set; }
23+
24+
public FindTypeMapObjectsStep (TaskLoggingHelper log) => Log = log;
25+
26+
public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
27+
{
28+
var destinationTypeMapXml = TypeMapObjectsXmlFile.GetTypeMapObjectsXmlFilePath (context.Destination.ItemSpec);
29+
30+
// We only care about assemblies that can contains JLOs
31+
if (!context.IsAndroidAssembly) {
32+
Log.LogDebugMessage ($"Skipping assembly '{assembly.Name.Name}' because it is not an Android assembly");
33+
TypeMapObjectsXmlFile.WriteEmptyFile (destinationTypeMapXml, Log);
34+
return;
35+
}
36+
37+
var types = ScanForJavaTypes (assembly);
38+
39+
var xml = new TypeMapObjectsXmlFile {
40+
AssemblyName = assembly.Name.Name,
41+
};
42+
43+
if (Debug) {
44+
var (javaToManaged, managedToJava, foundJniNativeRegistration) = TypeMapCecilAdapter.GetDebugNativeEntries (types, Context);
45+
46+
xml.JavaToManagedDebugEntries.AddRange (javaToManaged);
47+
xml.ManagedToJavaDebugEntries.AddRange (managedToJava);
48+
xml.FoundJniNativeRegistration = foundJniNativeRegistration;
49+
} else {
50+
var genState = TypeMapCecilAdapter.GetReleaseGenerationState (types, Context, out var foundJniNativeRegistration);
51+
xml.ModuleReleaseData = genState.TempModules.SingleOrDefault ().Value;
52+
}
53+
54+
xml.Export (destinationTypeMapXml, Log);
55+
}
56+
57+
List<TypeDefinition> ScanForJavaTypes (AssemblyDefinition assembly)
58+
{
59+
var types = new List<TypeDefinition> ();
60+
61+
var scanner = new XAJavaTypeScanner (Xamarin.Android.Tools.AndroidTargetArch.None, Log, Context) {
62+
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject
63+
};
64+
65+
foreach (ModuleDefinition md in assembly.Modules) {
66+
foreach (TypeDefinition td in md.Types) {
67+
scanner.AddJavaType (td, types);
68+
}
69+
}
70+
71+
return types;
72+
}
73+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ internal bool FixAbstractMethods (AssemblyDefinition assembly)
8383
}
8484

8585
#if !ILLINK
86-
public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context)
86+
public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
8787
{
8888
// Only run this step on non-main user Android assemblies
8989
if (context.IsMainAssembly || !context.IsAndroidUserAssembly)
90-
return false;
90+
return;
9191

92-
return FixAbstractMethods (assembly);
92+
context.IsAssemblyModified |= FixAbstractMethods (assembly);
9393
}
9494
#endif // !ILLINK
9595

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ protected override void LoadDesigner ()
7171
}
7272

7373
#if !ILLINK
74-
public bool ProcessAssembly (AssemblyDefinition assembly, Xamarin.Android.Tasks.StepContext context)
74+
public void ProcessAssembly (AssemblyDefinition assembly, Xamarin.Android.Tasks.StepContext context)
7575
{
7676
// Only run this step on non-main user Android assemblies
7777
if (context.IsMainAssembly || !context.IsAndroidUserAssembly)
78-
return false;
78+
return;
7979

80-
return ProcessAssemblyDesigner (assembly);
80+
context.IsAssemblyModified |= ProcessAssemblyDesigner (assembly);
8181
}
8282
#endif // !ILLINK
8383

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

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Java.Interop.Tools.TypeNameMappings;
99
using Microsoft.Android.Build.Tasks;
1010
using Microsoft.Build.Framework;
11+
using Microsoft.Build.Utilities;
1112
using Mono.Cecil;
1213
using MonoDroid.Tuner;
1314
using Xamarin.Android.Tools;
@@ -78,10 +79,6 @@ public override bool RunTask ()
7879
ReadSymbols = ReadSymbols,
7980
};
8081

81-
var writerParameters = new WriterParameters {
82-
DeterministicMvid = Deterministic,
83-
};
84-
8582
Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, Array.Empty<string> (), validate: false);
8683

8784
AssemblyPipeline? pipeline = null;
@@ -122,7 +119,7 @@ public override bool RunTask ()
122119

123120
Directory.CreateDirectory (Path.GetDirectoryName (destination.ItemSpec));
124121

125-
RunPipeline (pipeline!, source, destination, writerParameters);
122+
RunPipeline (pipeline!, source, destination);
126123
}
127124

128125
pipeline?.Dispose ();
@@ -141,9 +138,26 @@ protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCont
141138

142139
findJavaObjectsStep.Initialize (context);
143140
pipeline.Steps.Add (findJavaObjectsStep);
141+
142+
// SaveChangedAssemblyStep
143+
var writerParameters = new WriterParameters {
144+
DeterministicMvid = Deterministic,
145+
};
146+
147+
var saveChangedAssemblyStep = new SaveChangedAssemblyStep (Log, writerParameters);
148+
pipeline.Steps.Add (saveChangedAssemblyStep);
149+
150+
// FindTypeMapObjectsStep - this must be run after the assembly has been saved, as saving changes the MVID
151+
var findTypeMapObjectsStep = new FindTypeMapObjectsStep (Log) {
152+
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
153+
Debug = Debug,
154+
};
155+
156+
findTypeMapObjectsStep.Initialize (context);
157+
pipeline.Steps.Add (findTypeMapObjectsStep);
144158
}
145159

146-
void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination, WriterParameters writerParameters)
160+
void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination)
147161
{
148162
var assembly = pipeline.Resolver.GetAssembly (source.ItemSpec);
149163

@@ -157,17 +171,36 @@ void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destina
157171
IsUserAssembly = ResolvedUserAssemblies.Any (a => a.ItemSpec == source.ItemSpec),
158172
};
159173

160-
var changed = pipeline.Run (assembly, context);
174+
pipeline.Run (assembly, context);
175+
}
176+
}
177+
178+
class SaveChangedAssemblyStep : IAssemblyModifierPipelineStep
179+
{
180+
public TaskLoggingHelper Log { get; set; }
161181

162-
if (changed) {
163-
Log.LogDebugMessage ($"Saving modified assembly: {destination.ItemSpec}");
164-
Directory.CreateDirectory (Path.GetDirectoryName (destination.ItemSpec));
165-
writerParameters.WriteSymbols = assembly.MainModule.HasSymbols;
166-
assembly.Write (destination.ItemSpec, writerParameters);
182+
public WriterParameters WriterParameters { get; set; }
183+
184+
public SaveChangedAssemblyStep (TaskLoggingHelper log, WriterParameters writerParameters)
185+
{
186+
Log = log;
187+
WriterParameters = writerParameters;
188+
}
189+
190+
public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
191+
{
192+
if (context.IsAssemblyModified) {
193+
Log.LogDebugMessage ($"Saving modified assembly: {context.Destination.ItemSpec}");
194+
Directory.CreateDirectory (Path.GetDirectoryName (context.Destination.ItemSpec));
195+
WriterParameters.WriteSymbols = assembly.MainModule.HasSymbols;
196+
assembly.Write (context.Destination.ItemSpec, WriterParameters);
167197
} else {
168198
// If we didn't write a modified file, copy the original to the destination
169-
CopyIfChanged (source, destination);
199+
CopyIfChanged (context.Source, context.Destination);
170200
}
201+
202+
// We just saved the assembly, so it is no longer modified
203+
context.IsAssemblyModified = false;
171204
}
172205

173206
void CopyIfChanged (ITaskItem source, ITaskItem destination)

0 commit comments

Comments
 (0)