Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

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

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions