Skip to content

Fix wrong-TFM assembly loading producing empty JCW .jlo.xml files#11208

Open
jonathanpeppers wants to merge 1 commit intomainfrom
jonathanpeppers/fix-multi-tfm-jcw-generation
Open

Fix wrong-TFM assembly loading producing empty JCW .jlo.xml files#11208
jonathanpeppers wants to merge 1 commit intomainfrom
jonathanpeppers/fix-multi-tfm-jcw-generation

Conversation

@jonathanpeppers
Copy link
Copy Markdown
Member

Fix wrong-TFM assembly loading producing empty JCW .jlo.xml files

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

Copilot AI review requested due to automatic review settings April 24, 2026 15:01
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 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 ResolvedAssemblies into the DirectoryAssemblyResolver cache using their exact ItemSpec paths 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.

Comment thread src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs Outdated
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers/fix-multi-tfm-jcw-generation branch from 6b68eb6 to fc6eee8 Compare April 24, 2026 15:39
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>
@jonathanpeppers jonathanpeppers force-pushed the jonathanpeppers/fix-multi-tfm-jcw-generation branch from fc6eee8 to de65cd0 Compare April 24, 2026 15:40
Comment on lines +115 to +117
if (resolver.Load (assembly.ItemSpec) == null) {
Log.LogDebugMessage ($"Could not pre-load assembly '{assembly.ItemSpec}' into resolver cache.");
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This might regress build performance, but I'm retrying:

Image

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.

Wrong-TFM assembly loading, producing empty .jlo.xml files (0 JCW types)

2 participants