From 22f20015040507cd7d42702c103f2db22a7704b8 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 4 Jan 2023 11:34:34 -0600 Subject: [PATCH] [Xamarin.Android.Build.Tasks] fix cases of missing `@(Reference)` (#7642) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/dotnet/maui/issues/10154 Context? https://github.com/dotnet/maui/issues/11364 If you have a solution setup with: * `ApplicationA.csproj` has a `@(ProjectReference)` to `LibraryB.csproj`. * `LibraryB.csproj` which has a `@(Reference)` to `LibraryC.dll`, built by- * `LibraryC.csproj` The app will crash at runtime, due to a missing `LibraryC.dll`. The workaround is for `LibraryB.csproj` to use `@(ProjectReference)` to `LibraryC.csproj` instead of `@(Reference)` to `LibraryC.dll`. However, it appears the same situation works in a .NET 7 self-contained console app: % dotnet publish --self-contained -r win-x64 … % ls -1 .\bin\Debug\net7.0\win-x64\publish\LibraryC.dll LibraryC.dll The underlying issue appears to be due to [`$(_FindDependencies)`][0]: <_FindDependencies Condition="'$(BuildingProject)' != 'true' and '$(_ResolveReferenceDependencies)' != 'true'">false In the console app, `$(BuildingProject)`=true and `$(_FindDependencies)` is empty. In the Android app, `$(BuildingProject)`=false and `$(_FindDependencies)` is `false`. It appears that the `BuildOnlySettings` target *should* be running in Android apps when we do an "inner" build per `$(RuntimeIdentifier)`. Simply updating `_ComputeFilesToPublishForRuntimeIdentifiers` so that the `BuildOnlySettings` target is in `DependsOnTargets` fixes this. However, this also causes satellite assemblies to now be automatically found by the .NET SDK. Update `@(_AndroidResolvedSatellitePaths)` so that `@(ReferenceSatellitePaths)` is only included on Classic builds, preventing duplicate entries. [0]: https://github.com/dotnet/msbuild/blob/a2490dd3f78cce4abc8f9e6f1b5268437332818f/src/Tasks/Microsoft.Common.CurrentVersion.targets#L2322 --- ...oft.Android.Sdk.AssemblyResolution.targets | 2 +- .../Xamarin.Android.Build.Tests/XASdkTests.cs | 61 +++++++++++++++++++ .../Xamarin.Android.Common.targets | 3 +- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index 3ccabedc09d..e15b10a264f 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -44,7 +44,7 @@ _ResolveAssemblies MSBuild target. diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs index 68cb7728107..81f094ee126 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs @@ -1053,6 +1053,67 @@ public void DotNetIncremental ([Values (true, false)] bool isRelease, [Values (" } } + [Test] + public void ProjectDependencies ([Values(true, false)] bool projectReference) + { + // Setup dependencies App A -> Lib B -> Lib C + var path = Path.Combine ("temp", TestName); + + var libB = new XASdkProject (outputType: "Library") { + ProjectName = "LibraryB", + IsRelease = true, + }; + libB.Sources.Clear (); + libB.Sources.Add (new BuildItem.Source ("Foo.cs") { + TextContent = () => @"public class Foo { + public Foo () { + var bar = new Bar(); + } + }", + }); + + var libC = new XASdkProject (outputType: "Library") { + ProjectName = "LibraryC", + IsRelease = true, + }; + libC.Sources.Clear (); + libC.Sources.Add (new BuildItem.Source ("Bar.cs") { + TextContent = () => "public class Bar { }", + }); + + // Add a @(Reference) or @(ProjectReference) + if (projectReference) { + libB.AddReference (libC); + } else { + libB.OtherBuildItems.Add (new BuildItem.Reference ($@"..\{libC.ProjectName}\bin\Release\{libC.TargetFramework}\{libC.ProjectName}.dll")); + } + + // Build libraries + var libCBuilder = CreateDotNetBuilder (libC, Path.Combine (path, libC.ProjectName)); + Assert.IsTrue (libCBuilder.Build (), $"{libC.ProjectName} should succeed"); + var libBBuilder = CreateDotNetBuilder (libB, Path.Combine (path, libB.ProjectName)); + Assert.IsTrue (libBBuilder.Build (), $"{libB.ProjectName} should succeed"); + + var appA = new XASdkProject { + ProjectName = "AppA", + IsRelease = true, + Sources = { + new BuildItem.Source ("Bar.cs") { + TextContent = () => "public class Bar : Foo { }", + } + } + }; + appA.AddReference (libB); + var appBuilder = CreateDotNetBuilder (appA, Path.Combine (path, appA.ProjectName)); + Assert.IsTrue (appBuilder.Build (), $"{appA.ProjectName} should succeed"); + + var apkPath = Path.Combine (FullProjectDirectory, appA.OutputPath, $"{appA.PackageName}-Signed.apk"); + FileAssert.Exists (apkPath); + var helper = new ArchiveAssemblyHelper (apkPath); + helper.AssertContainsEntry ($"assemblies/{libB.ProjectName}.dll"); + helper.AssertContainsEntry ($"assemblies/{libC.ProjectName}.dll"); + } + [Test] public void SignAndroidPackage () { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 21017e14291..9b0386cc2b4 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1973,7 +1973,8 @@ because xbuild doesn't support framework reference assemblies. DependsOnTargets="_ResolveAssemblies" > - <_AndroidResolvedSatellitePaths Include="@(ReferenceSatellitePaths)" /> + + <_AndroidResolvedSatellitePaths Condition=" '$(UsingAndroidNETSdk)' != 'true' " Include="@(ReferenceSatellitePaths)" /> <_AndroidResolvedSatellitePaths Include="@(IntermediateSatelliteAssembliesWithTargetPath->'$(OutDir)%(Culture)\$(TargetName).resources.dll')" />