Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7d92d16
[NativeAOT] Make the trimmable typemap the default
simonrozsival Jun 30, 2026
5653743
[NativeAOT] Keep _PreTrimmingFixLegacyDesignerUpdateItems off the tri…
simonrozsival Jun 30, 2026
80b16b5
[Tests] Skip NativeAOT cases that inspect illink's linked/ output
simonrozsival Jun 30, 2026
f688339
[Tests] NativeAOT BuildHasNoWarnings is now warning-clean
simonrozsival Jun 30, 2026
c4efd24
[Tests] More NativeAOT test adjustments for the trimmable typemap def…
simonrozsival Jun 30, 2026
df2bf26
[Tests] XA4310 NativeAOT no longer emits the IL3053 aggregate warning
simonrozsival Jun 30, 2026
7e67041
[Tests] DotNetBuild expects mapping.txt for NativeAOT release
simonrozsival Jun 30, 2026
8df61fd
[NativeAOT] Only change the default typemap; keep managed/llvm-ir rea…
simonrozsival Jun 30, 2026
588b1f9
[Tests] Delete the BuildTest2.NativeAOT type-map test
simonrozsival Jun 30, 2026
9593bfa
[Tests] Skip SkiaSharpCanvasBasedAppRuns on NativeAOT
simonrozsival Jun 30, 2026
16e5ee6
[Tests] Skip BindingWithAndroidJavaSource on NativeAOT
simonrozsival Jul 1, 2026
f9e2131
[Tests] Skip CheckLintErrorsAndWarnings on NativeAOT
simonrozsival Jul 1, 2026
5899f81
[NativeAOT] Pass generated ACW keep rules to R8 on trimmable path
simonrozsival Jul 3, 2026
edd1d27
[TrimmableTypeMap] Exclude Java.Interop.ManagedPeer from the type map
simonrozsival Jul 3, 2026
55fb1b8
[TrimmableTypeMap][NativeAOT] Fix XA4321: collect ILC DGML from per-R…
simonrozsival Jul 3, 2026
2369f86
[Tests] Add NativeAOT regression test for R8-kept runtime ACWs
simonrozsival Jul 3, 2026
9c7b875
[TrimmableTypeMap][NativeAOT] Fix single-RID DGML path (both output-p…
simonrozsival Jul 3, 2026
1ffdb02
[TrimmableTypeMap][NativeAOT] Fall back to codegen DGML when scan DGM…
simonrozsival Jul 3, 2026
4a12606
[TrimmableTypeMap] Fix ManifestPlaceholders on the trimmable NativeAO…
simonrozsival Jul 3, 2026
c0e6563
[TrimmableTypeMap][NativeAOT] Add _AndroidTrimmableTypemapTrimJavaCod…
simonrozsival Jul 3, 2026
59c679d
[TrimmableTypeMap] Emit <layout> element for the [Layout] attribute
simonrozsival Jul 3, 2026
8e7c0da
[TrimmableTypeMap] Emit XA1010 for invalid $(AndroidManifestPlacehold…
simonrozsival Jul 3, 2026
887c92e
[Tests] BuildProguardEnabledProject: check references.cfg on NativeAOT
simonrozsival Jul 3, 2026
9c86f51
[TrimmableTypeMap][NativeAOT] Generate per-process runtime provider J…
simonrozsival Jul 4, 2026
3ea2dfa
[TrimmableTypeMap] Make generated typemap assemblies byte-deterministic
simonrozsival Jul 4, 2026
76f465a
[TrimmableTypeMap][NativeAOT] Keep user AndroidJavaSource under R8
simonrozsival Jul 4, 2026
c6caef3
[TrimmableTypeMap][NativeAOT] Scan runtime host ACWs via a ref assembly
simonrozsival Jul 4, 2026
99a03d9
[TrimmableTypeMap][NativeAOT] Auto-detect runtime-only ACW assemblies
simonrozsival Jul 4, 2026
5800dd4
[Tests] Skip JniNativeMethodRegistration tests on trimmable/NativeAOT
simonrozsival Jul 4, 2026
1cfe132
[TrimmableTypeMap] Resolve java/lang/Object to Java.Lang.Object, not …
simonrozsival Jul 5, 2026
1472552
[trimmable-typemap] Order java->managed aliases Mono.Android-first
simonrozsival Jul 5, 2026
fd9bc4c
[NativeAOT] Keep Resource.Designer assembly on the trimmable type map…
simonrozsival Jul 5, 2026
4732b55
[NativeAOT] Reference the designer assembly before rooting it for ILC
simonrozsival Jul 5, 2026
0970a43
[TrimmableTypeMap] Inline resource designer constants (experimental, …
simonrozsival Jul 6, 2026
5331852
[TrimmableTypeMap] Inline designer constants in the main assembly too
simonrozsival Jul 6, 2026
fdee6bf
[TrimmableTypeMap] Inline resource designer constants by default (tri…
simonrozsival Jul 6, 2026
fcf09ab
Revert inline-resource-designer-constants experiment
simonrozsival Jul 7, 2026
dd9f225
[TrimmableTypeMap] Merge library .aar manifests on the legacy merger …
simonrozsival Jul 7, 2026
7c39ca7
[Tests] Bump WorkManager test's AndroidX to post-KMP-split versions
simonrozsival Jul 7, 2026
07b6f87
[TrimmableTypeMap] Emit array proxy for the System.String built-in ma…
simonrozsival Jul 7, 2026
243f5c4
[Java.Interop] Add GetType array coverage for boxed nullable value types
simonrozsival Jul 7, 2026
3d0cd67
[TrimmableTypeMap] Emit array proxies for boxed nullable value types
simonrozsival Jul 7, 2026
f813b47
[TrimmableTypeMap] Split array-proxy rank: reference-1, primitive-3
simonrozsival Jul 7, 2026
f3f59af
[TrimmableTypeMap] Treat System.String as a rank-3 built-in array ele…
simonrozsival Jul 7, 2026
9f69df8
[TrimmableTypeMap] Treat boxed Nullable<T> as rank-3 built-in array e…
simonrozsival Jul 7, 2026
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
11 changes: 11 additions & 0 deletions Configuration.OperatingSystem.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<HostOsName Condition=" '$(HostOsName)' == '' ">macOS</HostOsName>
<HostOsFlavor Condition=" '$(HostOsFlavor)' == '' ">macOS</HostOsFlavor>
<HostOsRelease Condition=" '$(HostOsRelease)' == '' ">26.5 (25F71)</HostOsRelease>
<HostCpuCount Condition=" '$(HostCpuCount)' == '' ">10</HostCpuCount>
<HostBits Condition=" '$(HostBits)' == '' ">64</HostBits>
<NdkLlvmTag Condition=" '$(NdkLlvmTag)' == '' ">darwin-x86_64</NdkLlvmTag>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,20 @@ static void AssertGetJniTypeInfoForType (Type type, string jniType, bool isKeywo
AssertArrayTypesFromSignature<double[]> (manager, "[[D", typeof (JavaObjectArray<JavaPrimitiveArray<double>>));
AssertArrayTypesFromSignature<string[]> (manager, "[[Ljava/lang/String;", typeof (JavaObjectArray<JavaObjectArray<string>>));

// Nullable counterparts of the primitive value types: the boxed reference signatures
// java/lang/<Boxed> map to Nullable<T> (see JniBuiltinSimpleReferenceToType /
// GetBuiltInTypeForSimpleReference). Unlike the primitive keyword arrays above, these are
// non-keyword reference arrays, so the element type is the boxed Nullable<T> and the array
// family is T?[] and JavaObjectArray<T?> (no JavaArray<T>/JavaPrimitiveArray<T>).
AssertArrayTypesFromSignature<bool?> (manager, "[Ljava/lang/Boolean;", typeof (JavaObjectArray<bool?>));
AssertArrayTypesFromSignature<sbyte?> (manager, "[Ljava/lang/Byte;", typeof (JavaObjectArray<sbyte?>));
AssertArrayTypesFromSignature<char?> (manager, "[Ljava/lang/Character;", typeof (JavaObjectArray<char?>));
AssertArrayTypesFromSignature<short?> (manager, "[Ljava/lang/Short;", typeof (JavaObjectArray<short?>));
AssertArrayTypesFromSignature<int?> (manager, "[Ljava/lang/Integer;", typeof (JavaObjectArray<int?>));
AssertArrayTypesFromSignature<long?> (manager, "[Ljava/lang/Long;", typeof (JavaObjectArray<long?>));
AssertArrayTypesFromSignature<float?> (manager, "[Ljava/lang/Float;", typeof (JavaObjectArray<float?>));
AssertArrayTypesFromSignature<double?> (manager, "[Ljava/lang/Double;", typeof (JavaObjectArray<double?>));

// Yes, these look weird...
// Assume: class II {}
Assert.AreEqual (null, GetType ("II"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ public unsafe void RegisterNativeMethods_JniNativeMethod ()
static int NativeAdd (IntPtr jnienv, IntPtr self, int a, int b) => a + b;

[Test]
[Category ("NativeAOTIgnore")]
[Category ("TrimmableTypeMapUnsupported")]
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Test exercises non-AOT-compatible JniNativeMethodRegistration[] registration path.")]
public unsafe void RegisterNativeMethods_JniNativeMethodRegistration ()
{
Expand Down Expand Up @@ -253,6 +255,8 @@ public unsafe void RegisterNativeMethods_JniNativeMethodRegistration ()
static int ManagedAdd (IntPtr jnienv, IntPtr self, int a, int b) => a + b;

[Test]
[Category ("NativeAOTIgnore")]
[Category ("TrimmableTypeMapUnsupported")]
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Test exercises non-AOT-compatible JniNativeMethodRegistration[] registration path with many methods.")]
public unsafe void RegisterNativeMethods_JniNativeMethodRegistration_ManyMethods ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,11 @@ internal static void ApplyPlaceholders (XDocument doc, string? placeholders, str
var eqIndex = entry.IndexOf ('=');
if (eqIndex > 0) {
var key = entry.Substring (0, eqIndex).Trim ();
var value = entry.Substring (eqIndex + 1).Trim ();
// Normalize '\' to the platform directory separator to match the legacy pipeline,
// where the substituted manifest is re-encoded by aapt2 (which rewrites backslashes
// to '/' on Unix). The trimmable generator writes the merged manifest directly, so we
// apply the same normalization here to keep placeholder values byte-for-byte identical.
var value = entry.Substring (eqIndex + 1).Trim ().Replace ('\\', Path.DirectorySeparatorChar);
replacements ["${" + key + "}"] = value;
} else if (eqIndex < 0) {
// An entry without '=' is not a valid key=value pair. Mirror the legacy
Expand Down
120 changes: 111 additions & 9 deletions src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ static class ModelBuilder
new ("D", "Double", "System.Double", ["Java.Interop.JavaDoubleArray"]),
];

// Nullable value types map to the boxed java/lang/<Boxed> references (java/lang/Boolean etc.) via
// TrimmableTypeMapTypeManager.GetBuiltInTypeForSimpleReference. Like System.String they are neither
// scanned Java peers nor primitives, so their array proxies are emitted explicitly. Only the eight
// types with a boxed java/lang mapping are included (java/lang/Byte -> sbyte?, not byte?, etc.).
static readonly (string Name, string ManagedTypeName) [] NullableArrayProxies = [
("Boolean", "System.Boolean"),
("SByte", "System.SByte"),
("Char", "System.Char"),
("Int16", "System.Int16"),
("Int32", "System.Int32"),
("Int64", "System.Int64"),
("Single", "System.Single"),
("Double", "System.Double"),
];

static readonly HashSet<string> EssentialRuntimeTypes = new (StringComparer.Ordinal) {
"java/lang/Object",
"java/lang/Class",
Expand All @@ -55,7 +70,7 @@ static class ModelBuilder
/// Emit per-rank array <c>TypeMap</c> entries + <c>__ArrayMapRank{N}</c> sentinels
/// for ranks 1..<paramref name="maxArrayRank"/>. 0 disables array entry emission.
/// </param>
public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, string outputPath, string? assemblyName = null, int maxArrayRank = 0)
public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, string outputPath, string? assemblyName = null, int maxArrayRank = 0, int maxReferenceArrayRank = 0)
{
if (peers is null) {
throw new ArgumentNullException (nameof (peers));
Expand All @@ -66,13 +81,20 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
if (maxArrayRank < 0) {
throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank, "Must be >= 0.");
}
if (maxReferenceArrayRank < 0) {
throw new ArgumentOutOfRangeException (nameof (maxReferenceArrayRank), maxReferenceArrayRank, "Must be >= 0.");
}

assemblyName ??= Path.GetFileNameWithoutExtension (outputPath);

var model = new TypeMapAssemblyData {
AssemblyName = assemblyName,
ModuleName = Path.GetFileName (outputPath),
MaxArrayRank = maxArrayRank,
// The per-assembly __ArrayMapRank{N} anchor count must be uniform across every typemap
// assembly (the root generator builds a rectangular [assembly][rank] matrix), so the model
// always carries the overall maximum rank. Primitive and reference element types may
// populate different rank ranges within that shared anchor set.
MaxArrayRank = Math.Max (maxArrayRank, maxReferenceArrayRank),
};

// Invoker types are NOT emitted as separate proxies or TypeMap entries.
Expand Down Expand Up @@ -102,20 +124,34 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
string jniName = kvp.Key;
var peersForName = kvp.Value;

// Sort aliases by managed type name for deterministic proxy naming
// Order aliases to match the java→managed selection the native runtime performs (see
// clr_typemap_java_to_managed / monovm_typemap_java_to_managed and NativeTypeMappingData):
// the runtime builds its java→managed map by processing the Mono.Android module first and
// keeping the first managed type that claims a Java name (first-writer-wins). GetTypeForSimpleReference
// returns the first (index [0]) alias, so put the Mono.Android peer first — e.g. java/lang/Object
// must resolve to Java.Lang.Object (Mono.Android), not Java.Interop.JavaObject. Remaining peers are
// ordered by ordinal managed type name for deterministic proxy naming.
if (peersForName.Count > 1) {
peersForName.Sort ((a, b) => StringComparer.Ordinal.Compare (a.ManagedTypeName, b.ManagedTypeName));
peersForName.Sort ((a, b) => {
bool aMonoAndroid = string.Equals (a.AssemblyName, "Mono.Android", StringComparison.Ordinal);
bool bMonoAndroid = string.Equals (b.AssemblyName, "Mono.Android", StringComparison.Ordinal);
if (aMonoAndroid != bMonoAndroid) {
return aMonoAndroid ? -1 : 1;
}
return StringComparer.Ordinal.Compare (a.ManagedTypeName, b.ManagedTypeName);
});
}

EmitPeers (model, jniName, peersForName, assemblyName, usedProxyNames);

if (maxArrayRank > 0) {
EmitArrayEntries (model, jniName, peersForName, maxArrayRank);
// Java peer types are reference types: emit array proxies only up to the reference rank.
if (maxReferenceArrayRank > 0) {
EmitArrayEntries (model, jniName, peersForName, maxReferenceArrayRank);
}
}

if (maxArrayRank > 0 && string.Equals (assemblyName, "_Java.Interop.TypeMap", StringComparison.Ordinal)) {
EmitPrimitiveArrayEntries (model, maxArrayRank);
if (string.Equals (assemblyName, "_Java.Interop.TypeMap", StringComparison.Ordinal)) {
EmitPrimitiveArrayEntries (model, maxArrayRank, maxReferenceArrayRank);
}

BuildNativeRegistrations (model);
Expand Down Expand Up @@ -665,7 +701,14 @@ static void EmitArrayEntriesForPeer (TypeMapAssemblyData model, JavaPeerInfo pee
}
}

static void EmitPrimitiveArrayEntries (TypeMapAssemblyData model, int maxArrayRank)
// Emits array proxies for the built-in element types that are not scanned Java peers:
// * the keyword primitives (int/bool/...) up to maxArrayRank (jagged/multidim primitive arrays
// like int[][][] are cheap — a small fixed set of element types),
// * System.String up to maxArrayRank (also a built-in element type, so multidimensional string
// arrays like String[][] stay resolvable),
// * the boxed Nullable<T> value types up to maxArrayRank (also built-in element types mapping to
// java/lang/<Boxed>; a small fixed set, so multidim boxed arrays stay resolvable too).
static void EmitPrimitiveArrayEntries (TypeMapAssemblyData model, int maxArrayRank, int maxReferenceArrayRank)
{
foreach (var primitive in PrimitiveArrayProxies) {
for (int rank = 1; rank <= maxArrayRank; rank++) {
Expand Down Expand Up @@ -694,6 +737,65 @@ static void EmitPrimitiveArrayEntries (TypeMapAssemblyData model, int maxArrayRa
AddArrayProxyAssociations (model, proxy, proxyReference);
}
}

// System.String maps to java/lang/String but is a built-in element type injected at runtime by
// TrimmableTypeMapTypeManager.GetBuiltInTypeForSimpleReference, so it is neither a scanned Java
// peer (EmitArrayEntriesForPeer) nor a keyword primitive. Emit its array proxies here (Primitive
// is null, so it gets the reference-array family) so GetTypes ("[Ljava/lang/String;") yields
// System.String[] / JavaObjectArray<string> / JavaArray<string> on NativeAOT, matching CoreCLR.
// String is a single, fixed built-in element type (not part of the scanned-peer explosion), so —
// like the keyword primitives — it goes up to maxArrayRank. This keeps multidimensional string
// arrays (String[][], "[[Ljava/lang/String;") resolvable, matching the other type-map backends.
for (int rank = 1; rank <= maxArrayRank; rank++) {
var proxy = new ArrayProxyData {
TypeName = ManagedTypeNameToArrayProxyTypeName ("System.String", rank),
ElementType = new TypeRefData {
ManagedTypeName = "System.String",
AssemblyName = "System.Runtime",
},
Rank = rank,
};
model.ArrayProxyTypes.Add (proxy);
var proxyReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", model.AssemblyName);
model.Entries.Add (new TypeMapAttributeData {
MapKey = GetArrayProxyMapKey (proxy.ElementType),
ProxyTypeReference = proxyReference,
TargetTypeReference = proxyReference,
AnchorRank = rank,
});
AddArrayProxyAssociations (model, proxy, proxyReference);
}

// Nullable counterparts of the primitive value types map to the boxed java/lang/<Boxed>
// references and, like System.String, are built-in element mappings (no scanned peer, no
// primitive proxy). Emit reference-array proxies (Primitive is null) so GetTypes
// ("[Ljava/lang/Boolean;") yields bool?[] / JavaObjectArray<bool?> on NativeAOT. The element
// key uses the normalized generic form (simple assembly names) that
// TrimmableTypeMap.BuildManagedTypeKey produces at runtime for Nullable<T>. Like the primitives
// and System.String, these are a small fixed set of built-in element types (not the scanned-peer
// explosion), so they go up to maxArrayRank and keep multidim boxed arrays resolvable.
foreach (var nullablePrimitive in NullableArrayProxies) {
var elementTypeName = $"System.Nullable`1[[{nullablePrimitive.ManagedTypeName}, System.Runtime]]";
for (int rank = 1; rank <= maxArrayRank; rank++) {
var proxy = new ArrayProxyData {
TypeName = $"Nullable_{nullablePrimitive.Name}_ArrayProxy{rank}",
ElementType = new TypeRefData {
ManagedTypeName = elementTypeName,
AssemblyName = "System.Runtime",
},
Rank = rank,
};
model.ArrayProxyTypes.Add (proxy);
var proxyReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", model.AssemblyName);
model.Entries.Add (new TypeMapAttributeData {
MapKey = GetArrayProxyMapKey (proxy.ElementType),
ProxyTypeReference = proxyReference,
TargetTypeReference = proxyReference,
AnchorRank = rank,
});
AddArrayProxyAssociations (model, proxy, proxyReference);
}
}
}

static string Brackets (int rank) => rank switch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

Expand Down Expand Up @@ -107,12 +108,28 @@ public void WritePE (Stream stream)
new PEHeaderBuilder (imageCharacteristics: Characteristics.Dll),
new MetadataRootBuilder (Metadata),
ILBuilder,
mappedFieldData: _mappedFieldData.Count > 0 ? _mappedFieldData : null);
mappedFieldData: _mappedFieldData.Count > 0 ? _mappedFieldData : null,
// Derive the PE content id (and thus the header TimeDateStamp/debug id) from a hash of the
// image so the bytes are fully deterministic. Without this, ManagedPEBuilder falls back to a
// time-based id, so every regeneration produces different bytes even for identical input; that
// churns the generated typemap assemblies and breaks incremental packaging (a no-op rebuild
// re-touches the .dll, forcing repackage + re-sign). The MVID is already deterministic.
deterministicIdProvider: DeterministicContentId);
var peBlob = new BlobBuilder ();
peBuilder.Serialize (peBlob);
peBlob.WriteContentTo (stream);
}

static BlobContentId DeterministicContentId (IEnumerable<Blob> content)
{
using var hash = IncrementalHash.CreateHash (HashAlgorithmName.SHA256);
foreach (var blob in content) {
var segment = blob.GetBytes ();
hash.AppendData (segment.Array!, segment.Offset, segment.Count);
}
return BlobContentId.FromHash (hash.GetHashAndReset ());
}

/// <summary>
/// Adds (or retrieves from cache) an assembly reference.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ public TypeMapAssemblyGenerator (Version systemRuntimeVersion)
/// <param name="useSharedTypemapUniverse">
/// When true, uses <c>Java.Lang.Object</c> as the shared anchor type. When false, emits a per-assembly anchor.
/// </param>
/// <param name="maxArrayRank">Max rank for per-rank array <c>TypeMap</c> entries. 0 disables.</param>
public void Generate (IReadOnlyList<JavaPeerInfo> peers, Stream stream, string assemblyName, bool useSharedTypemapUniverse = false, int maxArrayRank = 0)
/// <param name="maxArrayRank">Max rank for per-rank primitive array <c>TypeMap</c> entries. 0 disables.</param>
/// <param name="maxReferenceArrayRank">Max rank for per-rank reference-type (Java peer, String, boxed Nullable) array <c>TypeMap</c> entries. 0 disables.</param>
public void Generate (IReadOnlyList<JavaPeerInfo> peers, Stream stream, string assemblyName, bool useSharedTypemapUniverse = false, int maxArrayRank = 0, int maxReferenceArrayRank = 0)
{
var model = ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName, maxArrayRank);
var model = ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName, maxArrayRank, maxReferenceArrayRank);
var emitter = new TypeMapAssemblyEmitter (_systemRuntimeVersion);
emitter.Emit (model, stream, useSharedTypemapUniverse);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public interface ITrimmableTypeMapLogger
void LogGeneratedJcwFilesInfo (int sourceCount);
void LogRootingManifestReferencedTypeInfo (string javaTypeName, string managedTypeName);
void LogManifestReferencedTypeNotFoundWarning (string javaTypeName);
void LogInvalidManifestPlaceholderWarning (string placeholders);
void LogLibraryManifestMergeWarning (string message);
void LogUnresolvableJavaPeerSkippedWarning (
string managedTypeName,
string assemblyName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen
// with the wrong AttributeName.
List<IntentFilterInfo>? intentFilters = null;
List<MetaDataInfo>? metaData = null;
Dictionary<string, object?>? layoutProperties = null;

foreach (var caHandle in typeDef.GetCustomAttributes ()) {
var ca = Reader.GetCustomAttribute (caHandle);
Expand Down Expand Up @@ -214,6 +215,8 @@ bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen
metaData ??= new List<MetaDataInfo> ();
var (mdName, mdProps) = ParseNameAndProperties (ca);
metaData.Add (CreateMetaDataInfo (mdName, mdProps));
} else if (attrName == "LayoutAttribute") {
layoutProperties = ParseLayoutAttribute (ca);
} else if (attrInfo is null && ImplementsJniNameProviderAttribute (ca)) {
// Custom attribute implementing IJniNameProviderAttribute (e.g., user-defined [CustomJniName])
var name = TryGetNameProperty (ca);
Expand All @@ -232,6 +235,9 @@ bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen
if (metaData is not null) {
attrInfo.MetaData.AddRange (metaData);
}
if (layoutProperties is not null) {
attrInfo.LayoutProperties = layoutProperties;
}
}

return (registerInfo, attrInfo);
Expand Down Expand Up @@ -425,6 +431,18 @@ RegisterInfo ParseRegisterInfo (CustomAttributeValue<string> value)
return null;
}

Dictionary<string, object?> ParseLayoutAttribute (CustomAttribute ca)
{
var value = DecodeAttribute (ca);
var properties = new Dictionary<string, object?> (StringComparer.Ordinal);
foreach (var named in value.NamedArguments) {
if (named.Name is not null) {
properties [named.Name] = named.Value;
}
}
return properties;
}

IntentFilterInfo ParseIntentFilterAttribute (CustomAttribute ca)
{
var value = DecodeAttribute (ca);
Expand Down Expand Up @@ -712,6 +730,12 @@ class TypeAttributeInfo (string attributeName)
/// Metadata entries declared on this type via [MetaData] attributes.
/// </summary>
public List<MetaDataInfo> MetaData { get; } = [];

/// <summary>
/// Named property values from a [Layout] attribute on this type, or null if none.
/// Maps to the &lt;layout&gt; child element of the component in the manifest.
/// </summary>
public Dictionary<string, object?>? LayoutProperties { get; set; }
}

sealed class ApplicationAttributeInfo () : TypeAttributeInfo ("ApplicationAttribute")
Expand Down
Loading