[TrimmableTypeMap] Add GenerateTrimmableTypeMap MSBuild task and targets#10924
Draft
simonrozsival wants to merge 13 commits intomainfrom
Draft
[TrimmableTypeMap] Add GenerateTrimmableTypeMap MSBuild task and targets#10924simonrozsival wants to merge 13 commits intomainfrom
simonrozsival wants to merge 13 commits intomainfrom
Conversation
d28814e to
04e0b2e
Compare
2d666a1 to
5bf8492
Compare
simonrozsival
commented
Mar 12, 2026
Member
Author
simonrozsival
left a comment
There was a problem hiding this comment.
🤖 AI Review Summary
Verdict: ✅ LGTM
Found 0 issues.
Reviewed against all 14 rule categories:
- ✅ MSBuild tasks: Extends
AndroidTask,TaskPrefixdefined, returns!Log.HasLoggedErrors,[Required]properties have defaults,[Output]properties nullable - ✅ MSBuild targets: Internal names prefixed with
_,Inputs/Outputswith stamp file for incrementality,AfterTargetsused appropriately (noDependsOnavailable), noTrimmerRootAssembly(correct — trimmer must process TypeMapAttributes) - ✅ Nullable: No null-forgiving
!, no#nullable enable(project-level) - ✅ Error handling:
ParseTargetFrameworkVersionthrows on bad input, no empty catch blocks - ✅ Performance: BCL assemblies filtered via
FrameworkAssembly/HasMonoAndroidReferencemetadata, per-assembly timestamp check skips unchanged typemaps, LINQ used cleanly - ✅ Code organization: Types made
publicfor cross-assembly consumption (build-time only, not shipped in apps),UsingTaskscoped to trimmable targets only - ✅ Formatting: Tabs, Mono style throughout
👍 Clean task design with good incrementality (per-assembly timestamp skipping). Benchmark data in the PR description validates the approach. RuntimeHostConfigurationOption correctly shared between CoreCLR and NativeAOT. AfterTargets usage is justified since there's no DependsOn property to hook into.
Review generated by android-reviewer from review guidelines.
Base automatically changed from
dev/simonrozsival/trimmable-typemap-04-jcw-generator
to
main
March 12, 2026 21:54
Add the MSBuild task that wires the TrimmableTypeMap scanner and generators into the build pipeline, replacing the stub _GenerateJavaStubs target. ### Task (GenerateTrimmableTypeMap) - Extends AndroidTask, TaskPrefix 'GTT' - Scans resolved assemblies for Java peer types - Generates per-assembly TypeMap .dll assemblies - Generates root _Microsoft.Android.TypeMaps.dll - Generates JCW .java source files for ACW types ### Targets - Microsoft.Android.Sdk.TypeMap.Trimmable.targets: replaces stub with real GenerateTrimmableTypeMap task call - CoreCLR.targets: adds generated assemblies as TrimmerRootAssembly, configures RuntimeHostConfigurationOption for TypeMappingEntryAssembly - NativeAOT.targets: adds to IlcReference, UnmanagedEntryPointsAssembly, and TrimmerRootAssembly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests using MockBuildEngine: - Empty assembly list succeeds with no outputs - Real Mono.Android.dll produces per-assembly + root typemap assemblies - Different TargetFrameworkVersion formats all parse correctly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Full build integration tests: - Build with _AndroidTypeMapImplementation=trimmable succeeds on CoreCLR - Incremental build skips _GenerateJavaStubs when nothing changed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n bad version - Extract Phase 1-5 into named methods (ScanAssemblies, GenerateTypeMapAssemblies, GenerateJcwJavaSources) — no more // Phase N comments - Filter BCL assemblies: skip FrameworkAssembly=true unless HasMonoAndroidReference - Throw on unparseable TargetFrameworkVersion instead of silent fallback - Use LINQ for grouping peers by assembly and filtering paths - Deterministic output ordering via OrderBy on assembly name Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Both GenerateTypeMapAssemblies and GenerateJcwJavaSources now return ITaskItem[] directly — no intermediate conversion in RunTask - Move Java output under typemap dir (typemap/java instead of android/src) - Remove TrimmerRootAssembly from generated assemblies — the trimmer must process TypeMapAttributes and trim entries whose trimTarget types were removed. TrimmerRootAssembly would prevent this, defeating the purpose. - NativeAOT: keep IlcReference + UnmanagedEntryPointsAssembly but remove TrimmerRootAssembly for the same reason. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compare source assembly timestamp against generated typemap .dll — skip emission when the output is newer. Root assembly only regenerated when any per-assembly typemap changed. Typical incremental build: only app assembly changed → scan all (for cross-assembly resolution) but only emit _MyApp.TypeMap.dll + root. Mono.Android scan is unavoidable (xref resolution) but its typemap emission is skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- SecondRun_SkipsUpToDateAssemblies: run twice with same inputs, verify typemap file timestamp unchanged and 'up to date' logged - SourceTouched_RegeneratesOnlyChangedAssembly: touch source assembly, verify typemap is regenerated with newer timestamp - InvalidTargetFrameworkVersion_Throws: verify ArgumentException - Extracted CreateTask helper to reduce test boilerplate - ParsesTargetFrameworkVersion converted to [TestCase] parameterized test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both CoreCLR and NativeAOT need to know the TypeMap entry assembly. RuntimeHostConfigurationOption works for both (runtimeconfig.json for CoreCLR, ILC feature switch for NativeAOT). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make types consumed by the MSBuild task public (they're build-time only, not shipped in apps): JavaPeerInfo, MarshalMethodInfo, JniParameterInfo, JavaConstructorInfo, ActivationCtorInfo, ActivationCtorStyle, JavaPeerScanner, TypeMapAssemblyGenerator, RootTypeMapAssemblyGenerator, JcwJavaSourceGenerator. Fix Execute_InvalidTargetFrameworkVersion test: AndroidTask catches exceptions and logs them as errors, so Assert.Throws doesn't work. Check task.Execute() returns false and errors are logged instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generate Java content to StringWriter first, compare with existing file. Only write to disk if content changed. This avoids unnecessary javac recompilation on incremental builds where types haven't changed. Benchmark showed JCW file writing was the biggest bottleneck (~511ms p50 for 315 files). With this change, incremental builds that don't change any types skip all disk writes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…builds" This reverts commit ac4227b.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
81102b5 to
791bd4d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #10800
Stacked on #10917.
Summary
Adds the
GenerateTrimmableTypeMapMSBuild task and wires it into the trimmable targets files, replacing the stub_GenerateJavaStubstarget with real typemap + JCW generation.Task (
GenerateTrimmableTypeMap)AndroidTask(TaskPrefixGTT).dllassemblies → generates root_Microsoft.Android.TypeMaps.dll→ generates JCW.javasource filesFrameworkAssembly=truewithoutHasMonoAndroidReference) to skip unnecessary scanning.TypeMap.dllis newer than source.dllTargets
Microsoft.Android.Sdk.TypeMap.Trimmable.targets: Replaces stub with realGenerateTrimmableTypeMaptask call. Defines_TypeMapOutputDirectoryand_TypeMapJavaOutputDirectory(co-located undertypemap/). ConfiguresRuntimeHostConfigurationOptionforTypeMappingEntryAssembly.Trimmable.CoreCLR.targets: Adds generated assemblies to_ResolvedAssembliesfor the linker (withoutTrimmerRootAssembly— the trimmer must processTypeMapAttributeentries and trim ones whose target types were removed).Trimmable.NativeAOT.targets: Adds toIlcReference+UnmanagedEntryPointsAssembly(withoutTrimmerRootAssembly).Performance
Benchmark on MacBook M1, scanning
Mono.Android.dll(~8870 types), 169 iterations over 5 minutes:Cold build cost is dominated by JCW file I/O (high p95 variance from disk contention). Incremental builds (unchanged assemblies) skip emit entirely — only the scan runs (~215ms).
Note: scanning currently rescans all assemblies on every build (needed for cross-assembly type resolution). This is a known limitation — future optimization could cache scan results or add a two-list scan overload that only produces
JavaPeerInfofor changed assemblies while still indexing all for cross-references. Profiling is needed before optimizing.Benchmark script
Tests
Unit tests (8 tests with
MockBuildEngine):Integration tests (full
dotnet build):_AndroidTypeMapImplementation=trimmableon CoreCLR succeeds_GenerateJavaStubsFollow-up work
Scanoverload or cached indices)_Mono.Android.TypeMap.dllduring SDK build ([CoreCLR] Build R2R images for SDK assemblies in Debug builds #10760)