5

I have foo.csproj,

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup Label="Globals">
            <ExtPath>$(SolutionDir)\WpfNetStandardSample\CustomProjectSystem\</ExtPath>
  </PropertyGroup>

  <Import Project="$(ExtPath)CustomProject.props" />

</Project>

referenced from solution file bar.sln.

To restore my NuGet packages from the continuous integration server, I run

msbuild /t:restore bar.sln

And I get the error:

"C:\Users\phelan\workspace\bar\bar.sln" (restore target) (1) -> "C:\Users\phelan\workspace\bar\foo\foo.csproj" (_IsProjectRestoreSupported target) (11) -> C:\Users\phelan\workspace\bar\foo\foo.csproj(7,3): error MSB4019: The imported project "C:\WpfNetStandardSample\CustomProjectSystem\CustomProject.props" was not found. Confirm that the path in the declaration is correct, and that the file exists on disk.

Main Question

How and why is SolutionDir resolving to C:\ and not the directory the solution file is in?

Notes

  • The entire solution builds from within Visual Studio.
  • Running msbuild foo.sln builds the entire solution (assuming NuGet packages are already restored)
  • MSBuild version is 15.3.409.57025
  • The solution is a mixture of projects, some using packages.config and some using PackageReference. Some of the projects using PackageReference are dotnetstandard libraries and some are net461 libraries.
  • There seems to be a variety of suggestion on how to restore NuGet packages
    • dotnet restore
    • nuget restore Enter image description here
    • msbuild /t:restore
    • dotnet msbuild /t:restore foo.sln

What is the correct process for my type of mixed setup?

GitHub repository for reproducing the error

https://github.com/bradphelan/msbuildbug

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217

2 Answers2

2

This happens because for the solution's Restore target, the SolutionDir is not passed to the generated project reference.

You can see this when running:

MSBuildEmitSolution=1 dotnet msbuild /t:Restore

For a foo.sln this will generate a foo.sln.metaproj file which contains the actual MSBuild interpretation of the solution format, along with extensions that are added (such as 15.0/SolutionFile/ImportAfter/Microsoft.NuGet.ImportAfter.targets in our case).

The generated Build target contains the following:

  <Target Name="Build" Outputs="@(CollectedBuildOutput)">
    <MSBuild Projects="@(ProjectReference)" BuildInParallel="True" Properties="BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)" SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)">
      <Output TaskParameter="TargetOutputs" ItemName="CollectedBuildOutput" />
    </MSBuild>
  </Target>

You can see that this explicitly sets SolutionDir and other solution-related properties. The same applies to numerous other targets generated into the solution.

The NuGet targets however do not do this (the technical reason is that they share the same MSBuild code for projects and solutions). When inspecting the <MSBuild> items created by NuGet, they do not set these properties, hence they are unavailable in the project during evaluation.

As a workaround, I recommend using MSBuild 15's Directory.Build.props logic, that auto-imports a file with this name into all projects that use the common properties/targets (nearly all project types).

In this file, placed at the solution directory or any directory above the project it applies to, you can either set a variable to use in projects (ExtPath) or set the desired common properties directly:

<Project>
  <PropertyGroup>
    <ExtPath>$(MSBuildThisFileDirectory)</ExtPath>
    <VersionPrefix>1.2.3</VersionPrefix>
  </PropertyGroup>
  <Import Project="$(ExtPath)Some\other.props" />
</Project>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Martin Ullrich
  • 94,744
  • 25
  • 252
  • 217
0

The following is a workaround, not a solution. Use a relative path:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup Label="Globals">
            <ExtPath>..\WpfNetStandardSample\CustomProjectSystem\</ExtPath>
  </PropertyGroup>

  <Import Project="$(ExtPath)CustomProject.props" />

</Project>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217