Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Xamarin.Android.Build.Tasks] Deploy changes to transitive reference assembly dependencies #4887

Merged
merged 1 commit into from
Aug 7, 2020

Conversation

brendanzagaeski
Copy link
Contributor

Fixes: https://developercommunity.visualstudio.com/content/problem/1086457/index.html
Context: 8f2ae24
Context: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/938468

If a Xamarin.Android app referenced a .NET Standard library that had
$(ProduceReferenceAssembly)=true, and that library in turn
referenced a second .NET Standard library, then changes to the second
library would not be deployed during incremental deployments in Visual
Studio unless the app project also directly referenced the second
library.

For additional context, note that as discussed in internal work item
938468, if a .NET Framework console application references a .NET
Standard library that has $(ProduceReferenceAssembly)=true, then
changes to the library are similarly missing in the running console app
after incremental builds in Visual Studio.

Although the problem in Xamarin.Android apps was similar to the .NET
Framework behavior, an important difference was that Xamarin.Android
apps did reflect changes correctly for libraries referenced directly by
the app. Only transitive references caused trouble in Xamarin.Android.

The reason direct references worked was that Visual Studio always ran
Xamarin.Android's Install target on each incremental deployment, and
the <ResolveAssemblies/> task in that target correctly located the
up-to-date assemblies for direct references.

For example, for the included build test, the <ResolveAssemblies/>
task resolved the Library1 reference to:

C:\Source\xamarin-android\bin\TestDebug\temp\TransitiveDependencyProduceReferenceAssembly\Library1\bin\Debug\netstandard2.0\Library1.dll

In contrast, the <ResolveAssemblies/> task resolved the Library2
reference to:

C:\Source\xamarin-android\bin\TestDebug\temp\TransitiveDependencyProduceReferenceAssembly\App\bin\Debug\Library2.dll

This was problematic because that copy of the library was left over from
the previous deployment. In a sense, this was a circular reference: The
input for the current deployment was the output from the previous
deployment.

To fix this behavior, include more of the items from
@(ReferenceCopyLocalPaths) in the @(FilteredAssemblies) input to
<ResolveAssemblies/> by removing the
'%(ReferenceCopyLocalPaths.RelativeDir)' == '' condition.

For the included build test, this means the following additional item is
now included in @(FilteredAssemblies):

C:\Source\xamarin-android\bin\TestDebug\temp\TransitiveDependencyProduceReferenceAssembly\Library1\bin\Debug\netstandard2.0\Library2.dll

This path is then treated as a top-level reference in
<ResolveAssemblies/>, so the task no longer needs to try to resolve it
from elsewhere.

In theory, another way to fix the problem could be to change how
<ResolveAssemblies/> resolves transitive dependencies, but
<ResolveAssemblies/> is only used in the legacy build targets, so
adding more sophistication now would have limited value.

Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

There is one test failure, that seems like this change caused it?

Xamarin.Android.Build.Tests.BuildTest.BuildWithResolveAssembliesFailure(False)
Should recieve 'error XA2002: Can not resolve reference:' regarding Microsoft.Azure.Amqp!
Expected: True
But was: False

Maybe some assertion needs updated:

"/Users/runner/runners/2.171.1/work/1/s/bin/TestRelease/temp/BuildWithResolveAssembliesFailure(False)/MyApp/MyApp.csproj" (Build;SignAndroidPackage target) (1) ->
(_ResolveAssemblies target) -> 
  /Library/Frameworks/Mono.framework/External/xbuild/Xamarin/Android/Xamarin.Android.Legacy.targets(234,5): error XA2002: Can not resolve reference: `Microsoft.Azure.Amqp`, referenced by `Microsoft.Azure.EventHubs`. Please add a NuGet package or assembly reference for `Microsoft.Azure.Amqp`, or remove the reference to `Microsoft.Azure.EventHubs`. [/Users/runner/runners/2.171.1/work/1/s/bin/TestRelease/temp/BuildWithResolveAssembliesFailure(False)/MyApp/MyApp.csproj]

@jonathanpeppers
Copy link
Member

I changed:

Other changes:

  * The `BuildWithResolveAssembliesFailure` test using `packages.config`
    gets a slightly different error message due to the head project
    knowing about `Microsoft.Azure.EventHubs`. I think the new behavior
    is OK.
  * `@(FilteredAssemblies)` can now have duplicates, so we need to use
    `->Distinct()`. Note that MSBuild preserves ordering:
 
 https://github.com/microsoft/msbuild/blob/9c2d922959e80ba72fefb7addec21ede227639c3/src/Build/Evaluation/Expander.cs#L2484-L2513

@jonpryor
Copy link
Member

@brendanzagaeski: Looks like this (somehow) broke satellite assembly use? https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=3894877&view=ms.vss-test-web.build-test-results-tab&runId=14457216&resultId=100003&paneView=debug

System.InvalidOperationException : Satellite assembly resource lookup failed for resource 'LibString' from culture 'fr-FR'.
Expected='Lib French'
Actual='Lib English'

@brendanzagaeski
Copy link
Contributor Author

