Skip to content

Commit 51e0fdf

Browse files
simonrozsivalCopilot
authored andcommitted
Skip unchanged typemap assembly emission
Persist versioned model fingerprints so incremental builds only emit typemap PE assemblies whose final model changed, while retaining alias-owner and root-reference invalidation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent dc8d21f commit 51e0fdf

7 files changed

Lines changed: 400 additions & 69 deletions

File tree

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/MetadataHelper.cs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Security.Cryptography;
45
using System.Text;
@@ -7,6 +8,8 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
78

89
static class MetadataHelper
910
{
11+
static readonly Guid GeneratorModuleVersionId = typeof (TypeMapAssemblyGenerator).Module.ModuleVersionId;
12+
1013
/// <summary>
1114
/// Produces a deterministic MVID by hashing the module name together with content-dependent data.
1215
/// Assemblies with the same name but different content will have different MVIDs.
@@ -66,6 +69,99 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data)
6669
return sha.ComputeHash (stream.GetBuffer (), 0, checked ((int) stream.Length));
6770
}
6871

72+
/// <summary>
73+
/// Computes a fingerprint of every input that affects a generated per-assembly typemap.
74+
/// Unlike <see cref="ComputeContentFingerprint"/>, this is an incremental-build contract,
75+
/// so it includes the generator binary identity and all model fields consumed by the emitter.
76+
/// </summary>
77+
public static byte [] ComputeIncrementalFingerprint (TypeMapAssemblyData data, Version systemRuntimeVersion, bool useSharedTypemapUniverse)
78+
{
79+
using var sha = SHA256.Create ();
80+
using var stream = new MemoryStream ();
81+
using var writer = new BinaryWriter (stream, Encoding.UTF8);
82+
writer.Write (GeneratorModuleVersionId.ToByteArray ());
83+
writer.Write (systemRuntimeVersion.ToString ());
84+
writer.Write (useSharedTypemapUniverse);
85+
writer.Write (data.AssemblyName);
86+
writer.Write (data.ModuleName);
87+
writer.Write (data.Entries.Count);
88+
foreach (var entry in data.Entries) {
89+
writer.Write (entry.MapKey);
90+
writer.Write (entry.ProxyTypeReference);
91+
writer.WriteOptionalString (entry.TargetTypeReference);
92+
}
93+
writer.Write (data.ProxyTypes.Count);
94+
foreach (var proxy in data.ProxyTypes) {
95+
writer.Write (proxy.TypeName);
96+
writer.Write (proxy.JniName);
97+
writer.Write (proxy.Namespace);
98+
writer.WriteTypeRef (proxy.TargetType);
99+
writer.WriteOptionalTypeRef (proxy.InvokerType);
100+
writer.Write (proxy.InvokerActivationCtorStyle.HasValue);
101+
if (proxy.InvokerActivationCtorStyle.HasValue) {
102+
writer.Write ((byte) proxy.InvokerActivationCtorStyle.Value);
103+
}
104+
writer.WriteOptionalActivationCtor (proxy.ActivationCtor);
105+
writer.Write (proxy.IsGenericDefinition);
106+
writer.Write (proxy.CannotRegisterInStaticConstructor);
107+
writer.Write (proxy.IsAcw);
108+
writer.Write (proxy.UcoMethods.Count);
109+
foreach (var method in proxy.UcoMethods) {
110+
writer.WriteUcoMethod (method);
111+
}
112+
writer.Write (proxy.UcoConstructors.Count);
113+
foreach (var constructor in proxy.UcoConstructors) {
114+
writer.WriteUcoConstructor (constructor);
115+
}
116+
writer.Write (proxy.NativeRegistrations.Count);
117+
foreach (var registration in proxy.NativeRegistrations) {
118+
writer.WriteNativeRegistration (registration);
119+
}
120+
}
121+
writer.Write (data.Associations.Count);
122+
foreach (var assoc in data.Associations) {
123+
writer.Write (assoc.SourceTypeReference);
124+
writer.Write (assoc.AliasProxyTypeReference);
125+
}
126+
writer.Write (data.AliasHolders.Count);
127+
foreach (var holder in data.AliasHolders) {
128+
writer.Write (holder.TypeName);
129+
writer.Write (holder.Namespace);
130+
writer.Write (holder.AliasKeys.Count);
131+
foreach (var aliasKey in holder.AliasKeys) {
132+
writer.Write (aliasKey);
133+
}
134+
}
135+
writer.Write (data.IgnoresAccessChecksTo.Count);
136+
foreach (var assemblyName in data.IgnoresAccessChecksTo) {
137+
writer.Write (assemblyName);
138+
}
139+
writer.Flush ();
140+
return sha.ComputeHash (stream.GetBuffer (), 0, checked ((int) stream.Length));
141+
}
142+
143+
/// <summary>
144+
/// Computes a fingerprint of every input that affects the root typemap assembly.
145+
/// </summary>
146+
public static byte [] ComputeRootIncrementalFingerprint (
147+
IReadOnlyList<string> perAssemblyTypeMapNames,
148+
Version systemRuntimeVersion,
149+
bool useSharedTypemapUniverse)
150+
{
151+
using var sha = SHA256.Create ();
152+
using var stream = new MemoryStream ();
153+
using var writer = new BinaryWriter (stream, Encoding.UTF8);
154+
writer.Write (GeneratorModuleVersionId.ToByteArray ());
155+
writer.Write (systemRuntimeVersion.ToString ());
156+
writer.Write (useSharedTypemapUniverse);
157+
writer.Write (perAssemblyTypeMapNames.Count);
158+
foreach (var assemblyName in perAssemblyTypeMapNames) {
159+
writer.Write (assemblyName);
160+
}
161+
writer.Flush ();
162+
return sha.ComputeHash (stream.GetBuffer (), 0, checked ((int) stream.Length));
163+
}
164+
69165
static void WriteTypeRef (this BinaryWriter writer, TypeRefData type)
70166
{
71167
writer.Write (type.ManagedTypeName);
@@ -78,15 +174,55 @@ static void WriteTypeRef (this BinaryWriter writer, TypeRefData type)
78174
}
79175
}
80176

177+
static void WriteOptionalTypeRef (this BinaryWriter writer, TypeRefData? type)
178+
{
179+
writer.Write (type is not null);
180+
if (type is not null) {
181+
writer.WriteTypeRef (type);
182+
}
183+
}
184+
185+
static void WriteOptionalString (this BinaryWriter writer, string? value)
186+
{
187+
writer.Write (value is not null);
188+
if (value is not null) {
189+
writer.Write (value);
190+
}
191+
}
192+
193+
static void WriteOptionalActivationCtor (this BinaryWriter writer, ActivationCtorData? constructor)
194+
{
195+
writer.Write (constructor is not null);
196+
if (constructor is not null) {
197+
writer.WriteTypeRef (constructor.DeclaringType);
198+
writer.Write (constructor.IsOnLeafType);
199+
writer.Write ((byte) constructor.Style);
200+
}
201+
}
202+
81203
static void WriteUcoMethod (this BinaryWriter writer, UcoMethodData method)
82204
{
83205
writer.Write (method.WrapperName);
84206
writer.Write (method.CallbackMethodName);
85207
writer.WriteTypeRef (method.CallbackType);
86208
writer.Write (method.JniSignature);
209+
writer.WriteOptionalStrings (method.CallbackParameterTypeNames);
210+
writer.WriteOptionalString (method.CallbackReturnTypeName);
87211
writer.WriteExportMethodDispatch (method.ExportMethodDispatch);
88212
}
89213

214+
static void WriteOptionalStrings (this BinaryWriter writer, IReadOnlyList<string>? values)
215+
{
216+
writer.Write (values is not null);
217+
if (values is null) {
218+
return;
219+
}
220+
writer.Write (values.Count);
221+
foreach (var value in values) {
222+
writer.Write (value);
223+
}
224+
}
225+
90226
static void WriteExportMethodDispatch (this BinaryWriter writer, ExportMethodDispatchData? dispatch)
91227
{
92228
writer.Write (dispatch is not null);

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyGenerator.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,22 @@ public TypeMapAssemblyGenerator (Version systemRuntimeVersion)
2929
/// </param>
3030
public void Generate (IReadOnlyList<JavaPeerInfo> peers, Stream stream, string assemblyName, bool useSharedTypemapUniverse = false)
3131
{
32-
var model = ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName);
32+
var model = CreateModel (peers, assemblyName);
33+
Generate (model, stream, useSharedTypemapUniverse);
34+
}
35+
36+
internal TypeMapAssemblyData CreateModel (IReadOnlyList<JavaPeerInfo> peers, string assemblyName)
37+
{
38+
return ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName);
39+
}
40+
41+
internal byte [] ComputeIncrementalFingerprint (TypeMapAssemblyData model, bool useSharedTypemapUniverse)
42+
{
43+
return MetadataHelper.ComputeIncrementalFingerprint (model, _systemRuntimeVersion, useSharedTypemapUniverse);
44+
}
45+
46+
internal void Generate (TypeMapAssemblyData model, Stream stream, bool useSharedTypemapUniverse)
47+
{
3348
var emitter = new TypeMapAssemblyEmitter (_systemRuntimeVersion);
3449
emitter.Emit (model, stream, useSharedTypemapUniverse);
3550
}

