Skip to content

[TrimmableTypeMap] Extend scanner integration tests for legacy parity#11011

Draft
simonrozsival wants to merge 30 commits intodev/simonrozsival/trimmable-typemap-build-pipelinefrom
dev/simonrozsival/scanner-integration-tests
Draft

[TrimmableTypeMap] Extend scanner integration tests for legacy parity#11011
simonrozsival wants to merge 30 commits intodev/simonrozsival/trimmable-typemap-build-pipelinefrom
dev/simonrozsival/scanner-integration-tests

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented Mar 25, 2026

Summary

Extends the scanner integration tests from 10 to 23 tests, comparing JavaPeerScanner output against the legacy Cecil-based pipeline (XAJavaTypeScanner + CecilImporter) across all 8,500+ Mono.Android types.

New comparison tests (vs legacy)

Test What it compares
ExactComponentAttributes (MonoAndroid + UserTypes) Component kind, name, properties
ExactAssemblyManifestAttributes uses-permission, uses-feature (incl. maxSdkVersion, required, glEsVersion)
ExactCompatJniNames Dot-format JNI names (acw-map.txt)
ExactNativeCallbackNames n_MethodName callback names
ExactDeclaringTypes Method declaring type/assembly
ExactConstructorSuperArgs super(...) argument strings
ExactInvokerTypes Interface invoker type resolution
ExactCannotRegisterInStaticCtor Application/Instrumentation flag
Export flags IsExport (wired into existing marshal methods test)

New validation tests

Test What it validates
ImplementorTypes_HaveCorrectMonoPrefix mono/ JNI prefix on implementor types
Scanner_NonPeerAssembly_ProducesEmptyResults Graceful empty scan
ExactJavaFields_UserTypesFixture [ExportField] field properties
ExportMethod_UserTypesFixture_IsDiscovered [Export] on unregistered types

New UserTypesFixture types

ExportWithThrows, FieldExporter, ShareActivity (with [IntentFilter]), TestRunner ([Instrumentation])

Dependencies

simonrozsival and others added 4 commits March 24, 2026 22:52
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>
@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Mar 25, 2026
simonrozsival and others added 18 commits March 25, 2026 11:11
…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>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch from b10fa8e to 5b102db Compare March 25, 2026 10:13
simonrozsival and others added 5 commits March 25, 2026 11:20
…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>
simonrozsival and others added 3 commits March 25, 2026 11:20
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>
@simonrozsival simonrozsival force-pushed the dev/simonrozsival/scanner-integration-tests branch from 3868d08 to 14fc56d Compare March 25, 2026 10:21
Copy link
Copy Markdown
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: ✅ 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 CompareNativeCallbackNames wisely only checks that both sides have a callback with n_ prefix rather than exact name match, since the naming conventions differ by design (legacy uses JNI names, new uses managed names).
  • The CompareInvokerTypes correctly 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 (after Assert.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.

@simonrozsival simonrozsival force-pushed the dev/simonrozsival/trimmable-typemap-build-pipeline branch 2 times, most recently from 6f42b4d to 384a5a8 Compare March 25, 2026 16:16
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.

1 participant