Skip to content

Commit 0d66bf2

Browse files
committed
[XABT] Move JLO scanning needed for typemap generation to a linker step.
1 parent b54ec05 commit 0d66bf2

8 files changed

+675
-50
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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 bool 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 false;
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) = TypeMapCecilAdapter.GetDebugNativeEntries (types, Context, out var foundJniNativeRegistration);
45+
46+
xml.JavaToManagedDebugEntries.AddRange (javaToManaged);
47+
xml.ManagedToJavaDebugEntries.AddRange (managedToJava);
48+
xml.FoundJniNativeRegistration = foundJniNativeRegistration;
49+
50+
if (!xml.HasDebugEntries) {
51+
Log.LogDebugMessage ($"No Java types found in '{assembly.Name.Name}'");
52+
TypeMapObjectsXmlFile.WriteEmptyFile (destinationTypeMapXml, Log);
53+
return false;
54+
}
55+
} else {
56+
var genState = TypeMapCecilAdapter.GetReleaseGenerationState (types, Context, out var foundJniNativeRegistration);
57+
xml.ModuleReleaseData = genState.TempModules.SingleOrDefault ().Value;
58+
59+
if (xml.ModuleReleaseData == null) {
60+
Log.LogDebugMessage ($"No Java types found in '{assembly.Name.Name}'");
61+
TypeMapObjectsXmlFile.WriteEmptyFile (destinationTypeMapXml, Log);
62+
return false;
63+
}
64+
}
65+
66+
xml.Export (destinationTypeMapXml);
67+
68+
Log.LogDebugMessage ($"Wrote '{destinationTypeMapXml}', {xml.JavaToManagedDebugEntries.Count} JavaToManagedDebugEntries, {xml.ManagedToJavaDebugEntries.Count} ManagedToJavaDebugEntries, FoundJniNativeRegistration: {xml.FoundJniNativeRegistration}");
69+
70+
// This step does not change the assembly
71+
return false;
72+
}
73+
74+
List<TypeDefinition> ScanForJavaTypes (AssemblyDefinition assembly)
75+
{
76+
var types = new List<TypeDefinition> ();
77+
78+
var scanner = new XAJavaTypeScanner (Xamarin.Android.Tools.AndroidTargetArch.None, Log, Context) {
79+
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject
80+
};
81+
82+
foreach (ModuleDefinition md in assembly.Modules) {
83+
foreach (TypeDefinition td in md.Types) {
84+
scanner.AddJavaType (td, types);
85+
}
86+
}
87+
88+
return types;
89+
}
90+
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCont
141141

142142
findJavaObjectsStep.Initialize (context);
143143
pipeline.Steps.Add (findJavaObjectsStep);
144+
145+
// FindTypeMapObjectsStep
146+
var findTypeMapObjectsStep = new FindTypeMapObjectsStep (Log) {
147+
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
148+
Debug = Debug,
149+
};
150+
151+
findTypeMapObjectsStep.Initialize (context);
152+
pipeline.Steps.Add (findTypeMapObjectsStep);
144153
}
145154

146155
void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination, WriterParameters writerParameters)

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

+79-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Collections.Concurrent;
44
using System.Collections.Generic;
55
using System.IO;
6+
using System.Linq;
67
using Java.Interop.Tools.Cecil;
78
using Microsoft.Android.Build.Tasks;
89
using Microsoft.Build.Framework;
@@ -11,6 +12,8 @@
1112

1213
namespace Xamarin.Android.Tasks;
1314

15+
// Note: If/When this is converted to an incremental task, every build still needs to set:
16+
// NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent
1417
public class GenerateTypeMappings : AndroidTask
1518
{
1619
public override string TaskPrefix => "GTM";
@@ -20,25 +23,86 @@ public class GenerateTypeMappings : AndroidTask
2023

2124
public bool Debug { get; set; }
2225

26+
public bool EnableMarshalMethods { get;set; }
27+
28+
[Output]
29+
public ITaskItem [] GeneratedBinaryTypeMaps { get; set; } = [];
30+
2331
[Required]
2432
public string IntermediateOutputDirectory { get; set; } = "";
2533

2634
public bool SkipJniAddNativeMethodRegistrationAttributeScan { get; set; }
2735

2836
[Required]
29-
public string TypemapOutputDirectory { get; set; } = "";
37+
public ITaskItem [] ResolvedAssemblies { get; set; } = [];
3038

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

3447
public string TypemapImplementation { get; set; } = "llvm-ir";
3548

49+
[Required]
50+
public string TypemapOutputDirectory { get; set; } = "";
51+
3652
AndroidRuntime androidRuntime;
3753

3854
public override bool RunTask ()
3955
{
56+
var useMarshalMethods = !Debug && EnableMarshalMethods;
57+
4058
androidRuntime = MonoAndroidHelper.ParseAndroidRuntime (AndroidRuntime);
59+
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
60+
// NativeAOT typemaps are generated in `Microsoft.Android.Sdk.ILLink.TypeMappingStep`
61+
Log.LogDebugMessage ("Skipping type maps for NativeAOT.");
62+
return !Log.HasLoggedErrors;
63+
}
64+
65+
// If using marshal methods, we cannot use the .typemap.xml files currently because
66+
// the type token ids were changed by the marshal method rewriter after we wrote the .xml files.
67+
if (!useMarshalMethods)
68+
GenerateAllTypeMappings ();
69+
70+
// Generate typemaps from the native code generator state (produced by the marshal method rewriter)
71+
if (RunCheckedBuild || useMarshalMethods)
72+
GenerateAllTypeMappingsFromNativeState (useMarshalMethods);
73+
74+
return !Log.HasLoggedErrors;
75+
}
76+
77+
void GenerateAllTypeMappings ()
78+
{
79+
var allAssembliesPerArch = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, SupportedAbis, validate: true);
80+
81+
foreach (var set in allAssembliesPerArch)
82+
GenerateTypeMap (set.Key, set.Value.Values.ToList ());
83+
}
84+
85+
void GenerateTypeMap (AndroidTargetArch arch, List<ITaskItem> assemblies)
86+
{
87+
Log.LogDebugMessage ($"Generating type maps for architecture '{arch}'");
88+
89+
var state = TypeMapObjectsFileAdapter.Create (arch, assemblies, Log);
4190

91+
// An error was already logged to Log.LogError
92+
if (state is null)
93+
return;
94+
95+
var tmg = new TypeMapGenerator (Log, state, androidRuntime);
96+
tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, TypemapOutputDirectory);
97+
98+
// Set for use by <GeneratePackageManagerJava/> task later
99+
NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = state.JniAddNativeMethodRegistrationAttributePresent;
100+
101+
AddOutputTypeMaps (tmg, state.TargetArch);
102+
}
103+
104+
void GenerateAllTypeMappingsFromNativeState (bool useMarshalMethods)
105+
{
42106
// Retrieve the stored NativeCodeGenState
43107
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>> (
44108
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
@@ -50,37 +114,41 @@ public override bool RunTask ()
50114
foreach (var kvp in nativeCodeGenStates) {
51115
NativeCodeGenState state = kvp.Value;
52116
templateCodeGenState = state;
53-
WriteTypeMappings (state);
117+
GenerateTypeMapFromNativeState (state, useMarshalMethods);
54118
}
55119

56120
if (templateCodeGenState is null)
57121
throw new InvalidOperationException ($"Internal error: no native code generator state defined");
58122

59123
// Set for use by <GeneratePackageManagerJava/> task later
60124
NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = templateCodeGenState.JniAddNativeMethodRegistrationAttributePresent;
61-
62-
return !Log.HasLoggedErrors;
63125
}
64126

65-
void WriteTypeMappings (NativeCodeGenState state)
127+
void GenerateTypeMapFromNativeState (NativeCodeGenState state, bool useMarshalMethods)
66128
{
67129
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
68130
// NativeAOT typemaps are generated in `Microsoft.Android.Sdk.ILLink.TypeMappingStep`
69131
Log.LogDebugMessage ("Skipping type maps for NativeAOT.");
70132
return;
71133
}
72-
Log.LogDebugMessage ($"Generating type maps for architecture '{state.TargetArch}'");
134+
Log.LogDebugMessage ($"Generating type maps from native state for architecture '{state.TargetArch}' (RunCheckedBuild = {RunCheckedBuild})");
73135

74136
if (TypemapImplementation != "llvm-ir") {
75137
Log.LogDebugMessage ($"TypemapImplementation='{TypemapImplementation}' will write an empty native typemap.");
76138
state = new NativeCodeGenState (state.TargetArch, new TypeDefinitionCache (), state.Resolver, [], [], state.Classifier);
77139
}
78140

79-
var tmg = new TypeMapGenerator (Log, state, androidRuntime);
141+
var tmg = new TypeMapGenerator (Log, new NativeCodeGenStateAdapter (state), androidRuntime) { RunCheckedBuild = RunCheckedBuild && !useMarshalMethods };
80142
tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, TypemapOutputDirectory);
81143

82-
string abi = MonoAndroidHelper.ArchToAbi (state.TargetArch);
144+
AddOutputTypeMaps (tmg, state.TargetArch);
145+
}
146+
147+
void AddOutputTypeMaps (TypeMapGenerator tmg, AndroidTargetArch arch)
148+
{
149+
string abi = MonoAndroidHelper.ArchToAbi (arch);
83150
var items = new List<ITaskItem> ();
151+
84152
foreach (string file in tmg.GeneratedBinaryTypeMaps) {
85153
var item = new TaskItem (file);
86154
string fileName = Path.GetFileName (file);
@@ -90,6 +158,6 @@ void WriteTypeMappings (NativeCodeGenState state)
90158
items.Add (item);
91159
}
92160

93-
GeneratedBinaryTypeMaps = items.ToArray ();
161+
GeneratedBinaryTypeMaps = GeneratedBinaryTypeMaps.Concat (items).ToArray ();
94162
}
95163
}

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

+45-16
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,25 @@ class TypeMapCecilAdapter
1616
{
1717
public static (List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> managedToJava) GetDebugNativeEntries (NativeCodeGenState state)
1818
{
19+
var (javaToManaged, managedToJava) = GetDebugNativeEntries (state.AllJavaTypes, state.TypeCache, out var foundJniNativeRegistration);
20+
21+
state.JniAddNativeMethodRegistrationAttributePresent = foundJniNativeRegistration;
22+
23+
return (javaToManaged, managedToJava);
24+
}
25+
26+
public static (List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> managedToJava) GetDebugNativeEntries (List<TypeDefinition> types, TypeDefinitionCache cache, out bool foundJniNativeRegistration)
27+
{
28+
var javaDuplicates = new Dictionary<string, List<TypeMapDebugEntry>> (StringComparer.Ordinal);
1929
var javaToManaged = new List<TypeMapDebugEntry> ();
2030
var managedToJava = new List<TypeMapDebugEntry> ();
31+
foundJniNativeRegistration = false;
2132

22-
var javaDuplicates = new Dictionary<string, List<TypeMapDebugEntry>> (StringComparer.Ordinal);
23-
foreach (TypeDefinition td in state.AllJavaTypes) {
24-
UpdateApplicationConfig (state, td);
33+
foreach (var td in types) {
34+
foundJniNativeRegistration = JniAddNativeMethodRegistrationAttributeFound (foundJniNativeRegistration, td);
2535

26-
TypeMapDebugEntry entry = GetDebugEntry (td, state.TypeCache);
27-
HandleDebugDuplicates (javaDuplicates, entry, td, state.TypeCache);
36+
TypeMapDebugEntry entry = GetDebugEntry (td, cache);
37+
HandleDebugDuplicates (javaDuplicates, entry, td, cache);
2838

2939
javaToManaged.Add (entry);
3040
managedToJava.Add (entry);
@@ -40,16 +50,29 @@ public static ReleaseGenerationState GetReleaseGenerationState (NativeCodeGenSta
4050
var genState = new ReleaseGenerationState ();
4151

4252
foreach (TypeDefinition td in state.AllJavaTypes) {
43-
ProcessReleaseType (state, genState, td);
53+
UpdateApplicationConfig (state, td);
54+
ProcessReleaseType (state.TypeCache, genState, td);
4455
}
4556

4657
return genState;
4758
}
4859

49-
static void ProcessReleaseType (NativeCodeGenState state, ReleaseGenerationState genState, TypeDefinition td)
60+
public static ReleaseGenerationState GetReleaseGenerationState (List<TypeDefinition> types, TypeDefinitionCache cache, out bool foundJniNativeRegistration)
5061
{
51-
UpdateApplicationConfig (state, td);
52-
genState.AddKnownAssembly (GetAssemblyName (td));
62+
var genState = new ReleaseGenerationState ();
63+
foundJniNativeRegistration = false;
64+
65+
foreach (TypeDefinition td in types) {
66+
foundJniNativeRegistration = JniAddNativeMethodRegistrationAttributeFound (foundJniNativeRegistration, td);
67+
ProcessReleaseType (cache, genState, td);
68+
}
69+
70+
return genState;
71+
}
72+
73+
static void ProcessReleaseType (TypeDefinitionCache cache, ReleaseGenerationState genState, TypeDefinition td)
74+
{
75+
//genState.AddKnownAssembly (GetAssemblyName (td));
5376

5477
// We must NOT use Guid here! The reason is that Guid sort order is different than its corresponding
5578
// byte array representation and on the runtime we need the latter in order to be able to binary search
@@ -74,7 +97,7 @@ static void ProcessReleaseType (NativeCodeGenState state, ReleaseGenerationState
7497
tempModules.Add (moduleUUID, moduleData);
7598
}
7699

77-
string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td, state.TypeCache);
100+
string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td, cache);
78101
// We will ignore generic types and interfaces when generating the Java to Managed map, but we must not
79102
// omit them from the table we output - we need the same number of entries in both java-to-managed and
80103
// managed-to-java tables. `SkipInJavaToManaged` set to `true` will cause the native assembly generator
@@ -147,6 +170,7 @@ static void HandleDebugDuplicates (Dictionary<string, List<TypeMapDebugEntry>> j
147170
// Fix things up so the abstract type is first, and the `Invoker` is considered a duplicate.
148171
duplicates.Insert (0, entry);
149172
oldEntry.SkipInJavaToManaged = false;
173+
oldEntry.IsInvoker = true;
150174
} else {
151175
// ¯\_(ツ)_/¯
152176
duplicates.Add (entry);
@@ -179,15 +203,20 @@ static void SyncDebugDuplicates (Dictionary<string, List<TypeMapDebugEntry>> jav
179203

180204
static void UpdateApplicationConfig (NativeCodeGenState state, TypeDefinition javaType)
181205
{
182-
if (state.JniAddNativeMethodRegistrationAttributePresent || !javaType.HasCustomAttributes) {
183-
return;
184-
}
206+
state.JniAddNativeMethodRegistrationAttributePresent = JniAddNativeMethodRegistrationAttributeFound (state.JniAddNativeMethodRegistrationAttributePresent, javaType);
207+
}
185208

209+
static bool JniAddNativeMethodRegistrationAttributeFound (bool alreadyFound, TypeDefinition javaType)
210+
{
211+
if (alreadyFound || !javaType.HasCustomAttributes) {
212+
return alreadyFound;
213+
}
214+
186215
foreach (CustomAttribute ca in javaType.CustomAttributes) {
187-
if (!state.JniAddNativeMethodRegistrationAttributePresent && String.Compare ("JniAddNativeMethodRegistrationAttribute", ca.AttributeType.Name, StringComparison.Ordinal) == 0) {
188-
state.JniAddNativeMethodRegistrationAttributePresent = true;
189-
break;
216+
if (string.Equals ("JniAddNativeMethodRegistrationAttribute", ca.AttributeType.Name, StringComparison.Ordinal)) {
217+
return true;
190218
}
191219
}
220+
return false;
192221
}
193222
}

0 commit comments

Comments
 (0)