-
Notifications
You must be signed in to change notification settings - Fork 569
Expand file tree
/
Copy pathGenerateJavaStubs.cs
More file actions
240 lines (188 loc) · 10.2 KB
/
GenerateJavaStubs.cs
File metadata and controls
240 lines (188 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright (C) 2011 Xamarin, Inc. All rights reserved.
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Java.Interop.Tools.Cecil;
using Java.Interop.Tools.Diagnostics;
using Java.Interop.Tools.JavaCallableWrappers;
using Java.Interop.Tools.JavaCallableWrappers.Adapters;
using Java.Interop.Tools.TypeNameMappings;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.Cecil;
using Xamarin.Android.Tools;
namespace Xamarin.Android.Tasks
{
using PackageNamingPolicyEnum = PackageNamingPolicy;
public class GenerateJavaStubs : AndroidTask
{
public const string NativeCodeGenStateRegisterTaskKey = ".:!MarshalMethods!:.";
public const string NativeCodeGenStateObjectRegisterTaskKey = ".:!MarshalMethodsObject!:.";
public override string TaskPrefix => "GJS";
[Required]
public ITaskItem[] ResolvedAssemblies { get; set; } = [];
[Required]
public ITaskItem[] ResolvedUserAssemblies { get; set; } = [];
[Required]
public ITaskItem [] FrameworkDirectories { get; set; } = [];
[Required]
public string [] SupportedAbis { get; set; } = [];
public string? IntermediateOutputDirectory { get; set; }
public bool EnableMarshalMethods { get; set; }
public bool Debug { get; set; }
[Required]
public string OutputDirectory { get; set; } = "";
public bool ErrorOnCustomJavaObject { get; set; }
public string? PackageNamingPolicy { get; set; }
[Required]
public string ApplicationJavaClass { get; set; } = "";
public string CodeGenerationTarget { get; set; } = "";
public bool EnableNativeRuntimeLinking { get; set; }
JavaPeerStyle codeGenerationTarget;
//[Output]
//public ITaskItem [] GeneratedJavaFilesOutput { get; set; }
internal const string AndroidSkipJavaStubGeneration = "AndroidSkipJavaStubGeneration";
public override bool RunTask ()
{
try {
codeGenerationTarget = MonoAndroidHelper.ParseCodeGenerationTarget (CodeGenerationTarget);
bool useMarshalMethods = !Debug && EnableMarshalMethods;
Run (useMarshalMethods);
} catch (XamarinAndroidException e) {
Log.LogCodedError (string.Format ("XA{0:0000}", e.Code), e.MessageWithoutCode);
if (MonoAndroidHelper.LogInternalExceptions)
Log.LogMessage (e.ToString ());
}
return !Log.HasLoggedErrors;
}
void Run (bool useMarshalMethods)
{
PackageNamingPolicy pnp;
JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64;
// We will process each architecture completely separately as both type maps and marshal methods are strictly per-architecture and
// the assemblies should be processed strictly per architecture. Generation of JCWs, and the manifest are ABI-agnostic.
// We will generate them only for the first architecture, whichever it is.
Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> allAssembliesPerArch = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, SupportedAbis, validate: true);
// Should "never" happen...
if (allAssembliesPerArch.Count != SupportedAbis.Length) {
// ...but it happens at least in our `BuildAMassiveApp` test, where `SupportedAbis` mentions only the `x86` and `armeabi-v7a` ABIs, but `ResolvedAssemblies` contains
// entries for all the ABIs we support, so let's be flexible and ignore the extra architectures but still error out if there are less architectures than supported ABIs.
if (allAssembliesPerArch.Count < SupportedAbis.Length) {
throw new InvalidOperationException ($"Internal error: number of architectures ({allAssembliesPerArch.Count}) must equal the number of target ABIs ({SupportedAbis.Length})");
}
}
// ...or this...
foreach (string abi in SupportedAbis) {
AndroidTargetArch arch = MonoAndroidHelper.AbiToTargetArch (abi);
if (!allAssembliesPerArch.ContainsKey (arch)) {
throw new InvalidOperationException ($"Internal error: no assemblies for architecture '{arch}', which corresponds to target abi '{abi}'");
}
}
// ...as well as this
Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> userAssembliesPerArch = MonoAndroidHelper.GetPerArchAssemblies (ResolvedUserAssemblies, SupportedAbis, validate: true);
foreach (var kvp in userAssembliesPerArch) {
if (!allAssembliesPerArch.TryGetValue (kvp.Key, out Dictionary<string, ITaskItem> allAssemblies)) {
throw new InvalidOperationException ($"Internal error: found user assemblies for architecture '{kvp.Key}' which isn't found in ResolvedAssemblies");
}
foreach (var asmKvp in kvp.Value) {
if (!allAssemblies.ContainsKey (asmKvp.Key)) {
throw new InvalidOperationException ($"Internal error: user assembly '{asmKvp.Value}' not found in ResolvedAssemblies");
}
}
}
// Now that "never" never happened, we can proceed knowing that at least the assembly sets are the same for each architecture
var nativeCodeGenStates = new ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState> ();
NativeCodeGenState? templateCodeGenState = null;
PinvokeScanner? pinvokeScanner = EnableNativeRuntimeLinking ? new PinvokeScanner (Log) : null;
var firstArch = allAssembliesPerArch.First ().Key;
// Process each architecture in parallel
Parallel.ForEach (allAssembliesPerArch, (kvp) => {
AndroidTargetArch arch = kvp.Key;
Dictionary<string, ITaskItem> archAssemblies = kvp.Value;
NativeCodeGenState? state = ScanTypesAndMaybeClassifyMarshalMethods (arch, archAssemblies, MaybeGetArchAssemblies (userAssembliesPerArch, arch), useMarshalMethods);
// If this is the first architecture, we need to store the state for later use
if (arch == firstArch) {
templateCodeGenState = state;
}
if (state != null) {
nativeCodeGenStates.TryAdd (arch, state);
if (pinvokeScanner != null && state != null) {
(bool success, List<PinvokeScanner.PinvokeEntryInfo>? pinfos) = ScanForUsedPinvokes (pinvokeScanner, arch, state.Resolver);
if (!success) {
return;
}
state.PinvokeInfos = pinfos;
Log.LogDebugMessage ($"Number of unique p/invokes for architecture '{arch}': {pinfos?.Count}");
}
}
});
if (templateCodeGenState == null) {
throw new InvalidOperationException ($"Internal error: no native code generator state defined");
}
JCWGenerator.EnsureAllArchitecturesAreIdentical (Log, nativeCodeGenStates);
// Save NativeCodeGenState for later tasks
Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenState)} to {nameof (NativeCodeGenStateRegisterTaskKey)}");
BuildEngine4.RegisterTaskObjectAssemblyLocal (MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory), nativeCodeGenStates, RegisteredTaskObjectLifetime.Build);
}
(bool success, List<PinvokeScanner.PinvokeEntryInfo>? pinfos) ScanForUsedPinvokes (PinvokeScanner scanner, AndroidTargetArch arch, XAAssemblyResolver resolver)
{
if (!EnableNativeRuntimeLinking) {
return (true, null);
}
var frameworkAssemblies = new List<ITaskItem> ();
foreach (ITaskItem asm in ResolvedAssemblies) {
string? metadata = asm.GetMetadata ("FrameworkAssembly");
if (String.IsNullOrEmpty (metadata)) {
continue;
}
if (!Boolean.TryParse (metadata, out bool isFrameworkAssembly) || !isFrameworkAssembly) {
continue;
}
frameworkAssemblies.Add (asm);
}
var pinfos = scanner.Scan (arch, resolver, frameworkAssemblies);
return (true, pinfos);
}
internal static Dictionary<string, ITaskItem> MaybeGetArchAssemblies (Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> dict, AndroidTargetArch arch)
{
if (!dict.TryGetValue (arch, out Dictionary<string, ITaskItem> archDict)) {
return new Dictionary<string, ITaskItem> (StringComparer.OrdinalIgnoreCase);
}
return archDict;
}
NativeCodeGenState? ScanTypesAndMaybeClassifyMarshalMethods (AndroidTargetArch arch, Dictionary<string, ITaskItem> assemblies, Dictionary<string, ITaskItem> userAssemblies, bool useMarshalMethods)
{
XAAssemblyResolver resolver = MonoAndroidHelper.MakeResolver (Log, useMarshalMethods, arch, assemblies);
var tdCache = new TypeDefinitionCache ();
(List<TypeDefinition> allJavaTypes, List<TypeDefinition> javaTypesForJCW) = ScanForJavaTypes (resolver, tdCache, assemblies, userAssemblies, useMarshalMethods);
// Marshal method classification is now done in the inner build by
// RewriteMarshalMethods, so we never classify here. The NativeCodeGenState
// will have a null Classifier; downstream tasks must handle that.
return new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, classifier: null);
}
(List<TypeDefinition> allJavaTypes, List<TypeDefinition> javaTypesForJCW) ScanForJavaTypes (XAAssemblyResolver res, TypeDefinitionCache cache, Dictionary<string, ITaskItem> assemblies, Dictionary<string, ITaskItem> userAssemblies, bool useMarshalMethods)
{
var scanner = new XAJavaTypeScanner (res.TargetArch, Log, cache) {
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
};
List<TypeDefinition> allJavaTypes = scanner.GetJavaTypes (assemblies.Values, res);
var javaTypesForJCW = new List<TypeDefinition> ();
// 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
// application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android
// build and stored in a jar file.
bool shouldSkipNonUserAssemblies = !useMarshalMethods && codeGenerationTarget == JavaPeerStyle.XAJavaInterop1;
Log.LogDebugMessage ($"Should skip non-user assemblies: {shouldSkipNonUserAssemblies} (useMarshalMethods: {useMarshalMethods})");
foreach (TypeDefinition type in allJavaTypes) {
if ((shouldSkipNonUserAssemblies && !userAssemblies.ContainsKey (type.Module.Assembly.Name.Name)) || JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (type, cache)) {
continue;
}
javaTypesForJCW.Add (type);
}
return (allJavaTypes, javaTypesForJCW);
}
}
}