-
Notifications
You must be signed in to change notification settings - Fork 538
/
Copy pathGenerateJavaStubs.cs
244 lines (192 loc) · 10.3 KB
/
GenerateJavaStubs.cs
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
241
242
243
244
// Copyright (C) 2011 Xamarin, Inc. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Mono.Cecil;
using Microsoft.Build.Utilities;
using Java.Interop.Tools.Cecil;
using Java.Interop.Tools.Diagnostics;
using Java.Interop.Tools.JavaCallableWrappers;
using Java.Interop.Tools.TypeNameMappings;
using Xamarin.Android.Tools;
using Microsoft.Android.Build.Tasks;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace Xamarin.Android.Tasks
{
using PackageNamingPolicyEnum = PackageNamingPolicy;
public class GenerateJavaStubs : AndroidTask
{
public const string NativeCodeGenStateRegisterTaskKey = ".:!MarshalMethods!:.";
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; }
public string AndroidSdkPlatform { get; set; }
public string OutputDirectory { get; set; }
public bool ErrorOnCustomJavaObject { get; set; }
public string PackageNamingPolicy { get; set; }
public string ApplicationJavaClass { get; set; }
public string CodeGenerationTarget { get; set; } = "";
JavaPeerStyle codeGenerationTarget;
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;
}
XAAssemblyResolver MakeResolver (bool useMarshalMethods, AndroidTargetArch targetArch, Dictionary<string, ITaskItem> assemblies)
{
var readerParams = new ReaderParameters ();
if (useMarshalMethods) {
readerParams.ReadWrite = true;
readerParams.InMemory = true;
}
var res = new XAAssemblyResolver (targetArch, Log, loadDebugSymbols: true, loadReaderParameters: readerParams);
var uniqueDirs = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
Log.LogDebugMessage ($"Adding search directories to new architecture {targetArch} resolver:");
foreach (var kvp in assemblies) {
string assemblyDir = Path.GetDirectoryName (kvp.Value.ItemSpec);
if (uniqueDirs.Contains (assemblyDir)) {
continue;
}
uniqueDirs.Add (assemblyDir);
res.SearchDirectories.Add (assemblyDir);
Log.LogDebugMessage ($" {assemblyDir}");
}
return res;
}
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;
var firstArch = allAssembliesPerArch.First ().Key;
var generateSucceeded = true;
// Generate Java sources in parallel
Parallel.ForEach (allAssembliesPerArch, (kvp) => {
AndroidTargetArch arch = kvp.Key;
Dictionary<string, ITaskItem> archAssemblies = kvp.Value;
// We only need to generate Java code for one ABI, as the Java code is ABI-agnostic
// Pick the "first" one as the one to generate Java code for
var generateJavaCode = arch == firstArch;
(bool success, NativeCodeGenState? state) = GenerateJavaSourcesAndMaybeClassifyMarshalMethods (arch, archAssemblies, MaybeGetArchAssemblies (userAssembliesPerArch, arch), useMarshalMethods, generateJavaCode);
if (!success) {
generateSucceeded = false;
}
// If this is the first architecture, we need to store the state for later use
if (generateJavaCode) {
templateCodeGenState = state;
}
nativeCodeGenStates.TryAdd (arch, state);
});
// If we hit an error generating the Java code, we should bail out now
if (!generateSucceeded)
return;
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);
}
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;
}
(bool success, NativeCodeGenState? stubsState) GenerateJavaSourcesAndMaybeClassifyMarshalMethods (AndroidTargetArch arch, Dictionary<string, ITaskItem> assemblies, Dictionary<string, ITaskItem> userAssemblies, bool useMarshalMethods, bool generateJavaCode)
{
XAAssemblyResolver resolver = MakeResolver (useMarshalMethods, arch, assemblies);
var tdCache = new TypeDefinitionCache ();
(List<TypeDefinition> allJavaTypes, List<TypeDefinition> javaTypesForJCW) = ScanForJavaTypes (resolver, tdCache, assemblies, userAssemblies, useMarshalMethods);
var jcwContext = new JCWGeneratorContext (arch, resolver, assemblies.Values, javaTypesForJCW, tdCache, useMarshalMethods);
var jcwGenerator = new JCWGenerator (Log, jcwContext) {
CodeGenerationTarget = codeGenerationTarget,
};
bool success;
if (generateJavaCode) {
success = jcwGenerator.GenerateAndClassify (AndroidSdkPlatform, outputPath: Path.Combine (OutputDirectory, "src"), ApplicationJavaClass);
} else {
success = jcwGenerator.Classify (AndroidSdkPlatform);
}
if (!success) {
return (false, null);
}
return (true, new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, jcwGenerator.Classifier));
}
(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;
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);
}
}
}