Fix XALNS7015: run FixAbstractMethodsStep before R2R in non-trimmed CoreCLR builds#11027
Fix XALNS7015: run FixAbstractMethodsStep before R2R in non-trimmed CoreCLR builds#11027simonrozsival wants to merge 4 commits intomainfrom
Conversation
…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>
There was a problem hiding this comment.
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
PostTrimmingPipelinebefore crossgen2/ReadyToRun for both trimmed and non-trimmedPublishReadyToRunbuilds, enablingFixAbstractMethodsonly in the non-trimmed path. - Update
PostTrimmingPipelineto optionally includeFixAbstractMethodsStepand mark processed assemblies as Android/user assemblies inStepContext. - Re-enable previously ignored CoreCLR test cases and add a unit regression test for
FixAbstractMethodsbehavior viaPostTrimmingPipeline.
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. |
| 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, | ||
| }; |
There was a problem hiding this comment.
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.
| // 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. |
There was a problem hiding this comment.
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.
| // 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. |
| 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)" |
There was a problem hiding this comment.
_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
| <Target Name="_PostTrimmingPipeline" | ||
| AfterTargets="ILLink" | ||
| Condition=" '$(PublishTrimmed)' == 'true' "> | ||
| BeforeTargets="CreateReadyToRunImages" | ||
| Condition=" '$(PublishTrimmed)' == 'true' or '$(PublishReadyToRun)' == 'true' "> | ||
| <PropertyGroup> |
There was a problem hiding this comment.
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.
…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>
Summary
Fixes #11025
In non-trimmed CoreCLR Release builds,
PublishReadyToRun=truecauses the inner build to compile IL assemblies to R2R images (via crossgen2) before the outer build's_LinkAssembliesNoShrinktarget runs. R2R assemblies have the ILONLY PE flag cleared, so whenFixAbstractMethodsStepdetects a missing abstract method and marks the assembly modified,SaveChangedAssemblyStepcallsassembly.Write()via Mono.Cecil, which throws:Changes
Fix: run
FixAbstractMethodsStepbefore R2R compilationThe fix mirrors the trimmed build path (
_PostTrimmingPipelineruns AfterTargets="ILLink", before crossgen2):PostTrimmingPipeline.cs: AddFixAbstractMethodsbool property; when true, prependFixAbstractMethodsStepto the pipeline stepsMicrosoft.Android.Sdk.TypeMap.LlvmIr.targets: Add_LinkAssembliesNoShrinkPreR2Rtarget that runsPostTrimmingPipelinewithFixAbstractMethods=truein the inner build,BeforeTargets=ComputeFilesToPublish(before crossgen2)Bug fix:
StepContextassembly flagsPostTrimmingPipeline.cs: FixStepContextnot marking assemblies as Android user assemblies —FixAbstractMethodsStep.ProcessAssemblyguards oncontext.IsAndroidUserAssembly, which was alwaysfalseTests
Assert.Ignoreworkarounds inBuildTest.SimilarAndroidXAssemblyNamesandLinkerTests.AndroidAddKeepAlivesfor the CoreCLR Release casePostTrimmingPipeline