[TrimmableTypeMap] Add ManifestGenerator#10991
[TrimmableTypeMap] Add ManifestGenerator#10991simonrozsival wants to merge 3 commits intodev/simonrozsival/trimmable-typemap-runtime-prfrom
Conversation
ManifestGenerator in Microsoft.Android.Sdk.TrimmableTypeMap assembly. Converts ComponentInfo records from JavaPeerScanner into AndroidManifest.xml. - Data-driven property mapping (7 static arrays, 9 enum converters) - MainLauncher intent-filter, runtime provider, template merging, deduplication - Assembly-level: Permission, UsesPermission, UsesFeature, UsesLibrary, UsesConfiguration, Application, MetaData, Property - ManifestPlaceholders, debuggable/extractNativeLibs, ApplicationJavaClass - XA4213 constructor validation, duplicate Application detection - VersionCode defaults to '1' matching legacy 23 unit tests (xunit). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a Cecil-free ManifestGenerator to the TrimmableTypeMap generator pipeline, producing AndroidManifest.xml from scanner-captured component/assembly attributes, plus a new unit test suite validating expected manifest output.
Changes:
- Introduces
ManifestGeneratorto synthesize/merge manifest elements (components, permissions/features/libraries, runtime provider injection, placeholder replacement). - Adds xUnit tests covering activity/service/receiver/provider/application/instrumentation emission, template merging, placeholder replacement, and enum conversions.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs | New manifest generation/merge implementation driven by scanned attribute metadata (no Cecil). |
| tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/ManifestGeneratorTests.cs | New unit tests validating generated manifest structure, merging, and attribute mapping behaviors. |
| int intValue = value switch { int i => i, long l => (int)l, short s => s, byte b => b, _ => 0 }; | ||
| if (intValue != 0) { | ||
| var strValue = m.EnumConverter (intValue); | ||
| if (strValue is not null) { | ||
| element.SetAttributeValue (AndroidNs + m.XmlAttributeName, strValue); | ||
| } | ||
| } |
There was a problem hiding this comment.
Enum-valued properties with a valid underlying value of 0 will never be emitted. ApplyMappings treats intValue == 0 as "unset" and skips conversion, but Android enums like LaunchMode.Multiple ("standard") and ActivityPersistableMode.RootOnly ("persistRootOnly") can legitimately be 0 when explicitly set in the attribute.
Consider removing the intValue != 0 guard and instead always calling EnumConverter (only setting the XML attribute when it returns non-null). Also add missing 0 mappings such as LaunchMode=0 => "standard" and DocumentLaunchMode=0 => "none" to match existing ManifestDocumentElement behavior.
| int intValue = value switch { int i => i, long l => (int)l, short s => s, byte b => b, _ => 0 }; | |
| if (intValue != 0) { | |
| var strValue = m.EnumConverter (intValue); | |
| if (strValue is not null) { | |
| element.SetAttributeValue (AndroidNs + m.XmlAttributeName, strValue); | |
| } | |
| } | |
| int intValue; | |
| switch (value) { | |
| case int i: | |
| intValue = i; | |
| break; | |
| case long l: | |
| intValue = (int)l; | |
| break; | |
| case short s2: | |
| intValue = s2; | |
| break; | |
| case byte b2: | |
| intValue = b2; | |
| break; | |
| default: | |
| continue; | |
| } | |
| var strValue = m.EnumConverter (intValue); | |
| if (strValue is not null) { | |
| element.SetAttributeValue (AndroidNs + m.XmlAttributeName, strValue); | |
| } |
There was a problem hiding this comment.
Fixed — removed intValue != 0 guard in ApplyMappings. Now always calls EnumConverter and only sets the attribute when it returns non-null.
| int intValue = value switch { | ||
| int i => i, | ||
| long l => (int)l, | ||
| short s => s, | ||
| byte b => b, | ||
| _ => 0, | ||
| }; | ||
| if (intValue != 0) { | ||
| var strValue = converter (intValue); | ||
| if (strValue is not null) { | ||
| element.SetAttributeValue (AndroidNs + xmlAttrName, strValue); | ||
| } | ||
| } |
There was a problem hiding this comment.
MapDictionaryEnumProperty skips writing enum values when the underlying integral value is 0. This prevents valid values like ProtectionLevel=normal (0) from being emitted even when explicitly set in [Permission] attributes.
Remove the intValue != 0 check and rely on the converter returning null for unsupported values instead.
| int intValue = value switch { | |
| int i => i, | |
| long l => (int)l, | |
| short s => s, | |
| byte b => b, | |
| _ => 0, | |
| }; | |
| if (intValue != 0) { | |
| var strValue = converter (intValue); | |
| if (strValue is not null) { | |
| element.SetAttributeValue (AndroidNs + xmlAttrName, strValue); | |
| } | |
| } | |
| int? intValue = value switch { | |
| int i => i, | |
| long l => (int)l, | |
| short s => s, | |
| byte b => b, | |
| _ => null, | |
| }; | |
| if (intValue is null) { | |
| return; | |
| } | |
| var strValue = converter (intValue.Value); | |
| if (strValue is not null) { | |
| element.SetAttributeValue (AndroidNs + xmlAttrName, strValue); | |
| } |
There was a problem hiding this comment.
Fixed — same approach in MapDictionaryEnumProperty. Removed intValue != 0 guard.
| IList<string> AddRuntimeProviders (XElement app) | ||
| { | ||
| string packageName = "mono"; | ||
| string className = "MonoRuntimeProvider"; | ||
|
|
||
| if (string.Equals (AndroidRuntime, "nativeaot", StringComparison.OrdinalIgnoreCase)) { | ||
| packageName = "net.dot.jni.nativeaot"; | ||
| className = "NativeAotRuntimeProvider"; | ||
| } | ||
|
|
||
| app.Add (CreateRuntimeProvider ($"{packageName}.{className}", null, --appInitOrder)); | ||
|
|
There was a problem hiding this comment.
AddRuntimeProviders unconditionally adds the base runtime provider to <application>. If the manifest template already contains the runtime provider (common in generated manifests), this will create duplicate <provider> entries with the same android:authorities, which can cause Android install/merge failures.
Before adding, check for an existing provider with matching android:name (or authorities ending in .__mono_init__) and skip/merge accordingly. Consider also handling per-process runtime providers similarly to avoid duplicates when the template already includes them.
There was a problem hiding this comment.
Fixed — now checks for existing provider with matching android:name or .__mono_init__ authorities before adding.
| [InlineData (ComponentKind.Service, "service")] | ||
| [InlineData (ComponentKind.BroadcastReceiver, "receiver")] | ||
| public void Component_BasicProperties (ComponentKind kind, string elementName) | ||
| { |
There was a problem hiding this comment.
This test uses [InlineData] but is missing the [Theory] attribute, so it won't be discovered/executed by xUnit.
Add [Theory] above the InlineData attributes (or convert to separate [Fact] tests) to ensure these cases run.
There was a problem hiding this comment.
Fixed — added [Theory] attribute.
| [InlineData ("", "", "1", "1.0")] | ||
| [InlineData ("42", "2.5", "42", "2.5")] | ||
| public void VersionDefaults (string versionCode, string versionName, string expectedCode, string expectedName) | ||
| { |
There was a problem hiding this comment.
This test uses [InlineData] but is missing the [Theory] attribute, so it won't be discovered/executed by xUnit.
Add [Theory] above the InlineData attributes (or convert to separate [Fact] tests) to ensure these cases run.
There was a problem hiding this comment.
Fixed — added [Theory] attribute.
| [InlineData (true, false, false, "debuggable", "true")] | ||
| [InlineData (false, true, false, "debuggable", "true")] | ||
| [InlineData (false, false, true, "extractNativeLibs", "true")] | ||
| public void ApplicationFlags (bool debug, bool forceDebuggable, bool forceExtractNativeLibs, string attrName, string expected) | ||
| { |
There was a problem hiding this comment.
This test uses [InlineData] but is missing the [Theory] attribute, so it won't be discovered/executed by xUnit.
Add [Theory] above the InlineData attributes (or convert to separate [Fact] tests) to ensure these cases run.
There was a problem hiding this comment.
Fixed — added [Theory] attribute.
Split into focused classes in Microsoft.Android.Sdk.TrimmableTypeMap: - ManifestGenerator: orchestration (load template, call builders, write output) - ManifestConstants: shared AndroidNs and AttName - PropertyMapper: data-driven property mapping (7 arrays, MappingKind enum) - AndroidEnumConverter: 9 enum-to-string converters - ComponentElementBuilder: Activity/Service/Receiver/Provider/Instrumentation XML - AssemblyLevelElementBuilder: permissions, uses-permissions, features, libraries Features: MainLauncher, runtime provider (with dedup), template merging, ManifestPlaceholders, debuggable/extractNativeLibs, XA4213 validation. 30 unit tests (xunit, 130ms). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
b686f08 to
c4c214f
Compare
71ed636 to
2b80737
Compare
Summary
Cecil-free Android manifest generation from component attributes captured by the SRM-based
JavaPeerScanner. Closes #10807.All code lives in the
Microsoft.Android.Sdk.TrimmableTypeMapassembly (netstandard2.0) — no MSBuild dependency.Depends on #10967. Used by #10980.
Features
Component attributes
[Activity],[Service],[BroadcastReceiver],[ContentProvider]with all named propertiesMainLauncher→<intent-filter>with MAIN/LAUNCHER +android:exported="true"[IntentFilter]→<intent-filter>with actions, categories, data elements[MetaData]→<meta-data>with name/value/resource[Application]→ updates<application>element attributes[Instrumentation]→<instrumentation>under<manifest>Assembly-level attributes
[Permission],[PermissionGroup],[PermissionTree]→<permission>etc. with dedup[UsesPermission]→<uses-permission>with maxSdkVersion[UsesFeature]→<uses-feature>(named + GL ES version variants)[UsesLibrary],[UsesConfiguration]→ inside<application>[assembly: Application]→ merged into<application>attributes[assembly: MetaData],[assembly: Property]→ inside<application>Build properties
ManifestPlaceholders—${key}→ value replacementCheckedBuild→android:debuggable+android:extractNativeLibsApplicationJavaClass→<application android:name="...">VersionCodedefaults to"1",VersionNamedefaults to"1.0"(matching legacy)Validation
Runtime provider
MonoRuntimeProvider/NativeAotRuntimeProviderinjectionTests
[Fact]/[Theory]), ~130ms