Skip to content

Fix XALNS7015: run FixAbstractMethodsStep before R2R in non-trimmed CoreCLR builds#11027

Draft
simonrozsival wants to merge 4 commits intomainfrom
fix/11025-r2r-abstract-methods
Draft

Fix XALNS7015: run FixAbstractMethodsStep before R2R in non-trimmed CoreCLR builds#11027
simonrozsival wants to merge 4 commits intomainfrom
fix/11025-r2r-abstract-methods

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

Summary

Fixes #11025

In non-trimmed CoreCLR Release builds, PublishReadyToRun=true causes the inner build to compile IL assemblies to R2R images (via crossgen2) before the outer build's _LinkAssembliesNoShrink target runs. R2R assemblies have the ILONLY PE flag cleared, so when FixAbstractMethodsStep detects a missing abstract method and marks the assembly modified, SaveChangedAssemblyStep calls assembly.Write() via Mono.Cecil, which throws:

NotSupportedException: Writing mixed-mode assemblies is not supported

Changes

Fix: run FixAbstractMethodsStep before R2R compilation

The fix mirrors the trimmed build path (_PostTrimmingPipeline runs AfterTargets="ILLink", before crossgen2):

  • PostTrimmingPipeline.cs: Add FixAbstractMethods bool property; when true, prepend FixAbstractMethodsStep to the pipeline steps
  • Microsoft.Android.Sdk.TypeMap.LlvmIr.targets: Add _LinkAssembliesNoShrinkPreR2R target that runs PostTrimmingPipeline with FixAbstractMethods=true in the inner build, BeforeTargets=ComputeFilesToPublish (before crossgen2)

Bug fix: StepContext assembly flags

  • PostTrimmingPipeline.cs: Fix StepContext not marking assemblies as Android user assemblies — FixAbstractMethodsStep.ProcessAssembly guards on context.IsAndroidUserAssembly, which was always false

Tests

  • Remove Assert.Ignore workarounds in BuildTest.SimilarAndroidXAssemblyNames and LinkerTests.AndroidAddKeepAlives for the CoreCLR Release case
  • Add unit test verifying that a Cecil-crafted binding assembly with a missing abstract method gets a stub injected by PostTrimmingPipeline

simonrozsival and others added 2 commits March 26, 2026 15:01
…oreCLR builds

Fixes #11025

In non-trimmed CoreCLR Release builds, `PublishReadyToRun=true` causes the
inner build to compile IL assemblies to R2R images (via crossgen2) before
the outer build's `_LinkAssembliesNoShrink` target runs. R2R assemblies
have the ILONLY PE flag cleared, so when `FixAbstractMethodsStep` detects
a missing abstract method and marks the assembly modified,
`SaveChangedAssemblyStep` calls `assembly.Write()` via Mono.Cecil, which
throws:

  NotSupportedException: Writing mixed-mode assemblies is not supported

The fix mirrors the trimmed build path (`_PostTrimmingPipeline` runs
AfterTargets="ILLink", before crossgen2): add a new
`_LinkAssembliesNoShrinkPreR2R` target that runs `PostTrimmingPipeline`
with `FixAbstractMethods=true` in the inner build, BeforeTargets=
"ComputeFilesToPublish" (i.e., before crossgen2). Once the IL is fixed
before R2R, the outer build's `_LinkAssembliesNoShrink` finds no missing
abstract methods, `IsAssemblyModified` stays false, and Mono.Cecil never
writes R2R assemblies.

Changes:
- PostTrimmingPipeline.cs: add `FixAbstractMethods` bool property; when
  true, prepend `FixAbstractMethodsStep` (via `MSBuildLinkContext` +
  `EmptyMarkContext`) to the step list before `StripEmbeddedLibrariesStep`
- Microsoft.Android.Sdk.TypeMap.LlvmIr.targets: add
  `_LinkAssembliesNoShrinkPreR2R` target (Condition: non-trimmed + R2R,
  BeforeTargets="ComputeFilesToPublish")
- Remove Assert.Ignore workarounds in BuildTest.SimilarAndroidXAssemblyNames
  and LinkerTests.AndroidAddKeepAlives for the CoreCLR Release case
- Add unit test PostTrimmingPipeline_FixAbstractMethods_AddsStubForMissingMethod
  verifying that a Cecil-crafted binding assembly with a missing abstract
  method gets a stub injected by PostTrimmingPipeline

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

FixAbstractMethodsStep.ProcessAssembly guards on context.IsAndroidUserAssembly before
processing an assembly. PostTrimmingPipeline was creating a bare StepContext with both
IsAndroidAssembly and IsUserAssembly at their default (false), so the step was silently
skipping every assembly.

All assemblies in @(ResolvedFileToPublish) are non-framework user assemblies published
as part of an Android app, so setting both flags to true is correct. The step's own
MightNeedFix() guard (IsSubclassOf("Java.Lang.Object")) further filters to only
concrete Java binding types that actually need a stub.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 26, 2026 16:08
@simonrozsival simonrozsival marked this pull request as draft March 26, 2026 16:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes the CoreCLR Release (non-trimmed) PublishReadyToRun=true build failure where FixAbstractMethodsStep tries to rewrite an already-R2R’d assembly and Mono.Cecil throws NotSupportedException for mixed-mode output.

Changes:

  • Run PostTrimmingPipeline before crossgen2/ReadyToRun for both trimmed and non-trimmed PublishReadyToRun builds, enabling FixAbstractMethods only in the non-trimmed path.
  • Update PostTrimmingPipeline to optionally include FixAbstractMethodsStep and mark processed assemblies as Android/user assemblies in StepContext.
  • Re-enable previously ignored CoreCLR test cases and add a unit regression test for FixAbstractMethods behavior via PostTrimmingPipeline.

Reviewed changes

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

File Description
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs Adds a regression unit test validating PostTrimmingPipeline can inject a missing abstract-method stub.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs Removes the CoreCLR non-trimmed ignore workaround now that the pipeline should run pre-R2R.
src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs Adds FixAbstractMethods support and updates StepContext flags so FixAbstractMethodsStep can run.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets Extends _PostTrimmingPipeline to also run for non-trimmed R2R builds and wires FixAbstractMethods into the task call.

Comment on lines +83 to +89
var context = new StepContext (item, item) {
// All assemblies processed here are non-framework user assemblies published as part
// of an Android app — steps that guard on IsAndroidUserAssembly (e.g.
// FixAbstractMethodsStep) should run on every assembly in this list.
IsAndroidAssembly = true,
IsUserAssembly = true,
};
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

PostTrimmingPipeline creates a StepContext without setting IsMainAssembly, so when FixAbstractMethods is enabled the FixAbstractMethodsStep guard (context.IsMainAssembly || !context.IsAndroidUserAssembly) will treat the app’s main assembly as non-main and may patch it if it references Java.Lang.Object. Consider passing TargetName/AssemblyName into this task (or deriving it from item metadata) and setting context.IsMainAssembly accordingly, or filtering the input Assemblies list to exclude the main assembly when running FixAbstractMethods.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +86
// All assemblies processed here are non-framework user assemblies published as part
// of an Android app — steps that guard on IsAndroidUserAssembly (e.g.
// FixAbstractMethodsStep) should run on every assembly in this list.
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The comment here says all processed assemblies are “non-framework user assemblies”, but the target passes @(ResolvedFileToPublish) which typically includes framework assemblies (and often the app’s main assembly). Either update this comment to match reality, or adjust the inputs so the task only receives the intended subset.

Suggested change
// All assemblies processed here are non-framework user assemblies published as part
// of an Android app — steps that guard on IsAndroidUserAssembly (e.g.
// FixAbstractMethodsStep) should run on every assembly in this list.
// Assemblies processed here are those supplied by the calling target. We mark them as
// Android user assemblies so that steps which guard on IsAndroidUserAssembly (e.g.
// FixAbstractMethodsStep) will run on each assembly in this list.

Copilot uses AI. Check for mistakes.
Comment on lines +260 to +270
Condition=" '$(PublishTrimmed)' == 'true' or '$(PublishReadyToRun)' == 'true' ">
<PropertyGroup>
<_PostTrimmingPipelineFixAbstractMethods>false</_PostTrimmingPipelineFixAbstractMethods>
<_PostTrimmingPipelineFixAbstractMethods Condition=" '$(PublishTrimmed)' != 'true' ">true</_PostTrimmingPipelineFixAbstractMethods>
</PropertyGroup>
<ItemGroup>
<_PostTrimmingAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " />
</ItemGroup>
<PostTrimmingPipeline
Assemblies="@(_PostTrimmingAssembly)"
FixAbstractMethods="$(_PostTrimmingPipelineFixAbstractMethods)"
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

