Skip to content

[Java.Interop] Retire experimental JavaInterop1 support - #13030

Merged
simonrozsival merged 6 commits into
mainfrom
simonrozsival-java-interop1-retirement
Oct 9, 2026
Merged

simonrozsival merged 6 commits into
mainfrom
simonrozsival-java-interop1-retirement

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Summary

Retire the experimental JavaInterop1 binding/Java Callable Wrapper (JCW) generation paths and their obsolete runtime infrastructure. Keep XAJavaInterop1 as the supported binding/legacy JCW shape, and keep the trimmable typemap's Java generator as a separate, targetless pipeline.

This implements the Android cleanup tracked by #11727, including removal of ManagedPeer, GCUserPeerable, the handwritten Java.Interop proxies, and java-interop.jar. Preserve managed-object transport and consolidate exception transport onto the existing generated Android.Runtime.JavaProxyThrowable for both CoreCLR and NativeAOT.

The branch is rebased onto main commit 058e71b5867c63fd92a00ae545c7899f2b60ed6e, including the Android test relocation in #13019 and the retained-typemap R8 pipeline in #12829. This remains a draft; local validation is documented below, not presented as a claim that CI is green. No tracking issue is auto-closed.

Android compatibility: why this can be removed

Normal .NET for Android and .NET MAUI codegen is unchanged

The standard .NET for Android application/binding pipeline, also used by .NET MAUI Android applications, uses XAJavaInterop1, not the experimental JavaInterop1 shape. This PR preserves that supported code-generation path.

NativeAOT previously selected JavaInterop1 for its JCWs. Android NativeAOT has remained an experimental runtime option, documented by XA1040, and merged #12133 already switched it to XAJavaInterop1 and unified the Android GC bridge around mono.android.IGCUserPeer. Before this PoC, the SDK defaults already selected XAJavaInterop1 for both AndroidCodegenTarget and _AndroidJcwCodegenTarget.

The old JavaInterop1 option was explicitly documented as experimental in the build-property documentation. Its historical NativeAOT-default description is stale after #12133 and is corrected here.

The supported Android paths have already migrated:

  • #12133 migrated NativeAOT's JCW shape and GC bridge.
  • #12152 removed the built-in proxies' dependency on ManagedPeer for native registration.
  • #12887 made the trimmable typemap the default and rejected legacy typemap selection.
  • #12890 removed the old native typemap/reflection-backed Android activation path.
  • #13018 removed the remaining reflection-backed JNI managers. Current Android runtimes use the trimmable managers.

All these prerequisites are merged. No supported current Android code-generation or activation path needs ManagedPeer.construct or JavaInterop1-style wrappers. Some obsolete helpers were still packaged or initialized through common runtime plumbing; removing that leftover machinery does not remove the codegen used by normal .NET for Android or .NET MAUI apps.

Each removed runtime component has an Android replacement or no supported consumer

Removed component Android justification / replacement
Java.Interop.ManagedPeer and net.dot.jni.ManagedPeer No supported generated JCW shape needs their reflective constructor/native-registration trampoline after #12133 and the trimmable migration. Remove initialization, feature-switch plumbing, built-in mappings, scanner exemptions, suppressions, and ProGuard retention together.
net.dot.jni.GCUserPeerable, jiAddManagedReference, and jiClearManagedReferences Android GC processing uses mono.android.IGCUserPeer. Migrate surviving handwritten Android fixtures to that contract; remove the obsolete runtime field, host/managed marker lookups, and matching managed/native initialization-struct field. Android GC/reference tracking is retained.
Java.Interop.JavaProxyObject and net.dot.jni.internal.JavaProxyObject No consumer remains in the active Android value-manager path. Keep the existing TrimmableJavaProxyObject and its Java identity semantics; the removed handwritten class is not its implementation.
Java.Interop.JavaProxyThrowable and net.dot.jni.internal.JavaProxyThrowable NativeAOT still used this exception-transport path, so it is replaced before removal, not simply deleted as unused. Route NativeAOT through the existing generated Android.Runtime.JavaProxyThrowable, as CoreCLR already did. No third TrimmableJavaProxyThrowable is introduced.
java-interop.jar After deleting its obsolete helpers, it has no remaining supported Android runtime content. Remove build/install wiring and references. Android fixture compilation references android.jar and java_runtime.jar instead.
Handwritten SelfRegistration, TestType, TestTypeTests, and the old Java.Interop CallNonvirtualDerived2 fixture These were unused or depended on the retired Java-to-managed constructor activation path; the obsolete test class was already excluded by capability guards. Keep the separate live generated-binding class with the same short name and preserve supported constructor/dispatch/exception scenarios.

The removed managed runtime implementation types are internal. Public JavaObject, JavaException, JniPeerMembers, and IJavaPeerable remain. The existing Android throwable and managed-object proxy implementations also remain. The explicit public configuration-property removal below is documented separately.

Supported construction and registration remain intact

Ordinary managed-to-Java construction does not need ManagedPeer: JavaObject allocates the Java object, associates the managed peer, then invokes the Java constructor through StartCreateInstance/FinishCreateInstance.

For Java-to-managed activation and callbacks, supported Android types use generated Android/trimmable dispatch and registration. The trimmable generator registers natives through TrimmableTypeMap.RegisterNativeMethods() / mono.android.Runtime.registerNatives(Class), independently of either removed JavaInterop1 enum value. No reflective compatibility shim is reintroduced.

Implementation changes

Generator and build surface

  • Remove CodeGenerationTarget.JavaInterop1, JavaPeerStyle.JavaInterop1, their generator/source-writer/JCW branches, the javainterop1 CLI selector, and unused SDK configuration selecting that shape.
  • Preserve XAJavaInterop1 output and the independent trimmable pipeline. This is not a rename of the typemap generator to another AndroidCodegenTarget value.
  • Reject retired AndroidCodegenTarget values with actionable XA4232 diagnostics for application and library builds. Retain the existing XA4240 rejection for retired _AndroidJcwCodegenTarget values, and test rejection rather than silently falling back.
  • Remove retired expected-output trees and conditional JAVA_INTEROP1/TODO_JAVA_INTEROP1/TryJavaInterop1 scaffolding. Move shared XML inputs/support sources from expected.ji into TestInputs so supported tests do not depend on a retired baseline directory.
  • Update directly related build-property/error documentation and the test catalog.

Shared Android exception handling

Add a shared internal AndroidExceptionManager and make CoreCLR and NativeAOT delegate wrapping/unwrapping to it. JniEnvironment.Exceptions.Throw(Exception) dispatches to the active runtime's RaisePendingException implementation.

The Android implementation wraps ordinary managed exceptions in the generated Android proxy, recognizes existing Java exception peers, and returns the original managed exception when its proxy returns through JNI or appears as a Java cause. Typed value-manager lookup distinguishes peer from unboxed exception: requests for Java.Lang.Throwable, JavaException, or IJavaPeerable return the Java peer; requests for compatible managed exception/object types return the original managed value.

Shared lookup releases incoming JNI references in an option-aware finally, on successful and failing extraction paths. Borrowed references remain valid; transferred local/global references are released exactly once. Permanent regressions cover typed lookup and four extraction failure stages across all three ownership modes.

The base JniRuntime.RaisePendingException still throws an existing JavaException. Android overrides supply managed-exception wrapping; a runtime without an implementation receives an explicit NotSupportedException instead of silently depending on a deleted proxy. The exception contract is documented.

Finish removing associated scaffolding

  • R8 roots: remove eight keep entries for the four retired Java helpers from proguard_typemap.cfg and proguard_typemap_coreclr.cfg, along with their misleading comments. Preserve Android bootstrap/GC roots. Update the existing R8 response tests to assert that neither retained-typemap mode emits retired-helper roots.
  • Registration metadata: retain JniAddNativeMethodRegistrationAttribute with warning-only [Obsolete], explicitly stating that it has no effect. Delete the unused public JniRuntime.CreationOptions.JniAddNativeMethodRegistrationAttributePresent property rather than retaining a no-op setting; record its accessor removals with *REMOVED* entries in PublicAPI.Unshipped.txt. Its unread internal copy and Android assignment are also gone.
  • Dead scanner/diagnostic code: remove the attribute-specific metadata pre-scan/flag, unused checker and commented rejection block, and unused XA4251 logger plumbing. Remove XA4251 from the English resource and its designer accessor. Other live metadata matching helpers remain.
  • AOT profile: delete the unreferenced custom.aotprofile ProjectTools fixture containing removed ManagedPeer.Init/.cctor records. Source searches found no direct or dynamically enumerated profile consumer; the resource wildcard was its remaining inclusion path. Rebuilt-assembly inspection confirms it is no longer embedded. Do not regenerate a profile for retired functionality.

No non-English .resx or generated localization files are modified.

Reconciliation with current main

Preserve #13019's local C# compilation and Java fixture glob under tests/Mono.Android-Tests/Java.Interop-Tests/, including its AndroidJavaSource exclusion to prevent duplicate compilation. Apply fixture deletions and IGCUserPeer conversions at the relocated paths; do not restore the external test source directory/globs. Keep the Android-specific GetThis implementation and comments.

Preserve upstream native host startup/logging and assembly-store changes. Preserve #12829's retained-key R8 pipeline; do not reintroduce DGML generation or alter new targets to accommodate the old validation setup.

Android coverage preserved and added

  • All 23 former expected.ji integration scenarios retain XA baselines and active runners. Restore Java_Lang_Object against XA and refresh its previously unused snapshot. Shared inputs are migrated, not deleted.
  • Retained Common/XA/NRT baselines cover 71 of 72 retired unit-baseline names; the unmatched WriteInterfaceFields snapshot already had no test consumer before cleanup.
  • Existing Android ConstructorActivationTests, generated-binding BindingTests.VirtualMethodBinding, and ExceptionTests.ManagedJavaManaged_FinallyExecuted retain activation, multi-level dispatch, and managed/Java/managed exceptions with Java finally execution.
  • Port array-mutation scenarios to a real Java fixture and generated XA bindings: int[], int[][], int[][][], with null/invalid/success results and complete copy-back assertions. These exercise generated JNIEnv.CopyArray, not handwritten replacement marshalling.
  • Add GC-peer contract coverage for surviving handwritten fixtures and the active managed-object proxy, invoking actual add/clear operations.
  • Add/strengthen exception identity, causes, unregistered throwable, Java-visible stack trace, callback identity, typed lookup, and throwable-array coverage.
  • Add 17 permanent regressions for typed/unboxed lookup and transferred-reference failures. All 17 passed on both runtimes after rebase, without skips.
  • Require the generated Android throwable in both runtimes' packaging/R8 tests and reject old proxies. Existing R8 response tests now also reject all four obsolete helper roots in both rule modes.

Most deletions are retired generated baselines and duplicate scaffolding, not supported Android runtime scenarios.

Local validation

Latest property/scanner/resource/profile cleanup

The following checks ran against the final cleanup sources with a rebuilt Debug Android SDK. These are distinct from the earlier complete post-rebase device runs below.

Command/check Result
bin/Debug/dotnet/dotnet build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -c Debug -v quiet -p:JavaCPath=/usr/bin/javac -p:JarPath=/usr/bin/jar and the same command with -c Release Passed, 0 warnings/errors in both configurations; public API removal records accepted.
bin/Debug/dotnet/dotnet build src/Mono.Android/Mono.Android.csproj -c Debug -v quiet Passed, 0 errors; no remaining source consumer of the deleted option.
bin/Debug/dotnet/dotnet build build-tools/scripts/Prepare.proj -t:BuildDotNet -c Debug -v quiet with Debug workload roots Passed, 0 errors; existing warnings. An initial overlapping scanner-test build locked a shared intermediate DLL (MSB4018); the SDK retry passed after that build completed, without source workarounds.
bin/Debug/dotnet/dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -c Debug -v quiet --filter 'FullyQualifiedName~TrimmableTypeMap|FullyQualifiedName~TypeMapProguardTargetsTests' 73 passed, 4 existing prerequisite skips, 0 failures. The filter uses the literal `
bin/Debug/dotnet/dotnet build src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj -c Debug -v quiet Passed, 0 errors; rebuilt task/resource and ProjectTools consumers.
Metadata inspection of rebuilt Debug/Release Java.Interop.dll and Xamarin.ProjectTools.dll Passed: option absent from both configurations, attribute retained with warning-only Obsolete, deleted profile absent from manifest resources.
English resource XML parse, active source/reference checks, git diff --check Passed; obsolete roots/diagnostic machinery absent and localization boundaries preserved.

This combined invocation passed 36 cases with 0 skips/failures: all R8 task cases, both retained-rule R8 integration modes, CoreCLR/NativeAOT throwable packaging, and ProGuard builds.

bin/Debug/dotnet/dotnet test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll \
  --filter 'FullyQualifiedName~R8Tests|Name~RetainedTypeMapRulesDriveR8|Name~TrimmableTypeMap_PackagesAndroidJavaProxyThrowable|Name~BuildProguardEnabledProject' \
  -v quiet

Full post-rebase runtime/device validation

Both configuration-specific SDKs were rebuilt after the rebase using Prepare.proj -t:BuildDotNet -c Debug/Release. Set matching DOTNETSDK_WORKLOAD_MANIFEST_ROOTS="$PWD/bin/<configuration>/lib/sdk-manifests" and DOTNETSDK_WORKLOAD_PACK_ROOTS="$PWD/bin/<configuration>/lib", with ANDROID_SERIAL=emulator-5554. Explicit SDK paths avoid dotnet-local.sh preferring an existing Release SDK over Debug.

# Debug workload roots, from repository root.
bin/Debug/dotnet/dotnet build -t:Install -c Debug \
  tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR \
  -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554' -v quiet
(
  cd tests/Mono.Android-Tests/Mono.Android-Tests
  ../../../bin/Debug/dotnet/dotnet test Mono.Android.NET-Tests.csproj \
    --no-build -c Debug --device emulator-5554 --report-trx \
    --results-directory ../../../bin/TestDebug/RebaseResults \
    -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR \
    -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554'
)

Passed: 897 CoreCLR tests, 7 existing skips, 0 failures, including relocated JNI tests and all 17 permanent regressions. This full CoreCLR suite also passed again during the subsequent deprecation step (results under ObsoleteRegistrationResults), before the latest host-only scanner/root/profile cleanup.

# Release workload roots, from repository root.
bin/Release/dotnet/dotnet build -t:Install -c Release \
  tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -m:1 -p:BuildInParallel=false \
  -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT \
  -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
  -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false \
  '-p:AdbTarget=-s emulator-5554' -v quiet
(
  cd tests/Mono.Android-Tests/Mono.Android-Tests
  ../../../bin/Release/dotnet/dotnet test Mono.Android.NET-Tests.csproj \
    --no-build -c Release --device emulator-5554 --report-trx \
    --results-directory ../../../bin/TestRelease/RebaseResults \
    -p:BuildInParallel=false \
    -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT \
    -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
    -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false \
    '-p:AdbTarget=-s emulator-5554'
)

Passed: 842 NativeAOT tests, 7 existing skips, 0 failures, including all 17 regressions. The current retained-key R8 pipeline produced 328 retained keys, including the Android throwable; r8.json confirmed shrinking enabled. No old native-output-path override or DGML workaround was used, and no trimming/R8/AOT was disabled. Build-only -m:1 is omitted from the test invocation because MTP rejects the forwarded --m option.

These complete device suites were not rerun for the latest scanner/resource/profile-only additions; the latest 36-case host integration run builds and verifies both CoreCLR and NativeAOT packages against the updated resources.

Other post-rebase tooling and generated-XA coverage

Command/check Recorded result
bin/Debug/dotnet/dotnet test external/Java.Interop/tests/generator-Tests/generator-Tests.csproj -c Debug -v quiet -p:JavaCPath=/usr/bin/javac -p:JarPath=/usr/bin/jar 478 passed, 0 skipped.
Same command shape for external/Java.Interop/tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers-Tests.csproj 50 passed, 0 skipped.
Same command shape for external/Java.Interop/tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj 18 passed, 0 skipped.
bin/Debug/dotnet/dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -c Debug -v quiet --filter FullyQualifiedName~GenerateTrimmableTypeMapTests 16 passed, 0 skipped after rebase; superseded by the broader latest 73-case run.
bin/Debug/dotnet/dotnet test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll --filter 'FullyQualifiedName~InvalidConfigTests|Name~TrimmableTypeMap_PackagesAndroidJavaProxyThrowable|Name~BuildProguardEnabledProject|Name~RetainedTypeMapRulesDriveR8' -v quiet 58 passed, 0 skips/failures after rebase; latest related resource/packaging coverage is the 36-case run above.

Generated-XA JCW device commands used tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj with build -t:Install, then matching test --no-build from its directory, --device emulator-5554 --report-trx, and configuration-specific workload roots:

  • CoreCLR: -c Debug -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR -p:AndroidCodegenTarget=XAJavaInterop1 -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554' — 46 passed, 2 existing explicit skips, including arrays, dispatch, and managed/Java/finally exceptions.
  • NativeAOT arrays: -c Release -p:BuildInParallel=false -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT -p:PublishAot=true -p:AndroidCodegenTarget=XAJavaInterop1 -p:RuntimeIdentifier=android-arm64 -p:IncludeCategories=ArrayMutation -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false '-p:AdbTarget=-s emulator-5554', plus build-only -m:1 — 3 passed, 0 skips/failures, with retained-key R8 trimming enabled.

Post-rebase artifact inspection verified all 10 Debug/Release runtime JARs omit the retired helpers, java-interop.jar is absent, the relocated fixture JAR omits deleted fixtures, and both signed runtime-test APKs contain the Android throwable without the retired helper descriptors.

Device coverage is arm64 only, not a complete ABI/device matrix. No CI success is claimed.

Compatibility and follow-ups

  • Old Java wrappers/handwritten code explicitly using the retired experimental ManagedPeer/JavaInterop1 contract must be regenerated/migrated. No compatibility shim is retained. #11635 tracks that question; [Java.Interop] Remove support for experimental JavaInterop1 codegen #11727 documents the removal direction. Normal .NET for Android/MAUI codegen remains intact.
  • The unused public JniAddNativeMethodRegistrationAttributePresent configuration property is intentionally removed; the attribute itself remains warning-only obsolete. Existing code setting that property must remove the setting, which no longer controlled any Android runtime behavior.
  • Developers should clean old intermediates when changing between pre-/post-removal trees. No permanent build invalidation/cleanup machinery is added solely for this transition.
  • Broader single-target generator-plumbing simplification and other legacy public API deprecations/removals are separate follow-ups, not mixed into this PR.
  • Successful shared exception lookup could additionally receive borrowed/global-reference success tests, complementing the existing failure matrix; this optional coverage is not claimed as implemented.

Related issues and PRs

  • #11727: experimental JavaInterop1 retirement tracker.
  • #11635: old ManagedPeer bytecode compatibility discussion.
  • #12133, #12152: merged NativeAOT/GC migration and proxy decoupling.
  • #12887, #12890: merged trimmable default and legacy activation/native typemap removal.
  • #13018: merged reflection-manager removal.
  • #13019: merged Android test relocation, reconciled here.
  • #12829: retained-key R8 pipeline replacing DGML, exercised by validation and preserved by this cleanup.
  • #11480: closed, unmerged earlier trimmable-only ManagedPeer removal; useful artifact-check precedent, not a claimed prerequisite. No performance numbers from it are claimed here.
  • dotnet/java-interop#1490: closed, unmerged ManagedPeer registration modernization; unnecessary once those Android consumers are removed.

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
  • Unit tests

@simonrozsival
simonrozsival force-pushed the simonrozsival-java-interop1-retirement branch from 68ad3fd to c7e07fc Compare October 7, 2026 09:56
simonrozsival added a commit that referenced this pull request Oct 8, 2026
After removing the unique Java.Interop proxy peers, the remaining object
and throwable aliases merge into the Mono.Android Release typemap.  No
separate Java.Interop typemap assembly is generated for these apps.

Require its absence from the CoreCLR APK and NativeAOT ILC inputs while
retaining expected Mono.Android/app typemaps and no-framework-export
assertions.  Leave Debug per-assembly typemap expectations unchanged.

Rebuild host tests and run both corrected cases plus Debug-with-trimming
packaging: four passed, one existing unsupported configuration skipped.

Context: #13030

Co-authored-by: Copilot App <[email protected]>
simonrozsival and others added 5 commits October 8, 2026 15:20
Remove JavaInterop1 binding and JCW generation after NativeAOT moved
to XAJavaInterop1 and Android moved to the trimmable typemap.  Retire
ManagedPeer, GCUserPeerable, the old proxy classes, and java-interop.jar.

Use Android.Runtime.JavaProxyThrowable for both Android runtimes.
Preserve managed exception identity, typed peer lookup, and transferred
JNI reference ownership.  Retain the XA generator scenarios and port
array mutation coverage to generated Android bindings.

Validated with rebuilt Debug/Release SDKs and CoreCLR/NativeAOT device
suites, including 17 regression cases for typed lookup and ownership.

Context: #11727
Context: #12133
Context: #13018

Co-authored-by: Copilot App <[email protected]>
Mark JniAddNativeMethodRegistrationAttribute and its CreationOptions
property obsolete with warning-only messages explaining that they have
no effect.  Retain the shipped declarations and property behavior for
compatibility, and remove the unread internal copy and Android setter.

Document generated Android native registration as the supported path.
Debug/Release Java.Interop builds and consumer warning/metadata checks
pass.  Rebuild the Debug Android SDK and run the CoreCLR device suite:
897 passed, 7 existing skips, no failures.

Co-authored-by: Copilot App <[email protected]>
Delete CreationOptions.JniAddNativeMethodRegistrationAttributePresent
instead of retaining an obsolete no-op property, as requested.  Record
its accessor removals in the unshipped API changes and update the docs.
The registration attribute remains warning-only obsolete.

Debug/Release builds pass.  Metadata checks confirm the property is gone
and the attribute remains available with its Obsolete annotation.

Co-authored-by: Copilot App <[email protected]>
Remove obsolete R8 roots for ManagedPeer, GCUserPeerable, and the two
handwritten proxies from both retained-typemap configurations.  Update
existing R8 tests to reject these roots in both modes.

Remove the dead registration-attribute scanner pass/checker and unused
XA4251 logging/resource plumbing.  Delete the unreferenced embedded AOT
profile fixture that still contains ManagedPeer records.  Leave generated
localization files and the active Android bootstrap roots unchanged.

The rebuilt SDK passes.  Scanner/typemap regressions pass 73 cases with
4 existing skips.  R8 and CoreCLR/NativeAOT packaging tests pass 36 cases
with no skips.  Metadata inspection confirms the profile is not embedded.

Co-authored-by: Copilot App <[email protected]>
After removing the unique Java.Interop proxy peers, the remaining object
and throwable aliases merge into the Mono.Android Release typemap.  No
separate Java.Interop typemap assembly is generated for these apps.

Require its absence from the CoreCLR APK and NativeAOT ILC inputs while
retaining expected Mono.Android/app typemaps and no-framework-export
assertions.  Leave Debug per-assembly typemap expectations unchanged.

Rebuild host tests and run both corrected cases plus Debug-with-trimming
packaging: four passed, one existing unsupported configuration skipped.

Context: #13030

Co-authored-by: Copilot App <[email protected]>
@simonrozsival
simonrozsival force-pushed the simonrozsival-java-interop1-retirement branch from 78313d8 to 656e253 Compare October 8, 2026 13:29
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

✅ Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #13030

@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.

✅ LGTM — 0 blocking issues, 0 warnings, 1 suggestion.

The retirement is comprehensive across generator/codegen, runtime/native plumbing, packaging, API compatibility records, documentation, and tests. The shared Android exception path preserves JNI reference ownership and managed-exception identity, while the trimmable value-manager changes retain the Android-specific proxy behavior removed from the base Java.Interop layer. Azure DevOps build #1628115 and all 42 reported checks passed.

Generated by Android PR Reviewer for #13030 · copilot · gpt56 · 256.7 AIC · ⌖ 11.1 AIC · ⊞ 26.4K
Comment /review to run again

Comment thread build-tools/scripts/Jar.targets Outdated
<_RuntimeJar_InTree>$([System.IO.Path]::GetFullPath ('$(XAInstallPrefix)'))\xbuild\Xamarin\Android\java_runtime.jar</_RuntimeJar_InTree>
<_RuntimeJar_System Condition=" '$(_XamarinAndroidMSBuildDirectory)' != '' ">$(_XamarinAndroidMSBuildDirectory)\java_runtime.jar</_RuntimeJar_System>
<_JavaRuntimeJarPath Condition=" Exists($(_RuntimeJar_InTree)) ">$(_RuntimeJar_InTree)</_JavaRuntimeJarPath>
<_JavaRuntimeJarPath Condition=" '$(_JavaRuntimeJarPath)' == '' ">$(_RuntimeJar_System)</_JavaRuntimeJarPath>

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.

🤖 💡 MSBuild targets — Consider gating this fallback with Exists($(_RuntimeJar_System)) (or changing the error below to also check !Exists('$(_JavaRuntimeJarPath)')). When _XamarinAndroidMSBuildDirectory is set but the runtime JAR is missing, this assignment makes the property nonempty, so the intended diagnostic is skipped and javac later reports misleading missing-package errors instead.

Rule: Validate file paths before use

@simonrozsival
simonrozsival marked this pull request as ready for review October 9, 2026 11:01
Copilot AI balanced review requested due to automatic review settings October 9, 2026 11:01
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Oct 9, 2026
@simonrozsival

Copy link
Copy Markdown
Member Author

@dalexsoto review

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.

🔵 Needs a closer look

The cross-runtime, native, generator, and public API cleanup is extensive, and the latest CI run remains in progress.

1 open finding
What changed in this PR

Retires experimental JavaInterop1 codegen/runtime infrastructure while preserving XAJavaInterop1 and trimmable Android pipelines.

Changes:

  • Removes legacy generators, proxies, JARs, baselines, and GC plumbing.
  • Unifies CoreCLR/NativeAOT exception transport.
  • Adds validation and regression coverage.
File area Description
external/​Java.Interop/​** Removes JavaInterop1 APIs, codegen, runtime helpers, and baselines.
src/​Mono.Android/​** Adds shared Android exception handling and GC cleanup.
src/​native/​** Removes obsolete initialization fields.
src/​java-runtime/​** Removes retired proxy classes and packaging.
src/​*Build.Tasks/​** Adds validation and updates R8/packaging behavior.
tests/​** Migrates fixtures and expands exception, ownership, GC, and array coverage.
Documentation/​** Documents supported targets and exception behavior.
build-tools/​** Removes obsolete JAR installation/build wiring.

🧠 Review effort: Balanced

Comment thread build-tools/scripts/Jar.targets Outdated
Co-authored-by: Copilot Autofix powered by AI <[email protected]>

@dalexsoto dalexsoto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The retirement is consistent across supported XA/trimmable generation, CoreCLR/NativeAOT exception and peer transport, and SDK packaging. The supported integration scenarios remain covered, and the runtime-JAR fallback now surfaces a missing SDK JAR correctly. The complete current-change and end-to-end review leaves no blocking issue.

@simonrozsival
simonrozsival merged commit 338725e into main Oct 9, 2026
42 checks passed
@simonrozsival
simonrozsival deleted the simonrozsival-java-interop1-retirement branch October 9, 2026 21:02
simonrozsival added a commit that referenced this pull request Oct 10, 2026
Small, independently mergeable .NET 12 cleanup follow-up to [#13030](#13030). Remove only internal `JniRuntime.JniValueManager.GetRuntimeType()` and `JniPeerMembers.JniInstanceMethods.AllocObject()` / `NewObject()` (two files, 26 deleted lines).

Semantic references contain only the declarations. Source, generators/templates, reflection strings, conditional sources, friend-assembly tests, and API records were checked for consumers. Public JNI allocation APIs, `StartCreateInstance()` / `FinishCreateInstance()`, constructor caches, `ActivatePeer`, expression marshalling, type management, and ownership logic are unchanged. No framework/package/API-gate upgrade is included.
simonrozsival added a commit that referenced this pull request Oct 10, 2026
Follow-up for .NET 12 to [#13030](#13030). With `XAJavaInterop1` now the only supported backend, the binding generator no longer needs to carry a target through its symbol table or indirect generator construction.

Remove internal target storage/propagation, construct the generator directly, and remove its unused inheritance hooks. Keep the CLI selector, retired-target rejection, options validation, and public target-taking constructors; the compatibility constructors now forward to target-free overloads and are marked obsolete.

Collapse single-target test scaffolding without adding or removing existing test cases. Nullable and non-nullable baselines remain distinct, and all 192 generated files captured from the original suite remain byte-for-byte identical. Changes are confined to the binding generator and its tests.

**Local validation**

- `dotnet build external/Java.Interop/tools/generator/generator.csproj -v minimal` — passed, 0 warnings/errors.
- `dotnet test external/Java.Interop/tests/generator-Tests/generator-Tests.csproj -v minimal` — passed, 478/478, 0 skipped; same count as the pre-change baseline.
- Generated-output SHA-256 comparison — passed for all 192 files, including NRT baselines. Direct CLI checks also confirmed identical default/explicit XA output and rejection of `JavaInterop1`.
- `make prepare && make all` — preparation passed; the first build hit a bootstrap evaluation failure where compilation lacked generated `ANDROID_*` constants. Re-running unchanged `make all` compiled successfully, then reached the known missing API 37.1 reference-pack bootstrap requirement.
- `./dotnet-local.sh build Microsoft.Android.slnx -t:BuildExtraApiLevels -c Debug -v quiet` — passed.
- `./dotnet-local.sh build build-tools/create-packs/Microsoft.Android.Sdk.proj -t:ConfigureLocalWorkload -c Debug -v quiet` — passed.
- Final `make all` — passed against this branch's own rebuilt SDK, without source/build-property workarounds.
- `./dotnet-local.sh test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll --filter 'FullyQualifiedName~BindingBuildTest&Name~DotNetBuildBinding|FullyQualifiedName~BindingBuildTest&Name~BindByteArrayInMethodParameter|FullyQualifiedName~BindingBuildTest&Name~NullableReferenceTypes|FullyQualifiedName~BindingBuildTest&Name~BindDefaultInterfaceMethods|FullyQualifiedName~BindingBuildTest&Name~BindingLibraryIncremental'` — passed, 10/10, 0 skipped; CoreCLR and NativeAOT binding coverage.

----

Pull Request
[title](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-summary) and
[description](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-body)
should follow the
[`commit-messages.md` workflow documentation](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md), and in particular should include:

- [x] Useful description of *why the change is necessary*.
- [ ] Links to issues fixed
- [x] Unit tests
simonrozsival added a commit that referenced this pull request Oct 10, 2026
…13055)

Follow-up to [#13030](#13030).

`Xamarin.Android.Tasks.AssemblyModifierPipeline` still parses a codegen
target and copies it into `StepContext.CodeGenerationTarget`, but no
assembly-rewriting step reads that value after JavaInterop1 retirement.
Remove the unused field, context property, parser, and task-argument
propagation.  Also remove `$(_AndroidJcwCodegenTarget)` from the build
properties cache, since it no longer affects assembly rewriting.

Keep the `CodeGenerationTarget` task input as a no-op so explicit external
MSBuild task invocations remain compatible.  Keep `JavaPeerStyle`, codegen
defaults, and the XA4232/XA4240 retired-value diagnostics unchanged.
Assembly-copy/rewrite logic and all other cache entries are untouched.

Only four production files change, with 19 deletions and no additions.
No tests are added or modified, as requested.

### Local validation

All builds and tests use this branch's locally built SDK.

| Command | Result |
| --- | --- |
| `make prepare && make all` | Preparation and solution compilation passed.  Initial workload configuration failed because the extra API 37.1 reference assembly had not been built; resolved by the existing targets below. |
| `./dotnet-local.sh build Microsoft.Android.slnx -t:BuildExtraApiLevels -c Debug -v quiet` | Passed: 0 errors. |
| `./dotnet-local.sh build build-tools/create-packs/Microsoft.Android.Sdk.proj -t:ConfigureLocalWorkload -c Debug -v quiet` | Passed: 0 warnings, 0 errors. |
| `./dotnet-local.sh build src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj -c Debug --no-restore -v quiet` | Passed: 38 existing warnings, 0 errors. |
| `./dotnet-local.sh test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll --filter 'FullyQualifiedName~LinkerTests\|Name~UnsupportedCodegenTargetIsRejected\|Name~UnsupportedJcwCodegenTargetIsRejected'` | Passed: 43 passed, 4 skipped, 0 failed. |
| `git diff --check` | Passed. |

The four skipped cases are existing intentional `Assert.Ignore` cases,
not missing prerequisites:

- `LinkDescription(False,CoreCLR)`: CoreCLR does not support builds without assembly stores.
- `RemoveDesigner(False,CoreCLR)`: CoreCLR supports only assembly stores.
- `WarnAboutAppDomains((True, CoreCLR))`: Release-case trimmer-warning issue [dotnet/linker#2982](dotnet/linker#2982).
- `WarnAboutAppDomains((True, NativeAOT))`: The same existing Release-case trimmer-warning issue.

----

Pull Request
[title](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-summary) and
[description](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-body)
should follow the
[`commit-messages.md` workflow documentation](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md), and in particular should include:

- [x] Useful description of *why the change is necessary*.
- [ ] Links to issues fixed
- [x] Unit tests
simonrozsival added a commit that referenced this pull request Oct 10, 2026
## Summary

Follow up on [#13030](#13030) by retiring the remaining obsolete expression-marshaling and activation contracts. **This is an intentional .NET 12 source/binary API break, without compatibility stubs.** Supported Android interop uses generated callbacks and trimmable typemap activation; source searches found no consumer of the removed expression pipeline or `ActivatePeer`.

Remove:
- `JniRuntime.JniMarshalMemberBuilder`, its members, the runtime `MarshalMemberBuilder` property, and `CreationOptions.MarshalMemberBuilder` / `UseMarshalMemberBuilder`.
- `Java.Interop.Expressions.JniValueMarshalerContext`, the three expression-generation hooks on `JniValueMarshaler`, and their expression-only helpers and primitive/string/array overrides.
- The expression-only `ColorValueMarshaler` and internal `IJavaObjectValueMarshaler`, including their referencing attributes.
- The unused `JniValueManager.ActivatePeer` contract and its throwing Android/sample overrides.

Retain non-expression `JniValueMarshaler` / argument-state conversion, primitive/string/array functionality, `CreateLocalObjectReferenceArgument`, peer/reference ownership, generated callbacks, and trimmable activation. Keep `GetRuntimeType`, `JniInstanceMethods` allocation helpers, and unrelated public JNI/type-signature APIs unchanged.

Update both T4 inputs and checked-in generated files. Remove stale `DynamicallyAccessedMembers` requirements from all eight primitive-array `CreateMarshaledValue` methods: their remaining conversion path constructs a known wrapper and uses type equality/assignability, not reflective activation of the requested target type. Record contract removals through `PublicAPI.Unshipped` entries and the existing Mono.Android ApiCompat baseline mechanism. Replace the obsolete architecture proposal with current behavior and add migration notes. **No new tests are included, per request.**

## Validation

All SDK/runtime validation uses this branch's own Release build and repository-pinned SDK `12.0.100-alpha.1.26480.102`, not sibling outputs. Framework paths/parameters below reflect the current checkout's build configuration; this change is intended for .NET 12.

| Command | Result |
| --- | --- |
| `make prepare CONFIGURATION=Release` | Passed. |
| `./dotnet-local.sh build build-tools/scripts/Prepare.proj -t:BuildExtraApiLevels -c Release -v minimal` | Passed; built API 37.1/37.2 and ran their API gates. |
| `./dotnet-local.sh build build-tools/create-packs/Microsoft.Android.Sdk.proj -t:ConfigureLocalWorkload -c Release -v minimal` | Passed. |
| `make all CONFIGURATION=Release` | Passed after completing additional-API bootstrap; initial workload setup lacked the 37.1 reference assembly. |
| `dotnet build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -c Release -v minimal -p:DotNetTargetFrameworkVersion=11.0` | Passed, including public API analyzer: zero warnings/errors. |
| `./dotnet-local.sh build src/Mono.Android/Mono.Android.csproj -c Release -v minimal -p:DisableApiCompatibilityCheck=False -m:1` | Passed, including stable reference ApiCompat; existing compiler/API-baseline warnings remain. |
| `dotnet test external/Java.Interop/tests/generator-Tests/generator-Tests.csproj -c Release -v minimal` | 478 passed, zero failures/skips. |
| `dotnet test external/Java.Interop/tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers-Tests.csproj -c Release -v minimal` | 50 passed, zero failures/skips. |

Host integration command:

```sh
./dotnet-local.sh test bin/TestRelease/net10.0/Xamarin.Android.Build.Tests.dll \
  --filter 'FullyQualifiedName~Build_WithTrimmableTypeMap_Succeeds|FullyQualifiedName~Build_TrimmableTypeMap_WithCollectionExport_Succeeds|FullyQualifiedName~Build_TrimmableTypeMap_UsesMonoAndroidImplementationMetadata|FullyQualifiedName~BindingCallbackFormatSupportsDefaultConsumer' \
  --logger 'trx;LogFileName=expression-retirement-host.trx' -v minimal
```

**Passed: 19/19**, covering CoreCLR/NativeAOT typemap builds, generated callbacks, collection exports, and implementation metadata.

Device install commands, on exclusively leased `emulator-5554` (arm64, API 36):

```sh
ANDROID_SERIAL=emulator-5554 ./dotnet-local.sh build -t:Install \
  tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -c Release -p:PublishAot=false -p:RuntimeIdentifier=android-arm64 \
  '-p:AdbTarget=-s emulator-5554' -m:1 -v minimal

ANDROID_SERIAL=emulator-5554 ./dotnet-local.sh build -t:Install \
  tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -c Release -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
  '-p:AdbTarget=-s emulator-5554' -m:1 -v minimal
```

Both installs passed. Each install was followed by its matching command below, from `tests/Mono.Android-Tests/Mono.Android-Tests`:

```sh
ANDROID_SERIAL=emulator-5554 ../../../dotnet-local.sh test Mono.Android.NET-Tests.csproj \
  --no-build -c Release -p:PublishAot=false -p:RuntimeIdentifier=android-arm64 \
  '-p:AdbTarget=-s emulator-5554' --report-trx \
  --results-directory ../../../bin/TestRelease/TestResults/expression-retirement-coreclr

ANDROID_SERIAL=emulator-5554 ../../../dotnet-local.sh test Mono.Android.NET-Tests.csproj \
  --no-build -c Release -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
  '-p:AdbTarget=-s emulator-5554' --report-trx \
  --results-directory ../../../bin/TestRelease/TestResults/expression-retirement-nativeaot
```

**CoreCLR: 924 passed, 7 existing skips, zero failures. NativeAOT: 869 passed, 7 existing skips, zero failures.** TRX results confirm JNI/primitive and object arrays, constructors, callbacks/exports, exceptions, peer control blocks, and reference ownership, including all 38 constructor activation tests and 58 transferred-reference tests on both runtimes. Only the test package was uninstalled afterward.

Both T4 templates regenerate the checked-in files exactly; `git diff --check` passes.

### Review follow-up

The runtime/SDK test results above were collected for commit `1251576d3d98f093ca1879314e12f7c97771091f`. Follow-up commit `ce74deda0408d0b8268adc8437d07f66a5d6627c` only removes the stale primitive-array trimming annotations and their unused constant.

`./dotnet-local.sh build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -c Release -v minimal -p:DotNetTargetFrameworkVersion=11.0 -p:EnableTrimAnalyzer=true -m:1` **passed with zero warnings/errors**, including API and trimming analyzers. Regeneration matches exactly. IL comparison confirmed all 1,333 emitted method bodies remain identical after normalizing RVA addresses and compiler-generated iterator numbering; all eight public conversion APIs remain and their stale parameter attributes are absent. Full SDK/device suites were not rerun for this metadata-only follow-up.

### Standalone NativeAOT sample

`./dotnet-local.sh build external/Java.Interop/samples/Hello-NativeAOTFromJNI/Hello-NativeAOTFromJNI.csproj -c Release -r osx-arm64 -v minimal -p:DotNetTargetFrameworkVersion=11.0 -p:RestoreConfigFile="$PWD/NuGet.Config" -m:1` passes. Running the compiled NativeAOT library from Java passes and returns `Hello from .NET NativeAOT!`.

Optional `publish` remains **failed with MSB3030 in the unchanged `_NativeLibRequiresLibPrefix` target**: it expects `Hello-NativeAOTFromJNI.dylib`, but the pinned SDK emits `libHello-NativeAOTFromJNI.dylib`. This is not claimed to be baseline-proven; no unrelated sample naming-target fix is included. The sample build and actual JNI execution validate the changed override removal.

----

Pull Request
[title](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-summary) and
[description](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-body)
should follow the
[`commit-messages.md` workflow documentation](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md), and in particular should include:

- [x] Useful description of *why the change is necessary*.
- [ ] Links to issues fixed
- [x] Unit tests

No issue is closed. Existing unit/integration/runtime coverage was run; no new tests were added.
simonrozsival added a commit that referenced this pull request Oct 10, 2026
)

Follow up to [#13030](#13030).

JavaInterop1 retirement leaves `java-interop-mono.cc` without any code and
`java-interop-mono.h` with only Mono header imports.  Native validation still
compiles that empty adapter and downloads a host Mono runtime pack pinned
to 8.0.13 solely for those unused headers.

Remove the empty adapter/header, CMake source/include wiring, Mono path
properties and arguments, and runtime-pack download.  Also remove the
unused Mono configuration overrides, dead `MonoInfo.props` import, and
orphaned `mono.mk` file.  No tracked reference, dynamic/wildcard include,
or documented entry point consumes that Make fragment.

Keep the native validation project and compilation of the shared dlfcn,
utility, and core sources.  Archive paths, native source/header inputs,
incremental behavior, and clean behavior remain unchanged.  Generic native
toolchain scripts and Android runtime/native-host CMake are untouched.

The third-party-notice audit requires no changes: the removed runtime pack
is Microsoft-owned and build-only, and the Java.Interop notice remains.

### Local validation

All commands below passed on macOS arm64; builds reported zero warnings
and errors.  For the isolated builds, `NUGET_PACKAGES` and
`NUGET_HTTP_CACHE_PATH` pointed to fresh, private cache directories without
removing or modifying shared/global packages.

| Command | Result |
| --- | --- |
| `dotnet restore external/Java.Interop/src/java-interop/java-interop.csproj -p:BaseIntermediateOutputPath=obj/no-mono/ -v:minimal` | Passed; isolated cache contains only NoTargets and .NET analyzers, with no Mono runtime pack. |
| `dotnet build external/Java.Interop/src/java-interop/java-interop.csproj --no-restore -p:BaseIntermediateOutputPath=obj/no-mono/ -v:minimal` | Passed; Debug arm64 archive contains exactly the three active source objects. |
| `dotnet build external/Java.Interop/src/java-interop/java-interop.csproj -c Release -p:BaseIntermediateOutputPath=obj/no-mono/ -v:minimal` | Passed; Release arm64 archive. |
| `dotnet build external/Java.Interop/src/java-interop/java-interop.csproj -p:BaseIntermediateOutputPath=obj/no-mono-x64/ -p:NETCoreSdkRuntimeIdentifier=osx-x64 -v:minimal` | Passed; x86_64 archive verified with `lipo`. |
| `dotnet clean external/Java.Interop/src/java-interop/java-interop.csproj -v:minimal` | Passed; default validation archive removed. |
| `dotnet build external/Java.Interop/src/java-interop/java-interop.csproj --no-restore -v:minimal` | Passed after clean and on the unchanged subsequent build; also passed after deleting `mono.mk`. |

Additional local checks passed: all three source files, four headers,
CMakeLists.txt, the project, and its targets invalidate `_BuildLibs` when
touched; unchanged builds skip that target and preserve the archive
mtime; clean and missing-output builds recreate the archive.  Fresh
CMake/Ninja configuration, output-directory override, incremental build,
and clean/rebuild also passed.  Restore graphs, active compiler header
dependencies, and compile commands contain no Mono dependency.

Ad hoc native smoke checks linked against the arm64 and x64 archives and
passed strdup/free and dynamic-library load/symbol/close success and error
cases; x64 execution used Rosetta.  Edited XML and `git diff --check` passed.
No new unit-test infrastructure is added for this unused-code removal.
Windows/MSVC and Linux hosts were unavailable locally and remain untested.
No full Android SDK/device build was run: active runtime sources and Android
CMake behavior are unchanged.

----

Pull Request
[title](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-summary) and
[description](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-body)
should follow the
[`commit-messages.md` workflow documentation](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md), and in particular should include:

- [x] Useful description of *why the change is necessary*.
- [ ] Links to issues fixed
- [ ] Unit tests
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