Part of #10958.
The trimmable typemap manifest path currently uses System.Xml.Linq (XDocument, XElement, XAttribute, XName, XNamespace) for both reading the manifest template and building/writing the generated manifest. This was called out in #10958 as a possible build-time/allocation optimization. This issue tracks the concrete locations and a proposed migration path; it does not propose changing generated manifest behavior.
Current LINQ-to-XML usage on main
Task entry point
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
- Loads the optional manifest template with
XDocument.Load(ManifestTemplate).
- Passes the loaded document into
TrimmableTypeMapGenerator.Execute(...).
- Writes
result.Manifest.Document.Save(ms) before Files.CopyIfStreamChanged(...).
Pipeline/rooting orchestration
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
- Public pipeline accepts
XDocument? manifestTemplate.
RootManifestReferencedTypes(...) walks doc.Root.Descendants() to find manifest component names and mark matching peers as unconditional/deferred-registration peers.
PrepareManifestForRooting(...) clones the template via new XDocument(manifestTemplate) or creates a synthetic <manifest> document, applies placeholders, ensures package/application values needed for rooting, then returns another XDocument.
GenerateManifest(...) delegates final manifest construction to ManifestGenerator and returns GeneratedManifest.
Manifest document construction/merge
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs
- Owns the main DOM merge flow: template/default document, manifest/application elements, compat-name rewrite, existing component detection, adding generated components, assembly-level elements, runtime providers, debuggable/extractNativeLibs/internet permission, and placeholder replacement.
Generate(...) returns (XDocument Document, IList<string> ProviderNames).
CreateDefaultManifest(), RewriteCompatNames(...), EnsureManifestAttributes(...), EnsureApplicationElement(...), AddRuntimeProviders(...), CreateRuntimeProvider(...), and ApplyPlaceholders(...) are DOM-based.
Element builders/helpers
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ComponentElementBuilder.cs
- Creates
<activity>, <service>, <receiver>, <provider>, <intent-filter>, <data>, <meta-data>, and <instrumentation> elements with XElement/XAttribute.
- Also mutates the existing application/component elements for launcher filters and application attributes.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/AssemblyLevelElementBuilder.cs
- Adds assembly-level
<permission>, <permission-group>, <permission-tree>, <uses-permission>, <uses-feature>, <uses-library>, <meta-data>, <property>, <uses-configuration>, <supports-gl-texture>, and internet permission elements.
- Uses DOM queries for duplicate detection before appending.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PropertyMapper.cs
- Maps captured component/application properties onto an
XElement by setting Android namespace attributes.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestConstants.cs
- Exposes
XNamespace/XName constants for the helpers above.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapTypes.cs
GeneratedManifest currently stores an XDocument.
Tests under tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests also use XDocument.Parse(...) for assertions/fixtures. Those can either remain test-only or move to a lighter assertion helper after production code is migrated.
Suggested replacement strategy
A direct XmlReader → XmlWriter rewrite is not enough on its own because the current implementation has read-modify-write semantics:
- resolve relative/bare manifest component names against the manifest package;
- rewrite compat component names to generated CRC names;
- detect duplicates before adding generated components and assembly-level elements;
- preserve existing template elements/attributes where appropriate;
- apply placeholders across all attribute values;
- add runtime providers for each distinct process discovered in application children;
- root manifest-referenced peers before final manifest generation.
Suggested approach:
- Introduce a small trimmable-specific manifest model instead of carrying
XDocument through the pipeline.
- Example shape: manifest attributes, application attributes, ordered top-level nodes, ordered application child nodes, and lightweight indexes for component names, provider authorities/processes, permissions, features, metadata, etc.
- Keep enough information to preserve existing template content and ordering where current behavior depends on it.
- Parse the template with
XmlReader into that lightweight model.
- Apply placeholders while reading attribute values.
- Resolve package/application defaults needed for rooting without cloning a DOM.
- Build the duplicate-detection indexes during the read.
- Split manifest rooting from final manifest writing.
- Rooting only needs the resolved component names and the subset of application/instrumentation names that require deferred registration; it should consume the lightweight indexes rather than walking
XDocument.
- Convert the current builders to write model nodes rather than
XElements.
- Replace
ComponentElementBuilder, AssemblyLevelElementBuilder, and PropertyMapper with helpers that populate the lightweight model or write attributes through a small abstraction.
- Keep the existing duplicate-detection semantics explicit in the model indexes.
- Emit the final manifest with
XmlWriter directly to the output stream used by Files.CopyIfStreamChanged(...).
- Change
GeneratedManifest to carry either a writer callback, serialized stream/string, or a model object instead of XDocument.
- Avoid building a full XML DOM just to immediately save it.
Acceptance criteria
- Production trimmable typemap manifest generation no longer depends on
System.Xml.Linq.
- Generated manifest output remains behavior-compatible with the current tests, including template preservation, placeholder replacement, compat-name rewriting, duplicate checks, runtime provider generation, assembly-level manifest attributes, and manifest-rooted peer handling.
- The task still writes through
Files.CopyIfStreamChanged(...) so timestamp-preserving output behavior is retained.
- Add or update focused tests for the new model/reader/writer path; test code may continue using
XDocument only as an assertion convenience if desired.
Part of #10958.
The trimmable typemap manifest path currently uses
System.Xml.Linq(XDocument,XElement,XAttribute,XName,XNamespace) for both reading the manifest template and building/writing the generated manifest. This was called out in #10958 as a possible build-time/allocation optimization. This issue tracks the concrete locations and a proposed migration path; it does not propose changing generated manifest behavior.Current LINQ-to-XML usage on
mainTask entry point
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.csXDocument.Load(ManifestTemplate).TrimmableTypeMapGenerator.Execute(...).result.Manifest.Document.Save(ms)beforeFiles.CopyIfStreamChanged(...).Pipeline/rooting orchestration
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.csXDocument? manifestTemplate.RootManifestReferencedTypes(...)walksdoc.Root.Descendants()to find manifest component names and mark matching peers as unconditional/deferred-registration peers.PrepareManifestForRooting(...)clones the template vianew XDocument(manifestTemplate)or creates a synthetic<manifest>document, applies placeholders, ensures package/application values needed for rooting, then returns anotherXDocument.GenerateManifest(...)delegates final manifest construction toManifestGeneratorand returnsGeneratedManifest.Manifest document construction/merge
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.csGenerate(...)returns(XDocument Document, IList<string> ProviderNames).CreateDefaultManifest(),RewriteCompatNames(...),EnsureManifestAttributes(...),EnsureApplicationElement(...),AddRuntimeProviders(...),CreateRuntimeProvider(...), andApplyPlaceholders(...)are DOM-based.Element builders/helpers
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ComponentElementBuilder.cs<activity>,<service>,<receiver>,<provider>,<intent-filter>,<data>,<meta-data>, and<instrumentation>elements withXElement/XAttribute.src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/AssemblyLevelElementBuilder.cs<permission>,<permission-group>,<permission-tree>,<uses-permission>,<uses-feature>,<uses-library>,<meta-data>,<property>,<uses-configuration>,<supports-gl-texture>, and internet permission elements.src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PropertyMapper.csXElementby setting Android namespace attributes.src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestConstants.csXNamespace/XNameconstants for the helpers above.src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapTypes.csGeneratedManifestcurrently stores anXDocument.Tests under
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Testsalso useXDocument.Parse(...)for assertions/fixtures. Those can either remain test-only or move to a lighter assertion helper after production code is migrated.Suggested replacement strategy
A direct
XmlReader→XmlWriterrewrite is not enough on its own because the current implementation has read-modify-write semantics:Suggested approach:
XDocumentthrough the pipeline.XmlReaderinto that lightweight model.XDocument.XElements.ComponentElementBuilder,AssemblyLevelElementBuilder, andPropertyMapperwith helpers that populate the lightweight model or write attributes through a small abstraction.XmlWriterdirectly to the output stream used byFiles.CopyIfStreamChanged(...).GeneratedManifestto carry either a writer callback, serialized stream/string, or a model object instead ofXDocument.Acceptance criteria
System.Xml.Linq.Files.CopyIfStreamChanged(...)so timestamp-preserving output behavior is retained.XDocumentonly as an assertion convenience if desired.