Skip to content

[TrimmableTypeMap] Build pipeline: targets, stubs, manifest integration, feature switches#11036

Draft
simonrozsival wants to merge 17 commits intomainfrom
dev/simonrozsival/trimmable-typemap-build-pipeline-v2
Draft

[TrimmableTypeMap] Build pipeline: targets, stubs, manifest integration, feature switches#11036
simonrozsival wants to merge 17 commits intomainfrom
dev/simonrozsival/trimmable-typemap-build-pipeline-v2

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

Summary

Build pipeline changes for the trimmable typemap feature. This PR stacks on PRs #11032, #11033, #11034, and #11035 — it includes their changes via a merge commit and adds the build infrastructure on top.

PR 4 commit changes (11 files, +492/-77):

New files

  • GenerateEmptyTypemapStub.cs — MSBuild task generating empty LLVM IR typemap stubs (typemap.{abi}.ll) per ABI. The native toolchain compiles these into the symbols libmonodroid.so expects.
  • ApplicationRegistration.Trimmable.java — Empty registerApplications() for the trimmable path (Application/Instrumentation types are activated via Runtime.registerNatives() + UCO wrappers).
  • Trimmable.CoreCLR.xml — ILLink preserve descriptor ensuring JNIEnvInit.Initialize survives trimming (native entry point from host.cc).

Rewritten targets

  • Trimmable.targets — Major rewrite:
    • New _GenerateTrimmableTypeMap target runs after CoreCompile (uses @(ReferencePath))
    • _GenerateJavaStubs becomes the orchestration target (copies JCW files, manifest, ApplicationRegistration.java, acw-map.txt)
    • _PrepareNativeAssemblySources generates empty typemap LLVM stubs via GenerateEmptyTypemapStub
    • _RemoveRegisterAttribute overridden as no-op (trimmable path needs [Register] at runtime)
    • Removed _CollectPerAssemblyAcwMaps and _MergeAcwMaps (merged acw-map now written directly by the task)
  • Trimmable.CoreCLR.targets — Major rewrite:
    • _AddTrimmableTypeMapToLinker adds TypeMap DLLs to ILLink
    • _ConfigureTrimmableTypeMapForLinker passes --typemap-entry-assembly
    • _AddTrimmableTypeMapAssembliesToStore adds per-ABI TypeMap DLLs to assembly store (tries linked/ first, falls back to untrimmed)

Feature switches

  • MonoVM.targets / NativeAOT.targets — Add Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap=false feature switch (trimmable path is CoreCLR-only for now)

Modified tasks

  • GenerateTrimmableTypeMap.cs — Added 14 manifest/config properties, ManifestConfig construction, merged acw-map.txt output, AdditionalProviderSources output
  • TrimmableTypeMapGenerator.cs — Added manifest generation, assembly manifest scanning, acw-map writing
  • TrimmableTypeMapTypes.cs — Added ManifestConfig record, AdditionalProviderSources to result
  • GenerateNativeApplicationConfigSources.cs — Gracefully handle trimmed JNIEnvInit tokens when TargetsCLR (use token 0 for missing tokens)

Dependencies

Depends on (merge first):

simonrozsival and others added 12 commits March 27, 2026 10:17
…or $ to .

JniNameToJavaName was only replacing '/' with '.' but JNI also uses '$'
for inner/nested classes. Java source uses '.' for both package and inner
class separators.

Before: android/view/View$OnClickListener → android.view.View$OnClickListener
After:  android/view/View$OnClickListener → android.view.View.OnClickListener

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n from Connector

NativeCallbackName was previously set to a simple "n_{managedName}" which
produced incorrect names like "n_OnCreate" instead of the correct
"n_OnCreate_Landroid_os_Bundle_" that the JNI native method expects.

The new GetNativeCallbackName() derives the correct name from the
[Register] Connector field (e.g. "GetOnCreate_Landroid_os_Bundle_Handler"
→ "n_OnCreate_Landroid_os_Bundle_").

Similarly, AddMarshalMethod was not populating DeclaringTypeName and
DeclaringAssemblyName for interface implementation methods. The new
ParseConnectorDeclaringType() extracts these from the Connector's
type qualifier (after the colon).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extract the core generation pipeline (scan → typemaps → JCW → acw-map) from
GenerateTrimmableTypeMap into a standalone TrimmableTypeMapGenerator class that
takes Action<string> for logging, keeping the TrimmableTypeMap project free of
Microsoft.Build.* dependencies. The MSBuild task becomes a thin adapter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The per-assembly acw-map files are consumed by existing targets. Keep
GeneratePerAssemblyAcwMaps in the task (it uses MSBuild types like
MemoryStreamPool and Files) rather than moving the merged acw-map.txt
logic to the generator, which is PR 4 work.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Restore GetJavaInteropAssemblyPaths (MonoAndroidHelper.IsMonoAndroidAssembly)
  filtering to match main's behavior — removing the filter is a build pipeline
  change that belongs in a later PR.
- Add ArgumentNullException checks to TrimmableTypeMapGenerator.Execute().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d.Tests

Move incremental build, parsing, and empty-input tests to
TrimmableTypeMapGeneratorTests (xUnit, uses TestFixtures.dll).
Keep MSBuild-specific tests (error handling, Mono.Android integration)
in GenerateTrimmableTypeMapTests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The scanner needs all assemblies — not just those with HasMonoAndroidReference.
Framework JCW pre-compilation is tracked by #10792; until then we generate
JCWs for everything.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This is MSBuild input parsing, not core generator logic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add scanning for assembly-level attributes ([Application], [Activity],
[Service], [BroadcastReceiver], [ContentProvider], permissions, etc.)
and wire them into the manifest generator via Generate(XDocument?) overload.

Replace ManifestModel.cs with individual Scanner/ record types for
better composability.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… types

Add assembly-level manifest attribute scanning to the TrimmableTypeMap scanner:

- AssemblyIndex.ScanAssemblyAttributes(): parses 9 assembly-level attribute types
  (UsesPermission, UsesLibrary, UsesFeature, UsesConfiguration, Permission,
  PermissionGroup, PermissionTree, MetaData, Property)
- JavaPeerScanner.ScanAssemblyManifestInfo(): aggregates attributes across assemblies
- JavaPeerScanner.ToComponentInfo()/HasPublicParameterlessCtor(): attaches component
  info to scanned peers
- ManifestGenerator.Generate(XDocument?): new overload accepting pre-loaded template
- ManifestGenerator.CreateDefaultManifest(): extracted from LoadOrCreateManifest
- Replace ManifestModel.cs (105-line flat class) with 12 focused record types
- Update JavaPeerInfo.ComponentAttribute doc comment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r-class', 'origin/dev/simonrozsival/fix-native-callback-name', 'origin/dev/simonrozsival/extract-trimmable-typemap-generator' and 'origin/dev/simonrozsival/scanner-manifest-attributes' into dev/simonrozsival/trimmable-typemap-build-pipeline-v2
- Update TrimmableTypeMapGenerator.Execute() with manifest generation,
  assembly manifest scanning, acw-map writing, and new optional parameters
- Add ManifestConfig record to TrimmableTypeMapTypes.cs
- Update TrimmableTypeMapResult with AdditionalProviderSources
- Update GenerateTrimmableTypeMap MSBuild task with manifest/config properties
- Create GenerateEmptyTypemapStub task for LLVM IR native typemap stubs
- Create ApplicationRegistration.Trimmable.java (empty registerApplications)
- Create Trimmable.CoreCLR.xml preserve list for JNIEnvInit.Initialize
- Rewrite Trimmable.targets with full build pipeline (separate generation
  and _GenerateJavaStubs targets, native stub generation, manifest handling)
- Rewrite Trimmable.CoreCLR.targets with ILLink integration and per-ABI
  assembly store support
- Add TrimmableTypeMap=false feature switch to MonoVM and NativeAOT targets
- Handle trimmed JNIEnvInit tokens in GenerateNativeApplicationConfigSources

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR wires the TrimmableTypeMap feature into the .NET for Android build pipeline, adding CoreCLR linker/store integration, generating required Java/native stubs, and expanding the typemap generator to emit manifest + merged acw-map outputs.

Changes:

  • Reworks trimmable typemap MSBuild targets to generate typemap assemblies/JCWs post-compile, integrate with ILLink, and populate assembly-store inputs.
  • Adds new build-time stubs and preserve config (LLVM IR typemap stubs, empty ApplicationRegistration for trimmable path, ILLink root descriptor).
  • Extends the TrimmableTypeMap generator/scanner to support manifest generation inputs and updated JNI/override naming behavior, with corresponding test updates.

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/OverrideDetectionTests.cs Updates expected native callback name derivation from Connector.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs Adjusts expectations for generated wrapper members.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs Aligns proxy type wrapper expectations with generator output.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs Adds direct unit tests for the core generator pipeline and incremental behavior.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/JcwJavaSourceGeneratorTests.cs Updates JNI name conversion and native method naming assertions.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateTrimmableTypeMapTests.cs Refactors MSBuild-task tests to match new outputs/behavior.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs Converts MSBuild task into adapter delegating to TrimmableTypeMapGenerator, adds manifest/acw-map integration inputs/outputs.
src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs Allows CoreCLR trimmable path to proceed when JNIEnvInit tokens are trimmed (token=0 fallback).
src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs New task generating per-ABI empty LLVM IR typemap stubs.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets Major build pipeline rewrite for trimmable typemap generation, stub copying, and native-stub prep.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.CoreCLR.targets Adds ILLink and assembly-store integration for typemap assemblies under CoreCLR.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets Disables TrimmableTypeMap runtime feature for NativeAOT.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.MonoVM.targets Disables TrimmableTypeMap runtime feature for MonoVM.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/ApplicationRegistration.Trimmable.java Adds trimmable-path ApplicationRegistration stub with empty registerApplications().
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/PreserveLists/Trimmable.CoreCLR.xml Adds ILLink root descriptor to preserve JNIEnvInit.Initialize.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapTypes.cs Introduces result/config records for manifest + provider sources.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs Implements end-to-end generator (scan → typemap → JCW → manifest/acw-map outputs).
src/Microsoft.Android.Sdk.TrimmableTypeMap/NullableExtensions.cs Adds NRT-friendly string helpers for netstandard2.0.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs Adds Generate overload accepting pre-loaded manifest template and refactors default creation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestModel.cs Removes old manifest model types (replaced by scanner records).
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs Fixes nested-class JNI name conversion for Java source output.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs Adds manifest scanning, component attribute capture, and correct native callback/declaring-type derivation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs Updates component attribute documentation and usage.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs Expands attribute decoding to capture component properties, intent-filters, metadata, and assembly-level manifest info.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyManifestInfo.cs Adds aggregated assembly-level manifest data model.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ComponentInfo.cs Adds component kind + data model for manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/IntentFilterInfo.cs Adds intent-filter data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/MetaDataInfo.cs Adds metadata data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/PermissionInfo.cs Adds permission data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/PermissionGroupInfo.cs Adds permission-group data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/PermissionTreeInfo.cs Adds permission-tree data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/UsesPermissionInfo.cs Adds uses-permission data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/UsesFeatureInfo.cs Adds uses-feature data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/UsesLibraryInfo.cs Adds uses-library data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/UsesConfigurationInfo.cs Adds uses-configuration data model used by manifest generation.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/PropertyInfo.cs Adds manifest property data model used by manifest generation.

Comment on lines +122 to +127
} else if (attrName == "IntentFilterAttribute") {
attrInfo ??= new TypeAttributeInfo ("IntentFilterAttribute");
attrInfo.IntentFilters.Add (ParseIntentFilterAttribute (ca));
} else if (attrName == "MetaDataAttribute") {
attrInfo ??= new TypeAttributeInfo ("MetaDataAttribute");
attrInfo.MetaData.Add (ParseMetaDataAttribute (ca));
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntentFilterAttribute / MetaDataAttribute can initialize attrInfo with a TypeAttributeInfo whose AttributeName is not a component attribute. If those attributes appear before [Activity]/[Service]/…, later component parsing won’t create the correct TypeAttributeInfo subtype (e.g., ApplicationAttributeInfo), and ToComponentInfo() won’t recognize the component kind, so manifest generation may silently drop component data/intent-filters/metadata. Consider keeping component-kind separate from intent-filter/metadata collection (e.g., always create the component TypeAttributeInfo via CreateTypeAttributeInfo() when a component attribute is encountered, and store intent-filters/metadata on a separate structure or on a type-info object that doesn’t overwrite the component attribute identity).

Copilot uses AI. Check for mistakes.
<Target Name="_PrepareNativeAssemblySources"
Condition=" '$(_AndroidRuntime)' != 'NativeAOT' "
Inputs="@(_BuildTargetAbis)"
Outputs="@(_BuildTargetAbis->'$(_NativeAssemblySourceDir)typemaps.%(Identity).ll')">
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_PrepareNativeAssemblySources declares Outputs as typemaps.%(Identity).ll, but GenerateEmptyTypemapStub writes files named typemap.{abi}.ll. This output mismatch breaks MSBuild incremental up-to-date checks (the target will look for non-existent outputs and rerun every build). Update the Outputs pattern (and/or the generated filename) so they match exactly.

Suggested change
Outputs="@(_BuildTargetAbis->'$(_NativeAssemblySourceDir)typemaps.%(Identity).ll')">
Outputs="@(_BuildTargetAbis->'$(_NativeAssemblySourceDir)typemap.%(Identity).ll')">

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +144
var generator = CreateGenerator ();
Assert.Throws<ArgumentNullException> (() => generator.Execute (
null!, Path.Combine (testDir, "out"), Path.Combine (testDir, "java"),
new Version (11, 0), new HashSet<string> ()));
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses the null-forgiving operator (null!) to pass a null argument. The repo guidance is to avoid ! even in tests; prefer a nullable local (e.g., IReadOnlyList<string>? assemblyPaths = null) and pass that, or otherwise structure the test so it doesn’t require !.

Copilot generated this review using guidance from repository custom instructions.
@simonrozsival simonrozsival marked this pull request as draft March 27, 2026 11:44
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Mar 27, 2026
simonrozsival and others added 5 commits March 27, 2026 13:04
…e enable

- Fix ParseAttributes: collect IntentFilter/MetaData in local lists and attach
  after the loop to avoid creating attrInfo with wrong AttributeName when
  [IntentFilter] appears before [Activity]
- Remove redundant #nullable enable from 13 files (project has <Nullable>enable)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…into dev/simonrozsival/trimmable-typemap-build-pipeline-v2
…/simonrozsival/trimmable-typemap-build-pipeline-v2
- Remove redundant #nullable enable from 6 Generator files and task file
- Convert string.IsNullOrEmpty to IsNullOrEmpty extension in task

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants