Skip to content

[TrimmableTypeMap] Replace XDocument/XElement manifest generation with lower-allocation APIs #11487

@simonrozsival

Description

@simonrozsival

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 XmlReaderXmlWriter 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

Metadata

Metadata

Labels

copilot`copilot-cli` or other AIs were used to author thistrimmable-type-map

Type

No fields configured for Task.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions