Fix wrong-TFM assembly loading producing empty JCW .jlo.xml files#11208
Open
jonathanpeppers wants to merge 1 commit intomainfrom
Open
Fix wrong-TFM assembly loading producing empty JCW .jlo.xml files#11208jonathanpeppers wants to merge 1 commit intomainfrom
jonathanpeppers wants to merge 1 commit intomainfrom
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes an issue where AssemblyModifierPipeline could load the wrong TFM variant of a multi-targeted assembly (by resolving by assembly name from search directories), leading to empty .jlo.xml outputs and missing JCW generation.
Changes:
- Pre-loads
ResolvedAssembliesinto theDirectoryAssemblyResolvercache using their exactItemSpecpaths to prevent wrong-TFM resolution. - Adds a regression test covering a transitive multi-TFM reference scenario (App → netX CoreLib → multi-TFM lib with Android-only Java types).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs | Preloads resolved assemblies into the resolver cache to ensure correct TFM assembly loading during pipeline execution. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildWithLibraryTests.cs | Adds a regression test reproducing wrong-TFM loading causing empty .jlo.xml files. |
6b68eb6 to
fc6eee8
Compare
In .NET 9, `GenerateJavaStubs` scanned for Java types using `XAJavaTypeScanner.GetJavaTypes`, which called `resolver.Load(asmItem.ItemSpec)` to load each assembly from its exact file path. This ensured the correct TFM version was always used. In ea399ed (#9893), JCW scanning was moved into `AssemblyModifierPipeline` as `FindJavaObjectsStep`. The pipeline's `RunPipeline` method uses `resolver.GetAssembly(source.ItemSpec)`, which strips the path and resolves by assembly **name** through search directories. When a multi-TFM library (e.g. `net11.0;net11.0-android`) is transitively referenced through a `net11.0`-only intermediary, the intermediary's output directory contains a CopyLocal'd `net11.0` copy of the assembly. If that directory appears in the resolver's search path before the correct `net11.0-android` directory, `GetAssembly` loads the wrong version — one without any Android types — producing empty `.jlo.xml` files with 0 JCW types. The fix pre-loads all `ResolvedAssemblies` into the resolver cache from their exact `ItemSpec` paths during resolver setup. This ensures that both `GetAssembly` (which checks the cache first) and Cecil's lazy reference resolution always find the correct TFM version. A regression test `MultiTfmTransitiveReference` reproduces the exact scenario: App -> CoreLib (net11.0) -> MultiTfmLib (net11.0; net11.0-android), where MultiTfmLib has an Android `BroadcastReceiver` behind `#if ANDROID`. Fixes #10858 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fc6eee8 to
de65cd0
Compare
jonathanpeppers
commented
Apr 24, 2026
Comment on lines
+115
to
+117
| if (resolver.Load (assembly.ItemSpec) == null) { | ||
| Log.LogDebugMessage ($"Could not pre-load assembly '{assembly.ItemSpec}' into resolver cache."); | ||
| } |
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fix wrong-TFM assembly loading producing empty JCW .jlo.xml files
In .NET 9,
GenerateJavaStubsscanned for Java types usingXAJavaTypeScanner.GetJavaTypes, which calledresolver.Load(asmItem.ItemSpec)to load each assembly from itsexact file path. This ensured the correct TFM version was always used.
In ea399ed (#9893), JCW scanning was moved into
AssemblyModifierPipelineasFindJavaObjectsStep. The pipeline'sRunPipelinemethod usesresolver.GetAssembly(source.ItemSpec),which strips the path and resolves by assembly name through search
directories. When a multi-TFM library (e.g.
net11.0;net11.0-android)is transitively referenced through a
net11.0-only intermediary, theintermediary's output directory contains a CopyLocal'd
net11.0copyof the assembly. If that directory appears in the resolver's search path
before the correct
net11.0-androiddirectory,GetAssemblyloadsthe wrong version — one without any Android types — producing empty
.jlo.xmlfiles with 0 JCW types.The fix pre-loads all
ResolvedAssembliesinto the resolver cachefrom their exact
ItemSpecpaths during resolver setup. This ensuresthat both
GetAssembly(which checks the cache first) and Cecil'slazy reference resolution always find the correct TFM version.
A regression test
MultiTfmTransitiveReferencereproduces the exactscenario: App -> CoreLib (net11.0) -> MultiTfmLib (net11.0;
net11.0-android), where MultiTfmLib has an Android
BroadcastReceiverbehind
#if ANDROID.Fixes #10858