Skip to content

Commit

Permalink
Scanned assemblies match, begin cleanup and Java output verification.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpobst committed Mar 8, 2025
1 parent f0541d1 commit bb13f01
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 429 deletions.
Original file line number Diff line number Diff line change
@@ -1,102 +1,53 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Java.Interop.Tools.JavaCallableWrappers;
using Java.Interop.Tools.JavaCallableWrappers.Adapters;
using Java.Interop.Tools.JavaCallableWrappers.CallableWrapperMembers;
using Microsoft.Android.Build.Tasks;

using Microsoft.Build.Utilities;
using Mono.Cecil;
using Mono.Linker;
using Mono.Linker.Steps;
using Xamarin.Android.Tasks;
using Xamarin.Android.Tasks.Utilities;

namespace MonoDroid.Tuner;

/// <summary>
/// Scans an assembly for JLOs that need JCWs generated and writes them to an XML file.
/// </summary>
public class FindJavaObjectsStep : BaseStep
{
public string ApplicationJavaClass { get; set; }

public bool Debug { get; set; }
public string ApplicationJavaClass { get; set; } = "";

public bool ErrorOnCustomJavaObject { get; set; }

public bool UseMarshalMethods { get; set; }

public List<string> UserAssemblies { get; set; } = [];

public JavaPeerStyle CodeGenerationTarget { get; set; }

public TaskLoggingHelper Log { get; set; }

// Names of assemblies which don't have Mono.Android.dll references, or are framework assemblies, but which must
// be scanned for Java types.
static readonly HashSet<string> SpecialAssemblies = new HashSet<string> (StringComparer.OrdinalIgnoreCase) {
"Java.Interop",
"Mono.Android",
"Mono.Android.Runtime",
};
public FindJavaObjectsStep (TaskLoggingHelper log) => Log = log;

public bool ProcessAssembly (AssemblyDefinition assembly, string destination, bool hasMonoAndroidReference)
public bool ProcessAssembly (AssemblyDefinition assembly, string destinationJLOXml)
{
var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;

if (action == AssemblyAction.Delete)
return false;

var destinationJLOXml = destination;

// See if we should process this assembly
//if (!ShouldProcessAssembly (assembly, hasMonoAndroidReference)) {
// // We need to write an empty file for incremental builds
// WriteEmptyXmlFile (destinationJLOXml);
// return false;
//}

var types = ScanForJavaTypes (assembly);
var initial_count = types.Count;

// Filter out Java types we don't care about
types = types.Where (t => !t.IsInterface && !JavaTypeScanner.ShouldSkipJavaCallableWrapperGeneration (t, Context)).ToList ();

LogMessage ($"{assembly.Name.Name} - Found {initial_count} Java types, filtered to {types.Count}");
ExportAsCallableWrappers (destinationJLOXml, types);
//ExportAsJavaTypeSystem (destination + "2", types);

return true;
}

bool ShouldProcessAssembly (AssemblyDefinition assembly, bool hasMonoAndroidReference)
{
// Don't bother scanning the assembly if it doesn't have a Java.Lang.Object reference
if (!hasMonoAndroidReference && !SpecialAssemblies.Contains (assembly.Name.Name) && !assembly.MainModule.HasTypeReference ("Java.Lang.Object") && !assembly.MainModule.AssemblyReferences.Any (r => r.Name == "Mono.Android" || r.Name == "Java.Interop")) {
LogMessage ($"Skipping assembly '{assembly.Name.Name}' because it doesn't reference Java.Lang.Object");
return false;
}

// If we don't need JLOs from non-user assemblies, skip scanning them
var shouldSkipNonUserAssemblies = (Debug || !UseMarshalMethods) && CodeGenerationTarget == JavaPeerStyle.XAJavaInterop1;

if (shouldSkipNonUserAssemblies && !UserAssemblies.Contains (assembly.Name.Name)) {
LogMessage ($"Skipping assembly '{assembly.Name.Name}' because it is not a user assembly and we don't need JLOs from non-user assemblies");
return false;
}

return true;
}
Log.LogDebugMessage ($"{assembly.Name.Name} - Found {initial_count} Java types, filtered to {types.Count}");

void ExportAsCallableWrappers (string destination, List<TypeDefinition> types)
{
var wrappers = ConvertToCallableWrappers (types);
XmlExporter.Export (destination, wrappers, true);
}
XmlExporter.Export (destinationJLOXml, wrappers, true);

void LogMessage (string message)
{
Console.WriteLine (message);
return true;
}

public static void WriteEmptyXmlFile (string destination)
Expand Down Expand Up @@ -127,14 +78,11 @@ List<CallableWrapperType> ConvertToCallableWrappers (List<TypeDefinition> types)

var reader_options = new CallableWrapperReaderOptions {
DefaultApplicationJavaClass = ApplicationJavaClass,
DefaultGenerateOnCreateOverrides = false, // this was used only when targetting Android API <= 10, which is no longer supported
DefaultMonoRuntimeInitialization = "mono.MonoPackageManager.LoadApplication (context);",
};

if (UseMarshalMethods) {
Log.LogDebugMessage ("Using MarshalMethodsClassifier");
reader_options.MethodClassifier = MakeClassifier ();
}
if (UseMarshalMethods)
reader_options.MethodClassifier = new MarshalMethodsClassifier (Context, Context.Resolver, Log);

foreach (var type in types) {
var wrapper = CecilImporter.CreateType (type, Context, reader_options);
Expand All @@ -143,6 +91,4 @@ List<CallableWrapperType> ConvertToCallableWrappers (List<TypeDefinition> types)

return wrappers;
}

MarshalMethodsClassifier MakeClassifier () => new MarshalMethodsClassifier (Context, Context.Resolver, Log);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Java.Interop.Tools.TypeNameMappings;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using PackageNamingPolicyEnum = Java.Interop.Tools.TypeNameMappings.PackageNamingPolicy;

namespace Xamarin.Android.Tasks;
Expand All @@ -36,10 +37,8 @@ public class GenerateJavaCallableWrappers : AndroidTask
[Required]
public string [] SupportedAbis { get; set; } = [];

[Required]
public ITaskItem [] LegacyGeneratedJavaFiles { get; set; } = [];

List<string> GeneratedJavaFiles = [];
[Output]
public ITaskItem [] GeneratedJavaFilesOutput { get; set; } = [];

public override bool RunTask ()
{
Expand All @@ -52,18 +51,16 @@ public override bool RunTask ()

GenerateWrappers (singleArchAssemblies);

//EnsureSameFilesWritten ();

return !Log.HasLoggedErrors;
}

void GenerateWrappers (List<ITaskItem> assemblies)
{
Directory.CreateDirectory (OutputDirectory);

var sw = Stopwatch.StartNew ();
// Deserialize JavaCallableWrappers
// Deserialize JavaCallableWrappers XML files
var wrappers = new List<CallableWrapperType> ();
var sw = Stopwatch.StartNew ();

foreach (var assembly in assemblies) {
var assemblyPath = assembly.ItemSpec;
Expand All @@ -77,56 +74,32 @@ void GenerateWrappers (List<ITaskItem> assemblies)

wrappers.AddRange (XmlImporter.Import (wrappersPath, out var _));
}
Log.LogDebugMessage ($"Deserialized Java callable wrappers in: '{sw.ElapsedMilliseconds}ms'");

Log.LogDebugMessage ($"Deserialized {wrappers.Count} Java callable wrappers in {sw.ElapsedMilliseconds}ms");
sw.Restart ();

// Write JavaCallableWrappers to Java files
var writer_options = new CallableWrapperWriterOptions {
CodeGenerationTarget = MonoAndroidHelper.ParseCodeGenerationTarget (CodeGenerationTarget)
};

var generated_files = new List<ITaskItem> ();

foreach (var generator in wrappers) {
using var writer = MemoryStreamPool.Shared.CreateStreamWriter ();

generator.Generate (writer, writer_options);
writer.Flush ();


var path = generator.GetDestinationPath (OutputDirectory);

//if (Files.HasStreamChanged (writer.BaseStream, path)) {
// Files.CopyIfStreamChanged (writer.BaseStream, path.Replace (@"\src\", @"\src2\"));
// Log.LogError ($"Java callable wrapper code changed: '{path}'");
// continue;
//}

var changed = Files.CopyIfStreamChanged (writer.BaseStream, path);
Log.LogDebugMessage ($"*NEW* Generated Java callable wrapper code: '{path}' (changed: {changed})");

//if (changed)
// Log.LogError ($"Java callable wrapper code changed: '{path}'");

GeneratedJavaFiles.Add (path);
generated_files.Add (new TaskItem (path));
}
Log.LogDebugMessage ($"Wrote Java callable wrappers in: '{sw.ElapsedMilliseconds}ms'");
}

void EnsureSameFilesWritten ()
{
var new_generated_java_files = GeneratedJavaFiles.Select (f => f.Replace ("src2", "src")).ToList ();
var old_generated_java_files = LegacyGeneratedJavaFiles.Select (f => f.ItemSpec).ToList ();

var extra_new_files = new_generated_java_files.Except (old_generated_java_files).ToList ();

if (extra_new_files.Count > 0)
Log.LogWarning ($"The following Java files were generated but not previously generated: {string.Join (", ", extra_new_files)}");

var missing_old_files = old_generated_java_files.Except (new_generated_java_files).ToList ();

if (missing_old_files.Count > 0)
Log.LogWarning ($"The following Java files were previously generated but not generated this time: {string.Join (", ", missing_old_files)}");

if (extra_new_files.Count > 0 || missing_old_files.Count > 0) {
Log.LogError ($"New JCW gen ({new_generated_java_files.Count}) mismatch with old JCW gen ({old_generated_java_files.Count})");
}
Log.LogDebugMessage ($"Generated {generated_files.Count} Java callable wrapper files in {sw.ElapsedMilliseconds}ms");
GeneratedJavaFilesOutput = generated_files.ToArray ();
}
}
Loading

0 comments on commit bb13f01

Please sign in to comment.