dotnet / NuGet.BuildTasks

The build tasks used to pick up package content from project.lock.json.
MIT License
44 stars 60 forks source link

"Sequence contains no elements" exception during msbuild after restore - .NET 7 #154

Open erikmav opened 1 year ago

erikmav commented 1 year ago

After converting a fully working .NET 6 repo to .NET 7 and VS 17.4.2, command line builds started failing with exceptions from NuGet.BuildTasks. This occurs on Windows only, and only within .vcxproj projects. The repo uses Directory.Packages.props global packages. It does not occur if dotnet restore is used (see below), just when restoring with msbuild /t:Restore. It usually does not happen in Visual Studio. Our ADO based build pipeline does not repro this, but it uses dotnet restore.

Repro sequence in our repo:

Interestingly, with the same exact machine, dotnet restore instead of msbuild /t:Restore does not reproduce the problem.

Exact MSBuild version from console output:

msbuild /t:Restore
MSBuild version 17.4.0+18d5aef85 for .NET Framework

The exception stack:

C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Microsoft\NuGet\17.0\Microsoft.NuGet.targets(198,5): error : Sequence contains no elements [C:\myrepo\src\\MyUnmanaged\exe\MyUnmanaged.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Microsoft\NuGet\17.0\Microsoft.NuGet.targets(198,5): error :    at System.Linq.Enumerable.First[TSource](IEnumerable`1 source) [C:\myrepo\src\\MyUnmanaged\exe\MyUnmanaged.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Microsoft\NuGet\17.0\Microsoft.NuGet.targets(198,5): error :    at Microsoft.NuGet.Build.Tasks.ResolveNuGetPackageAssets.GiveErrorForMissingFramework() [C:\myrepo\src\\MyUnmanaged\exe\MyUnmanaged.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Microsoft\NuGet\17.0\Microsoft.NuGet.targets(198,5): error :    at Microsoft.NuGet.Build.Tasks.ResolveNuGetPackageAssets.GetTargetOrAttemptFallback(JObject lockFile, Boolean needsRuntimeIdentifier) [C:\myrepo\src\\MyUnmanaged\exe\MyUnmanaged.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Microsoft\NuGet\17.0\Microsoft.NuGet.targets(198,5): error :    at Microsoft.NuGet.Build.Tasks.ResolveNuGetPackageAssets.GetReferences(JObject lockFile) [C:\myrepo\src\\MyUnmanaged\exe\MyUnmanaged.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Microsoft\NuGet\17.0\Microsoft.NuGet.targets(198,5): error :    at Microsoft.NuGet.Build.Tasks.ResolveNuGetPackageAssets.ExecuteCore() [C:\myrepo\src\\MyUnmanaged\exe\MyUnmanaged.vcxproj]
C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Microsoft\NuGet\17.0\Microsoft.NuGet.targets(198,5): error :    at Microsoft.NuGet.Build.Tasks.ResolveNuGetPackageAssets.Execute() [C:\myrepo\src\\MyUnmanaged\exe\MyUnmanaged.vcxproj]
erikmav commented 1 year ago

More info: dotnet restore kicks out this warning, hence it is not trying a restore in the vcxproj:

C:\myrepo\src\MyUnmanaged\exe\MyUnmanaged.vcxproj : warning NU1503: Skipping restore for project 'C:\myrepo\src\myUnmanaged\exe\MyUnmanaged.vcxproj'. The project file may be invalid or missing targets required for restore. [C:\myrepo\repo.sln]

Whereas msbuild /t:Restore generates a project.assets.json for the vcxproj.

erikmav commented 1 year ago

A project.assets.json is actually the desired result, as there is a global package reference in Directory.Packages.props for an MSBuild plugin that needs to be applied to all projects.

erikmav commented 1 year ago

Verified still present in just-released VS 17.4.3 (MSBuild version 17.4.1+9a89d02ff for .NET Framework)

erikmav commented 1 year ago

For future reference on the NU1503 error under dotnet restore: vcxproj restore using dotnet restore runs into a Windows MSBuild.exe specific chicken-and-egg problem involving the VCTargetsPath macro definition. It is not set at all during a dotnet restore, only in MSBuild.exe /t:Restore, as its main definition is in MSBuild.exe.config next to MSBuild.exe. See here for the code. dotnet does not have an equivalent property definition. To get global packages to restore in .vcxproj files I had to add this EnsureVCTargetsPathSetForDotnetRestore.props file to my repo, then include it at the top of each .vcxproj:

<!-- Must be first import for .vcxproj files. On Windows, $(VCTargetsPath) is needed to bootstrap common and C++
     .props and .targets files but it's only set in an MSBuild.exe environment via
     MSBuild.exe.config (see https://github.com/dotnet/msbuild/blob/main/src/MSBuild/app.amd64.config#L194 ).
     'dotnet restore' does not invoke MSBuild.exe and there is no MSBuild.dll.config with this property,
     hence VCTargetsPath is never set, making it impossible to bootstrap C++ imports.
     Additionally in dotnet restore $(MSBuildExtensionsPath32) is not the same as for MSBuild.exe.
     Set the default value to what MSBuild would have done. -->
<Project>
  <PropertyGroup>
    <VCTargetsPath Condition=" '$(OS)' == 'Windows_NT' and '$(VCTargetsPath)'=='' ">$(ProgramFiles)\Microsoft Visual Studio\2022\Enterprise\MSBuild\Microsoft\VC\v170\</VCTargetsPath>
  </PropertyGroup>
</Project>
smaine commented 1 year ago

I'm hitting this same problem and would love to see this get fixed!

erikmav commented 9 months ago

Found one workaround from https://github.com/japj/CppWithNuGetPackageReference/blob/master/Directory.Build.props that adds a nuget fallback. When I set this in Directory.Build.props globally things work once again:

  <!-- Enable NuGet TargetMonikers fallback to vcxproj. https://github.com/japj/CppWithNuGetPackageReference/blob/master/Directory.Build.props -->
  <PropertyGroup>
    <_NuGetTargetFallbackMoniker>$(_NuGetTargetFallbackMoniker);native,Version=v0.0</_NuGetTargetFallbackMoniker>
  </PropertyGroup>
Arcnor commented 6 months ago

Not sure if this will help anybody until this issue is fixed, but got the same annoying problem on a CMake mixed CXX + C# project. My Directory.Build.props ended up containing:

<PropertyGroup>
    <_NuGetTargetFallbackMoniker>$(_NuGetTargetFallbackMoniker);net8.0</_NuGetTargetFallbackMoniker>
    <RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
</PropertyGroup>

Obviously your RuntimeIdentifiers might change, but having netX.0 (instead of native,Version=v0.0) & the RuntimeIdentifiers part made everything work.

chrarnoldus commented 1 month ago

In case it helps someone, I got this error because multiple projects (generated by CMake) were sharing the same 'obj' folder. I could work around it by creating a Directory.Build.props file ensuring the obj-path for each project is unique:

<Project>
    <PropertyGroup>
        <BaseIntermediateOutputPath>$(MSBuildProjectDirectory)\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
    </PropertyGroup>
</Project>