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

[Bug]: Static Graph Restore failed with a NullReferenceException when a non-SDK-style project refs an SDK-style project with SetTargetFramework #11680

Open
dfederm opened this issue Mar 16, 2022 · 8 comments
Labels
Area:RestoreStaticGraph Issues related to the Static Graph restore Category:Quality Week Issues that should be considered for quality week Functionality:Restore Priority:2 Issues for the current backlog. Type:Bug

Comments

@dfederm
Copy link

dfederm commented Mar 16, 2022

NuGet Product Used

MSBuild.exe

Product Version

msbuild 17

Worked before?

No

Impact

It's more difficult to complete my work

Repro Steps & Context

Dep\Dep.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net472;net5.0</TargetFrameworks>
  </PropertyGroup>
</Project>

Broken\Broken.csproj:

<Project>
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  <PropertyGroup>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Dep\Dep.csproj">
      <SetTargetFramework>TargetFramework=net472</SetTargetFramework>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Do a static graph restore on Broken\Broken.csproj. The output looks like:

Microsoft (R) Build Engine version 17.2.0-dev-22166-01+719247ede for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
C:\Program Files\Microsoft Visual Studio\2022\IntPreview\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGet.RestoreEx.targets(19,5): error : Object reference not set to an instance of an object. [C:\Code\tmp\GraphRestoreRepro\Broken\Broken.csproj]

Build FAILED.

C:\Program Files\Microsoft Visual Studio\2022\IntPreview\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGet.RestoreEx.targets(19,5): error : Object reference not set to an instance of an object. [C:\Code\tmp\GraphRestoreRepro\Broken\Broken.csproj]
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:00:06.70

The NRE specifically is in MSBuildStaticGraphRestore.GetPackageSpec. The caller iterates each ProjectWithInnerNodes and calls GetPackageSpec(project.OuterProject), but project.OuterProject is null for the Dep\Dep.csproj entry.

If any of the following is changed, it works:

  1. Non-static graph restore
  2. Remove the SetTargetFramework metadata from Broken\Broken.csproj
  3. Migrate Broken\Broken.csproj to SDK-style

Verbose Logs

No response

@jeffkl jeffkl self-assigned this Mar 17, 2022
@jeffkl jeffkl added Area:RestoreStaticGraph Issues related to the Static Graph restore Functionality:Restore and removed Triage:Untriaged labels Mar 17, 2022
@nkolev92 nkolev92 added Priority:2 Issues for the current backlog. Category:Quality Week Issues that should be considered for quality week labels Mar 17, 2022
@nkolev92
Copy link
Member

Note that standard restore doesn't really support SetTargetFramework either.

NuGet chooses the best framework of the available ones.

@AArnott
Copy link
Contributor

AArnott commented Mar 18, 2022

NuGet chooses the best framework of the available ones.

What does that mean? NuGet doesn't choose the target framework at all across a P2P that explicitly sets SetTargetFramework, I thought. We set it when NuGet isn't present to make the decision, at least. I expected (perhaps in ignorance) that the metadata would carry weight even on nuget-aware projects as well.

@jeffkl
Copy link
Contributor

jeffkl commented Mar 18, 2022

My initial investigation found some interesting results. As David noted, removing SetTargetFramework works and migrating to SDK-style projects works.

This is because SDK-style projects are treated as having an inner and outer node:

https://github.com/dotnet/msbuild/blob/46adf8052f15b8dd618e9bd46d226267105de0c8/src/Build/Graph/ProjectInterpretation.cs#L67-L82

For SDK-style projects, the SetTargetFramework applies but later on the TargetFramework global property is removed because project graph knows that this project is SDK-style. If the project is legacy, project graph treats its references as legacy and will only do a single evaluation of them. This results in the follow evaluations:

Legacy with SetTargetFramework -> SDK-style (net472, net5.0)

Project Global Properties
Legacy
referenced SDK-style TargetFramework=net472

Legacy without SetTargetFramework -> SDK-style

Project Global Properties
Legacy
referenced SDK-style
referenced SDK-style TargetFramework=net472
referenced SDK-style TargetFramework=net5.0

SDK-style -> SDK-style

Project Global Properties
SDK-style
referenced SDK-style
referenced SDK-style TargetFramework=net472
referenced SDK-style TargetFramework=net5.0

