Skip to content

Commit cf62814

Browse files
authored
[XABT] Refactor manifest merging and ACW map generation out of GenerateJavaStubs. (#9827)
Begin breaking down the `<GenerateJavaStubs>` task into smaller, more manageable pieces by moving the manifest merging and ACW map generation into separate tasks. Once this is complete, we should be able to start moving these steps that require Cecil assembly scanning into linker steps (or equivalent), eventually saving a Cecil assembly scan per build. Additionally, create a new `GetProjectBuildSpecificTaskObjectKey` to be used with `GetRegisteredTaskObject`. This version additional uses `$(IntermediateOutpuPath)` in the key to help ensure data from multi-targeted builds (`net8.0-android,net9.0-android`) is not overwritten.
1 parent e28531d commit cf62814

File tree

6 files changed

+308
-192
lines changed

6 files changed

+308
-192
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#nullable enable
2+
using System.Collections.Concurrent;
3+
using System.Linq;
4+
using Microsoft.Android.Build.Tasks;
5+
using Microsoft.Build.Framework;
6+
using Xamarin.Android.Tools;
7+
8+
namespace Xamarin.Android.Tasks;
9+
10+
public class GenerateACWMap : AndroidTask
11+
{
12+
public override string TaskPrefix => "ACW";
13+
14+
[Required]
15+
public string AcwMapFile { get; set; } = "";
16+
17+
[Required]
18+
public string IntermediateOutputDirectory { get; set; } = "";
19+
20+
public override bool RunTask ()
21+
{
22+
// Retrieve the stored NativeCodeGenState
23+
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>> (
24+
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
25+
RegisteredTaskObjectLifetime.Build
26+
);
27+
28+
// We only need the first architecture, since this task is architecture-agnostic
29+
var templateCodeGenState = nativeCodeGenStates.First ().Value;
30+
31+
var acwMapGen = new ACWMapGenerator (Log);
32+
33+
if (!acwMapGen.Generate (templateCodeGenState, AcwMapFile)) {
34+
Log.LogDebugMessage ("ACW map generation failed");
35+
}
36+
37+
if (Log.HasLoggedErrors) {
38+
// Ensure that on a rebuild, we don't *skip* the `_GenerateJavaStubs` target,
39+
// by ensuring that the target outputs have been deleted.
40+
Files.DeleteFile (AcwMapFile, Log);
41+
}
42+
43+
return !Log.HasLoggedErrors;
44+
}
45+
}

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

Lines changed: 7 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
using Xamarin.Android.Tools;
1717
using Microsoft.Android.Build.Tasks;
18-
using Java.Interop.Tools.JavaCallableWrappers.Adapters;
1918
using System.Threading.Tasks;
2019
using System.Collections.Concurrent;
2120

@@ -35,9 +34,6 @@ public class GenerateJavaStubs : AndroidTask
3534
[Required]
3635
public ITaskItem[] ResolvedUserAssemblies { get; set; }
3736

38-
[Required]
39-
public string AcwMapFile { get; set; }
40-
4137
[Required]
4238
public ITaskItem [] FrameworkDirectories { get; set; }
4339

@@ -54,40 +50,20 @@ public class GenerateJavaStubs : AndroidTask
5450
public bool LinkingEnabled { get; set; }
5551
public bool HaveMultipleRIDs { get; set; }
5652
public bool EnableMarshalMethods { get; set; }
57-
public string ManifestTemplate { get; set; }
58-
public string[] MergedManifestDocuments { get; set; }
5953

6054
public bool Debug { get; set; }
61-
public bool MultiDex { get; set; }
62-
public string ApplicationLabel { get; set; }
63-
public string PackageName { get; set; }
64-
public string VersionName { get; set; }
65-
public string VersionCode { get; set; }
66-
public string [] ManifestPlaceholders { get; set; }
67-
68-
public string AndroidSdkDir { get; set; }
6955

7056
public string AndroidSdkPlatform { get; set; }
7157
public string OutputDirectory { get; set; }
72-
public string MergedAndroidManifestOutput { get; set; }
73-
74-
public bool EmbedAssemblies { get; set; }
75-
public bool NeedsInternet { get; set; }
7658

7759
public bool ErrorOnCustomJavaObject { get; set; }
7860

79-
public string BundledWearApplicationName { get; set; }
80-
8161
public string PackageNamingPolicy { get; set; }
8262

8363
public string ApplicationJavaClass { get; set; }
8464

8565
public bool SkipJniAddNativeMethodRegistrationAttributeScan { get; set; }
8666

87-
public string CheckedBuild { get; set; }
88-
89-
public string SupportedOSPlatformVersion { get; set; }
90-
9167
public ITaskItem[] Environments { get; set; }
9268

9369
[Output]
@@ -98,9 +74,6 @@ public class GenerateJavaStubs : AndroidTask
9874

9975
public string CodeGenerationTarget { get; set; } = "";
10076

101-
[Required]
102-
public string TargetName { get; set; } = "";
103-
10477
AndroidRuntime androidRuntime;
10578
JavaPeerStyle codeGenerationTarget;
10679

@@ -119,13 +92,6 @@ public override bool RunTask ()
11992
Log.LogMessage (e.ToString ());
12093
}
12194

122-
if (Log.HasLoggedErrors) {
123-
// Ensure that on a rebuild, we don't *skip* the `_GenerateJavaStubs` target,
124-
// by ensuring that the target outputs have been deleted.
125-
Files.DeleteFile (MergedAndroidManifestOutput, Log);
126-
Files.DeleteFile (AcwMapFile, Log);
127-
}
128-
12995
return !Log.HasLoggedErrors;
13096
}
13197

@@ -275,137 +241,18 @@ void Run (bool useMarshalMethods)
275241
// Set for use by <GeneratePackageManagerJava/> task later
276242
NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = templateCodeGenState.JniAddNativeMethodRegistrationAttributePresent;
277243

278-
var acwMapGen = new ACWMapGenerator (Log);
279-
if (!acwMapGen.Generate (templateCodeGenState, AcwMapFile)) {
280-
Log.LogDebugMessage ("ACW map generation failed");
281-
}
282-
283-
IList<string> additionalProviders = MergeManifest (templateCodeGenState, MaybeGetArchAssemblies (userAssembliesPerArch, templateCodeGenState.TargetArch));
284-
GenerateAdditionalProviderSources (templateCodeGenState, additionalProviders);
285-
286-
if (useMarshalMethods) {
287-
// Save NativeCodeGenState for <GeneratePackageManagerJava/> task later
288-
Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenState)} to {nameof (NativeCodeGenStateRegisterTaskKey)}");
289-
BuildEngine4.RegisterTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (NativeCodeGenStateRegisterTaskKey), nativeCodeGenStates, RegisteredTaskObjectLifetime.Build);
290-
} else {
291-
// Otherwise, dispose all XAAssemblyResolvers
292-
Log.LogDebugMessage ($"Disposing all {nameof (NativeCodeGenState)}.{nameof (NativeCodeGenState.Resolver)}");
293-
foreach (var state in nativeCodeGenStates.Values) {
294-
state.Resolver.Dispose ();
295-
}
296-
}
297-
298-
Dictionary<string, ITaskItem> MaybeGetArchAssemblies (Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> dict, AndroidTargetArch arch)
299-
{
300-
if (!dict.TryGetValue (arch, out Dictionary<string, ITaskItem> archDict)) {
301-
return new Dictionary<string, ITaskItem> (StringComparer.OrdinalIgnoreCase);
302-
}
303-
304-
return archDict;
305-
}
306-
}
307-
308-
void GenerateAdditionalProviderSources (NativeCodeGenState codeGenState, IList<string> additionalProviders)
309-
{
310-
if (androidRuntime != Xamarin.Android.Tasks.AndroidRuntime.CoreCLR) {
311-
// Create additional runtime provider java sources.
312-
bool isMonoVM = androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.MonoVM;
313-
string providerTemplateFile = isMonoVM ?
314-
"MonoRuntimeProvider.Bundled.java" :
315-
"NativeAotRuntimeProvider.java";
316-
string providerTemplate = GetResource (providerTemplateFile);
317-
318-
foreach (var provider in additionalProviders) {
319-
var contents = providerTemplate.Replace (isMonoVM ? "MonoRuntimeProvider" : "NativeAotRuntimeProvider", provider);
320-
var real_provider = isMonoVM ?
321-
Path.Combine (OutputDirectory, "src", "mono", provider + ".java") :
322-
Path.Combine (OutputDirectory, "src", "net", "dot", "jni", "nativeaot", provider + ".java");
323-
Files.CopyIfStringChanged (contents, real_provider);
324-
}
325-
} else {
326-
Log.LogDebugMessage ($"Skipping android.content.ContentProvider generation for: {androidRuntime}");
327-
}
328-
329-
// For NativeAOT, generate JavaInteropRuntime.java
330-
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
331-
const string fileName = "JavaInteropRuntime.java";
332-
string template = GetResource (fileName);
333-
var contents = template.Replace ("@MAIN_ASSEMBLY_NAME@", TargetName);
334-
var path = Path.Combine (OutputDirectory, "src", "net", "dot", "jni", "nativeaot", fileName);
335-
Log.LogDebugMessage ($"Writing: {path}");
336-
Files.CopyIfStringChanged (contents, path);
337-
}
338-
339-
// Create additional application java sources.
340-
StringWriter regCallsWriter = new StringWriter ();
341-
regCallsWriter.WriteLine ("// Application and Instrumentation ACWs must be registered first.");
342-
foreach (TypeDefinition type in codeGenState.JavaTypesForJCW) {
343-
if (JavaNativeTypeManager.IsApplication (type, codeGenState.TypeCache) || JavaNativeTypeManager.IsInstrumentation (type, codeGenState.TypeCache)) {
344-
if (codeGenState.Classifier != null && !codeGenState.Classifier.FoundDynamicallyRegisteredMethods (type)) {
345-
continue;
346-
}
347-
348-
string javaKey = JavaNativeTypeManager.ToJniName (type, codeGenState.TypeCache).Replace ('/', '.');
349-
regCallsWriter.WriteLine (
350-
codeGenerationTarget == JavaPeerStyle.XAJavaInterop1 ?
351-
"\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);" :
352-
"\t\tnet.dot.jni.ManagedPeer.registerNativeMembers ({1}.class, {1}.__md_methods);",
353-
type.GetAssemblyQualifiedName (codeGenState.TypeCache),
354-
javaKey
355-
);
356-
}
357-
}
358-
regCallsWriter.Close ();
359-
360-
var real_app_dir = Path.Combine (OutputDirectory, "src", "net", "dot", "android");
361-
string applicationTemplateFile = "ApplicationRegistration.java";
362-
SaveResource (
363-
applicationTemplateFile,
364-
applicationTemplateFile,
365-
real_app_dir,
366-
template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ())
367-
);
244+
// Save NativeCodeGenState for later tasks
245+
Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenState)} to {nameof (NativeCodeGenStateRegisterTaskKey)}");
246+
BuildEngine4.RegisterTaskObjectAssemblyLocal (MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory), nativeCodeGenStates, RegisteredTaskObjectLifetime.Build);
368247
}
369248

