Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[XABT] Refactor manifest merging and ACW map generation out of GenerateJavaStubs. #9827

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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