src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ public TrimmableTypeMapGenerator (ITrimmableTypeMapLogger logger)
3030
/// Set to <see langword="false"/> when callers do not consume method metadata for types
3131
/// that cannot generate Java callable wrappers.
3232
/// </param>
33+
/// <param name="shouldGenerateTypeMapAssembly">
34+
/// Optional incremental-build callback. It receives each output assembly name and a
35+
/// content-model fingerprint, and returns whether that assembly should be emitted.
36+
/// Assemblies skipped by the callback are omitted from
37+
/// <see cref="TrimmableTypeMapResult.GeneratedAssemblies"/>. When omitted, all typemap
38+
/// assemblies are generated.
39+
/// </param>
3340
public TrimmableTypeMapResult Execute (
3441
IReadOnlyList<AssemblyInput> assemblies,
3542
Version systemRuntimeVersion,
@@ -40,7 +47,8 @@ public TrimmableTypeMapResult Execute (
4047
string? packageNamingPolicy = null,
4148
bool generateTypeMapAssemblies = true,
4249
bool errorOnCustomJavaObject = true,
43-
bool collectMarshalMethodsForNonAcw = true)
50+
bool collectMarshalMethodsForNonAcw = true,
51+
Func<string, byte [], bool>? shouldGenerateTypeMapAssembly = null)
4452
{
4553
_ = assemblies ?? throw new ArgumentNullException (nameof (assemblies));
4654
_ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
@@ -65,7 +73,7 @@ public TrimmableTypeMapResult Execute (
6573
}
6674

6775
var generatedAssemblies = generateTypeMapAssemblies
68-
? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse)
76+
? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse, shouldGenerateTypeMapAssembly)
6977
: [];
7078
var jcwPeers = allPeers.Where (ShouldGenerateJcw).ToList ();
7179
logger.LogGeneratingJcwFilesInfo (jcwPeers.Count, allPeers.Count);
@@ -284,10 +292,11 @@ GeneratedManifest GenerateManifest (List<JavaPeerInfo> allPeers, AssemblyManifes
284292
return (peers, manifestInfo);
285293
}
286294

287-
List<GeneratedAssembly> GenerateTypeMapAssemblies (
295+
internal List<GeneratedAssembly> GenerateTypeMapAssemblies (
288296
List<JavaPeerInfo> allPeers,
289297
Version systemRuntimeVersion,
290-
bool useSharedTypemapUniverse)
298+
bool useSharedTypemapUniverse,
299+
Func<string, byte [], bool>? shouldGenerateTypeMapAssembly = null)
291300
{
292301
List<(string AssemblyName, List<JavaPeerInfo> Peers)> peersByAssembly;
293302

@@ -315,18 +324,33 @@ List<GeneratedAssembly> GenerateTypeMapAssemblies (
315324
foreach (var (assemblyName, peers) in peersByAssembly) {
316325
string typeMapAssemblyName = $"_{assemblyName}.TypeMap";
317326
perAssemblyNames.Add (typeMapAssemblyName);
327+
var model = generator.CreateModel (peers, typeMapAssemblyName);
328+
if (shouldGenerateTypeMapAssembly is not null) {
329+
var fingerprint = generator.ComputeIncrementalFingerprint (model, useSharedTypemapUniverse);
330+
if (!shouldGenerateTypeMapAssembly (typeMapAssemblyName, fingerprint)) {
331+
continue;
332+
}
333+
}
318334
var stream = new MemoryStream ();
319-
generator.Generate (peers, stream, typeMapAssemblyName, useSharedTypemapUniverse);
335+
generator.Generate (model, stream, useSharedTypemapUniverse);
320336
stream.Position = 0;
321337
generatedAssemblies.Add (new GeneratedAssembly (typeMapAssemblyName, stream));
322338
logger.LogGeneratedTypeMapAssemblyInfo (typeMapAssemblyName, peers.Count);
323339
}
324-
var rootStream = new MemoryStream ();
325-
var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
326-
rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream);
327-
rootStream.Position = 0;
328-
generatedAssemblies.Add (new GeneratedAssembly ("_Microsoft.Android.TypeMaps", rootStream));
329-
logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count);
340+
const string rootAssemblyName = "_Microsoft.Android.TypeMaps";
341+
bool generateRoot = true;
342+
if (shouldGenerateTypeMapAssembly is not null) {
343+
var rootFingerprint = MetadataHelper.ComputeRootIncrementalFingerprint (perAssemblyNames, systemRuntimeVersion, useSharedTypemapUniverse);
344+
generateRoot = shouldGenerateTypeMapAssembly (rootAssemblyName, rootFingerprint);
345+
}
346+
if (generateRoot) {
347+
var rootStream = new MemoryStream ();
348+
var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
349+
rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream);
350+
rootStream.Position = 0;
351+
generatedAssemblies.Add (new GeneratedAssembly (rootAssemblyName, rootStream));
352+
logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count);
353+
}
330354
logger.LogGeneratedTypeMapAssembliesInfo (generatedAssemblies.Count);
331355
return generatedAssemblies;
332356
}

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
Outputs without making the target run on every build. The stamp is always
4747
touched, so the target is correctly skipped when none of its Inputs changed. -->
4848
<_TrimmableTypeMapOutputStamp>$(_TypeMapOutputDirectory)_GenerateTrimmableTypeMap.stamp</_TrimmableTypeMapOutputStamp>
49+
<_TrimmableTypeMapFingerprintsFile>$(_TypeMapOutputDirectory)typemap-fingerprints.txt</_TrimmableTypeMapFingerprintsFile>
4950
<_TrimmableRemoveRegisterFlag>$(_AndroidStampDirectory)_RemoveRegisterAttribute.stamp</_TrimmableRemoveRegisterFlag>
5051
<_TrimmableRemoveRegisterTarget Condition=" '$(_AndroidRuntime)' == 'CoreCLR' ">_RemoveRegisterAttributeCoreClr</_TrimmableRemoveRegisterTarget>
5152
<_TrimmableRemoveRegisterTarget Condition=" '$(_AndroidRuntime)' == 'NativeAOT' ">_RemoveRegisterAttributeNativeAot</_TrimmableRemoveRegisterTarget>
@@ -170,6 +171,7 @@
170171
ApplicationJavaClass="$(AndroidApplicationJavaClass)"
171172
ErrorOnCustomJavaObject="$(AndroidErrorOnCustomJavaObject)"
172173
GeneratedAssembliesListFile="$(_TypeMapAssembliesListFile)"
174+
TypeMapFingerprintsFile="$(_TrimmableTypeMapFingerprintsFile)"
173175
AcwMapOutputFile="$(_PreTrimTypeMapAcwMapOutputFile)"
174176
ApplicationRegistrationOutputFile="$(_PreTrimTypeMapApplicationRegistrationOutputFile)">
175177
<Output TaskParameter="GeneratedAssemblies" ItemName="_GeneratedTypeMapAssemblies" />
@@ -200,6 +202,7 @@
200202
<FileWrites Include="@(_GeneratedTypeMapAssemblies)" />
201203
<FileWrites Include="@(_GeneratedJavaFiles)" />
202204
<FileWrites Include="$(_TypeMapAssembliesListFile)" />
205+
<FileWrites Include="$(_TrimmableTypeMapFingerprintsFile)" />
203206
<FileWrites Include="$(_TypeMapJavaFilesList)" />
204207
<FileWrites Include="$(_TrimmableTypeMapOutputStamp)" />
205208
<FileWrites Include="$(_TypeMapBaseOutputDir)AndroidManifest.xml" />

0 commit comments

Comments
 (0)