370-
IList<string> MergeManifest (NativeCodeGenState codeGenState, Dictionary<string, ITaskItem> userAssemblies)
249+
internal static Dictionary<string, ITaskItem> MaybeGetArchAssemblies (Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> dict, AndroidTargetArch arch)
371250
{
372-
var manifest = new ManifestDocument (ManifestTemplate) {
373-
PackageName = PackageName,
374-
VersionName = VersionName,
375-
ApplicationLabel = ApplicationLabel ?? PackageName,
376-
Placeholders = ManifestPlaceholders,
377-
Resolver = codeGenState.Resolver,
378-
SdkDir = AndroidSdkDir,
379-
TargetSdkVersion = AndroidSdkPlatform,
380-
MinSdkVersion = MonoAndroidHelper.ConvertSupportedOSPlatformVersionToApiLevel (SupportedOSPlatformVersion).ToString (),
381-
Debug = Debug,
382-
MultiDex = MultiDex,
383-
NeedsInternet = NeedsInternet,
384-
AndroidRuntime = androidRuntime,
385-
};
386-
// Only set manifest.VersionCode if there is no existing value in AndroidManifest.xml.
387-
if (manifest.HasVersionCode) {
388-
Log.LogDebugMessage ($"Using existing versionCode in: {ManifestTemplate}");
389-
} else if (!string.IsNullOrEmpty (VersionCode)) {
390-
manifest.VersionCode = VersionCode;
391-
}
392-
manifest.Assemblies.AddRange (userAssemblies.Values.Select (item => item.ItemSpec));
393-
394-
if (!String.IsNullOrWhiteSpace (CheckedBuild)) {
395-
// We don't validate CheckedBuild value here, this will be done in BuildApk. We just know that if it's
396-
// on then we need android:debuggable=true and android:extractNativeLibs=true
397-
manifest.ForceDebuggable = true;
398-
manifest.ForceExtractNativeLibs = true;
399-
}
400-
401-
IList<string> additionalProviders = manifest.Merge (Log, codeGenState.TypeCache, codeGenState.AllJavaTypes, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments);
402-
403-
// Only write the new manifest if it actually changed
404-
if (manifest.SaveIfChanged (Log, MergedAndroidManifestOutput)) {
405-
Log.LogDebugMessage ($"Saving: {MergedAndroidManifestOutput}");
251+
if (!dict.TryGetValue (arch, out Dictionary<string, ITaskItem> archDict)) {
252+
return new Dictionary<string, ITaskItem> (StringComparer.OrdinalIgnoreCase);
406253
}
407254

408-
return additionalProviders;
255+
return archDict;
409256
}
410257

411258
(bool success, NativeCodeGenState? stubsState) GenerateJavaSourcesAndMaybeClassifyMarshalMethods (AndroidTargetArch arch, Dictionary<string, ITaskItem> assemblies, Dictionary<string, ITaskItem> userAssemblies, bool useMarshalMethods, bool generateJavaCode)
@@ -464,20 +311,6 @@ void RewriteMarshalMethods (NativeCodeGenState state, bool brokenExceptionTransi
464311
rewriter.Rewrite (brokenExceptionTransitionsEnabled);
465312
}
466313

467-
string GetResource (string resource)
468-
{
469-
using (var stream = GetType ().Assembly.GetManifestResourceStream (resource))
470-
using (var reader = new StreamReader (stream))
471-
return reader.ReadToEnd ();
472-
}
473-
474-
void SaveResource (string resource, string filename, string destDir, Func<string, string> applyTemplate)
475-
{
476-
string template = GetResource (resource);
477-
template = applyTemplate (template);
478-
Files.CopyIfStringChanged (template, Path.Combine (destDir, filename));
479-
}
480-
481314
void WriteTypeMappings (NativeCodeGenState state)
482315
{
483316
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {

0 commit comments

Comments
 (0)