Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -29,10 +28,10 @@ public static class AcwMapWriter
public static void Write (TextWriter writer, IEnumerable<JavaPeerInfo> peers)
{
foreach (var peer in peers.OrderBy (p => p.ManagedTypeName, StringComparer.Ordinal)) {
string javaKey = peer.JavaName.Replace ('/', '.');
string javaKey = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
string managedKey = peer.ManagedTypeName;
string partialAsmQualifiedName = $"{managedKey}, {peer.AssemblyName}";
string compatJniName = peer.CompatJniName.Replace ('/', '.');
string compatJniName = JniSignatureHelper.JniNameToJavaName (peer.CompatJniName);

// Line 1: PartialAssemblyQualifiedName;JavaKey
writer.Write (partialAsmQualifiedName);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System.Collections.Generic;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Globalization;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System;
using System.Globalization;
using System.Linq;
Expand Down Expand Up @@ -157,7 +155,7 @@ internal static XElement CreateMetaDataElement (MetaDataInfo meta)

internal static void UpdateApplicationElement (XElement app, JavaPeerInfo peer)
{
string jniName = peer.JavaName.Replace ('/', '.');
string jniName = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
app.SetAttributeValue (AttName, jniName);

var component = peer.ComponentAttribute;
Expand All @@ -169,7 +167,7 @@ internal static void UpdateApplicationElement (XElement app, JavaPeerInfo peer)

internal static void AddInstrumentation (XElement manifest, JavaPeerInfo peer)
{
string jniName = peer.JavaName.Replace ('/', '.');
string jniName = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
var element = new XElement ("instrumentation",
new XAttribute (AttName, jniName));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ static void WriteStaticInitializer (JavaPeerInfo type, TextWriter writer)
{
// Application and Instrumentation types cannot call registerNatives in their
// static initializer — the native library isn't loaded yet at that point.
// Their registration is deferred to ApplicationRegistration.registerApplications().
// Their registerNatives call is emitted in the generated
// ApplicationRegistration.registerApplications() method instead.
if (type.CannotRegisterInStaticConstructor) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System.Xml.Linq;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;

Expand All @@ -23,9 +22,9 @@ class ManifestGenerator
public string ApplicationLabel { get; set; } = "";
public string VersionCode { get; set; } = "";
public string VersionName { get; set; } = "";
public string MinSdkVersion { get; set; } = "21";
public string TargetSdkVersion { get; set; } = "36";
public string AndroidRuntime { get; set; } = "coreclr";
public string MinSdkVersion { get; set; } = "";
public string TargetSdkVersion { get; set; } = "";
public string RuntimeProviderJavaName { get; set; } = "";
public bool Debug { get; set; }
public bool NeedsInternet { get; set; }
public bool EmbedAssemblies { get; set; }
Expand All @@ -38,11 +37,10 @@ class ManifestGenerator
/// Generates the merged manifest from an optional pre-loaded template and writes it to <paramref name="outputPath"/>.
/// Returns the list of additional content provider names (for ApplicationRegistration.java).
/// </summary>
public IList<string> Generate (
public (XDocument Document, IList<string> ProviderNames) Generate (
XDocument? manifestTemplate,
IReadOnlyList<JavaPeerInfo> allPeers,
AssemblyManifestInfo assemblyInfo,
string outputPath)
AssemblyManifestInfo assemblyInfo)
{
var doc = manifestTemplate ?? CreateDefaultManifest ();
var manifest = doc.Root;
Expand Down Expand Up @@ -78,7 +76,7 @@ public IList<string> Generate (
continue;
}

string jniName = peer.JavaName.Replace ('/', '.');
string jniName = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
if (existingTypes.Contains (jniName)) {
continue;
}
Expand Down Expand Up @@ -122,14 +120,7 @@ public IList<string> Generate (
ApplyPlaceholders (doc, placeholders);
}

// Write output
var outputDir = Path.GetDirectoryName (outputPath);
if (outputDir is not null) {
Directory.CreateDirectory (outputDir);
}
doc.Save (outputPath);

return providerNames;
return (doc, providerNames);
}

XDocument CreateDefaultManifest ()
Expand Down Expand Up @@ -161,6 +152,12 @@ void EnsureManifestAttributes (XElement manifest)

// Add <uses-sdk>
if (!manifest.Elements ("uses-sdk").Any ()) {
if (MinSdkVersion.IsNullOrEmpty ()) {
throw new InvalidOperationException ("MinSdkVersion must be provided by MSBuild.");
}
if (TargetSdkVersion.IsNullOrEmpty ()) {
throw new InvalidOperationException ("TargetSdkVersion must be provided by MSBuild.");
}
manifest.AddFirst (new XElement ("uses-sdk",
new XAttribute (AndroidNs + "minSdkVersion", MinSdkVersion),
new XAttribute (AndroidNs + "targetSdkVersion", TargetSdkVersion)));
Expand All @@ -184,16 +181,19 @@ XElement EnsureApplicationElement (XElement manifest)

IList<string> AddRuntimeProviders (XElement app)
{
string packageName = "mono";
string className = "MonoRuntimeProvider";

if (string.Equals (AndroidRuntime, "nativeaot", StringComparison.OrdinalIgnoreCase)) {
packageName = "net.dot.jni.nativeaot";
className = "NativeAotRuntimeProvider";
if (RuntimeProviderJavaName.IsNullOrEmpty ()) {
throw new InvalidOperationException ("RuntimeProviderJavaName must be provided by MSBuild.");
}
int lastDot = RuntimeProviderJavaName.LastIndexOf ('.');
if (lastDot < 0 || lastDot == RuntimeProviderJavaName.Length - 1) {
throw new InvalidOperationException ($"RuntimeProviderJavaName must be a fully-qualified Java type name: '{RuntimeProviderJavaName}'.");
}

string packageName = RuntimeProviderJavaName.Substring (0, lastDot);
string className = RuntimeProviderJavaName.Substring (lastDot + 1);

// Check if runtime provider already exists in template
string runtimeProviderName = $"{packageName}.{className}";
string runtimeProviderName = RuntimeProviderJavaName;
if (!app.Elements ("provider").Any (p => {
var name = (string?)p.Attribute (ManifestConstants.AttName);
return name == runtimeProviderName ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Globalization;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Xml.Linq;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

Expand All @@ -15,16 +16,23 @@ public TrimmableTypeMapGenerator (Action<string> log)
this.log = log ?? throw new ArgumentNullException (nameof (log));
}

/// <summary>
/// Runs the full generation pipeline: scan assemblies, generate typemap
/// assemblies, generate JCW Java sources, and optionally generate a merged manifest.
/// No file IO is performed — all results are returned in memory.
/// </summary>
public TrimmableTypeMapResult Execute (
IReadOnlyList<(string Name, PEReader Reader)> assemblies,
Version systemRuntimeVersion,
HashSet<string> frameworkAssemblyNames)
HashSet<string> frameworkAssemblyNames,
ManifestConfig? manifestConfig = null,
XDocument? manifestTemplate = null)
{
_ = assemblies ?? throw new ArgumentNullException (nameof (assemblies));
_ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
_ = frameworkAssemblyNames ?? throw new ArgumentNullException (nameof (frameworkAssemblyNames));

var allPeers = ScanAssemblies (assemblies);
var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies);
if (allPeers.Count == 0) {
log ("No Java peer types found, skipping typemap generation.");
return new TrimmableTypeMapResult ([], [], allPeers);
Expand All @@ -36,15 +44,66 @@ public TrimmableTypeMapResult Execute (
|| p.JavaName.StartsWith ("mono/", StringComparison.Ordinal)).ToList ();
log ($"Generating JCW files for {jcwPeers.Count} types (filtered from {allPeers.Count} total).");
var generatedJavaSources = GenerateJcwJavaSources (jcwPeers);
return new TrimmableTypeMapResult (generatedAssemblies, generatedJavaSources, allPeers);

// Collect Application/Instrumentation types that need deferred registerNatives
var appRegTypes = allPeers
.Where (p => p.CannotRegisterInStaticConstructor && !p.IsAbstract)
.Select (p => JniSignatureHelper.JniNameToJavaName (p.JavaName))
.ToList ();
if (appRegTypes.Count > 0) {
log ($"Found {appRegTypes.Count} Application/Instrumentation types for deferred registration.");
}

var manifest = manifestConfig is not null
? GenerateManifest (allPeers, assemblyManifestInfo, manifestConfig, manifestTemplate)
: null;

return new TrimmableTypeMapResult (generatedAssemblies, generatedJavaSources, allPeers, manifest, appRegTypes);
}

GeneratedManifest GenerateManifest (List<JavaPeerInfo> allPeers, AssemblyManifestInfo assemblyManifestInfo,
ManifestConfig config, XDocument? manifestTemplate)
{
string minSdk = config.SupportedOSPlatformVersion ?? throw new InvalidOperationException ("SupportedOSPlatformVersion must be provided by MSBuild.");
if (Version.TryParse (minSdk, out var sopv)) {
minSdk = sopv.Major.ToString (System.Globalization.CultureInfo.InvariantCulture);
}

string targetSdk = config.AndroidApiLevel ?? throw new InvalidOperationException ("AndroidApiLevel must be provided by MSBuild.");
if (Version.TryParse (targetSdk, out var apiVersion)) {
targetSdk = apiVersion.Major.ToString (System.Globalization.CultureInfo.InvariantCulture);
}

bool forceDebuggable = !config.CheckedBuild.IsNullOrEmpty ();

var generator = new ManifestGenerator {
PackageName = config.PackageName,
ApplicationLabel = config.ApplicationLabel ?? config.PackageName,
VersionCode = config.VersionCode ?? "",
VersionName = config.VersionName ?? "",
MinSdkVersion = minSdk,
TargetSdkVersion = targetSdk,
RuntimeProviderJavaName = config.RuntimeProviderJavaName ?? throw new InvalidOperationException ("RuntimeProviderJavaName must be provided by MSBuild."),
Debug = config.Debug,
NeedsInternet = config.NeedsInternet,
EmbedAssemblies = config.EmbedAssemblies,
ForceDebuggable = forceDebuggable,
ForceExtractNativeLibs = forceDebuggable,
ManifestPlaceholders = config.ManifestPlaceholders,
ApplicationJavaClass = config.ApplicationJavaClass,
};

var (doc, providerNames) = generator.Generate (manifestTemplate, allPeers, assemblyManifestInfo);
return new GeneratedManifest (doc, providerNames.Count > 0 ? providerNames.ToArray () : []);
}

List<JavaPeerInfo> ScanAssemblies (IReadOnlyList<(string Name, PEReader Reader)> assemblies)
(List<JavaPeerInfo> peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList<(string Name, PEReader Reader)> assemblies)
{
using var scanner = new JavaPeerScanner ();
var peers = scanner.Scan (assemblies);
var manifestInfo = scanner.ScanAssemblyManifestInfo ();
log ($"Scanned {assemblies.Count} assemblies, found {peers.Count} Java peer types.");
return peers;
return (peers, manifestInfo);
}

List<GeneratedAssembly> GenerateTypeMapAssemblies (List<JavaPeerInfo> allPeers, Version systemRuntimeVersion)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,49 @@
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

public record TrimmableTypeMapResult (
IReadOnlyList<GeneratedAssembly> GeneratedAssemblies,
IReadOnlyList<GeneratedJavaSource> GeneratedJavaSources,
IReadOnlyList<JavaPeerInfo> AllPeers);
IReadOnlyList<JavaPeerInfo> AllPeers,
GeneratedManifest? Manifest = null,
IReadOnlyList<string>? ApplicationRegistrationTypes = null)
{
/// <summary>
/// Java class names (dot-separated) of Application/Instrumentation types
/// that need deferred <c>Runtime.registerNatives()</c> calls in
/// <c>ApplicationRegistration.registerApplications()</c>.
/// </summary>
public IReadOnlyList<string> ApplicationRegistrationTypes { get; init; } =
ApplicationRegistrationTypes ?? [];
}

public record GeneratedAssembly (string Name, MemoryStream Content);

public record GeneratedJavaSource (string RelativePath, string Content);

/// <summary>
/// The in-memory result of manifest generation: the merged document and
/// any additional content provider class names for ApplicationRegistration.java.
/// </summary>
public record GeneratedManifest (XDocument Document, string[] AdditionalProviderSources);

/// <summary>
/// Configuration values for manifest generation. Passed from MSBuild properties.
/// </summary>
public record ManifestConfig (
string PackageName,
string? ApplicationLabel = null,
string? VersionCode = null,
string? VersionName = null,
string? AndroidApiLevel = null,
string? SupportedOSPlatformVersion = null,
string? RuntimeProviderJavaName = null,
bool Debug = false,
bool NeedsInternet = false,
bool EmbedAssemblies = false,
string? ManifestPlaceholders = null,
string? CheckedBuild = null,
string? ApplicationJavaClass = null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<linker>
<assembly fullname="Mono.Android">
<!-- Native entry point: called from host.cc via create_delegate -->
<type fullname="Android.Runtime.JNIEnvInit">
<method name="Initialize" />
</type>
</assembly>
</linker>
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
<_BeforeCompileToDalvik>$(_BeforeCompileToDalvik);_GetMonoPlatformJarPath</_BeforeCompileToDalvik>
</PropertyGroup>

<ItemGroup>
<RuntimeHostConfigurationOption Include="Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap"
Value="false"
Trim="true"
/>
</ItemGroup>

<PropertyGroup>
<_GenerateJavaStubsDependsOnTargets>
_SetLatestTargetFrameworkVersion;
Expand Down
Loading