Skip to content

[CoreCLR] Stabilize Debug typemaps across C# rebuilds - #12216

Merged
jonathanpeppers merged 15 commits into
mainfrom
jonathanpeppers-fix-incremental-deploy
Jul 29, 2026
Merged

[CoreCLR] Stabilize Debug typemaps across C# rebuilds#12216
jonathanpeppers merged 15 commits into
mainfrom
jonathanpeppers-fix-incremental-deploy

Conversation

@jonathanpeppers

@jonathanpeppers jonathanpeppers commented Jul 23, 2026

Copy link
Copy Markdown
Member

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 assembly MVIDs, so every C# rebuild rewrote the generated native typemap even when Java type mappings did not change.  This forced native compilation, APK rebuilding, and signing during Fast Deployment.

Key Debug managed-to-Java entries by assembly full name instead.  Preserve the Release MVID lookup and use a trimmer feature switch so Release builds do not retrieve or marshal Assembly.FullName.

Extend the incremental build test to verify a C#-only change does not rewrite typemaps or the signed APK and skips the native typemap, APK, and signing targets.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 54c1e7b6-a69c-41bc-812d-5acc5bfdffee

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

Stabilizes CoreCLR Debug typemap generation so C#-only rebuilds don’t force native typemap regeneration (and therefore avoid unnecessary native compilation, APK rebuild, and signing) during Fast Deployment. This is done by switching Debug managed-to-Java typemap keys from assembly MVID-based lookup to stable assembly identity strings, while keeping Release behavior MVID-based behind a feature switch.

Changes:

  • CoreCLR Debug managed-to-Java typemap entries now use Assembly.FullName-based keys; removed the Debug MVID table/type_map_unique_assemblies plumbing.
  • Added a runtime feature switch (enabled for Debug via MSBuild) to control whether managed-to-Java lookup passes/uses assembly full name.
  • Extended incremental build coverage to verify typemap + signed APK remain unchanged for C#-only edits and that key fast-deploy targets are skipped.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Xamarin.Android.Build.Tasks/Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs Switch Debug managed-to-Java entries to assembly full name; remove unique-assemblies MVID table emission.
src/Xamarin.Android.Build.Tasks/Utilities/TypeMapObjectsXmlFile.cs Persist/import assembly-full-name for Debug typemap object XML.
src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs Add AssemblyFullName to debug typemap entry model.
src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs Populate AssemblyFullName from Cecil for debug entries.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs Add incremental test ensuring typemap and signed APK unchanged for C#-only edits (CoreCLR Debug).
src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs Remove debug unique-assemblies stub symbol to match new Debug layout.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targets Enable new runtime feature switch for Debug via RuntimeHostConfigurationOption.
src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindTypeMapObjectsStep.cs Emit assembly full name into typemap XML in Debug.
src/native/nativeaot/host/internal-pinvoke-stubs.cc Update internal-pinvoke stub signature for new parameter.
src/native/clr/xamarin-app-stub/application_dso_stub.cc Remove Debug unique-assemblies fields/symbols from stub DSO.
src/native/clr/include/xamarin-app.hh Remove Debug TypeMapAssembly + type_map_unique_assemblies exports; adjust TypeMap layout.
src/native/clr/include/runtime-base/internal-pinvokes.hh Update clr_typemap_managed_to_java signature to include assembly full name.
src/native/clr/include/host/typemap.hh Conditionalize managed-to-Java API for Release (MVID) vs Debug (assembly full name).
src/native/clr/host/typemap.cc Implement Debug lookup via typeName + \", \" + assemblyFullName; remove MVID-to-name lookup path.
src/native/clr/host/internal-pinvokes-clr.cc Route managed-to-Java internal pinvoke to the new Debug signature vs Release signature.
src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs Add ManagedToJavaUsesAssemblyFullName feature switch definition.
src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs Extend P/Invoke to pass optional assembly full name.
src/Mono.Android/Android.Runtime/JNIEnv.cs In CoreCLR path, pass assembly full name only when feature is enabled.

Comment thread src/native/clr/host/typemap.cc
Comment thread src/native/clr/host/internal-pinvokes-clr.cc Outdated
Comment thread src/Xamarin.Android.Build.Tasks/Tasks/GenerateEmptyTypemapStub.cs Outdated
Keep the empty Debug LLVM typemap stub layouts synchronized with the native structures after removing the MVID table.  Mark configuration-specific internal P/Invoke parameters as potentially unused and clarify the missing assembly full-name diagnostic.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 54c1e7b6-a69c-41bc-812d-5acc5bfdffee
Mono.Cecil does not escape assembly display-name characters in the same way as System.Reflection.Assembly.FullName.  Format Debug typemap assembly identities through System.Reflection.AssemblyName so generated keys match runtime keys for names containing commas and other escaped characters.

Add coverage for an escaped assembly simple name.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 54c1e7b6-a69c-41bc-812d-5acc5bfdffee
@jonathanpeppers

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actions github-actions Bot 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.

🤖 Code Review — ⚠️ Looks solid, minor suggestions

Thorough, well-scoped change. The Debug CoreCLR typemap now keys managed→Java lookups on the stable assembly full name (Name, Version, Culture, PublicKeyToken) embedded directly in the string blob, dropping the per-assembly MVID table (type_map_unique_assemblies) and its binary search. Because MVIDs change on every C# rebuild, removing them from the Debug typemap is what lets a C#-only edit skip native compile / APK rebuild / signing during Fast Deployment — the incremental test verifies exactly that. Release keeps its MVID-based lookup untouched.

Design consistency verified. All three Debug/Release toggles line up on $(AndroidIncludeDebugSymbols):

  • native runtime flavor (libnet-android.debug vs .release, i.e. DEBUG/RELEASE compile) — ProcessNativeLibraries
  • _TypeMapKind = strings-asm vs mvid
  • the new ManagedToJavaUsesAssemblyFullName feature switch

So the managed side only pays type.Assembly.FullName in Debug, the trimmer can drop it in Release, and there's no Debug-native/Release-typemap mismatch. The #if RELEASE split of managed_to_java/clr_typemap_managed_to_java and the null-assemblyFullName guard are correct, and the NativeAOT/stub signatures were updated in lockstep. The UniqueAssemblies sort was switched from MVID order to Ordinal name order, which is what makes the assembly-names blob deterministic across rebuilds. 👍

Suggestions (non-blocking): 2 inline 💡 comments

  • Confirm/harden the assumption that the build-time GetRuntimeAssemblyFullName is byte-identical to runtime Assembly.FullName for all mapped assemblies (a unit test would help).
  • Optional simplification of the EndsWith/Substring reconstruction and a note that AssemblyFullName isn't validated non-empty.

