Skip to content

[TrimmableTypeMap] Implement pre-trimming build task #10789

Description

@simonrozsival

Part of #10788
Details outlined in #10757

Implementation Plan

Implement a build-time code generation step that runs before the trimmer and produces:

  1. Per-assembly typemap assemblies — one per input assembly containing Java peer types, plus a root `_Microsoft.Android.TypeMaps.dll` that ties them together via `TypeMapAssemblyTargetAttribute`
  2. JCW Java source files (`.java`) with `registerNatives` in `static {}` blocks

Uses System.Reflection.Metadata (SRM) for both reading and emitting — no Mono.Cecil.

Key design decisions

Decision Choice
Assembly reading/writing SRM only, no Cecil
Marshal method registration `RegisterNatives` (no LLVM IR)
Access to protected members `IgnoresAccessChecksTo`
Incremental builds Per-assembly typemap assemblies via `TypeMapAssemblyTargetAttribute`
TypeMap entries `TypeMapAttribute` 2-arg (unconditional) and 3-arg (trimmable)
Proxy pattern Self-application: `[Proxy] class Proxy : JavaPeerProxyAttribute`
JCW Java stubs `registerNatives` in `static {}`, actual JNI package names (no CRC hash)
Task assembly Separate `Microsoft.Android.Build.TypeMap` project (fast inner dev loop)

Architecture: Separate task assembly

New project `src/Microsoft.Android.Build.TypeMap/` (`netstandard2.0`) keeps the scanner, generators, and MSBuild task in a dedicated assembly separate from `Xamarin.Android.Build.Tasks`. This enables a fast inner dev loop (~5s build+test) without building the full solution.

Incremental builds: Per-assembly typemap generation

Mono.Android.dll  ──→  _Mono.Android.TypeMap.dll     (only rebuilt when Mono.Android changes)
AndroidX.Core.dll ──→  _AndroidX.Core.TypeMap.dll    (only rebuilt when NuGet updates)
MyApp.dll         ──→  _MyApp.TypeMap.dll             (rebuilt on every app code change)
                  ──→  _Microsoft.Android.TypeMaps.dll (root: TypeMapAssemblyTarget refs + aliases)

Typical incremental build cost: <0.5s (only re-scan the app assembly + regenerate root).

Testing strategy

  • Side-by-side legacy comparison: Run legacy `XAJavaTypeScanner` + `TypeMapCecilAdapter` (Cecil) and new `JavaPeerScanner` (SRM) on the same assemblies, compare results
  • Hand-crafted test assembly (~20 types) for TDD inner loop (<1s)
  • Real Mono.Android.dll (~8000 types) smoke test (~5s)
  • Integration tests in existing `Xamarin.Android.Build.Tests` project

References

Activity

  1. added this to the .NET 11 milestone on Feb 10, 2026
  2. simonrozsival commented on Feb 12, 2026

    @simonrozsival
    MemberAuthor

    Follow-up: _GenerateJavaStubs mega-target decomposition

    Filed #10807 to track decomposing the legacy _GenerateJavaStubs mega-target. Key finding from the scanner work:

    The trimmable path (#10779) overrides _GenerateJavaStubs to a no-op, which inadvertently skips shared tasks that are NOT typemap-specific:

    • GenerateMainAndroidManifest — every Android app needs a merged manifest
    • GenerateAdditionalProviderSources — runtime provider .java files
    • GenerateACWMap → acw-map.txt — consumed by _ConvertCustomView for layout XML fixup

    These need to be extracted from the LLVM IR mega-target so both legacy and trimmable paths can use them. The scanner (JavaPeerScanner) already computes CRC64 JNI names, so generating acw-map.txt from JavaPeerInfo is trivial — no need to reuse the Cecil-based task.

    See #10807 for the full analysis, proposed targets structure, and definition of done.

  3. locked and limited conversation to collaborators on May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Area: CoreCLRIssues that only occur when using CoreCLR.Area: NativeAOTIssues that only occur when using NativeAOT.needs-triageIssues that need to be assigned.trimmable-type-map

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions