Skip to content

[TrimmableTypeMap] Build pipeline: targets, scanner, task, assembly store#10980

Open
simonrozsival wants to merge 25 commits intomainfrom
dev/simonrozsival/trimmable-typemap-build-pipeline
Open

[TrimmableTypeMap] Build pipeline: targets, scanner, task, assembly store#10980
simonrozsival wants to merge 25 commits intomainfrom
dev/simonrozsival/trimmable-typemap-build-pipeline

Conversation

@simonrozsival
Copy link
Member

@simonrozsival simonrozsival commented Mar 20, 2026

Summary

Build pipeline for the trimmable typemap. Wires up manifest generation, assembly store,
and JCW generation. HelloWorld app runs end-to-end on device with button Click events.

Closes #10807.
Closes #10800.

Depends on #10991 (ManifestGenerator), #10967 (runtime).

Scanner extensions (AssemblyIndex.cs, JavaPeerInfo.cs, JavaPeerScanner.cs)

  • ComponentInfo, IntentFilterInfo, MetaDataInfo data model for type-level component attributes
  • AssemblyManifestInfo with ScanAssemblyAttributes() for assembly-level attrs
  • HasPublicParameterlessCtor via SRM method signature inspection (XA4213)
  • ScanAssemblyManifestInfo() returns assembly-level data alongside peer list

GenerateTrimmableTypeMap task

JCW generation fixes

  • JniNameToJavaName: converts $. for inner class references in Java source (javac requires ., not $)
  • GetNativeCallbackName: splits [Register] Connector string on : before checking Get…Handler pattern
  • ParseConnectorDeclaringType: extracts the Invoker type from the Connector so UCO wrappers resolve callbacks on the correct type

Trimmer size regression prevention

  • Set TrimmableTypeMap=false via RuntimeHostConfigurationOption in MonoVM.targets and NativeAOT.targets so ILLink can eliminate trimmable typemap code paths
  • Add ILLink substitutions for IsMonoRuntime and IsCoreClrRuntime
  • TrimmableTypeMap uses private ctor + Initialize() + singleton pattern — no type references leak outside feature guards
  • TrimmableTypeMapTypeManager uses TrimmableTypeMap.Instance singleton (no constructor parameter)
  • _RemoveRegisterAttribute: empty target — [Register] attributes needed at runtime for TryGetJniNameForType

MSBuild targets

Trimmable.targets

  • _GenerateTrimmableTypeMap: AfterTargets="CoreCompile" with Inputs/Outputs for incremental builds
  • _GenerateJavaStubs: assembly store wiring, TypeMap DLLs to _ResolvedAssemblies
  • Path normalization for macOS (Replace('\','/'))

Trimmable.CoreCLR.targets

  • ILLink integration: ManagedAssemblyToLink, --typemap-entry-assembly
  • _AddTrimmableTypeMapAssembliesToStore: per-ABI batched, linked/ to typemap/ fallback

Tests

  • 296/296 unit tests pass
  • HelloWorld verified on emulator: Activity, layout XML, FindViewById<Button>, Click events
  • PublishTrimmed=true (ILLink inner build) verified: builds and runs successfully
  • BuildReleaseArm64SimpleDotNet.MonoVM apkdesc baseline updated

Other

File Purpose
GenerateEmptyTypemapStub.cs LLVM IR stub symbols (uses Files.CopyIfStringChanged)
GenerateNativeApplicationConfigSources.cs Token=0 fallback gated behind TargetsCLR
Trimmable.CoreCLR.xml Preserves JNIEnvInit.Initialize
TypeMapAssemblyEmitter.cs Self-application attribute on proxy types
HelloWorld sample _AndroidTypeMapImplementation=trimmable

Part of #10807 (build pipeline, targets, manifest integration, native stubs)

@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes [WIP][TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes Mar 20, 2026
@simonrozsival simonrozsival changed the title [WIP][TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes [TrimmableTypeMap] Build pipeline + manifest generation Mar 21, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from e2214d6 to 483dc03 Compare March 21, 2026 06:54
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Build pipeline + manifest generation [TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes Mar 21, 2026
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Mar 21, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch 2 times, most recently from 12a1c2b to 4d55775 Compare March 21, 2026 09:12
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-runtime-pr branch from 76a490e to aad2ddb Compare March 21, 2026 09:14
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Build pipeline: targets, scanner, generator fixes [TrimmableTypeMap] Build pipeline + manifest generation Mar 21, 2026
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch 4 times, most recently from af78ed0 to 5a4a1f3 Compare March 21, 2026 21:31
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from 5a4a1f3 to 2bdc637 Compare March 21, 2026 22:11
@simonrozsival simonrozsival changed the base branch from dev/simonrozsival/trimmable-typemap-runtime-pr to dev/simonrozsival/trimmable-typemap-manifest-generation March 21, 2026 22:14
@simonrozsival simonrozsival changed the title [TrimmableTypeMap] Build pipeline + manifest generation [TrimmableTypeMap] Build pipeline: targets, scanner, task, assembly store Mar 21, 2026
@simonrozsival simonrozsival marked this pull request as ready for review March 21, 2026 22:38
Copilot AI review requested due to automatic review settings March 21, 2026 22:38
simonrozsival and others added 5 commits March 25, 2026 13:29
Skip _RemoveRegisterAttribute when PublishTrimmed=true because ILLink
handles assembly processing in the inner build. The original TypeMap
DLLs may no longer exist at their original paths after ILLink consumes
them, causing MSB3030 'file not found' errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The trimmable path needs [Register] attributes at runtime for type
resolution (TryGetJniNameForType uses IJniNameProviderAttribute).
The legacy target strips them to reduce size, but stripping breaks
the trimmable runtime. _ShrunkAssemblies is already set to
_ResolvedAssemblies by AssemblyResolution.targets, so no copy needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Revert MainActivity.cs to original. Keep only _AndroidTypeMapImplementation
and UseMonoRuntime properties in the .csproj.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix NuGetPackageId filter: only use FrameworkReferenceName to detect
  framework assemblies. User NuGet libraries need JCW generation.
- Fix GenerateEmptyTypemapStub doc: output is LLVM IR, not C files.
- Use Files.CopyIfStringChanged for stub generation (incremental builds).
- Gate GetRequiredTokens token=0 fallback behind TargetsCLR (trimmable
  path only). Non-CLR builds throw if tokens are missing.
- Only create acw-map.txt if it doesn't exist (avoid touching every build).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- acw-map.txt: restore AlwaysCreate=true (downstream targets use it as
  Input). Add comment explaining it's an empty placeholder for the
  trimmable path.
- Fix misleading JCW filter comment: framework types don't have
  pre-generated compatible JCWs yet. The filter is about framework
  binding types being in java_runtime.dex. Reference #10792 for
  future pre-generation work.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Assert.Contains (".ctor", methodNames);
Assert.Contains ("CreateInstance", methodNames);
Assert.Contains ("get_TargetType", methodNames);
// get_TargetType is inherited from JavaPeerProxy<T>, not emitted on the proxy type
Copy link
Member Author

Choose a reason for hiding this comment

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

Remove this line instead of just commenting it out

Assert.Contains (".ctor", methods);
Assert.Contains ("CreateInstance", methods);
Assert.Contains ("get_TargetType", methods);
// get_TargetType is inherited from JavaPeerProxy<T>, not emitted on the proxy type
Copy link
Member Author

Choose a reason for hiding this comment

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

Remove this line instead of just commenting it out

File="$(IntermediateOutputPath)android\src\net\dot\android\ApplicationRegistration.java"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
Lines="package net.dot.android%3b;public class ApplicationRegistration { public static android.content.Context Context%3b public static void registerApplications () { } }" />
Copy link
Member Author

Choose a reason for hiding this comment

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

Why isn't this a ApplicationRegistration.java file we'd just copy over to the generated files? Why do we inline the Java code here? I really don't like it.

simonrozsival and others added 9 commits March 25, 2026 17:16
…t providers

- Fix Categories parsing: remove TryGetNamedArgument<string> guard that
  always returns false for string[] properties. Directly iterate named
  arguments with IReadOnlyCollection<> cast.
- Delete empty ManifestModel.cs placeholder (all types in JavaPeerInfo.cs).
- Document ApplicationRegistration.java: registerApplications() is
  intentionally empty in the trimmable path (types activated via
  registerNatives + UCO wrappers, not Runtime.register).
- Add TODO for multi-process per-provider .java generation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Enables ILLink to trim TrimmableTypeMap code paths when ILLink runs
(e.g. with PublishTrimmed=true or AOT). Without this, the feature switch
is unknown to the trimmer and both branches are preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
get_TargetType is no longer emitted on proxy types — it's inherited
from the generic base class JavaPeerProxy<T>. Update test assertions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use $(IntermediateOutputPath) instead of manually constructing the path
from $(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/.
The manual construction breaks when AppendTargetFrameworkToOutputPath
is false, as in the test infrastructure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. Add $(_AndroidManifestAbs) to _GenerateTrimmableTypeMap Inputs so
   manifest changes trigger re-generation.
2. Use @(ReferencePath->Count()) instead of '@(ReferencePath)' != ''
   to avoid string-joining hundreds of assembly paths.
3. Add XA4212 and XA4217 error codes for duplicate/conflicting
   [Application] attributes (replacing bare Log.LogError calls).
4. Add copied Java files, ApplicationRegistration.java, AndroidManifest,
   and acw-map.txt to @(FileWrites) so IncrementalClean won't delete them.
5. Add Inputs/Outputs to _PrepareNativeAssemblySources so MSBuild can
   skip it on incremental builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pass AcwMapOutputFile to GenerateTrimmableTypeMap so AcwMapWriter
produces real managed→Java mappings for _ConvertCustomView and layout
fixup. The <Touch> in _GenerateJavaStubs now only creates an empty
placeholder when _GenerateTrimmableTypeMap was skipped. The generated
acw-map.txt is tracked in @(FileWrites) from both targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move business logic into a core class in the TrimmableTypeMap project.
The MSBuild task becomes a thin adapter. Tests instantiate the generator
directly — no MSBuild ceremony needed for unit testing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from 6f42b4d to 384a5a8 Compare March 25, 2026 16:16
- Remove commented-out lines in TypeMapAssemblyGeneratorTests and TypeMapModelBuilderTests
- Replace WriteLinesToFile inline Java with a static ApplicationRegistration.Trimmable.java file
- Remove UseMonoRuntime from HelloWorld.DotNet.csproj (not needed)
- Move MSBuildPackageReferenceVersion to eng/Versions.props (centralize with other package versions)
- Fix TrimmableTypeMap feature switch in NativeAOT.targets: Value=true (NativeAOT uses trimmable typemap)

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

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

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

🤖 AI Review Summary

Verdict: ⚠️ Needs Changes

Found 5 issues: 2 errors, 2 warnings, 1 suggestion.

  • Localization — Error messages hardcoded as strings instead of Properties.Resources.XA#### (TrimmableTypeMapGenerator.cs:247,261,263)
  • Platform-specific — Backslash path separators in cross-platform target will break the Mac/Linux build (Microsoft.Android.Sdk.TypeMap.Trimmable.targets:103,105,108)
  • ⚠️ Code organization — ~13 new public types added to JavaPeerInfo.cs; one type per file (JavaPeerInfo.cs:322+)
  • ⚠️ Nullablestring.IsNullOrEmpty(x) should use the .IsNullOrEmpty() extension (multiple files)
  • 💡 PatternsTrimmableTypeMapResult record exposes List<string> in public API; prefer IReadOnlyList<string>

👍 Great use of sealed record types for data carriers, proper #nullable enable, thorough XML docs, and clean Inputs/Outputs for incremental builds. The GetNativeCallbackName/ParseConnectorDeclaringType refactoring is well-reasoned.

⚠️ CI: Linux build passed ✅, but Mac and Windows builds are still pending — do not merge until they are green.


Review generated by android-reviewer from review guidelines.

- Use Properties.Resources.XA#### for error messages (XA4212, XA4213, XA4217)
  - Add Properties/Resources.resx + Resources.Designer.cs to TrimmableTypeMap project
  - Remove XA4212/XA4217 from Xamarin.Android.Build.Tasks resources (they are
    only used in TrimmableTypeMapGenerator, not in any Build.Tasks code path)

- Fix cross-platform path separator bugs in Trimmable.targets
  - Replace \**\*.java glob with /**/*.java
  - Replace android\src\ destination paths with android/src/
  - Replace ..\ TrimmerRootDescriptor path with ../

- Split 13 new types out of JavaPeerInfo.cs (one type per file)
  - ComponentInfo.cs  (ComponentKind + ComponentInfo)
  - IntentFilterInfo.cs
  - MetaDataInfo.cs
  - AssemblyManifestInfo.cs
  - ManifestAttributeInfo.cs  (Permission*, UsesPermission, UsesFeature,
                                UsesLibrary, UsesConfiguration, PropertyInfo)
  - TrimmableTypeMapTypes.cs  (ManifestConfig + TrimmableTypeMapResult)

- Replace string.IsNullOrEmpty() static calls with .IsNullOrEmpty() extension
  - Add NullableExtensions.cs to TrimmableTypeMap project
  - ManifestGenerator.cs: 6 occurrences
  - TrimmableTypeMapGenerator.cs: 3 occurrences

- Change List<string> to IReadOnlyList<string> in TrimmableTypeMapResult

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

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

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

🤖 AI Review Summary

Verdict: ⚠️ Needs Changes

Found 4 issues: 1 error + 2 warnings + 1 suggestion.

  • MSBuild targets: AcwMapOutputFile passed to task but no such property exists → MSB4064 build error (Microsoft.Android.Sdk.TypeMap.Trimmable.targets:77)
  • ⚠️ Code organization: 8 types in ManifestAttributeInfo.cs — one type per file (Scanner/ManifestAttributeInfo.cs:7)
  • ⚠️ Code organization: ManifestGenerator unnecessarily made public — new helpers default to internal (Generator/ManifestGenerator.cs:16)
  • 💡 MSBuild targets: AfterTargets="CoreCompile" runs even if CoreCompile fails; consider $(MSBuildLastTaskResult) guard

👍 The overall architecture is solid: AndroidTask with correct TaskPrefix, [Required] property conventions followed, error codes from Properties.Resources, IsNullOrEmpty() extension used consistently throughout, Files.CopyIfStringChanged in GenerateEmptyTypemapStub, and FileWrites tracked correctly for incremental builds. The JniNameToJavaName fix for $. in inner class names is correct and well-tested.


Review generated by android-reviewer from review guidelines.

ManifestPlaceholders="$(AndroidManifestPlaceholders)"
CheckedBuild="$(_AndroidCheckedBuild)"
ApplicationJavaClass="$(AndroidApplicationJavaClass)"
AcwMapOutputFile="$(IntermediateOutputPath)acw-map.txt">
Copy link
Member Author

Choose a reason for hiding this comment

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

🤖 ❌ MSBuild targetsAcwMapOutputFile is passed here but the GenerateTrimmableTypeMap C# task class has no such property. MSBuild will fail with MSB4064 ("The 'GenerateTrimmableTypeMap' task does not support the parameter 'AcwMapOutputFile'"). Either add the property to the task class, or remove this line if it was left from an earlier refactor.

Rule: Incremental builds / task parameter accuracy

- Fix AcwMapOutputFile: add property to GenerateTrimmableTypeMap task,
  wire through TrimmableTypeMapGenerator.Execute, and write acw-map.txt
  via AcwMapWriter so _ConvertCustomView and _UpdateAndroidResgen get
  real managed→Java mappings
- Split ManifestAttributeInfo.cs: move each of the 8 types to its own
  file (one type per file rule)
- Make ManifestGenerator internal: no external consumers; tests use
  InternalsVisibleTo
- Return IReadOnlyList<string> from public Generate overloads (was IList)

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

3 participants