Skip to content

Commit

Permalink
[XABT] Refactor manifest merging and ACW map generation out of `Gener…
Browse files Browse the repository at this point in the history
…ateJavaStubs`. (#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.
  • Loading branch information
jpobst authored Feb 26, 2025
1 parent e28531d commit cf62814
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 192 deletions.
45 changes: 45 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/GenerateACWMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#nullable enable
using System.Collections.Concurrent;
using System.Linq;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Xamarin.Android.Tools;

namespace Xamarin.Android.Tasks;

public class GenerateACWMap : AndroidTask
{
public override string TaskPrefix => "ACW";

[Required]
public string AcwMapFile { get; set; } = "";

[Required]
public string IntermediateOutputDirectory { get; set; } = "";

public override bool RunTask ()
{
// Retrieve the stored NativeCodeGenState
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal<ConcurrentDictionary<AndroidTargetArch, NativeCodeGenState>> (
MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
RegisteredTaskObjectLifetime.Build
);

// We only need the first architecture, since this task is architecture-agnostic
var templateCodeGenState = nativeCodeGenStates.First ().Value;

var acwMapGen = new ACWMapGenerator (Log);

if (!acwMapGen.Generate (templateCodeGenState, AcwMapFile)) {
Log.LogDebugMessage ("ACW map generation failed");
}

if (Log.HasLoggedErrors) {
// Ensure that on a rebuild, we don't *skip* the `_GenerateJavaStubs` target,
// by ensuring that the target outputs have been deleted.
Files.DeleteFile (AcwMapFile, Log);
}

return !Log.HasLoggedErrors;
}
}
181 changes: 7 additions & 174 deletions src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

using Xamarin.Android.Tools;
using Microsoft.Android.Build.Tasks;
using Java.Interop.Tools.JavaCallableWrappers.Adapters;
using System.Threading.Tasks;
using System.Collections.Concurrent;

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

[Required]
public string AcwMapFile { get; set; }

[Required]
public ITaskItem [] FrameworkDirectories { get; set; }

Expand All @@ -54,40 +50,20 @@ public class GenerateJavaStubs : AndroidTask
public bool LinkingEnabled { get; set; }
public bool HaveMultipleRIDs { get; set; }
public bool EnableMarshalMethods { get; set; }
public string ManifestTemplate { get; set; }
public string[] MergedManifestDocuments { get; set; }

public bool Debug { get; set; }
public bool MultiDex { get; set; }
public string ApplicationLabel { get; set; }
public string PackageName { get; set; }
public string VersionName { get; set; }
public string VersionCode { get; set; }
public string [] ManifestPlaceholders { get; set; }

public string AndroidSdkDir { get; set; }

public string AndroidSdkPlatform { get; set; }
public string OutputDirectory { get; set; }
public string MergedAndroidManifestOutput { get; set; }

public bool EmbedAssemblies { get; set; }
public bool NeedsInternet { get; set; }

public bool ErrorOnCustomJavaObject { get; set; }

public string BundledWearApplicationName { get; set; }

public string PackageNamingPolicy { get; set; }

public string ApplicationJavaClass { get; set; }

public bool SkipJniAddNativeMethodRegistrationAttributeScan { get; set; }

public string CheckedBuild { get; set; }

public string SupportedOSPlatformVersion { get; set; }

public ITaskItem[] Environments { get; set; }

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

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

[Required]
public string TargetName { get; set; } = "";

AndroidRuntime androidRuntime;
JavaPeerStyle codeGenerationTarget;

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

if (Log.HasLoggedErrors) {
// Ensure that on a rebuild, we don't *skip* the `_GenerateJavaStubs` target,
// by ensuring that the target outputs have been deleted.
Files.DeleteFile (MergedAndroidManifestOutput, Log);
Files.DeleteFile (AcwMapFile, Log);
}

return !Log.HasLoggedErrors;
}

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

var acwMapGen = new ACWMapGenerator (Log);
if (!acwMapGen.Generate (templateCodeGenState, AcwMapFile)) {
Log.LogDebugMessage ("ACW map generation failed");
}

IList<string> additionalProviders = MergeManifest (templateCodeGenState, MaybeGetArchAssemblies (userAssembliesPerArch, templateCodeGenState.TargetArch));
GenerateAdditionalProviderSources (templateCodeGenState, additionalProviders);

if (useMarshalMethods) {
// Save NativeCodeGenState for <GeneratePackageManagerJava/> task later
Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenState)} to {nameof (NativeCodeGenStateRegisterTaskKey)}");
BuildEngine4.RegisterTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (NativeCodeGenStateRegisterTaskKey), nativeCodeGenStates, RegisteredTaskObjectLifetime.Build);
} else {
// Otherwise, dispose all XAAssemblyResolvers
Log.LogDebugMessage ($"Disposing all {nameof (NativeCodeGenState)}.{nameof (NativeCodeGenState.Resolver)}");
foreach (var state in nativeCodeGenStates.Values) {
state.Resolver.Dispose ();
}
}

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;
}
}

void GenerateAdditionalProviderSources (NativeCodeGenState codeGenState, IList<string> additionalProviders)
{
if (androidRuntime != Xamarin.Android.Tasks.AndroidRuntime.CoreCLR) {
// Create additional runtime provider java sources.
bool isMonoVM = androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.MonoVM;
string providerTemplateFile = isMonoVM ?
"MonoRuntimeProvider.Bundled.java" :
"NativeAotRuntimeProvider.java";
string providerTemplate = GetResource (providerTemplateFile);

foreach (var provider in additionalProviders) {
var contents = providerTemplate.Replace (isMonoVM ? "MonoRuntimeProvider" : "NativeAotRuntimeProvider", provider);
var real_provider = isMonoVM ?
Path.Combine (OutputDirectory, "src", "mono", provider + ".java") :
Path.Combine (OutputDirectory, "src", "net", "dot", "jni", "nativeaot", provider + ".java");
Files.CopyIfStringChanged (contents, real_provider);
}
} else {
Log.LogDebugMessage ($"Skipping android.content.ContentProvider generation for: {androidRuntime}");
}

// For NativeAOT, generate JavaInteropRuntime.java
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
const string fileName = "JavaInteropRuntime.java";
string template = GetResource (fileName);
var contents = template.Replace ("@MAIN_ASSEMBLY_NAME@", TargetName);
var path = Path.Combine (OutputDirectory, "src", "net", "dot", "jni", "nativeaot", fileName);
Log.LogDebugMessage ($"Writing: {path}");
Files.CopyIfStringChanged (contents, path);
}

// Create additional application java sources.
StringWriter regCallsWriter = new StringWriter ();
regCallsWriter.WriteLine ("// Application and Instrumentation ACWs must be registered first.");
foreach (TypeDefinition type in codeGenState.JavaTypesForJCW) {
if (JavaNativeTypeManager.IsApplication (type, codeGenState.TypeCache) || JavaNativeTypeManager.IsInstrumentation (type, codeGenState.TypeCache)) {
if (codeGenState.Classifier != null && !codeGenState.Classifier.FoundDynamicallyRegisteredMethods (type)) {
continue;
}

string javaKey = JavaNativeTypeManager.ToJniName (type, codeGenState.TypeCache).Replace ('/', '.');
regCallsWriter.WriteLine (
codeGenerationTarget == JavaPeerStyle.XAJavaInterop1 ?
"\t\tmono.android.Runtime.register (\"{0}\", {1}.class, {1}.__md_methods);" :
"\t\tnet.dot.jni.ManagedPeer.registerNativeMembers ({1}.class, {1}.__md_methods);",
type.GetAssemblyQualifiedName (codeGenState.TypeCache),
javaKey
);
}
}
regCallsWriter.Close ();

var real_app_dir = Path.Combine (OutputDirectory, "src", "net", "dot", "android");
string applicationTemplateFile = "ApplicationRegistration.java";
SaveResource (
applicationTemplateFile,
applicationTemplateFile,
real_app_dir,
template => template.Replace ("// REGISTER_APPLICATION_AND_INSTRUMENTATION_CLASSES_HERE", regCallsWriter.ToString ())
);
// Save NativeCodeGenState for later tasks
Log.LogDebugMessage ($"Saving {nameof (NativeCodeGenState)} to {nameof (NativeCodeGenStateRegisterTaskKey)}");
BuildEngine4.RegisterTaskObjectAssemblyLocal (MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory), nativeCodeGenStates, RegisteredTaskObjectLifetime.Build);
}

IList<string> MergeManifest (NativeCodeGenState codeGenState, Dictionary<string, ITaskItem> userAssemblies)
internal static Dictionary<string, ITaskItem> MaybeGetArchAssemblies (Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> dict, AndroidTargetArch arch)
{
var manifest = new ManifestDocument (ManifestTemplate) {
PackageName = PackageName,
VersionName = VersionName,
ApplicationLabel = ApplicationLabel ?? PackageName,
Placeholders = ManifestPlaceholders,
Resolver = codeGenState.Resolver,
SdkDir = AndroidSdkDir,
TargetSdkVersion = AndroidSdkPlatform,
MinSdkVersion = MonoAndroidHelper.ConvertSupportedOSPlatformVersionToApiLevel (SupportedOSPlatformVersion).ToString (),
Debug = Debug,
MultiDex = MultiDex,
NeedsInternet = NeedsInternet,
AndroidRuntime = androidRuntime,
};
// Only set manifest.VersionCode if there is no existing value in AndroidManifest.xml.
if (manifest.HasVersionCode) {
Log.LogDebugMessage ($"Using existing versionCode in: {ManifestTemplate}");
} else if (!string.IsNullOrEmpty (VersionCode)) {
manifest.VersionCode = VersionCode;
}
manifest.Assemblies.AddRange (userAssemblies.Values.Select (item => item.ItemSpec));

if (!String.IsNullOrWhiteSpace (CheckedBuild)) {
// We don't validate CheckedBuild value here, this will be done in BuildApk. We just know that if it's
// on then we need android:debuggable=true and android:extractNativeLibs=true
manifest.ForceDebuggable = true;
manifest.ForceExtractNativeLibs = true;
}

IList<string> additionalProviders = manifest.Merge (Log, codeGenState.TypeCache, codeGenState.AllJavaTypes, ApplicationJavaClass, EmbedAssemblies, BundledWearApplicationName, MergedManifestDocuments);

// Only write the new manifest if it actually changed
if (manifest.SaveIfChanged (Log, MergedAndroidManifestOutput)) {
Log.LogDebugMessage ($"Saving: {MergedAndroidManifestOutput}");
if (!dict.TryGetValue (arch, out Dictionary<string, ITaskItem> archDict)) {
return new Dictionary<string, ITaskItem> (StringComparer.OrdinalIgnoreCase);
}

return additionalProviders;
return archDict;
}

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

string GetResource (string resource)
{
using (var stream = GetType ().Assembly.GetManifestResourceStream (resource))
using (var reader = new StreamReader (stream))
return reader.ReadToEnd ();
}

void SaveResource (string resource, string filename, string destDir, Func<string, string> applyTemplate)
{
string template = GetResource (resource);
template = applyTemplate (template);
Files.CopyIfStringChanged (template, Path.Combine (destDir, filename));
}

void WriteTypeMappings (NativeCodeGenState state)
{
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
Expand Down
Loading

0 comments on commit cf62814

Please sign in to comment.