diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs index 08a558de918..a6e5abbc0ac 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs @@ -160,7 +160,11 @@ protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCont void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination) { - var assembly = pipeline.Resolver.GetAssembly (source.ItemSpec); + // Use Load with the exact ItemSpec path to ensure the correct TFM version + // is loaded, rather than GetAssembly which strips the path and resolves by + // name through search directories (which may find wrong-TFM copies). + var assembly = pipeline.Resolver.Load (source.ItemSpec) + ?? throw new FileNotFoundException ($"Could not load assembly '{source.ItemSpec}'.", source.ItemSpec); var context = new StepContext (source, destination) { CodeGenerationTarget = codeGenerationTarget, diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildWithLibraryTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildWithLibraryTests.cs index ad947b5faf0..fb558936ae2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildWithLibraryTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildWithLibraryTests.cs @@ -895,5 +895,121 @@ public void DotNetLibraryAarChanges () } } + /// + /// Regression test for https://github.com/dotnet/android/issues/10858 + /// + /// When a solution has the structure: + /// App (net10.0-android) → CoreLib (net10.0) → MultiTfmLib (net10.0;net10.0-android) + /// + /// And MultiTfmLib contains Java-interop types (e.g. BroadcastReceiver) that only + /// exist in the net10.0-android TFM, the build tasks must load the Android-TFM assembly + /// and generate JCWs for those types. Previously, the net10.0 (non-Android) assembly + /// could be loaded instead, causing FindJavaObjectsStep to report "Found 0 Java types" + /// and producing empty .jlo.xml files. + /// + [Test] + public void MultiTfmTransitiveReference () + { + var path = Path.Combine ("temp", TestName); + var dotnetVersion = "net10.0"; + + // 1. Multi-TFM library (net10.0 + net10.0-android) with a BroadcastReceiver + var multiTfmLib = new DotNetStandard { + ProjectName = "MultiTfmLib", + Sdk = "Microsoft.NET.Sdk", + Sources = { + new BuildItem.Source ("MyReceiver.cs") { + TextContent = () => +@"#if ANDROID +using Android.Content; + +namespace MultiTfmLib +{ + public class MyReceiver : BroadcastReceiver + { + public override void OnReceive (Context? context, Intent? intent) { } + } +} +#endif +" + }, + new BuildItem.Source ("SharedClass.cs") { + TextContent = () => +@"namespace MultiTfmLib +{ + public class SharedClass + { + public string Name => ""Hello""; + } +} +" + }, + }, + }; + multiTfmLib.TargetFrameworks = $"{dotnetVersion};{dotnetVersion}-android"; + multiTfmLib.SetProperty ("Nullable", "enable"); + multiTfmLib.SetProperty ("AndroidGenerateResourceDesigner", "false"); + multiTfmLib.SetProperty ("SupportedOSPlatformVersion", "24", + "$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'"); + // 2.Non-Android library (net10.0 only) referencing the multi-TFM library + var coreLib = new DotNetStandard { + ProjectName = "CoreLib", + Sdk = "Microsoft.NET.Sdk", + Sources = { + new BuildItem.Source ("CoreClass.cs") { + TextContent = () => +@"namespace CoreLib +{ + public class CoreClass + { + public MultiTfmLib.SharedClass Shared => new (); + } +} +" + }, + }, + }; + coreLib.TargetFramework = dotnetVersion; + coreLib.AddReference (multiTfmLib); + + // 3. Android app that references CoreLib (NOT MultiTfmLib directly) + var app = new XamarinAndroidApplicationProject { + ProjectName = "MyApp", + Sources = { + new BuildItem.Source ("Usage.cs") { + TextContent = () => +@"public class Usage +{ + public CoreLib.CoreClass Core => new (); +} +" + }, + }, + }; + app.AddReference (coreLib); + + using var multiTfmBuilder = CreateDllBuilder (Path.Combine (path, multiTfmLib.ProjectName)); + Assert.IsTrue (multiTfmBuilder.Build (multiTfmLib), $"{multiTfmLib.ProjectName} should build"); + + using var coreBuilder = CreateDllBuilder (Path.Combine (path, coreLib.ProjectName)); + Assert.IsTrue (coreBuilder.Build (coreLib), $"{coreLib.ProjectName} should build"); + + using var appBuilder = CreateApkBuilder (Path.Combine (path, app.ProjectName)); + Assert.IsTrue (appBuilder.Build (app), $"{app.ProjectName} should build"); + + // Verify: MultiTfmLib.jlo.xml should NOT be empty (i.e. the assembly was scanned as an Android assembly) + var jloXml = appBuilder.Output.GetIntermediaryPath ( + Path.Combine ("android", "assets", "arm64-v8a", "MultiTfmLib.jlo.xml")); + FileAssert.Exists (jloXml); + + var jloXmlInfo = new FileInfo (jloXml); + Assert.IsTrue (jloXmlInfo.Length > 0, + "MultiTfmLib.jlo.xml should not be empty — the Android-TFM assembly was not loaded (wrong TFM loaded instead)"); + + var jloContent = File.ReadAllText (jloXml); + Assert.IsTrue (jloContent.Contains ("MyReceiver"), + $"MultiTfmLib.jlo.xml should contain the MyReceiver JCW type, but got: {jloContent}"); + } + } }