For builds, its okay if the graph is incomplete since all of the projects that will actually be built are there. However, for restore we need every project and all inner nodes representing their target frameworks.

We could fix the exception here but we'd probably need to fail restore saying that the project graph is incomplete. For now the workaround of removing SetTargetFramework seems like the best option.

@jeffkl
Copy link
Contributor

jeffkl commented Mar 18, 2022

What does that mean? NuGet doesn't choose the target framework at all across a P2P that explicitly sets SetTargetFramework, I thought. We set it when NuGet isn't present to make the decision, at least. I expected (perhaps in ignorance) that the metadata would carry weight even on nuget-aware projects as well.

During restore, SetTargetFramework is completely disregarded because NuGet needs a full representation of all projects and all target frameworks, not just the ones being built. It then does a complete restore for each project while the build might be more scoped and only build certain target frameworks. So at evaluation time, NuGet gets all projects, and at build time MSBuild only builds projects based on stuff like SetTargetFramework.

@AArnott
Copy link
Contributor

AArnott commented Mar 21, 2022

So at evaluation time, NuGet gets all projects, and at build time MSBuild only builds projects based on stuff like SetTargetFramework.

How can we accommodate both? Is there some Condition we should add to the SetTargetFramework metadata so that nuget restore ignores it but build uses it? Or if as it sounds, NuGet already ignores it during restore, why is restore failing?

@jeffkl
Copy link
Contributor

jeffkl commented Mar 21, 2022

How can we accommodate both? Is there some Condition we should add to the SetTargetFramework metadata so that nuget restore ignores it but build uses it? Or if as it sounds, NuGet already ignores it during restore, why is restore failing?

Static graph-based restore is expecting to get a graph back from MSBuild that represents every outer and inner evaluation. However, you've discovered a bug/feature where if you use SetTargetFramework with a legacy project referencing an SDK-style project, the graph is incomplete. NuGet then reads the graph and can't find the outer project evaluation representing the project and its target frameworks and unfortunately at the moment it throws an exception rather than logging an error.

We think the solution is to have two modes for static graph. One mode tells MSBuild to get all project edges that represent the complete graph and another mode (which is the current one) that only gets project edges that are to be built. This second mode leaves out some outer project evaluations that NuGet needs but would potentially cause MSBuild to build more than it needed to. But a complete graph is needed for restore.

@AArnott
Copy link
Contributor

AArnott commented Mar 22, 2022

Makes sense.
Would that perchance also fix this bug?
This doesn't work: dotnet build /r /p:TargetFramework=net472
Note that I want the build of the project to only build net472. But adding the /r switch, which should restore the project before the build, also gets infected by the TargetFramework global property and (IIRC) propagates to P2Ps and infects them, leading to restore failure. Perhaps I should file a separate bug to track that. What do you think?

@nkolev92
Copy link
Member

@AArnott That sounds like a dup of #11653.

@jeffkl jeffkl removed their assignment Mar 17, 2023
SingleAccretion added a commit to SingleAccretion/runtimelab that referenced this issue Jun 12, 2024
Static graph restore breaks with SetTargetFramework (NuGet/Home#11680).

Live ILLink targets (illink.targets) uses SetTargetFramework and so breaks restore of BuildInteration.proj.

The previous workaround was not fully functional because it broke './build nativeaot.build'.
SingleAccretion added a commit to SingleAccretion/runtimelab that referenced this issue Jun 12, 2024
Static graph restore breaks with SetTargetFramework (NuGet/Home#11680).

Live ILLink targets (illink.targets) use SetTargetFramework and so break restore of BuildInteration.proj.

The previous workaround was not fully functional because it broke './build nativeaot.build'.
maraf pushed a commit to dotnet/runtimelab that referenced this issue Jun 17, 2024
…restore for BuildIntegration.proj (#2613)

* Don't always build the runtime

* Work around static graph restore issues in a better way

Static graph restore breaks with SetTargetFramework (NuGet/Home#11680).

Live ILLink targets (illink.targets) use SetTargetFramework and so break restore of BuildInteration.proj.

The previous workaround was not fully functional because it broke './build nativeaot.build'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area:RestoreStaticGraph Issues related to the Static Graph restore Category:Quality Week Issues that should be considered for quality week Functionality:Restore Priority:2 Issues for the current backlog. Type:Bug
Projects
None yet
Development

No branches or pull requests

4 participants