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
Expand Up @@ -42,43 +42,26 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
public sealed class JcwJavaSourceGenerator
{
/// <summary>
/// Generates .java source files for all ACW types and writes them to the output directory.
/// Returns the list of generated file paths.
/// Generates .java source content for all ACW types and returns them as in-memory
/// (relativePath, content) pairs. No filesystem IO is performed.
/// </summary>
public IReadOnlyList<string> Generate (IReadOnlyList<JavaPeerInfo> types, string outputDirectory)
public IReadOnlyList<GeneratedJavaSource> GenerateContent (IReadOnlyList<JavaPeerInfo> types)
{
if (types is null) {
throw new ArgumentNullException (nameof (types));
}
if (outputDirectory is null) {
throw new ArgumentNullException (nameof (outputDirectory));
}

var generatedFiles = new List<string> ();

if (types is null) throw new ArgumentNullException (nameof (types));
var results = new List<GeneratedJavaSource> ();
foreach (var type in types) {
if (type.DoNotGenerateAcw || type.IsInterface) {
continue;
}

string filePath = GetOutputFilePath (type, outputDirectory);
string? dir = Path.GetDirectoryName (filePath);
if (dir != null) {
Directory.CreateDirectory (dir);
}

using var writer = new StreamWriter (filePath);
if (type.DoNotGenerateAcw || type.IsInterface) continue;
using var writer = new StringWriter ();
Generate (type, writer);
generatedFiles.Add (filePath);
results.Add (new GeneratedJavaSource (GetRelativePath (type), writer.ToString ()));
}

return generatedFiles;
return results;
}

/// <summary>
/// Generates a single .java source file for the given type.
/// </summary>
internal void Generate (JavaPeerInfo type, TextWriter writer)
public void Generate (JavaPeerInfo type, TextWriter writer)
{
writer.NewLine = "\n";
WritePackageDeclaration (type, writer);
Expand All @@ -91,13 +74,13 @@ internal void Generate (JavaPeerInfo type, TextWriter writer)
WriteClassClose (writer);
}

static string GetOutputFilePath (JavaPeerInfo type, string outputDirectory)
static string GetRelativePath (JavaPeerInfo type)
{
JniSignatureHelper.ValidateJniName (type.JavaName);
string relativePath = type.JavaName + ".java";
return Path.Combine (outputDirectory, relativePath);
return type.JavaName + ".java";
}


/// <summary>
/// Validates that the JNI name is well-formed: non-empty, each segment separated by '/'
/// contains only valid Java identifier characters (letters, digits, '_', '$').
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,6 @@ public void EmitPreamble (string assemblyName, string moduleName, ReadOnlySpan<b
MetadataTokens.MethodDefinitionHandle (1));
}

/// <summary>
/// Serialises the metadata + IL into a PE DLL at <paramref name="outputPath"/>.
/// </summary>
public void WritePE (string outputPath)
{
var dir = Path.GetDirectoryName (outputPath);
if (!string.IsNullOrEmpty (dir)) {
Directory.CreateDirectory (dir);
}

using var fs = File.Create (outputPath);
WritePE (fs);
}

/// <summary>
/// Serialises the metadata + IL into a PE DLL and writes it to the given <paramref name="stream"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,6 @@ public RootTypeMapAssemblyGenerator (Version systemRuntimeVersion)
_systemRuntimeVersion = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
}

/// <summary>
/// Generates the root typemap assembly and writes it to a file.
/// </summary>
/// <param name="perAssemblyTypeMapNames">Names of per-assembly typemap assemblies to reference.</param>
/// <param name="outputPath">Path to write the output .dll.</param>
/// <param name="assemblyName">Optional assembly name (defaults to _Microsoft.Android.TypeMaps).</param>
public void Generate (IReadOnlyList<string> perAssemblyTypeMapNames, string outputPath, string? assemblyName = null)
{
if (outputPath is null) {
throw new ArgumentNullException (nameof (outputPath));
}

var dir = Path.GetDirectoryName (outputPath);
if (!string.IsNullOrEmpty (dir)) {
Directory.CreateDirectory (dir);
}

var moduleName = Path.GetFileName (outputPath);
using var fs = File.Create (outputPath);
Generate (perAssemblyTypeMapNames, fs, assemblyName, moduleName);
}

/// <summary>
/// Generates the root typemap assembly and writes it to the given stream.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,6 @@ public TypeMapAssemblyEmitter (Version systemRuntimeVersion)
_pe = new PEAssemblyBuilder (_systemRuntimeVersion);
}

/// <summary>
/// Emits a PE assembly from the given model and writes it to <paramref name="outputPath"/>.
/// </summary>
public void Emit (TypeMapAssemblyData model, string outputPath)
{
if (model is null) {
throw new ArgumentNullException (nameof (model));
}
if (outputPath is null) {
throw new ArgumentNullException (nameof (outputPath));
}

EmitCore (model);
_pe.WritePE (outputPath);
}

/// <summary>
/// Emits a PE assembly from the given model and writes it to <paramref name="stream"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,6 @@ public TypeMapAssemblyGenerator (Version systemRuntimeVersion)
_systemRuntimeVersion = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
}

/// <summary>
/// Generates a TypeMap PE assembly from the given Java peer info records.
/// </summary>
/// <param name="peers">Scanned Java peer types.</param>
/// <param name="outputPath">Path where the output .dll will be written.</param>
/// <param name="assemblyName">Optional explicit assembly name. Derived from outputPath if null.</param>
public void Generate (IReadOnlyList<JavaPeerInfo> peers, string outputPath, string? assemblyName = null)
{
var model = ModelBuilder.Build (peers, outputPath, assemblyName);
var emitter = new TypeMapAssemblyEmitter (_systemRuntimeVersion);
emitter.Emit (model, outputPath);
}

/// <summary>
/// Generates a TypeMap PE assembly from the given Java peer info records and writes it to <paramref name="stream"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

// The static methods in System.String are not NRT annotated in netstandard2.0,
// so we need our own extension methods to make them nullable aware.
static class NullableExtensions
{
public static bool IsNullOrEmpty ([NotNullWhen (false)] this string? str)
{
return string.IsNullOrEmpty (str);
}

public static bool IsNullOrWhiteSpace ([NotNullWhen (false)] this string? str)
{
return string.IsNullOrWhiteSpace (str);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;

Expand All @@ -18,7 +17,6 @@ sealed class AssemblyIndex : IDisposable

public MetadataReader Reader { get; }
public string AssemblyName { get; }
public string FilePath { get; }

/// <summary>
/// Maps full managed type name (e.g., "Android.App.Activity") to its TypeDefinitionHandle.
Expand All @@ -35,21 +33,18 @@ sealed class AssemblyIndex : IDisposable
/// </summary>
public Dictionary<TypeDefinitionHandle, TypeAttributeInfo> AttributesByType { get; } = new ();

AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName, string filePath)
AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName)
{
this.peReader = peReader;
this.customAttributeTypeProvider = new CustomAttributeTypeProvider (reader);
Reader = reader;
AssemblyName = assemblyName;
FilePath = filePath;
}

public static AssemblyIndex Create (string filePath)
public static AssemblyIndex Create (PEReader peReader, string assemblyName)
{
var peReader = new PEReader (File.OpenRead (filePath));
var reader = peReader.GetMetadataReader ();
var assemblyName = reader.GetString (reader.GetAssemblyDefinition ().Name);
var index = new AssemblyIndex (peReader, reader, assemblyName, filePath);
var index = new AssemblyIndex (peReader, reader, assemblyName);
index.Build ();
return index;
}
Expand Down Expand Up @@ -262,7 +257,6 @@ static bool TryGetNamedArgument<T> (CustomAttributeValue<string> value, string a

public void Dispose ()
{
peReader.Dispose ();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

Expand Down Expand Up @@ -79,24 +80,18 @@ bool TryResolveType (string typeName, string assemblyName, out TypeDefinitionHan
/// Phase 1: Build indices for all assemblies.
/// Phase 2: Scan all types and produce JavaPeerInfo records.
/// </summary>
public List<JavaPeerInfo> Scan (IReadOnlyList<string> assemblyPaths)
public List<JavaPeerInfo> Scan (IReadOnlyList<(string Name, PEReader Reader)> assemblies)
{
// Phase 1: Build indices for all assemblies
foreach (var path in assemblyPaths) {
var index = AssemblyIndex.Create (path);
foreach (var (name, reader) in assemblies) {
var index = AssemblyIndex.Create (reader, name);
assemblyCache [index.AssemblyName] = index;
}

// Phase 2: Analyze types using cached indices
var resultsByManagedName = new Dictionary<string, JavaPeerInfo> (StringComparer.Ordinal);

foreach (var index in assemblyCache.Values) {
ScanAssembly (index, resultsByManagedName);
}

// Phase 3: Force unconditional on types referenced by [Application] attributes
ForceUnconditionalCrossReferences (resultsByManagedName, assemblyCache);

return new List<JavaPeerInfo> (resultsByManagedName.Values);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

public class TrimmableTypeMapGenerator
{
readonly Action<string> log;

public TrimmableTypeMapGenerator (Action<string> log)
{
this.log = log ?? throw new ArgumentNullException (nameof (log));
}

public TrimmableTypeMapResult Execute (
IReadOnlyList<(string Name, PEReader Reader)> assemblies,
Version systemRuntimeVersion,
HashSet<string> frameworkAssemblyNames)
{
_ = assemblies ?? throw new ArgumentNullException (nameof (assemblies));
_ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
_ = frameworkAssemblyNames ?? throw new ArgumentNullException (nameof (frameworkAssemblyNames));

var allPeers = ScanAssemblies (assemblies);
if (allPeers.Count == 0) {
log ("No Java peer types found, skipping typemap generation.");
return new TrimmableTypeMapResult ([], [], allPeers);
}

var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion);
var jcwPeers = allPeers.Where (p =>
!frameworkAssemblyNames.Contains (p.AssemblyName)
|| 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);
}

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

List<GeneratedAssembly> GenerateTypeMapAssemblies (List<JavaPeerInfo> allPeers, Version systemRuntimeVersion)
{
var peersByAssembly = allPeers.GroupBy (p => p.AssemblyName, StringComparer.Ordinal).OrderBy (g => g.Key, StringComparer.Ordinal);
var generatedAssemblies = new List<GeneratedAssembly> ();
var perAssemblyNames = new List<string> ();
var generator = new TypeMapAssemblyGenerator (systemRuntimeVersion);
foreach (var group in peersByAssembly) {
string assemblyName = $"_{group.Key}.TypeMap";
perAssemblyNames.Add (assemblyName);
var peers = group.ToList ();
var stream = new MemoryStream ();
generator.Generate (peers, stream, assemblyName);
stream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly (assemblyName, stream));
log ($" {assemblyName}: {peers.Count} types");
}
var rootStream = new MemoryStream ();
var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
rootGenerator.Generate (perAssemblyNames, rootStream);
rootStream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly ("_Microsoft.Android.TypeMaps", rootStream));
log ($" Root: {perAssemblyNames.Count} per-assembly refs");
log ($"Generated {generatedAssemblies.Count} typemap assemblies.");
return generatedAssemblies;
}

List<GeneratedJavaSource> GenerateJcwJavaSources (List<JavaPeerInfo> allPeers)
{
var jcwGenerator = new JcwJavaSourceGenerator ();
var sources = jcwGenerator.GenerateContent (allPeers);
log ($"Generated {sources.Count} JCW Java source files.");
return sources.ToList ();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.IO;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

public record TrimmableTypeMapResult (
IReadOnlyList<GeneratedAssembly> GeneratedAssemblies,
IReadOnlyList<GeneratedJavaSource> GeneratedJavaSources,
IReadOnlyList<JavaPeerInfo> AllPeers);

public record GeneratedAssembly (string Name, MemoryStream Content);

public record GeneratedJavaSource (string RelativePath, string Content);
Loading