Testing: Good coverage — new incremental assertion (typemap .ll + signed APK unchanged, _CompileNativeAssemblySources/_CreateApplicationSharedLibraries/_BuildApkFastDev/_Sign skipped after a C#-only change) plus the GetRuntimeAssemblyFullName escaping unit test.

CI: In progress at review time; Linux build and the completed test legs are green, no failures observed. Verify all dotnet-android legs (especially the CoreCLR Debug device/emulator tests) go green before merge.

Generated by Android PR Reviewer for #12216 · 208.4 AIC · ⌖ 13.5 AIC · ⊞ 6.8K
Comment /review to run again

Comment thread src/Xamarin.Android.Build.Tasks/Utilities/TypeMapCecilAdapter.cs
Fail generation when a managed-to-Java entry lacks the assembly full name required by CoreCLR Debug lookup.  Verify the Cecil-to-runtime formatter against the actual strong-named build-task assembly in addition to escaped simple-name coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 54c1e7b6-a69c-41bc-812d-5acc5bfdffee
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 54c1e7b6-a69c-41bc-812d-5acc5bfdffee
@jonathanpeppers

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actions github-actions Bot 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.

⚠️ Review Summary — Needs Changes (minor)

Solid, well-scoped change. Replacing the Debug MVID-based typemap with stable assembly full names is the right fix for the incremental/fast-deploy churn, and the C++/C# struct layouts stay in sync (TypeMap, TypeMapManagedTypeInfo, the stub in application_dso_stub.cc, and GenerateEmptyTypemapStub all match). Release keeps its MVID path, correctly gated behind the ManagedToJavaUsesAssemblyFullName feature switch, which is driven by the same $(AndroidIncludeDebugSymbols) property that selects the debug native runtime and the strings-based typemap — so the managed switch and native DEBUG/RELEASE compile path stay consistent. Good test coverage: the new IncrementalBuildTest asserts typemap + APK hash stability across a C#-only change, and LlvmIrGeneratorTests covers the escaping/strong-name formatting of GetRuntimeAssemblyFullName.

Issue counts: ⚠️ 1 · 💡 2 (all inline)

Positive callouts:

  • Removing the TypeMapAssembly table and its binary-search-by-MVID meaningfully simplifies managed_to_java_debug.
  • GetRuntimeAssemblyFullName correctly reproduces reflection escaping/PublicKeyToken formatting rather than relying on Cecil's FullName.
  • NativeAOT and CoreCLR p/invoke stubs were both updated for the new signature.

Main thing to confirm (inline): the removed native fallback means a build-time vs runtime assembly-full-name mismatch now fails silently. Please confirm behavior for projects with non-deterministic/wildcard assembly versions, or document that stable versions are required for Debug fast deployment.

Note: mergeable_state is blocked (pending review/required checks) — not verified as a code failure here.

Generated by Android PR Reviewer for #12216 · 151.1 AIC · ⌖ 19 AIC · ⊞ 6.8K
Comment /review to run again

Comment thread src/native/clr/host/typemap.cc
Comment thread src/Mono.Android/Android.Runtime/JNIEnv.cs Outdated
jonathanpeppers and others added 5 commits July 24, 2026 13:35
The Debug CoreCLR typemap is keyed on the assembly display name and the
native side ignores the MVID argument, yet `TypemapManagedToJava` still
computed `Module.ModuleVersionId` for every lookup. Split the lookup so
the MVID is only computed when it is actually consumed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
Keep the single `fixed`/runtime dispatch block and only guard the MVID
computation itself, instead of duplicating the CoreCLR call.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
@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 27, 2026
@rolfbjarne

Copy link
Copy Markdown
Member

The existing incremental build test now performs a C#-only change and verifies that the typemap and signed APK remain unchanged and that _CompileNativeAssemblySources, _CreateApplicationSharedLibraries, _BuildApkFastDev, and _Sign are skipped.

What if the C#-only change is such that the type map should change? Is that automatically detected, or will users have to clean their projects to get the right things to rebuild?

@jonathanpeppers

Copy link
Copy Markdown
Member Author

What if the C#-only change is such that the type map should change? Is that automatically detected, or will users have to clean their projects to get the right things to rebuild?

This test does this:

[Test]
public void FastDeployUpdatesTypeMapAfterAssemblyEdit ()
{
var proj = new XamarinAndroidApplicationProject {
PackageName = "com.xamarin.fastdeploy_typemap",
};
proj.SetDefaultTargetDevice ();
proj.SetProperty ("_AndroidFastDevStrategy", "FastDeploy");
proj.MainActivity = proj.DefaultMainActivity
.Replace ("//${AFTER_ONCREATE}", "StartActivity (new Android.Content.Intent (this, typeof (SecondActivity)));")
.Replace ("//${AFTER_MAINACTIVITY}", """
[Activity (Label = "Fast Deploy Result")]
public sealed class SecondActivity : Activity
{
}
""");
using var builder = CreateApkBuilder ();
Assert.IsTrue (builder.Install (proj), "Initial install should have succeeded.");
AssertSecondActivityStarts ("initial-launch.log");
proj.MainActivity += "// Incremental C# edit.";
proj.Touch ("MainActivity.cs");
Assert.IsTrue (builder.Install (proj, doNotCleanupOnUpdate: true, saveProject: false), "Incremental install should have succeeded.");
AssertSecondActivityStarts ("incremental-launch.log");
Assert.IsTrue (builder.Uninstall (proj), "Uninstall should have succeeded.");
void AssertSecondActivityStarts (string logFileName)
{
ClearAdbLogcat ();
AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity");
Assert.IsTrue (
WaitForActivityToStart (
proj.PackageName,
"SecondActivity",
Path.Combine (Root, builder.ProjectDirectory, logFileName),
ActivityStartTimeoutInSeconds
),
"SecondActivity should have started."
);
}
}

Asserts a new activity class can be navigated to, which means the Java code and typemap were updated.

Let me add some assertions to it, to verify the appropriate targets are skipped/not skipped.

@jonathanpeppers

Copy link
Copy Markdown
Member Author

Actually, the above test should only add the activity on the second deploy.

jonathanpeppers and others added 2 commits July 28, 2026 08:07
`FastDeployUpdatesTypeMapAfterAssemblyEdit` declared `SecondActivity`
before the first build, so both deployments shipped the same set of
Java-callable types and the test could not tell whether the type map was
updated by the incremental build.

Deploy only `MainActivity` first, then add `SecondActivity` with a
C#-only edit for the second build, and assert at each step that the Java
stub, dex, native, packaging, and signing targets all run.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
Split `FastDeployUpdatesTypeMapAfterAssemblyEdit` into three
build + deploy steps:

1. Deploy with only `MainActivity`.
2. Add a `SecondActivity`, which introduces a new Java-callable
   type. Fast deployment only syncs managed assemblies, so the Java
   stubs, .dex, type map, .apk and signature all have to be rebuilt
   and the package reinstalled. This answers the review question of
   whether a C#-only edit that *does* change the type map is picked
   up automatically: it is, no clean required.
3. Add a `Console.WriteLine()` to `SecondActivity.OnCreate()`.
   No Java-callable types change, so the type map is stable and
   `_CompileNativeAssemblySources`, `_CreateApplicationSharedLibraries`,
   `_BuildApkFastDev` and `_Sign` are all skipped and the .apk is
   not reinstalled. The message is asserted in `logcat` to prove the
   updated assembly really was deployed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
`FastDeployUpdatesTypeMapAfterAssemblyEdit` failed on CI with:

    The .apk should have been installed by the initial build.
    Expected: True
    But was: False

`BuildOutput.IsApkInstalled` scans the build output for the
`Installed Package` message, which `FastDeploy` writes with
`LogDebugMessageWithTiming()`. That is `MessageImportance.Low`, so
it never appears at the default `LoggerVerbosity.Normal` and the
property is always `false`.

`SkipFastDevAlreadyInstalledResources` already sets
`LoggerVerbosity.Detailed` for the same reason, so do the same here.
This also makes the `Assert.IsFalse (IsApkInstalled)` in the final
step meaningful -- at `Normal` verbosity it passed no matter what the
build actually did.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
jonathanpeppers and others added 2 commits July 28, 2026 10:53
`FastDeploy2` is the default strategy, so pinning the test to the
legacy `FastDeploy` only exercised the path most users never take.
Parameterize `FastDeployUpdatesTypeMapAfterAssemblyEdit` over both
strategies instead, with a distinct package name per case so the two
runs cannot interfere with each other on the device.

The type map assertions are strategy-independent -- both strategies
only sync managed assemblies -- but the .apk install assertions are
not, so it is worth confirming both behave the same.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
`BuildOutput.IsApkInstalled` scans the build output for the
`Installed Package` message that `FastDeploy` and `FastDeploy2`
log with `MessageImportance.Low`. Below `LoggerVerbosity.Detailed`
that message is never in the output, so the property silently returns
`false` regardless of what the build did.

That is a bad failure mode in both directions: `Assert.IsTrue` fails
with a confusing message -- which is how
`FastDeployUpdatesTypeMapAfterAssemblyEdit` broke on CI -- and
`Assert.IsFalse` passes without proving anything at all.

Document the requirement and throw an `InvalidOperationException`
naming the current verbosity when it is not met. All existing callers
already set `LoggerVerbosity.Detailed`.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 39550ed5-9b8f-46dc-ad55-2c43f2ea3789
@jonathanpeppers
jonathanpeppers merged commit 9cc92b0 into main Jul 29, 2026
44 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers-fix-incremental-deploy branch July 29, 2026 13:04
jonathanpeppers added a commit that referenced this pull request Jul 29, 2026
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.

(cherry picked from commit 9cc92b0)

Co-authored-by: Copilot App <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

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.

4 participants