dotnet / dotnet/android

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

Open
#11,487 0 comments 1 reaction 2 assignees Claimed by @simonrozsival View on GitHub
copilot trimmable-type-map
Dominant language
C#
Stars
2.1k
Forks
579
Avg merge
1d 19h
Merged PRs (30d)
252

Description

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 `` 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 ProviderNames)`.
- `CreateDefaultManifest()`, `RewriteCompatNames(...)`, `EnsureManifestAttributes(...)`, `EnsureApplicationElement(...)`, `AddRuntimeProviders(...)`, `CreateRuntimeProvider(...)`, and `ApplyPlaceholders(...)` are DOM-based.

### Element builders/helpers

- `src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ComponentElementBuilder.cs`
- Creates ``, ``, ``, ``, ``, ``, ``, and `` 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 ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, 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:

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 `XElement`s.
- 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.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.