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

Restore: Prefer TargetFramework over TargetFrameworks when it is specified as a global property. #4587

Merged
merged 20 commits into from
May 27, 2022

Conversation

nkolev92
Copy link
Member

@nkolev92 nkolev92 commented Apr 21, 2022

Bug

Fixes: NuGet/Home#11653

Regression? Last working version:

Description

NuGet/Home#11653 contains details about the scenario at hand, but the gist is that:

$ dotnet new maui
$ dotnet build -f:net6.0-ios -r ios-arm64

does not work because nuget prefers TargetFrameworks over TargetFramework, so it essentially "tries" to do a restore for net6.0-android + ios-arm64 which obviously isn't something that works.

This change allows NuGet to detect the user intent in the scenarios like the above, and only restore for the framework passed on the commandline.

The change boils down to NuGet preferring the TargetFramework when it is specified on the commandline, as a global property.

While working on this I discovered NuGet/Home#11761, which basically means static graph can't support the equivalent at this point.

Given that this change requires multiple teams to validate it's good enough, I've added a means to disable it in case something does go really wrong while we're attempting that validation. _DisableNuGetRestoreTargetFrameworksOverride.

PR Checklist

  • PR has a meaningful title

  • PR has a linked issue.

  • Described changes

  • Tests

    • Automated tests added
    • OR
    • Test exception
    • OR
    • N/A
  • Documentation

    • Documentation PR or issue filled
    • OR
    • N/A

@nkolev92 nkolev92 marked this pull request as ready for review April 21, 2022 01:00
@nkolev92 nkolev92 requested a review from a team as a code owner April 21, 2022 01:00
@nkolev92
Copy link
Member Author

@rolfbjarne and @dsplaisted - please take a look.

I've made NuGet prefer the global TargetFramework property when set.

@rolfbjarne
Copy link
Contributor

CC @jonathanpeppers

@nkolev92
Copy link
Member Author

Thanks for the review @jonathanpeppers and @rolfbjarne

Addressed your feedback.

@dsplaisted
Copy link

@rainersigwald Is there a better way to check whether a property is a global property than to do a separate MSBuild task invocation? Would something like this work to do it in simple evaluation?

<PropertyGroup>
  <_TargetFrameworkBackup>($TargetFramework)</TargetFrameworkBackup>
  <TargetFramework>FakeOverriddenValue</TargetFramework>
  <TargetFrameworkIsGlobal Condition="'$(TargetFramework)' == '$(TargetFrameworkBackup)'">true</TargetFrameworkIsGlobal>
  <TargetFramework>$(_TargetFrameworkBackup)</TargetFramework>
</PropertyGroup>

@rainersigwald
Copy link
Contributor

@rainersigwald Is there a better way to check whether a property is a global property than to do a separate MSBuild task invocation?

There's now (since 16.5) an API that will allow doing it inside a task: dotnet/msbuild#4939. @nkolev92 would that let you do this inside the NuGet tasks themselves?

Would something like this work to do it in simple evaluation?

<PropertyGroup>
  <_TargetFrameworkBackup>($TargetFramework)</TargetFrameworkBackup>
  <TargetFramework>FakeOverriddenValue</TargetFramework>
  <TargetFrameworkIsGlobal Condition="'$(TargetFramework)' == '$(TargetFrameworkBackup)'">true</TargetFrameworkIsGlobal>
  <TargetFramework>$(_TargetFrameworkBackup)</TargetFramework>
</PropertyGroup>

This does work in simple evaluation, but beware: if you put the same property settings inside a target, it does not work.

@nkolev92
Copy link
Member Author

That API could work. I think we use it in static graph scenarios already.

Right now we only use that to decide which frameworks to run inner builds for. I'd probably have to add an extra task to just get the effective TargetFramework(s).

<MSBuild
BuildInParallel="$(RestoreBuildInParallel)"
Condition=" '$(TargetFrameworks)' != '' "
Projects="$(MSBuildThisFileFullPath)"
Copy link
Contributor

Choose a reason for hiding this comment

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

I would put some comments in here that you're "building" NuGet.targets just to detect if a global property is set. More explanation of how this target works would be very helpful.


// Act
var additionalArgs = useStaticGraphEvaluation ? "/p:RestoreUseStaticGraphEvaluation=true" : string.Empty;
var result = _msbuildFixture.RunDotnet(pathContext.SolutionRoot, args: $"restore a.csproj {additionalArgs} /p:TargetFramework=\"net5.0\"", ignoreExitCode: true);
Copy link
Contributor

Choose a reason for hiding this comment

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

The result of this restore will only work for one framework and subsequent builds without a restore would fail right? Is it not possible to just restore a whole project regardless of target framework(s) and just let the build be scoped? We can remove global properties when generating the ProjectSpec items

https://docs.microsoft.com/en-us/dotnet/api/microsoft.build.tasks.msbuild.removeproperties?view=msbuild-17-netcore

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, it'd only restore/build for that specific framework.

When a RID is passed, we simply can't know whether we'd need to include for all or only a specific framework, so we only restore for that framework.

@nkolev92 nkolev92 force-pushed the dev-nkolev92-respectTargetFrameworkOverrides branch from 2b04ad9 to 6bf125a Compare May 3, 2022 00:35
@nkolev92 nkolev92 requested a review from jeffkl May 6, 2022 00:22
@nkolev92
Copy link
Member Author

Ready for another look @rolfbjarne @jonathanpeppers @dsplaisted @NuGet/nuget-client

rolfbjarne
rolfbjarne previously approved these changes May 11, 2022
@nkolev92 nkolev92 force-pushed the dev-nkolev92-respectTargetFrameworkOverrides branch from 93ae383 to 8593454 Compare May 13, 2022 00:38
@nkolev92 nkolev92 requested a review from a team May 13, 2022 00:39
IReadOnlyDictionary<string, string> msBuildGlobalProperties = null;

// MSBuild 16.5 added a new interface, IBuildEngine6, which has a GetGlobalProperties() method. However, we compile against
// Microsoft.Build.Framework version 4.0 when targeting .NET Framework, so reflection is required since type checking
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason we're building with such an old version of Microsoft.Build.Framework? Does nuget.exe even work without a recent-ish msbuild.exe? Or is it to avoid ilmerging Microsoft.Build.Framework.dll into nuget.exe, since I guess version 4.0 is in the GAC?

IReadOnlyDictionary<string, string> msBuildGlobalProperties = null;

// MSBuild 16.5 added a new interface, IBuildEngine6, which has a GetGlobalProperties() method. However, we compile against
// Microsoft.Build.Framework version 4.0 when targeting .NET Framework, so reflection is required since type checking
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason we're building with such an old version of Microsoft.Build.Framework? Does nuget.exe even work without a recent-ish msbuild.exe? Or is it to avoid ilmerging Microsoft.Build.Framework.dll into nuget.exe, since I guess version 4.0 is in the GAC?

Copy link
Member Author

Choose a reason for hiding this comment

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

NuGet.exe uses whichever version of msbuild is available on their machine.
We don't/won't merge msbuild because it is used to discover imports and all that.

Copy link
Member Author

Choose a reason for hiding this comment

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

actually, I misunderstood your question.

Not 100% sure if we can update. I can look into it, but we're doing this in a bunch of different places so I just largely followed that logic.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, it might be reasonable to rebaseline, since I suspect you don't support running modern nuget.exe against dev11. But I don't think it's urgent.

Copy link
Member Author

Choose a reason for hiding this comment

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

you don't support running modern nuget.exe against dev11

NuGet.exe restore help unfortunately lists a lot of msbuild versions.

We don't really explicitly talk about what's supported vs what's not.

It's a ymmv type of thing.

@nkolev92 nkolev92 requested a review from zivkan May 16, 2022 21:20
@nkolev92 nkolev92 force-pushed the dev-nkolev92-respectTargetFrameworkOverrides branch from 8593454 to 587dd8b Compare May 20, 2022 19:24
@nkolev92
Copy link
Member Author

@NuGet/nuget-client

Hoping to get some reviews here so I can merge? :)

Copy link
Member

@zivkan zivkan left a comment

Choose a reason for hiding this comment

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

I'm curious if there's a measurable performance degradation with this change? The PR adds a new target, which calls a C# task. But MSBuild is an interpreted scripting language, so calling targets is a lot more expensive than calling methods in a JITed language, and the task uses reflection.

I hope it's too small to be measurable, but we seem to be concerned about the pdb PR's impact.

@nkolev92
Copy link
Member Author

nkolev92 commented May 27, 2022

I'm curious if there's a measurable performance degradation with this change? The PR adds a new target, which calls a C# task. But MSBuild is an interpreted scripting language, so calling targets is a lot more expensive than calling methods in a JITed language, and the task uses reflection.

It registers as 1ms only. #4587 (comment)

Note that this is only called on the commandline and not in VS, so the concerns are lesser.

Copy link
Contributor

@jeffkl jeffkl left a comment

Choose a reason for hiding this comment

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

There was no CI build for this change so I kicked one off

@nkolev92 nkolev92 merged commit 0da9fda into dev May 27, 2022
@nkolev92 nkolev92 deleted the dev-nkolev92-respectTargetFrameworkOverrides branch May 27, 2022 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants