Skip to content

[release/11.0.1xx-preview7] [CoreCLR] Stabilize Debug typemaps across C# rebuilds - #12260

Merged
jonathanpeppers merged 1 commit into
release/11.0.1xx-preview7from
jonathanpeppers-backport-12216-preview7
Jul 29, 2026
Merged

[release/11.0.1xx-preview7] [CoreCLR] Stabilize Debug typemaps across C# rebuilds#12260
jonathanpeppers merged 1 commit into
release/11.0.1xx-preview7from
jonathanpeppers-backport-12216-preview7

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Backport of #12216 to release/11.0.1xx-preview7.


Pull Request
title and
description
should follow the
commit-messages.md workflow documentation, and in particular should include:

  • Useful description of why the change is necessary.
  • Links to issues fixed: N/A
  • Unit tests

CoreCLR Debug typemaps embedded application assembly MVIDs. Because an MVID changes on every compilation, a C#-only rebuild rewrote the native typemap even when no Java mappings had changed. That cascaded into native compilation, APK rebuilding, and re-signing on every Fast Deployment cycle.

This changes Debug managed-to-Java entries to key on stable assembly full names and removes the Debug MVID table entirely. Release continues to use its MVID-based lookup, and a trimmer feature switch prevents Assembly.FullName retrieval and marshalling from being rooted in Release builds.

Runtime

clr_typemap_managed_to_java now takes an assembly display name alongside the type name, and TypeMapper::managed_to_java is split on RELEASE so each configuration only sees the parameter it uses. When $(RuntimeFeature.ManagedToJavaUsesAssemblyFullName) is enabled, JNIEnv.TypemapManagedToJava skips computing the MVID altogether rather than computing it and discarding it. A null display name is left for the native side to report, which keeps a single diagnostic path for all callers.

TypeMapCecilAdapter.GetRuntimeAssemblyFullName reproduces reflection's escaping and PublicKeyToken formatting so that the build-time name matches what Assembly.FullName returns at runtime; a mismatch here would silently break every lookup.

Tests

IncrementalBuildTest performs a C#-only change on the CoreCLR Debug path and asserts the typemap and signed APK are byte-for-byte unchanged, and that _CompileNativeAssemblySources, _CreateApplicationSharedLibraries, _BuildApkFastDev, and _Sign are all skipped.

FastDeployUpdatesTypeMapAfterAssemblyEdit is a device test covering three deployments, parameterized over both FastDeploy and FastDeploy2:

  1. Deploy with only MainActivity.
  2. Add a SecondActivity. This is a C#-only edit, but it introduces a new Java-callable type. Fast deployment only syncs managed assemblies, so the Java stubs, .dex, typemap, APK and signature must all be rebuilt and the package reinstalled. This confirms a typemap-affecting C# change is picked up automatically, with no clean required.
  3. Add a Console.WriteLine() to SecondActivity.OnCreate(). No Java-callable types change, so the packaging and signing targets are skipped and the APK is not reinstalled. The message is asserted in logcat to prove the updated assembly really was deployed rather than merely that nothing was rebuilt.

BuildOutput.IsApkInstalled now throws below LoggerVerbosity.Detailed. It scans for an Installed Package message logged at MessageImportance.Low, so at a lower verbosity it silently returned false no matter what the build did — which broke the new test on CI and had been quietly making some pre-existing Assert.IsFalse calls vacuous.

CoreCLR Debug typemaps embedded application assembly MVIDs. Because an MVID changes on every compilation, a C#-only rebuild rewrote the native typemap even when no Java mappings had changed. That cascaded into native compilation, APK rebuilding, and re-signing on every Fast Deployment cycle.

This changes Debug managed-to-Java entries to key on stable assembly full names and removes the Debug MVID table entirely. Release continues to use its MVID-based lookup, and a trimmer feature switch prevents `Assembly.FullName` retrieval and marshalling from being rooted in Release builds.

## Runtime

`clr_typemap_managed_to_java` now takes an assembly display name alongside the type name, and `TypeMapper::managed_to_java` is split on `RELEASE` so each configuration only sees the parameter it uses. When `$(RuntimeFeature.ManagedToJavaUsesAssemblyFullName)` is enabled, `JNIEnv.TypemapManagedToJava` skips computing the MVID altogether rather than computing it and discarding it. A null display name is left for the native side to report, which keeps a single diagnostic path for all callers.

`TypeMapCecilAdapter.GetRuntimeAssemblyFullName` reproduces reflection's escaping and `PublicKeyToken` formatting so that the build-time name matches what `Assembly.FullName` returns at runtime; a mismatch here would silently break every lookup.

## Tests

`IncrementalBuildTest` performs a C#-only change on the CoreCLR Debug path and asserts the typemap and signed APK are byte-for-byte unchanged, and that `_CompileNativeAssemblySources`, `_CreateApplicationSharedLibraries`, `_BuildApkFastDev`, and `_Sign` are all skipped.

`FastDeployUpdatesTypeMapAfterAssemblyEdit` is a device test covering three deployments, parameterized over both `FastDeploy` and `FastDeploy2`:

1. Deploy with only `MainActivity`.
2. Add a `SecondActivity`. This is a C#-only edit, but it introduces a new Java-callable type. Fast deployment only syncs managed assemblies, so the Java stubs, `.dex`, typemap, APK and signature must all be rebuilt and the package reinstalled. This confirms a typemap-affecting C# change is picked up automatically, with no clean required.
3. Add a `Console.WriteLine()` to `SecondActivity.OnCreate()`. No Java-callable types change, so the packaging and signing targets are skipped and the APK is not reinstalled. The message is asserted in `logcat` to prove the updated assembly really was deployed rather than merely that nothing was rebuilt.

`BuildOutput.IsApkInstalled` now throws below `LoggerVerbosity.Detailed`. It scans for an `Installed Package` message logged at `MessageImportance.Low`, so at a lower verbosity it silently returned `false` no matter what the build did — which broke the new test on CI and had been quietly making some pre-existing `Assert.IsFalse` calls vacuous.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit 9cc92b0)
Copilot AI review requested due to automatic review settings July 29, 2026 13:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Backport to release/11.0.1xx-preview7 that stabilizes CoreCLR Debug typemap generation across C#-only rebuilds by removing MVID dependence, reducing unnecessary native recompiles and APK rebuild/sign cycles during Fast Deployment.

Changes:

  • Switch Debug managed-to-Java typemap keys from per-build MVIDs to stable assembly display names (full names) and remove the Debug MVID table from the native typemap.
  • Add a runtime feature switch (ManagedToJavaUsesAssemblyFullName) enabled for Debug to avoid rooting Assembly.FullName in Release builds.
  • Add/extend incremental + device tests to verify typemap/APK stability and that Fast Deployment correctly rebuilds packaging only when Java-callable surface changes.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/MSBuildDeviceIntegration/Tests/FastDevTest.cs Adds a parameterized FastDeploy/FastDeploy2 device test validating typemap-sensitive vs typemap-neutral edits and APK reinstall behavior.
src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs Updates CoreCLR Debug typemap generation to embed assembly full names (and drops Debug unique-assemblies/MVID table).
src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs Persists/loads assembly-full-name in typemap object XML to support Debug full-name-based keys.
src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs Adds AssemblyFullName to debug entries used by typemap generators.
src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs Implements reflection-compatible assembly full name formatting for build-time typemap generation.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/BuildOutput.cs Makes IsApkInstalled throw when verbosity is too low to observe install messages (avoids vacuous assertions).
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LlvmIrGeneratorTests.cs Adds unit tests verifying build-time assembly full name formatting matches runtime escaping/strong-name formatting.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs Adds an incremental test asserting typemap + signed APK remain byte-identical after C#-only edit (CoreCLR Debug).
src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs Updates Debug empty typemap stub structs to match updated Debug typemap layout.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targets Plumbs host config option to enable ManagedToJavaUsesAssemblyFullName for Debug builds.
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs Emits assembly-full-name into typemap object XML for Debug scans.
src/native/nativeaot/host/internal-pinvoke-stubs.cc Updates internal pinvoke stub signature to include assemblyFullName parameter.
src/native/clr/xamarin-app-stub/application_dso_stub.cc Removes Debug unique-assemblies fields from the CoreCLR app stub typemap definitions.
src/native/clr/include/xamarin-app.hh Removes Debug TypeMapAssembly and unique_assemblies_count from TypeMap layout.
src/native/clr/include/runtime-base/internal-pinvokes.hh Updates clr_typemap_managed_to_java native signature to include assemblyFullName.
src/native/clr/include/host/typemap.hh Splits TypeMapper::managed_to_java signature by RELEASE vs DEBUG to accept MVID vs assembly full name.
src/native/clr/host/typemap.cc Debug managed-to-Java lookup now hashes typeName + \", \" + assemblyFullName instead of MVID->assembly mapping.
src/native/clr/host/internal-pinvokes-clr.cc Routes the new pinvoke signature to the correct RELEASE/DEBUG TypeMapper::managed_to_java overload.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs Adds ManagedToJavaUsesAssemblyFullName feature switch definition.
src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs Updates LibraryImport signature for clr_typemap_managed_to_java to include optional assembly full name.
src/Mono.Android/Android.Runtime/JNIEnv.cs Skips MVID computation in CoreCLR Debug when full-name keyed typemaps are enabled; passes type.Assembly.FullName.

@jonathanpeppers jonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jul 29, 2026
@jonathanpeppers

Copy link
Copy Markdown
Member Author

The one test failure has been addressed on main:
image

@jonathanpeppers
jonathanpeppers merged commit a12079f into release/11.0.1xx-preview7 Jul 29, 2026
43 of 45 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers-backport-12216-preview7 branch July 29, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants