dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.06k stars 4.04k forks source link

MSBuild 16.4.0.56107 and targets GetPathsOfAllDirectoriesAbove error #40532

Closed jcurl closed 4 years ago

jcurl commented 4 years ago

Version Used: VS 16.4.1 with MsBuild 16.4.0 or later

Steps to Reproduce: When loading a project using Microsoft.Build.ProjectCollection.Load, get the exception exception of type Microsoft.Build.Exceptions.InvalidProjectFileException with the text The expression ""Datastructures\ReusableList.cs".GetPathsOfAllDirectoriesAbove()" cannot be evaluated.

This is introduced with Roslyn Microsoft.Build since 16.4, and was not present in 16.3.

I'm using Microsoft.Build.ProjectCollection from NuGet 16.0.x to load a .csproj (.NET 4.0 and .NET 4.5 projects). I'm setting a few properties as given by Dave Glick on his site https://daveaglick.com/posts/running-a-design-time-build-with-msbuild-apis. With VS2017 15.9.17 and VS2019 16.3.10 I don't have issues.

I was using NuGet packages in my project from 16.0.461, upgraded to 16.4.0 and the problem still exists (Microsoft.Build, Build.Engine, Build.Framework, Build.Utilities.Core) with VS2019 16.4.2 installed.

Specifically, the code I use is:

[Test]
public void LoadSolutionProjectTest()
{
    string csProjectFileName = Path.Combine(Deploy.TestDirectory, "TestDeployment", "FullProject1", "framework", "datastructures", "code", "HBAS.Datastructures.csproj");

    Dictionary<string, string> propertyBag = new Dictionary<string, string>();

    Data.MsBuild.CsProperties properties = new Data.MsBuild.CsProperties() {
        Configuration = "Debug",
        TargetFramework = "v4.0"
    };

    if (properties.Configuration != null) propertyBag.Add("Configuration", properties.Configuration);
    if (properties.Constants != null) propertyBag.Add("DefineConstants", properties.Constants);
    if (properties.OutputPath != null) propertyBag.Add("OutputPath", properties.OutputPath);
    if (properties.Platform != null) propertyBag.Add("Platform", properties.Platform);
    if (properties.TargetFramework != null) propertyBag.Add("TargetFrameworkVersion", properties.TargetFramework);

    //string msBuildPath = ToolLocationHelper.GetPathToBuildToolsFile("msbuild.exe", ToolLocationHelper.CurrentToolsVersion);
    // This appears to work with VS 2017 15.9.17
    string msBuildPath = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe";
    // This used to work with VS 2019 16.3.10, but no longer with VS 2019 16.4.2
    //string msBuildPath = @"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe";
    string toolsPath = Path.GetDirectoryName(msBuildPath);

    string solutionDir = Path.GetDirectoryName(csProjectFileName);
    string extensionsPath = Path.GetFullPath(Path.Combine(toolsPath, @"..\..\"));
    string sdksPath = Path.Combine(extensionsPath, "Sdks");
    string roslynTargetsPath = Path.Combine(toolsPath, "Roslyn");

    propertyBag.Add("SolutionDir", solutionDir);
    propertyBag.Add("MSBuildExtensionsPath", extensionsPath);
    propertyBag.Add("MSBuildSDKsPath", sdksPath);
    propertyBag.Add("RoslynTargetsPath", roslynTargetsPath);

    ProjectCollection collection;
    List<Microsoft.Build.Framework.ILogger> loggers = new List<Microsoft.Build.Framework.ILogger>();
    loggers.Add(new Microsoft.Build.BuildEngine.ConsoleLogger(Microsoft.Build.Framework.LoggerVerbosity.Detailed));
    collection = new ProjectCollection(propertyBag, loggers, ToolsetDefinitionLocations.Default);
    // Or instead of "15.0", use ToolLocationHelper.CurrentToolsVersion doesn't really help
    collection.AddToolset(new Toolset("15.0", toolsPath, collection, string.Empty));

    Project project = collection.LoadProject(csProjectFileName);
    foreach (ProjectItem item in project.Items) {
        if (item.ItemType.Equals("ProjectReference", StringComparison.Ordinal)) {
            Console.WriteLine("Reference: {0}", item.EvaluatedInclude);
        }
    }
}

The goal is I want to iterate through all the items and look for dependencies - I don't need to build, just read the project format and analyze separately the dependency graph.

Expected Behavior: Shouldn't raise an exception

Actual Behavior: I get an exception of type Microsoft.Build.Exceptions.InvalidProjectFileException with the text The expression ""Datastructures\ReusableList.cs".GetPathsOfAllDirectoriesAbove()" cannot be evaluated. Method 'System.String.GetPathsOfAllDirectoriesAbove' not found. C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn\Microsoft.Managed.Core.targets.

The last two lines of output is:

Property reassignment: $(MSBuildAllProjects)="C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.CSharp.targets;;C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\NuGet\16.0\Microsoft.NuGet.targets" (previous value: ";C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\NuGet\16.0\Microsoft.NuGet.targets") at 
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn\Microsoft.Managed.Core.targets(60,27): error MSB4184: The expression ""Collections\Generic\NamedItemCollection.cs".GetPathsOfAllDirectoriesAbove()" cannot be evaluated. Method 'System.String.GetPathsOfAllDirectoriesAbove' not found.

It appears to be related to the new functionality for .editorconfig, but I don't know enough about MSBuild to determine how to provide it everything it needs prior to properly load.

<!--
    ========================
    .editorconfig Support
    ========================

    -->
  <ItemGroup>
    <_AllDirectoriesAbove Include="@(Compile->GetPathsOfAllDirectoriesAbove())" Condition="'$(DiscoverEditorConfigFiles)' != 'false'" />
    <!-- Work around a GetPathsOfAllDirectoriesAbove() bug where it can return multiple equivalent paths when the 
         compilation includes linked files with relative paths - https://github.com/microsoft/msbuild/issues/4392 -->
    <PotentialEditorConfigFiles Include="@(_AllDirectoriesAbove->'%(FullPath)'->Distinct()->Combine('.editorconfig'))" Condition="'$(DiscoverEditorConfigFiles)' != 'false'" />
    <EditorConfigFiles Include="@(PotentialEditorConfigFiles->Exists())" Condition="'$(DiscoverEditorConfigFiles)' != 'false'" />
  </ItemGroup>
jaredpar commented 4 years ago

@rainersigwald should we move this to the MSBuild repository?

jcurl commented 4 years ago

Hello, do you have any ideas how I might go ahead and debug this situation?

jcurl commented 4 years ago

I recently upgraded the project to .NET 4.8, MSBuild from 16.0.461 to 16.4.0 and the original test code given in this ticket no longer reproduces the problem. Thus, I believe this problem is solved.

ArsenShnurkov commented 4 years ago

https://stackoverflow.com/questions/59276192/getpathsofalldirectoriesabove-cannot-be-evaluated-after-updating-net-framewor

jcurl commented 4 years ago

Thanks. None of these were related to the original problem, as:

The solution was to upgrade the MSBuild NuGet packages to VS2019, then somehow everything builds properly, including VS2017 (so VS2019 is backwards compatible). Now that it's no longer installed in the GAC, it causes problems that when a user upgrades their tools, my tool will break.