Skip to content

Commit e187164

Browse files
committed
[XABT] Move JLO scanning needed for typemap generation to a linker step.
1 parent 787a2a6 commit e187164

8 files changed

+657
-60
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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;
9+
using Mono.Linker.Steps;
10+
using Xamarin.Android.Tasks;
11+
12+
namespace MonoDroid.Tuner;
13+
14+
/// <summary>
15+
/// Scans an assembly for JLOs that need to be in the typemap and writes them to an XML file.
16+
/// </summary>
17+
public class FindTypeMapObjectsStep : BaseStep
18+
{
19+
public bool Debug { get; set; }
20+
21+
public bool ErrorOnCustomJavaObject { get; set; }
22+
23+
public TaskLoggingHelper Log { get; set; }
24+
25+
public FindTypeMapObjectsStep (TaskLoggingHelper log) => Log = log;
26+
27+
public bool ProcessAssembly (AssemblyDefinition assembly, string destinationTypeMapXml)
28+
{
29+
var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;
30+
31+
if (action == AssemblyAction.Delete)
32+
return false;
33+
34+
var types = ScanForJavaTypes (assembly);
35+
36+
var xml = new TypeMapObjectsXmlFile ();
37+
38+
if (Debug) {
39+
var (javaToManaged, managedToJava) = TypeMapCecilAdapter.GetDebugNativeEntries (types, Context, out var foundJniNativeRegistration);
40+
41+
xml.JavaToManagedDebugEntries.AddRange (javaToManaged);
42+
xml.ManagedToJavaDebugEntries.AddRange (managedToJava);
43+
xml.FoundJniNativeRegistration = foundJniNativeRegistration;
44+
45+
if (!xml.HasDebugEntries) {
46+
Log.LogDebugMessage ($"No Java types found in '{assembly.Name.Name}'");
47+
return false;
48+
}
49+
} else {
50+
var genState = TypeMapCecilAdapter.GetReleaseGenerationState (types, Context, out var foundJniNativeRegistration);
51+
xml.ModuleReleaseData = genState.TempModules.SingleOrDefault ().Value;
52+
53+
if (xml.ModuleReleaseData == null) {
54+
Log.LogDebugMessage ($"No Java types found in '{assembly.Name.Name}'");
55+
return false;
56+
}
57+
}
58+
59+
xml.Export (destinationTypeMapXml);
60+
61+
Log.LogDebugMessage ($"Wrote '{destinationTypeMapXml}', {xml.JavaToManagedDebugEntries.Count} JavaToManagedDebugEntries, {xml.ManagedToJavaDebugEntries.Count} ManagedToJavaDebugEntries, FoundJniNativeRegistration: {xml.FoundJniNativeRegistration}");
62+
63+
return true;
64+
}
65+
66+
List<TypeDefinition> ScanForJavaTypes (AssemblyDefinition assembly)
67+
{
68+
var types = new List<TypeDefinition> ();
69+
70+
var scanner = new XAJavaTypeScanner (Xamarin.Android.Tools.AndroidTargetArch.None, Log, Context) {
71+
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject
72+
};
73+
74+
foreach (ModuleDefinition md in assembly.Modules) {
75+
foreach (TypeDefinition td in md.Types) {
76+
scanner.AddJavaType (td, types);
77+
}
78+
}
79+
80+
return types;
81+
}
82+
}

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

+35-3
Original file line numberDiff line numberDiff line change
@@ -143,21 +143,36 @@ protected virtual void CreateRunState (RunState runState, MSBuildLinkContext con
143143
findJavaObjectsStep.Initialize (context);
144144

145145
runState.findJavaObjectsStep = findJavaObjectsStep;
146+
147+
var findTypeMapObjectsStep = new FindTypeMapObjectsStep (Log) {
148+
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
149+
Debug = Debug,
150+
};
151+
152+
findTypeMapObjectsStep.Initialize (context);
153+
154+
runState.findTypeMapObjectsStep = findTypeMapObjectsStep;
146155
}
147156

148157
protected virtual void RunPipeline (ITaskItem source, ITaskItem destination, RunState runState, WriterParameters writerParameters)
149158
{
150-
var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (destination.ItemSpec);
151159

152160
if (!TryScanForJavaObjects (source, destination, runState, writerParameters)) {
153161
// Even if we didn't scan for Java objects, we still write an empty .xml file for later steps
162+
var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (destination.ItemSpec);
154163
JavaObjectsXmlFile.WriteEmptyFile (destinationJLOXml, Log);
155164
}
165+
166+
if (!TryScanForTypeMapObjects (source, destination, runState, writerParameters)) {
167+
// Even if we didn't scan for Java objects, we still write an empty .xml file for later steps
168+
var destinationTypeMapXml = TypeMapObjectsXmlFile.GetTypeMapObjectsXmlFilePath (destination.ItemSpec);
169+
TypeMapObjectsXmlFile.WriteEmptyFile (destinationTypeMapXml, Log);
170+
}
156171
}
157172

158173
bool TryScanForJavaObjects (ITaskItem source, ITaskItem destination, RunState runState, WriterParameters writerParameters)
159174
{
160-
if (!ShouldScanAssembly (source))
175+
if (!ShouldScanAssembly (source, false))
161176
return false;
162177

163178
var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (destination.ItemSpec);
@@ -168,14 +183,29 @@ bool TryScanForJavaObjects (ITaskItem source, ITaskItem destination, RunState ru
168183
return scanned;
169184
}
170185

171-
bool ShouldScanAssembly (ITaskItem source)
186+
bool TryScanForTypeMapObjects (ITaskItem source, ITaskItem destination, RunState runState, WriterParameters writerParameters)
187+
{
188+
if (!ShouldScanAssembly (source, true))
189+
return false;
190+
191+
var destinationTypeMapXml = TypeMapObjectsXmlFile.GetTypeMapObjectsXmlFilePath (destination.ItemSpec);
192+
var assemblyDefinition = runState.resolver!.GetAssembly (source.ItemSpec);
193+
var scanned = runState.findTypeMapObjectsStep!.ProcessAssembly (assemblyDefinition, destinationTypeMapXml);
194+
195+
return scanned;
196+
}
197+
198+
bool ShouldScanAssembly (ITaskItem source, bool scanAllAndroidAssemblies)
172199
{
173200
// Skip this assembly if it is not an Android assembly
174201
if (!IsAndroidAssembly (source)) {
175202
Log.LogDebugMessage ($"Skipping assembly '{source.ItemSpec}' because it is not an Android assembly");
176203
return false;
177204
}
178205

206+
if (scanAllAndroidAssemblies)
207+
return true;
208+
179209
// When marshal methods or non-JavaPeerStyle.XAJavaInterop1 are in use we do not want to skip non-user assemblies (such as Mono.Android) - we need to generate JCWs for them during
180210
// application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android
181211
// build and stored in a jar file.
@@ -217,6 +247,7 @@ protected sealed class RunState : IDisposable
217247
public AddKeepAlivesStep? addKeepAliveStep = null;
218248
public FixLegacyResourceDesignerStep? fixLegacyResourceDesignerStep = null;
219249
public FindJavaObjectsStep? findJavaObjectsStep = null;
250+
public FindTypeMapObjectsStep? findTypeMapObjectsStep = null;
220251
bool disposed_value;
221252

222253
public RunState (DirectoryAssemblyResolver resolver)
@@ -233,6 +264,7 @@ private void Dispose (bool disposing)
233264
fixLegacyResourceDesignerStep = null;
234265
addKeepAliveStep = null;
235266
findJavaObjectsStep = null;
267+
findTypeMapObjectsStep = null;
236268
}
237269
disposed_value = true;
238270
}

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

+79-20
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
using System;
33
using System.Collections.Concurrent;
44
using System.Collections.Generic;
5+
using System.Diagnostics;
56
using System.IO;
7+
using System.Linq;
68
using Java.Interop.Tools.Cecil;
79
using Microsoft.Android.Build.Tasks;
810
using Microsoft.Build.Framework;
@@ -20,48 +22,99 @@ public class GenerateTypeMappings : AndroidTask
2022

2123
public bool Debug { get; set; }
2224

25+
[Output]
26+
public ITaskItem [] GeneratedBinaryTypeMaps { get; set; } = [];
27+
2328
[Required]
2429
public string IntermediateOutputDirectory { get; set; } = "";
2530

2631
public bool SkipJniAddNativeMethodRegistrationAttributeScan { get; set; }
2732

2833
[Required]
29-
public string TypemapOutputDirectory { get; set; } = "";
34+
public ITaskItem [] ResolvedAssemblies { get; set; } = [];
3035

31-
[Output]
32-
public ITaskItem [] GeneratedBinaryTypeMaps { get; set; } = [];
36+
// This property is temporary and is used to ensure that the new "linker step"
37+
// JLO scanning produces the same results as the old process. It will be removed
38+
// once the process is complete.
39+
public bool RunCheckedBuild { get; set; }
40+
41+
[Required]
42+
public string [] SupportedAbis { get; set; } = [];
3343

3444
public string TypemapImplementation { get; set; } = "llvm-ir";
3545

46+
[Required]
47+
public string TypemapOutputDirectory { get; set; } = "";
48+
3649
AndroidRuntime androidRuntime;
3750

3851
public override bool RunTask ()
3952
{
40-
androidRuntime = MonoAndroidHelper.ParseAndroidRuntime (AndroidRuntime);
53+
// Temporarily used to ensure we still generate the same as the old code
54+
if (RunCheckedBuild) {
55+
androidRuntime = MonoAndroidHelper.ParseAndroidRuntime (AndroidRuntime);
56+
57+
// Retrieve the stored NativeCodeGenState
58+
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>> (
59+
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
60+
RegisteredTaskObjectLifetime.Build
61+
);
62+
63+
NativeCodeGenState? templateCodeGenState = null;
64+
65+
foreach (var kvp in nativeCodeGenStates) {
66+
NativeCodeGenState state = kvp.Value;
67+
templateCodeGenState = state;
68+
WriteTypeMappings (state);
69+
}
4170

42-
// Retrieve the stored NativeCodeGenState
43-
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>> (
44-
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
45-
RegisteredTaskObjectLifetime.Build
46-
);
71+
if (templateCodeGenState is null)
72+
throw new InvalidOperationException ($"Internal error: no native code generator state defined");
4773

48-
NativeCodeGenState? templateCodeGenState = null;
74+
// Set for use by <GeneratePackageManagerJava/> task later
75+
NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = templateCodeGenState.JniAddNativeMethodRegistrationAttributePresent;
4976

50-
foreach (var kvp in nativeCodeGenStates) {
51-
NativeCodeGenState state = kvp.Value;
52-
templateCodeGenState = state;
53-
WriteTypeMappings (state);
77+
return !Log.HasLoggedErrors;
5478
}
5579

56-
if (templateCodeGenState is null)
57-
throw new InvalidOperationException ($"Internal error: no native code generator state defined");
80+
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
81+
// NativeAOT typemaps are generated in `Microsoft.Android.Sdk.ILLink.TypeMappingStep`
82+
Log.LogDebugMessage ("Skipping type maps for NativeAOT.");
83+
return !Log.HasLoggedErrors;
84+
}
5885

59-
// Set for use by <GeneratePackageManagerJava/> task later
60-
NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = templateCodeGenState.JniAddNativeMethodRegistrationAttributePresent;
86+
GenerateAllTypeMappings ();
6187

6288
return !Log.HasLoggedErrors;
6389
}
6490

91+
void GenerateAllTypeMappings ()
92+
{
93+
var allAssembliesPerArch = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, SupportedAbis, validate: true);
94+
95+
foreach (var set in allAssembliesPerArch)
96+
GenerateTypeMap (set.Key, set.Value.Values.ToList ());
97+
}
98+
99+
void GenerateTypeMap (AndroidTargetArch arch, List<ITaskItem> assemblies)
100+
{
101+
Log.LogDebugMessage ($"Generating type maps for architecture '{arch}'");
102+
103+
var sw = Stopwatch.StartNew ();
104+
var state = TypeMapObjectsFileAdapter.Create (arch, assemblies, Log);
105+
Log.LogDebugMessage ($"Type map deserialization took {sw.ElapsedMilliseconds}ms");
106+
sw.Restart ();
107+
// An error was already logged to Log.LogError
108+
if (state is null)
109+
return;
110+
111+
var tmg = new TypeMapGenerator (Log, state, androidRuntime);
112+
tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, TypemapOutputDirectory);
113+
114+
Log.Equals ($"Type map generation took {sw.ElapsedMilliseconds}ms");
115+
AddOutputTypeMaps (tmg, state.TargetArch);
116+
}
117+
65118
void WriteTypeMappings (NativeCodeGenState state)
66119
{
67120
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
@@ -76,11 +129,17 @@ void WriteTypeMappings (NativeCodeGenState state)
76129
state = new NativeCodeGenState (state.TargetArch, new TypeDefinitionCache (), state.Resolver, [], [], state.Classifier);
77130
}
78131

79-
var tmg = new TypeMapGenerator (Log, state, androidRuntime);
132+
var tmg = new TypeMapGenerator (Log, new NativeCodeGenStateAdapter (state), androidRuntime);
80133
tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, TypemapOutputDirectory);
81134

82-
string abi = MonoAndroidHelper.ArchToAbi (state.TargetArch);
135+
AddOutputTypeMaps (tmg, state.TargetArch);
136+
}
137+
138+
void AddOutputTypeMaps (TypeMapGenerator tmg, AndroidTargetArch arch)
139+
{
140+
string abi = MonoAndroidHelper.ArchToAbi (arch);
83141
var items = new List<ITaskItem> ();
142+
84143
foreach (string file in tmg.GeneratedBinaryTypeMaps) {
85144
var item = new TaskItem (file);
86145
string fileName = Path.GetFileName (file);

0 commit comments

Comments
 (0)