Shoot, yup. Looking into that satellite assembly behavior now.

@brendanzagaeski
Copy link
Contributor Author

Changes in the latest force-push:

To fix the broken SatelliteAssemblyValues test, undo the original fix, and instead add the @(ReferenceDependencyPaths) items to @(FilteredAssemblies):

+      <FilteredAssemblies
+          Include="@(ReferenceDependencyPaths)"
+          Condition="'%(ReferenceDependencyPaths.ResolvedFrom)' != 'ImplicitlyExpandDesignTimeFacades' And Exists('%(ReferenceDependencyPaths.Identity)') "
+      />

Update the commit message accordingly:

-To fix this behavior, include more of the items from
-`@(ReferenceCopyLocalPaths)` in the `@(FilteredAssemblies)` input to
-`<ResolveAssemblies/>` by removing the
-`'%(ReferenceCopyLocalPaths.RelativeDir)' == ''` condition.
+To fix this behavior, add the `@(ReferenceDependencyPaths)` items that
+are found by the `<ResolveAssemblyReference/>` task to the
+`@(FilteredAssemblies)` input.

Explanation:

Having satellite assemblies included in the input to <ResolveAssemblies/> causes trouble, so use a different item list (@(ReferenceDependencyPaths)) that still includes the desired transitive dependencies but doesn't include the satellite assemblies. The @(ReferenceDependencyPaths) item list is also nice because its name lines up closely with the scenario this PR aims to fix.

Longer explanation:

After the original fix, two satellite assemblies were being passed as additional inputs to <ResolveAssemblies/> in the failing test:

C:\Source\xamarin-android\bin\TestRelease\de-DE\LibraryResources.resources.dll
C:\Source\xamarin-android\bin\TestRelease\fr-FR\LibraryResources.resources.dll

<ResolveAssemblies/> did not expect this. It discarded the de-DE assembly entirely, but kept the fr-FR assembly and treated it just like any other non-satellite assembly.

As a consequence, bin\TestRelease\fr-FR\LibraryResources.resources.dll was copied to:

obj\Release\110\android\assets\LibraryResources.resources.dll

And then the linking step created:

obj\Release\110\android\assets\shrunk\LibraryResources.resources.dll

After that, the <BuildApk/> task placed the shrunk assembly into the APK, oddly preserving the shrunk subdirectory, so that the final path in the APK was:

assemblies/shrunk/LibraryResources.resources.dll

(This is part of the problem happens with non-satellite assemblies too, as a user reported a little while back: #3745.)

Finally, when the APK included the incorrect assemblies/shrunk/LibraryResources.resources.dll entry in addition to the correct assemblies/fr-FR/LibraryResources.resources.dll entry, that somehow interfered with the resource lookup process. I'll plan to investigate this a little part of the problem a little more tomorrow because the results I've seen so far are interesting. In one case, I got the test to fail in an APK where the only difference compared to the working APK was an additional NOTICES entry.

Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

Otherwise, I think this new approach looks good, the ->Distinct() should fix any duplicates.

}
};
lib2.SetProperty ("ProduceReferenceAssembly", "True");
lib1.OtherBuildItems.Add (new BuildItem.ProjectReference ($"..\\{lib2.ProjectName}\\{lib2.ProjectName}.csproj", lib2.ProjectName, lib2.ProjectGuid));
Copy link
Member

Choose a reason for hiding this comment

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

Why does this use lib1.OtherBuildItems and not lib1.References?

Copy link
Contributor Author

@brendanzagaeski brendanzagaeski Jul 17, 2020

Choose a reason for hiding this comment

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

Oh yeah, that's a little tricky thing I learned when setting up this test. What happens is that XmlUtils.ToXml() adds only OtherBuildItems in projects that have $(EnableDefaultItems)=true:

https://github.com/xamarin/xamarin-android/blob/99ec9f231bc52b4e9c451b955f2493517afc1fc2/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Utilities/XmlUtils.cs#L32-L51

And DotNetStandard has $(EnableDefaultItems)=true:

https://github.com/xamarin/xamarin-android/blob/99ec9f231bc52b4e9c451b955f2493517afc1fc2/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetStandard.cs#L42

@brendanzagaeski
Copy link
Contributor Author

brendanzagaeski commented on Jul 16 #4887 (comment)

Finally, when the APK included the incorrect assemblies/shrunk/LibraryResources.resources.dll entry in addition to the correct assemblies/fr-FR/LibraryResources.resources.dll entry, that somehow interfered with the resource lookup process. I'll plan to investigate this a little part of the problem a little more tomorrow because the results I've seen so far are interesting. In one case, I got the test to fail in an APK where the only difference compared to the working APK was an additional NOTICES entry.


I got a chance to dig into this more and found that the difference for the failing APKs was related to $(AndroidEnableAssemblyCompression) and whether the satellite assemblies were compressed or not. I put the details in a new issue #4967. Long story short, that behavior isn't a concern for this PR anymore because this PR no longer adds satellite assemblies to any extra item lists.