_PostTrimmingPipeline builds its Assemblies list from @(ResolvedFileToPublish), which likely includes the app’s main assembly. Combined with PostTrimmingPipeline currently not setting StepContext.IsMainAssembly, enabling FixAbstractMethods for non-trimmed R2R builds can cause FixAbstractMethodsStep to run on the main assembly too. Consider excluding the main assembly from _PostTrimmingAssembly (e.g., based on $(TargetName)/$(AssemblyName)), or pass TargetName into the task so it can set IsMainAssembly correctly.

Copilot uses AI. Check for mistakes.
Comment on lines +257 to +261
<Target Name="_PostTrimmingPipeline"
AfterTargets="ILLink"
Condition=" '$(PublishTrimmed)' == 'true' ">
BeforeTargets="CreateReadyToRunImages"
Condition=" '$(PublishTrimmed)' == 'true' or '$(PublishReadyToRun)' == 'true' ">
<PropertyGroup>
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

PR description mentions adding a new “_LinkAssembliesNoShrinkPreR2R” target, but the diff instead extends the existing _PostTrimmingPipeline target to cover non-trimmed ReadyToRun builds. Please update the PR description (or the implementation) so they match, since this affects how reviewers reason about target ordering and scope.

Copilot uses AI. Check for mistakes.
simonrozsival and others added 2 commits March 26, 2026 17:46
…build

Fixes #11025

The _LinkAssembliesNoShrink target (which runs FixAbstractMethodsStep,
AddKeepAlivesStep, etc.) previously only ran in the outer build — after
the inner Publish pipeline had already converted IL to R2R via crossgen2.
When FixAbstractMethodsStep found a missing abstract method and marked
the assembly modified, SaveChangedAssemblyStep tried to write via
Mono.Cecil, which threw NotSupportedException for R2R (mixed-mode)
assemblies.

The fix adds BeforeTargets="CreateReadyToRunImages" to
_LinkAssembliesNoShrink so it also fires in the inner build before
crossgen2.  In the inner build, @(ResolvedAssemblies) is empty (only
populated in the outer build), so _LinkAssembliesNoShrinkInputs now
also gathers assemblies from @(ResolvedFileToPublish) when running
inside _ComputeFilesToPublishForRuntimeIdentifiers.  Items are marked
with HasMonoAndroidReference=True so FixAbstractMethodsStep inspects
them; the step's own MightNeedFix() guard filters to only Java binding
types that actually need a stub.

In the outer build, _LinkAssembliesNoShrink still runs for the copy to
MonoAndroidIntermediateAssemblyDir.  Since the inner build already fixed
abstract methods, the outer build finds nothing to modify and just
copies.

Changes:
- Xamarin.Android.Common.targets: add BeforeTargets="CreateReadyToRunImages"
  to _LinkAssembliesNoShrink; adapt inputs/outputs for inner build context
- Revert PostTrimmingPipeline FixAbstractMethods changes (no longer needed)
- Revert _PostTrimmingPipeline target condition changes
- Remove Assert.Ignore workarounds in BuildTest.SimilarAndroidXAssemblyNames
  and LinkerTests.AndroidAddKeepAlives for the CoreCLR Release case

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove _LinkAssembliesNoShrink from the outer build entirely:

- Remove from _LinkAssemblies DependsOnTargets (outer build no longer invokes it)
- Add Condition to only run when _ComputeFilesToPublishForRuntimeIdentifiers=true
- Remove _LinkAssembliesNoShrinkInputs target (no longer needed)
- Inline _AllResolvedAssemblies population into _AfterILLinkAdditionalSteps
- Simplify _PrepareAssemblies: non-trimmed builds now use @(ResolvedAssemblies)
  directly (like trimmed builds) instead of mapping through
  MonoAndroidIntermediateAssemblyDir — the inner build already modified
  assemblies in-place before crossgen2

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XALNS7015: Writing mixed-mode assemblies is not supported when FixAbstractMethodsStep patches an R2R-compiled Java binding assembly (CoreCLR, .NET 11)

2 participants