Skip to content

Commit af78ed0

Browse files
[TrimmableTypeMap] Build pipeline + manifest generation
Build pipeline: - GenerateTrimmableTypeMap task (SRM scanner + TypeMap IL generator + JCW) - Trimmable.targets: _GenerateTrimmableTypeMap, _GenerateJavaStubs override, assembly store integration with per-ABI batching - Trimmable.CoreCLR.targets: ILLink integration, linked/ to typemap/ fallback - GenerateEmptyTypemapStub: LLVM IR stub symbols - GenerateNativeApplicationConfigSources: tolerant JNIEnvInit token check - PreserveLists/Trimmable.CoreCLR.xml: preserves JNIEnvInit.Initialize Manifest generation (#10807): - TrimmableManifestGenerator: data-driven property mapping (7 arrays, 9 enum converters), MainLauncher, runtime provider, template merging, deduplication - Assembly-level attrs: Permission, UsesPermission, UsesFeature, UsesLibrary, UsesConfiguration, Application, MetaData, Property - ManifestPlaceholders, debuggable/extractNativeLibs, ApplicationJavaClass - XA4213 constructor validation, duplicate Application detection - 30 unit tests (130ms) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 65974f3 commit af78ed0

File tree

14 files changed

+2571
-131
lines changed

14 files changed

+2571
-131
lines changed

samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
<TargetFramework>$(DotNetAndroidTargetFramework)</TargetFramework>
44
<OutputType>Exe</OutputType>
55
<RootNamespace>HelloWorld</RootNamespace>
6+
<UseMonoRuntime>false</UseMonoRuntime>
7+
<_AndroidTypeMapImplementation>trimmable</_AndroidTypeMapImplementation>
8+
<RunAOTCompilation>false</RunAOTCompilation>
9+
<PublishReadyToRun>false</PublishReadyToRun>
610
</PropertyGroup>
711
<ItemGroup>
812
<ProjectReference Include="..\HelloLibrary\HelloLibrary.DotNet.csproj" />

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ void EmitTypeReferences ()
168168
{
169169
var metadata = _pe.Metadata;
170170
_javaPeerProxyRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
171-
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy"));
171+
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy`1"));
172172
_iJavaPeerableRef = metadata.AddTypeReference (_javaInteropRef,
173173
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("IJavaPeerable"));
174174
_jniHandleOwnershipRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
@@ -331,7 +331,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinition
331331
// .ctor — call JavaPeerProxy<T>..ctor()
332332
var genericBaseCtorRef = _pe.AddMemberRef (genericBaseSpec, ".ctor",
333333
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }));
334-
_pe.EmitBody (".ctor",
334+
var ctorDefHandle = _pe.EmitBody (".ctor",
335335
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
336336
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (0, rt => rt.Void (), p => { }),
337337
encoder => {
@@ -340,6 +340,9 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinition
340340
encoder.OpCode (ILOpCode.Ret);
341341
});
342342

343+
// Self-application: [ProxyType] on ProxyType — lets GetCustomAttribute<JavaPeerProxy>() instantiate it
344+
metadata.AddCustomAttribute (typeDefHandle, ctorDefHandle, _pe.BuildAttributeBlob (_ => { }));
345+
343346
// CreateInstance
344347
EmitCreateInstance (proxy);
345348

src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs

Lines changed: 295 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,33 @@ void Build ()
9898
// [Export] is a method-level attribute; it is parsed at scan time by JavaPeerScanner
9999
} else if (IsKnownComponentAttribute (attrName)) {
100100
attrInfo ??= CreateTypeAttributeInfo (attrName);
101-
var name = TryGetNameProperty (ca);
101+
var value = DecodeAttribute (ca);
102+
103+
// Capture all named properties
104+
foreach (var named in value.NamedArguments) {
105+
if (named.Name is not null) {
106+
attrInfo.Properties [named.Name] = named.Value;
107+
}
108+
}
109+
110+
var name = TryGetNameFromDecodedAttribute (value);
102111
if (name is not null) {
103112
attrInfo.JniName = name.Replace ('.', '/');
104113
}
105114
if (attrInfo is ApplicationAttributeInfo applicationAttributeInfo) {
106-
applicationAttributeInfo.BackupAgent = TryGetTypeProperty (ca, "BackupAgent");
107-
applicationAttributeInfo.ManageSpaceActivity = TryGetTypeProperty (ca, "ManageSpaceActivity");
115+
if (TryGetNamedArgument<string> (value, "BackupAgent", out var backupAgent)) {
116+
applicationAttributeInfo.BackupAgent = backupAgent;
117+
}
118+
if (TryGetNamedArgument<string> (value, "ManageSpaceActivity", out var manageSpace)) {
119+
applicationAttributeInfo.ManageSpaceActivity = manageSpace;
120+
}
108121
}
122+
} else if (attrName == "IntentFilterAttribute") {
123+
attrInfo ??= new TypeAttributeInfo ("IntentFilterAttribute");
124+
attrInfo.IntentFilters.Add (ParseIntentFilterAttribute (ca));
125+
} else if (attrName == "MetaDataAttribute") {
126+
attrInfo ??= new TypeAttributeInfo ("MetaDataAttribute");
127+
attrInfo.MetaData.Add (ParseMetaDataAttribute (ca));
109128
} else if (attrInfo is null && ImplementsJniNameProviderAttribute (ca)) {
110129
// Custom attribute implementing IJniNameProviderAttribute (e.g., user-defined [CustomJniName])
111130
var name = TryGetNameProperty (ca);
@@ -239,6 +258,14 @@ RegisterInfo ParseRegisterInfo (CustomAttributeValue<string> value)
239258
}
240259

241260
var value = DecodeAttribute (ca);
261+
return TryGetNameFromDecodedAttribute (value);
262+
}
263+
264+
static string? TryGetNameFromDecodedAttribute (CustomAttributeValue<string> value)
265+
{
266+
if (TryGetNamedArgument<string> (value, "Name", out var name) && !string.IsNullOrEmpty (name)) {
267+
return name;
268+
}
242269

243270
// Fall back to first constructor argument (e.g., [CustomJniName("...")])
244271
if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is string ctorName && !string.IsNullOrEmpty (ctorName)) {
@@ -248,6 +275,69 @@ RegisterInfo ParseRegisterInfo (CustomAttributeValue<string> value)
248275
return null;
249276
}
250277

278+
IntentFilterInfo ParseIntentFilterAttribute (CustomAttribute ca)
279+
{
280+
var value = DecodeAttribute (ca);
281+
282+
// First ctor argument is string[] actions
283+
var actions = new List<string> ();
284+
if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is IReadOnlyCollection<CustomAttributeTypedArgument<string>> actionArgs) {
285+
foreach (var arg in actionArgs) {
286+
if (arg.Value is string action) {
287+
actions.Add (action);
288+
}
289+
}
290+
}
291+
292+
var categories = new List<string> ();
293+
if (TryGetNamedArgument<string> (value, "Categories", out _)) {
294+
// Categories is a string[] property — decode it from the raw named argument
295+
foreach (var named in value.NamedArguments) {
296+
if (named.Name == "Categories" && named.Value is IReadOnlyCollection<CustomAttributeTypedArgument<string>> catArgs) {
297+
foreach (var arg in catArgs) {
298+
if (arg.Value is string cat) {
299+
categories.Add (cat);
300+
}
301+
}
302+
}
303+
}
304+
}
305+
306+
var properties = new Dictionary<string, object?> (StringComparer.Ordinal);
307+
foreach (var named in value.NamedArguments) {
308+
if (named.Name is not null && named.Name != "Categories") {
309+
properties [named.Name] = named.Value;
310+
}
311+
}
312+
313+
return new IntentFilterInfo {
314+
Actions = actions,
315+
Categories = categories,
316+
Properties = properties,
317+
};
318+
}
319+
320+
MetaDataInfo ParseMetaDataAttribute (CustomAttribute ca)
321+
{
322+
var value = DecodeAttribute (ca);
323+
324+
string name = "";
325+
if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is string nameArg) {
326+
name = nameArg;
327+
}
328+
329+
string? metaValue = null;
330+
string? resource = null;
331+
TryGetNamedArgument<string> (value, "Value", out metaValue);
332+
TryGetNamedArgument<string> (value, "Resource", out resource);
333+
334+
return new MetaDataInfo {
335+
Name = name,
336+
Value = metaValue,
337+
Resource = resource,
338+
};
339+
}
340+
251341
static bool TryGetNamedArgument<T> (CustomAttributeValue<string> value, string argumentName, [MaybeNullWhen (false)] out T argumentValue) where T : notnull
252342
{
253343
foreach (var named in value.NamedArguments) {
@@ -260,6 +350,193 @@ static bool TryGetNamedArgument<T> (CustomAttributeValue<string> value, string a
260350
return false;
261351
}
262352

353+
/// <summary>
354+
/// Scans assembly-level custom attributes for manifest-related data.
355+
/// </summary>
356+
internal void ScanAssemblyAttributes (AssemblyManifestInfo info)
357+
{
358+
var asmDef = Reader.GetAssemblyDefinition ();
359+
foreach (var caHandle in asmDef.GetCustomAttributes ()) {
360+
var ca = Reader.GetCustomAttribute (caHandle);
361+
var attrName = GetCustomAttributeName (ca, Reader);
362+
if (attrName is null) {
363+
continue;
364+
}
365+
366+
switch (attrName) {
367+
case "PermissionAttribute":
368+
info.Permissions.Add (ParsePermissionAttribute (ca));
369+
break;
370+
case "PermissionGroupAttribute":
371+
info.PermissionGroups.Add (ParsePermissionGroupAttribute (ca));
372+
break;
373+
case "PermissionTreeAttribute":
374+
info.PermissionTrees.Add (ParsePermissionTreeAttribute (ca));
375+
break;
376+
case "UsesPermissionAttribute":
377+
info.UsesPermissions.Add (ParseUsesPermissionAttribute (ca));
378+
break;
379+
case "UsesFeatureAttribute":
380+
info.UsesFeatures.Add (ParseUsesFeatureAttribute (ca));
381+
break;
382+
case "UsesLibraryAttribute":
383+
info.UsesLibraries.Add (ParseUsesLibraryAttribute (ca));
384+
break;
385+
case "UsesConfigurationAttribute":
386+
info.UsesConfigurations.Add (ParseUsesConfigurationAttribute (ca));
387+
break;
388+
case "MetaDataAttribute":
389+
info.MetaData.Add (ParseMetaDataAttribute (ca));
390+
break;
391+
case "PropertyAttribute":
392+
info.Properties.Add (ParsePropertyAttribute (ca));
393+
break;
394+
case "ApplicationAttribute":
395+
info.ApplicationProperties ??= new Dictionary<string, object?> (StringComparer.Ordinal);
396+
var appValue = DecodeAttribute (ca);
397+
foreach (var named in appValue.NamedArguments) {
398+
if (named.Name is not null) {
399+
info.ApplicationProperties [named.Name] = named.Value;
400+
}
401+
}
402+
break;
403+
}
404+
}
405+
}
406+
407+
(string name, Dictionary<string, object?> props) ParseNameAndProperties (CustomAttribute ca)
408+
{
409+
var value = DecodeAttribute (ca);
410+
string name = "";
411+
var props = new Dictionary<string, object?> (StringComparer.Ordinal);
412+
foreach (var named in value.NamedArguments) {
413+
if (named.Name == "Name" && named.Value is string n) {
414+
name = n;
415+
}
416+
if (named.Name is not null) {
417+
props [named.Name] = named.Value;
418+
}
419+
}
420+
return (name, props);
421+
}
422+
423+
PermissionInfo ParsePermissionAttribute (CustomAttribute ca)
424+
{
425+
var (name, props) = ParseNameAndProperties (ca);
426+
return new PermissionInfo { Name = name, Properties = props };
427+
}
428+
429+
PermissionGroupInfo ParsePermissionGroupAttribute (CustomAttribute ca)
430+
{
431+
var (name, props) = ParseNameAndProperties (ca);
432+
return new PermissionGroupInfo { Name = name, Properties = props };
433+
}
434+
435+
PermissionTreeInfo ParsePermissionTreeAttribute (CustomAttribute ca)
436+
{
437+
var (name, props) = ParseNameAndProperties (ca);
438+
return new PermissionTreeInfo { Name = name, Properties = props };
439+
}
440+
441+
UsesPermissionInfo ParseUsesPermissionAttribute (CustomAttribute ca)
442+
{
443+
var value = DecodeAttribute (ca);
444+
string name = "";
445+
int? maxSdk = null;
446+
if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is string n) {
447+
name = n;
448+
}
449+
foreach (var named in value.NamedArguments) {
450+
if (named.Name == "Name" && named.Value is string nameVal) {
451+
name = nameVal;
452+
} else if (named.Name == "MaxSdkVersion" && named.Value is int max) {
453+
maxSdk = max;
454+
}
455+
}
456+
return new UsesPermissionInfo { Name = name, MaxSdkVersion = maxSdk };
457+
}
458+
459+
UsesFeatureInfo ParseUsesFeatureAttribute (CustomAttribute ca)
460+
{
461+
var value = DecodeAttribute (ca);
462+
string? name = null;
463+
int glesVersion = 0;
464+
bool required = true;
465+
foreach (var named in value.NamedArguments) {
466+
if (named.Name == "Name" && named.Value is string n) {
467+
name = n;
468+
} else if (named.Name == "GLESVersion" && named.Value is int v) {
469+
glesVersion = v;
470+
} else if (named.Name == "Required" && named.Value is bool r) {
471+
required = r;
472+
}
473+
}
474+
return new UsesFeatureInfo { Name = name, GLESVersion = glesVersion, Required = required };
475+
}
476+
477+
UsesLibraryInfo ParseUsesLibraryAttribute (CustomAttribute ca)
478+
{
479+
var value = DecodeAttribute (ca);
480+
string name = "";
481+
bool required = true;
482+
if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is string n) {
483+
name = n;
484+
}
485+
foreach (var named in value.NamedArguments) {
486+
if (named.Name == "Name" && named.Value is string nameVal) {
487+
name = nameVal;
488+
} else if (named.Name == "Required" && named.Value is bool r) {
489+
required = r;
490+
}
491+
}
492+
return new UsesLibraryInfo { Name = name, Required = required };
493+
}
494+
495+
UsesConfigurationInfo ParseUsesConfigurationAttribute (CustomAttribute ca)
496+
{
497+
var value = DecodeAttribute (ca);
498+
bool reqFiveWayNav = false;
499+
bool reqHardKeyboard = false;
500+
string? reqKeyboardType = null;
501+
string? reqNavigation = null;
502+
string? reqTouchScreen = null;
503+
foreach (var named in value.NamedArguments) {
504+
switch (named.Name) {
505+
case "ReqFiveWayNav" when named.Value is bool b: reqFiveWayNav = b; break;
506+
case "ReqHardKeyboard" when named.Value is bool b: reqHardKeyboard = b; break;
507+
case "ReqKeyboardType" when named.Value is string s: reqKeyboardType = s; break;
508+
case "ReqNavigation" when named.Value is string s: reqNavigation = s; break;
509+
case "ReqTouchScreen" when named.Value is string s: reqTouchScreen = s; break;
510+
}
511+
}
512+
return new UsesConfigurationInfo {
513+
ReqFiveWayNav = reqFiveWayNav,
514+
ReqHardKeyboard = reqHardKeyboard,
515+
ReqKeyboardType = reqKeyboardType,
516+
ReqNavigation = reqNavigation,
517+
ReqTouchScreen = reqTouchScreen,
518+
};
519+
}
520+
521+
PropertyInfo ParsePropertyAttribute (CustomAttribute ca)
522+
{
523+
var value = DecodeAttribute (ca);
524+
string name = "";
525+
string? propValue = null;
526+
string? resource = null;
527+
if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is string n) {
528+
name = n;
529+
}
530+
foreach (var named in value.NamedArguments) {
531+
switch (named.Name) {
532+
case "Name" when named.Value is string s: name = s; break;
533+
case "Value" when named.Value is string s: propValue = s; break;
534+
case "Resource" when named.Value is string s: resource = s; break;
535+
}
536+
}
537+
return new PropertyInfo { Name = name, Value = propValue, Resource = resource };
538+
}
539+
263540
public void Dispose ()
264541
{
265542
peReader.Dispose ();
@@ -290,6 +567,21 @@ class TypeAttributeInfo (string attributeName)
290567
{
291568
public string AttributeName { get; } = attributeName;
292569
public string? JniName { get; set; }
570+
571+
/// <summary>
572+
/// All named property values from the component attribute.
573+
/// </summary>
574+
public Dictionary<string, object?> Properties { get; } = new (StringComparer.Ordinal);
575+
576+
/// <summary>
577+
/// Intent filters declared on this type via [IntentFilter] attributes.
578+
/// </summary>
579+
public List<IntentFilterInfo> IntentFilters { get; } = [];
580+
581+
/// <summary>
582+
/// Metadata entries declared on this type via [MetaData] attributes.
583+
/// </summary>
584+
public List<MetaDataInfo> MetaData { get; } = [];
293585
}
294586

295587
sealed class ApplicationAttributeInfo () : TypeAttributeInfo ("ApplicationAttribute")

0 commit comments

Comments
 (0)