[TrimmableTypeMap] Extend scanner integration tests for legacy parity#11011
Draft
simonrozsival wants to merge 30 commits intodev/simonrozsival/trimmable-typemap-build-pipelinefrom
Draft
Conversation
ManifestGenerator in Microsoft.Android.Sdk.TrimmableTypeMap assembly. Converts ComponentInfo records from JavaPeerScanner into AndroidManifest.xml. - Data-driven property mapping (7 static arrays, 9 enum converters) - MainLauncher intent-filter, runtime provider, template merging, deduplication - Assembly-level: Permission, UsesPermission, UsesFeature, UsesLibrary, UsesConfiguration, Application, MetaData, Property - ManifestPlaceholders, debuggable/extractNativeLibs, ApplicationJavaClass - XA4213 constructor validation, duplicate Application detection - VersionCode defaults to '1' matching legacy 23 unit tests (xunit). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Split into focused classes in Microsoft.Android.Sdk.TrimmableTypeMap: - ManifestGenerator: orchestration (load template, call builders, write output) - ManifestConstants: shared AndroidNs and AttName - PropertyMapper: data-driven property mapping (7 arrays, MappingKind enum) - AndroidEnumConverter: 9 enum-to-string converters - ComponentElementBuilder: Activity/Service/Receiver/Provider/Instrumentation XML - AssemblyLevelElementBuilder: permissions, uses-permissions, features, libraries Features: MainLauncher, runtime provider (with dedup), template merging, ManifestPlaceholders, debuggable/extractNativeLibs, XA4213 validation. 30 unit tests (xunit, 130ms). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tore Wire up the full trimmable typemap build pipeline: Scanner extensions: - ComponentInfo, IntentFilterInfo, MetaDataInfo data model - Assembly-level attribute scanning (Permission, UsesPermission, etc.) - ScanAssemblyManifestInfo, HasPublicParameterlessCtor GenerateTrimmableTypeMap task: - Manifest generation via ManifestGenerator (from #10990) - XA4213 validation, duplicate Application detection - Assembly scanning with manifest info collection Targets: - _GenerateTrimmableTypeMap: AfterTargets=CoreCompile, manifest properties - _GenerateJavaStubs: JCW copy, assembly store wiring, manifest copy - _AddTrimmableTypeMapAssembliesToStore: per-ABI batched, linked/ fallback - GenerateNativeApplicationConfigSources with TypeMap DLL count - Path normalization for macOS Also: GenerateEmptyTypemapStub, PreserveLists, HelloWorld sample config Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add AcwMapDirectory property (from main merge) - Rename TrimmableManifestGenerator -> ManifestGenerator - Remove Log parameter from Generate() call - Make ManifestGenerator public for cross-assembly access - Wire AcwMapDirectory in Trimmable.targets Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bug) FindViewById<T> uses JavaCast<T> which disposes the intermediate View peer before casting. Use non-generic FindViewById + as-cast instead. The trimmable typemap pipeline works end-to-end: - TypeMap DLLs generated, trimmed, in assembly store (184 assemblies) - Manifest generated from [Activity] attributes - Activity launches, OnCreate runs, SetContentView works - TextView/Button creation works - App displays on device in ~2s Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
FindViewById<Button> and JavaCast<Button> now work correctly with the CreatePeer override. Button text set to 'Hello from Trimmable TypeMap!'. Click event handler removed — requires View_OnClickListenerImplementor JCW which isn't generated for framework types in the trimmable path yet. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Framework binding types (Activity, View, etc.) have pre-built JCWs in the SDK.
But Implementor types (View_OnClickListenerImplementor, etc.) must be generated
per-app at build time. Include framework types whose JNI name starts with 'mono/'
in JCW generation.
This fixes button.Click += delegate { } which requires the
View_OnClickListenerImplementor JCW class.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove JCW generation filter: generate JCWs for ALL types including framework. The legacy path does this too — framework JCWs are NOT pre-built in the SDK pack. - Fix OverrideDetectionTests: native callback name is now derived from the Connector field (n_OnCreate_Landroid_os_Bundle_ not n_OnCreate). - Fix JcwJavaSourceGeneratorTests: same native callback naming fix. 289/289 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three bugs prevented framework event listener JCWs (e.g. View_OnClickListenerImplementor) from working: 1. JniNameToJavaName: replace $ with . for inner class references in Java source code (84 javac compilation failures) 2. GetNativeCallbackName: split Connector on ':' before checking Get…Handler pattern (wrong native callback name → MissingMethodException) 3. ParseConnectorDeclaringType: extract Invoker type from Connector so UCO wrapper resolves the callback on the right type Also removes duplicate type definitions from ManifestModel.cs (now in JavaPeerInfo.cs) and restores mono/ prefix JCW filter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
…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>
b10fa8e to
5b102db
Compare
…tor coverage New scanner comparison tests: - ExactComponentAttributes_MonoAndroid/UserTypesFixture: compare component attribute extraction (kind, name) against legacy Cecil pipeline - ExactExportFlags_MonoAndroid: compare IsExport/JavaAccess on marshal methods - ImplementorTypes_HaveCorrectMonoPrefix: verify mono/ JNI name prefix - Scanner_NonPeerAssembly_ProducesEmptyResults: graceful empty scan New UserTypesFixture types: - ExportWithThrows: [Export] with Throws for export metadata testing - FieldExporter: [ExportField] for Java field generation testing - ShareActivity: Activity with [IntentFilter] and categories - TestRunner: Instrumentation component Infrastructure: - TypeDataBuilder: BuildLegacyComponentData/BuildNewComponentData for component attribute comparison - ComparisonDiffHelper: CompareComponentAttributes - MarshalMethodDiffHelper: CompareExportFlags, extended MethodEntry with IsExport/JavaAccess - ScannerRunner: extract [Export] from Cecil, populate export fields from JavaPeerInfo - Minimum-count assertions on all Exact*_MonoAndroid tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compare uses-permission and uses-feature assembly-level attributes extracted by the new scanner (ScanAssemblyManifestInfo) against the legacy Cecil-based extraction. - TypeDataBuilder: BuildLegacyManifestData/BuildNewManifestData - ComparisonDiffHelper: CompareAssemblyManifestAttributes - New test: ExactAssemblyManifestAttributes_MonoAndroid Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CompareComponentAttributes: now compares component properties (MainLauncher, Label, Exported, etc.), not just kind/name - ExactMarshalMethods_MonoAndroid: wire CompareExportFlags call so IsExport metadata is actually asserted - Manifest comparison: encode MaxSdkVersion, Required, GLESVersion into comparison strings instead of comparing bare names only Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ExactJavaFields_UserTypesFixture: verify [ExportField] scanning produces fields with all required properties populated - ExportWithThrows_HasThrownNames: verify [Export(Throws=...)] scanning populates ThrownNames on the marshal method Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ExactComponentAttributes_MonoAndroid: remove >50 minimum count assertion — Mono.Android has no component-attributed types (MCW bindings use DoNotGenerateAcw=true). Test still verifies parity. - ExportWithThrows_HasThrownNames → ExportMethod_UserTypesFixture_IsDiscovered: the scanner reads the internal ThrownNames property, not the public Throws (Type[]) property. Test now verifies the export method is discovered with correct JNI name/signature instead. - Fix EncodePermission/EncodeFeature: move from file scope into TypeDataBuilder class body. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add tests comparing additional JavaPeerScanner fields against the legacy Cecil-based pipeline: 1. ExactCompatJniNames_MonoAndroid - compares CompatJniName 2. ExactNativeCallbackNames_MonoAndroid - verifies NativeCallbackName presence and format (n_ prefix) for methods from CecilImporter 3. ExactDeclaringTypes_MonoAndroid - compares DeclaringTypeName where legacy pipeline tracks it 4. ExactConstructorSuperArgs_UserTypesFixture - compares SuperCall vs SuperArgumentsString for Export constructors 5. ExactInvokerTypes_MonoAndroid - compares InvokerTypeName from Register attribute (interface types) 6. ExactCannotRegisterInStaticCtor_MonoAndroid - compares the CannotRegisterInStaticConstructor flag Extended TypeComparisonData with CompatJniName, CannotRegisterInStaticConstructor, and InvokerTypeName fields. Extended MethodEntry with NativeCallbackName and DeclaringTypeName. Added ConstructorSuperArgData record and builder methods. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mono.Android has 8,516 types. The previous >3000 threshold was too conservative — it would still pass if half the types were missing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mono.Android has 8,516 types. Using 8,500 as the threshold catches any significant regression in type discovery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3868d08 to
14fc56d
Compare
simonrozsival
commented
Mar 25, 2026
Member
Author
simonrozsival
left a comment
There was a problem hiding this comment.
🤖 AI Review Summary
Verdict: ✅ LGTM
Found 0 blocking issues. This is a well-structured test-only PR with no production code changes.
What this PR does
Extends the scanner integration test suite from 10 to 23 tests, comparing JavaPeerScanner output against the legacy Cecil-based pipeline across all 8,500+ Mono.Android types. Adds comparison coverage for: component attributes, assembly manifest attributes, compat JNI names, native callback names, declaring types, constructor super-args, invoker types, export flags, and the CannotRegisterInStaticConstructor flag.
Observations
- The comparison helpers follow a consistent pattern: build data from both pipelines, intersect keys, diff values. This is well-factored and easy to extend.
- The
CompareNativeCallbackNameswisely only checks that both sides have a callback withn_prefix rather than exact name match, since the naming conventions differ by design (legacy uses JNI names, new uses managed names). - The
CompareInvokerTypescorrectly skips cases where legacy has null but new scanner has a value, since Cecil attribute extraction only finds invokers on interface types. - Threshold bump from 3000 to 8500 is appropriate — Mono.Android has 8,516 types.
Minor notes (not blocking)
- 💡
paths!null-forgiving operator appears on 4 new lines, but this matches the pre-existing convention in the file (afterAssert.NotNull(paths)). - 💡
string.IsNullOrEmpty()static calls (vs.IsNullOrEmpty()extension) also match the pre-existing pattern in this test project.
CI
⏳ CI is skipping (base branch dependency). Code review is based on code inspection + local test verification.
Remaining gaps (documented in plan, not blocking)
- ACW map end-to-end comparison
- JCW Java source comparison
Review generated by android-reviewer from review guidelines.
6f42b4d to
384a5a8
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.
Summary
Extends the scanner integration tests from 10 to 23 tests, comparing
JavaPeerScanneroutput against the legacy Cecil-based pipeline (XAJavaTypeScanner+CecilImporter) across all 8,500+ Mono.Android types.New comparison tests (vs legacy)
ExactComponentAttributes(MonoAndroid + UserTypes)ExactAssemblyManifestAttributesExactCompatJniNamesExactNativeCallbackNamesn_MethodNamecallback namesExactDeclaringTypesExactConstructorSuperArgssuper(...)argument stringsExactInvokerTypesExactCannotRegisterInStaticCtorIsExport(wired into existing marshal methods test)New validation tests
ImplementorTypes_HaveCorrectMonoPrefixmono/JNI prefix on implementor typesScanner_NonPeerAssembly_ProducesEmptyResultsExactJavaFields_UserTypesFixture[ExportField]field propertiesExportMethod_UserTypesFixture_IsDiscovered[Export]on unregistered typesNew UserTypesFixture types
ExportWithThrows,FieldExporter,ShareActivity(with[IntentFilter]),TestRunner([Instrumentation])Dependencies