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,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
Expand All @@ -7,6 +8,8 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;

static class MetadataHelper
{
static readonly Guid GeneratorModuleVersionId = typeof (TypeMapAssemblyGenerator).Module.ModuleVersionId;

/// <summary>
/// Produces a deterministic MVID by hashing the module name together with content-dependent data.
/// Assemblies with the same name but different content will have different MVIDs.
Expand Down Expand Up @@ -66,6 +69,99 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data)
return sha.ComputeHash (stream.GetBuffer (), 0, checked ((int) stream.Length));
}

/// <summary>
/// Computes a fingerprint of every input that affects a generated per-assembly typemap.
/// Unlike <see cref="ComputeContentFingerprint"/>, this is an incremental-build contract,
/// so it includes the generator binary identity and all model fields consumed by the emitter.
/// </summary>
public static byte [] ComputeIncrementalFingerprint (TypeMapAssemblyData data, Version systemRuntimeVersion, bool useSharedTypemapUniverse)
{
using var sha = SHA256.Create ();
using var stream = new MemoryStream ();
using var writer = new BinaryWriter (stream, Encoding.UTF8);
writer.Write (GeneratorModuleVersionId.ToByteArray ());
writer.Write (systemRuntimeVersion.ToString ());
writer.Write (useSharedTypemapUniverse);
writer.Write (data.AssemblyName);
writer.Write (data.ModuleName);
writer.Write (data.Entries.Count);
foreach (var entry in data.Entries) {
writer.Write (entry.MapKey);
writer.Write (entry.ProxyTypeReference);
writer.WriteOptionalString (entry.TargetTypeReference);
}
writer.Write (data.ProxyTypes.Count);
foreach (var proxy in data.ProxyTypes) {
writer.Write (proxy.TypeName);
writer.Write (proxy.JniName);
writer.Write (proxy.Namespace);
writer.WriteTypeRef (proxy.TargetType);
writer.WriteOptionalTypeRef (proxy.InvokerType);
writer.Write (proxy.InvokerActivationCtorStyle.HasValue);
if (proxy.InvokerActivationCtorStyle.HasValue) {
writer.Write ((byte) proxy.InvokerActivationCtorStyle.Value);
}
writer.WriteOptionalActivationCtor (proxy.ActivationCtor);
writer.Write (proxy.IsGenericDefinition);
writer.Write (proxy.CannotRegisterInStaticConstructor);
writer.Write (proxy.IsAcw);
writer.Write (proxy.UcoMethods.Count);
foreach (var method in proxy.UcoMethods) {
writer.WriteUcoMethod (method);
}
writer.Write (proxy.UcoConstructors.Count);
foreach (var constructor in proxy.UcoConstructors) {
writer.WriteUcoConstructor (constructor);
}
writer.Write (proxy.NativeRegistrations.Count);
foreach (var registration in proxy.NativeRegistrations) {
writer.WriteNativeRegistration (registration);
}
}
writer.Write (data.Associations.Count);
foreach (var assoc in data.Associations) {
writer.Write (assoc.SourceTypeReference);
writer.Write (assoc.AliasProxyTypeReference);
}
writer.Write (data.AliasHolders.Count);
foreach (var holder in data.AliasHolders) {
writer.Write (holder.TypeName);
writer.Write (holder.Namespace);
writer.Write (holder.AliasKeys.Count);
foreach (var aliasKey in holder.AliasKeys) {
writer.Write (aliasKey);
}
}
writer.Write (data.IgnoresAccessChecksTo.Count);
foreach (var assemblyName in data.IgnoresAccessChecksTo) {
writer.Write (assemblyName);
}
writer.Flush ();
return sha.ComputeHash (stream.GetBuffer (), 0, checked ((int) stream.Length));
}

/// <summary>
/// Computes a fingerprint of every input that affects the root typemap assembly.
/// </summary>
public static byte [] ComputeRootIncrementalFingerprint (
IReadOnlyList<string> perAssemblyTypeMapNames,
Version systemRuntimeVersion,
bool useSharedTypemapUniverse)
{
using var sha = SHA256.Create ();
using var stream = new MemoryStream ();
using var writer = new BinaryWriter (stream, Encoding.UTF8);
writer.Write (GeneratorModuleVersionId.ToByteArray ());
writer.Write (systemRuntimeVersion.ToString ());
writer.Write (useSharedTypemapUniverse);
writer.Write (perAssemblyTypeMapNames.Count);
foreach (var assemblyName in perAssemblyTypeMapNames) {
writer.Write (assemblyName);
}
writer.Flush ();
return sha.ComputeHash (stream.GetBuffer (), 0, checked ((int) stream.Length));
}

static void WriteTypeRef (this BinaryWriter writer, TypeRefData type)
{
writer.Write (type.ManagedTypeName);
Expand All @@ -78,15 +174,55 @@ static void WriteTypeRef (this BinaryWriter writer, TypeRefData type)
}
}

static void WriteOptionalTypeRef (this BinaryWriter writer, TypeRefData? type)
{
writer.Write (type is not null);
if (type is not null) {
writer.WriteTypeRef (type);
}
}

static void WriteOptionalString (this BinaryWriter writer, string? value)
{
writer.Write (value is not null);
if (value is not null) {
writer.Write (value);
}
}

static void WriteOptionalActivationCtor (this BinaryWriter writer, ActivationCtorData? constructor)
{
writer.Write (constructor is not null);
if (constructor is not null) {
writer.WriteTypeRef (constructor.DeclaringType);
writer.Write (constructor.IsOnLeafType);
writer.Write ((byte) constructor.Style);
}
}

static void WriteUcoMethod (this BinaryWriter writer, UcoMethodData method)
{
writer.Write (method.WrapperName);
writer.Write (method.CallbackMethodName);
writer.WriteTypeRef (method.CallbackType);
writer.Write (method.JniSignature);
writer.WriteOptionalStrings (method.CallbackParameterTypeNames);
writer.WriteOptionalString (method.CallbackReturnTypeName);
writer.WriteExportMethodDispatch (method.ExportMethodDispatch);
}

static void WriteOptionalStrings (this BinaryWriter writer, IReadOnlyList<string>? values)
{
writer.Write (values is not null);
if (values is null) {
return;
}
writer.Write (values.Count);
foreach (var value in values) {
writer.Write (value);
}
}

static void WriteExportMethodDispatch (this BinaryWriter writer, ExportMethodDispatchData? dispatch)
{
writer.Write (dispatch is not null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,22 @@ public TypeMapAssemblyGenerator (Version systemRuntimeVersion)
/// </param>
public void Generate (IReadOnlyList<JavaPeerInfo> peers, Stream stream, string assemblyName, bool useSharedTypemapUniverse = false)
{
var model = ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName);
var model = CreateModel (peers, assemblyName);
Generate (model, stream, useSharedTypemapUniverse);
}

internal TypeMapAssemblyData CreateModel (IReadOnlyList<JavaPeerInfo> peers, string assemblyName)
{
return ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName);
}

internal byte [] ComputeIncrementalFingerprint (TypeMapAssemblyData model, bool useSharedTypemapUniverse)
{
return MetadataHelper.ComputeIncrementalFingerprint (model, _systemRuntimeVersion, useSharedTypemapUniverse);
}

internal void Generate (TypeMapAssemblyData model, Stream stream, bool useSharedTypemapUniverse)
{
var emitter = new TypeMapAssemblyEmitter (_systemRuntimeVersion);
emitter.Emit (model, stream, useSharedTypemapUniverse);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ public TrimmableTypeMapGenerator (ITrimmableTypeMapLogger logger)
/// Set to <see langword="false"/> when callers do not consume method metadata for types
/// that cannot generate Java callable wrappers.
/// </param>
/// <param name="shouldGenerateTypeMapAssembly">
/// Optional incremental-build callback. It receives each output assembly name and a
/// content-model fingerprint, and returns whether that assembly should be emitted.
/// Assemblies skipped by the callback are omitted from
/// <see cref="TrimmableTypeMapResult.GeneratedAssemblies"/>. When omitted, all typemap
/// assemblies are generated.
/// </param>
public TrimmableTypeMapResult Execute (
IReadOnlyList<AssemblyInput> assemblies,
Version systemRuntimeVersion,
Expand All @@ -40,7 +47,8 @@ public TrimmableTypeMapResult Execute (
string? packageNamingPolicy = null,
bool generateTypeMapAssemblies = true,
bool errorOnCustomJavaObject = true,
bool collectMarshalMethodsForNonAcw = true)
bool collectMarshalMethodsForNonAcw = true,
Func<string, byte [], bool>? shouldGenerateTypeMapAssembly = null)
{
_ = assemblies ?? throw new ArgumentNullException (nameof (assemblies));
_ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
Expand All @@ -65,7 +73,7 @@ public TrimmableTypeMapResult Execute (
}

var generatedAssemblies = generateTypeMapAssemblies
? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse)
? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse, shouldGenerateTypeMapAssembly)
: [];
var jcwPeers = allPeers.Where (ShouldGenerateJcw).ToList ();
logger.LogGeneratingJcwFilesInfo (jcwPeers.Count, allPeers.Count);
Expand Down Expand Up @@ -284,10 +292,11 @@ GeneratedManifest GenerateManifest (List<JavaPeerInfo> allPeers, AssemblyManifes
return (peers, manifestInfo);
}

List<GeneratedAssembly> GenerateTypeMapAssemblies (
internal List<GeneratedAssembly> GenerateTypeMapAssemblies (
List<JavaPeerInfo> allPeers,
Version systemRuntimeVersion,
bool useSharedTypemapUniverse)
bool useSharedTypemapUniverse,
Func<string, byte [], bool>? shouldGenerateTypeMapAssembly = null)
{
List<(string AssemblyName, List<JavaPeerInfo> Peers)> peersByAssembly;

Expand Down Expand Up @@ -315,18 +324,33 @@ List<GeneratedAssembly> GenerateTypeMapAssemblies (
foreach (var (assemblyName, peers) in peersByAssembly) {
string typeMapAssemblyName = $"_{assemblyName}.TypeMap";
perAssemblyNames.Add (typeMapAssemblyName);
var model = generator.CreateModel (peers, typeMapAssemblyName);
if (shouldGenerateTypeMapAssembly is not null) {
var fingerprint = generator.ComputeIncrementalFingerprint (model, useSharedTypemapUniverse);
if (!shouldGenerateTypeMapAssembly (typeMapAssemblyName, fingerprint)) {
continue;
}
}
var stream = new MemoryStream ();
generator.Generate (peers, stream, typeMapAssemblyName, useSharedTypemapUniverse);
generator.Generate (model, stream, useSharedTypemapUniverse);
stream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly (typeMapAssemblyName, stream));
logger.LogGeneratedTypeMapAssemblyInfo (typeMapAssemblyName, peers.Count);
}
var rootStream = new MemoryStream ();
var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream);
rootStream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly ("_Microsoft.Android.TypeMaps", rootStream));
logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count);
const string rootAssemblyName = "_Microsoft.Android.TypeMaps";
bool generateRoot = true;
if (shouldGenerateTypeMapAssembly is not null) {
var rootFingerprint = MetadataHelper.ComputeRootIncrementalFingerprint (perAssemblyNames, systemRuntimeVersion, useSharedTypemapUniverse);
generateRoot = shouldGenerateTypeMapAssembly (rootAssemblyName, rootFingerprint);
}
if (generateRoot) {
var rootStream = new MemoryStream ();
var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream);
rootStream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly (rootAssemblyName, rootStream));
logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count);
}
logger.LogGeneratedTypeMapAssembliesInfo (generatedAssemblies.Count);
return generatedAssemblies;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
Outputs without making the target run on every build. The stamp is always
touched, so the target is correctly skipped when none of its Inputs changed. -->
<_TrimmableTypeMapOutputStamp>$(_TypeMapOutputDirectory)_GenerateTrimmableTypeMap.stamp</_TrimmableTypeMapOutputStamp>
<_TrimmableTypeMapFingerprintsFile>$(_TypeMapOutputDirectory)typemap-fingerprints.txt</_TrimmableTypeMapFingerprintsFile>
<_TrimmableRemoveRegisterFlag>$(_AndroidStampDirectory)_RemoveRegisterAttribute.stamp</_TrimmableRemoveRegisterFlag>
<_TrimmableRemoveRegisterTarget Condition=" '$(_AndroidRuntime)' == 'CoreCLR' ">_RemoveRegisterAttributeCoreClr</_TrimmableRemoveRegisterTarget>
<_TrimmableRemoveRegisterTarget Condition=" '$(_AndroidRuntime)' == 'NativeAOT' ">_RemoveRegisterAttributeNativeAot</_TrimmableRemoveRegisterTarget>
Expand Down Expand Up @@ -170,6 +171,7 @@
ApplicationJavaClass="$(AndroidApplicationJavaClass)"
ErrorOnCustomJavaObject="$(AndroidErrorOnCustomJavaObject)"
GeneratedAssembliesListFile="$(_TypeMapAssembliesListFile)"
TypeMapFingerprintsFile="$(_TrimmableTypeMapFingerprintsFile)"
AcwMapOutputFile="$(_PreTrimTypeMapAcwMapOutputFile)"
ApplicationRegistrationOutputFile="$(_PreTrimTypeMapApplicationRegistrationOutputFile)">
<Output TaskParameter="GeneratedAssemblies" ItemName="_GeneratedTypeMapAssemblies" />
Expand Down Expand Up @@ -200,6 +202,7 @@
<FileWrites Include="@(_GeneratedTypeMapAssemblies)" />
<FileWrites Include="@(_GeneratedJavaFiles)" />
<FileWrites Include="$(_TypeMapAssembliesListFile)" />
<FileWrites Include="$(_TrimmableTypeMapFingerprintsFile)" />
Comment thread
simonrozsival marked this conversation as resolved.
<FileWrites Include="$(_TypeMapJavaFilesList)" />
<FileWrites Include="$(_TrimmableTypeMapOutputStamp)" />
<FileWrites Include="$(_TypeMapBaseOutputDir)AndroidManifest.xml" />
Expand Down Expand Up @@ -274,6 +277,7 @@
<FileWrites Include="$(_PostTrimTypeMapJavaFilesList)" Condition="Exists('$(_PostTrimTypeMapJavaFilesList)')" />
<FileWrites Include="$(_PostTrimTrimmableTypeMapJavaStamp)" Condition="Exists('$(_PostTrimTrimmableTypeMapJavaStamp)')" />
<FileWrites Include="$(_TypeMapAssembliesListFile)" />
<FileWrites Include="$(_TrimmableTypeMapFingerprintsFile)" Condition="Exists('$(_TrimmableTypeMapFingerprintsFile)')" />
<FileWrites Include="$(_TypeMapJavaFilesList)" Condition="Exists('$(_TypeMapJavaFilesList)')" />
<FileWrites Include="$(_TrimmableTypeMapOutputStamp)" Condition="Exists('$(_TrimmableTypeMapOutputStamp)')" />
<FileWrites Include="$(_TypeMapBaseOutputDir)AndroidManifest.xml" Condition="Exists('$(_TypeMapBaseOutputDir)AndroidManifest.xml')" />
Expand Down
Loading
Loading