@brendanzagaeski
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Comment on lines 261 to 264
<FilteredAssemblies
Include="@(ReferenceDependencyPaths)"
Condition="'%(ReferenceDependencyPaths.ResolvedFrom)' != 'ImplicitlyExpandDesignTimeFacades' And Exists('%(ReferenceDependencyPaths.Identity)') "
/>
Copy link
Member

@jonathanpeppers jonathanpeppers Jul 29, 2020

Choose a reason for hiding this comment

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

I think this needs to use @(_ReferenceDependencyPaths):

https://github.com/xamarin/xamarin-android/blob/eb6ea0b55c89ce7e0b107c72900be9d019fb4b5b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets#L420-L426

This way we will include the %(AndroidSkip...) metadata, it matches the use of @(_ReferencePath) below.

Copy link
Contributor

Choose a reason for hiding this comment

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

@brendanzagaeski I think with this change ^ we can probably merge this. Its looking good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ooo, nice catch. I've added that change now.

…assembly dependencies

Fixes: https://developercommunity.visualstudio.com/content/problem/1086457/index.html
Context: 8f2ae24
Context: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/938468

If a Xamarin.Android app referenced a .NET Standard library that had
`$(ProduceReferenceAssembly)`=`true`, and that library in turn
referenced a second .NET Standard library, then changes to the second
library would not be deployed during incremental deployments in Visual
Studio unless the app project also directly referenced the second
library.

For additional context, note that as discussed in internal work item
938468, if a .NET Framework console application references a .NET
Standard library that has `$(ProduceReferenceAssembly)`=`true`, then
changes to the library are similarly missing in the running console app
after incremental builds in Visual Studio.

Although the problem in Xamarin.Android apps was similar to the .NET
Framework behavior, an important difference was that Xamarin.Android
apps did reflect changes correctly for libraries referenced directly by
the app.  Only transitive references caused trouble in Xamarin.Android.

The reason direct references worked was that Visual Studio always ran
Xamarin.Android's `Install` target on each incremental deployment, and
the `<ResolveAssemblies/>` task in that target correctly located the
up-to-date assemblies for direct references.

For example, for the included build test, the `<ResolveAssemblies/>`
task resolved the `Library1` reference to:

	C:\Source\xamarin-android\bin\TestDebug\temp\TransitiveDependencyProduceReferenceAssembly\Library1\bin\Debug\netstandard2.0\Library1.dll

In contrast, the `<ResolveAssemblies/>` task resolved the `Library2`
reference to:

	C:\Source\xamarin-android\bin\TestDebug\temp\TransitiveDependencyProduceReferenceAssembly\App\bin\Debug\Library2.dll

This was problematic because that copy of the library was left over from
the previous deployment.  In a sense, this was a circular reference: The
input for the current deployment was the output from the previous
deployment.

To fix this behavior, add the `@(ReferenceDependencyPaths)` items that
are found by the `<ResolveAssemblyReference/>` task to the
`@(FilteredAssemblies)` input.

For the included build test, this means the following additional item is
now included in `@(FilteredAssemblies)`:

	C:\Source\xamarin-android\bin\TestDebug\temp\TransitiveDependencyProduceReferenceAssembly\Library1\bin\Debug\netstandard2.0\Library2.dll

This path is then treated as a top-level reference in
`<ResolveAssemblies/>`, so the task no longer needs to try to resolve it
from elsewhere.

In theory, another way to fix the problem could be to change how
`<ResolveAssemblies/>` resolves transitive dependencies, but
`<ResolveAssemblies/>` is only used in the legacy build targets, so
adding more sophistication now would have limited value.

Other changes:

* The `BuildWithResolveAssembliesFailure` test using `packages.config`
  gets a slightly different error message due to the head project
  knowing about `Microsoft.Azure.EventHubs`. I think the new behavior
  is OK.
* `@(FilteredAssemblies)` can now have duplicates, so we need to use
  `->Distinct()`. Note that MSBuild preserves ordering:
  https://github.com/microsoft/msbuild/blob/9c2d922959e80ba72fefb7addec21ede227639c3/src/Build/Evaluation/Expander.cs#L2484-L2513

Co-authored-by: Jonathan Peppers <jonathan.peppers@gmail.com>
@brendanzagaeski
Copy link
Contributor Author

Changes in the latest force-push:

Change ReferenceDependencyPaths to _ReferenceDependencyPaths so that the items include the %(AndroidSkip...) metadata.

@jonathanpeppers jonathanpeppers merged commit de70822 into dotnet:master Aug 7, 2020
@jonathanpeppers
Copy link
Member

jonathanpeppers commented Aug 7, 2020

Something weird happened when I merged this... GitHub failed, then displayed a retry button. When I clicked retry, it lost what I edited in the commit message?

GitHub suggested this to me twice, and I had removed the second one...

Co-authored-by: Jonathan Peppers <jonathan.peppers@gmail.com>

Co-authored-by: Jonathan Peppers <jonathan.peppers@gmail.com>

But then, this was still in the final commit.

@github-actions github-actions bot locked and limited conversation to collaborators Jan 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants