microsoft / MSBuildLocator

An API to locate MSBuild assemblies from an installed Visual Studio location. Use this to ensure that calling the MSBuild API will use the same toolset that a build from Visual Studio or msbuild.exe would.
Other
216 stars 83 forks source link

Trying to load a project with Sdk.WindowDesktop fails. #120

Open vpenades opened 3 years ago

vpenades commented 3 years ago

I've been trying to load a csproj using new Microsoft.Build.Evaluation.Project("project.csproj", null, null"); and it throws this exception:

The specified SDK "Microsoft.NET.Sdk.WindowsDesktop" could not be found. Source: Microsoft.Build Error: MSB4236 MSBuild.CouldNotResolveSdk

The loaded csproj looks like this:

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

  <PropertyGroup>
    <TargetFramework>net471</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

And the host application registers the MSBuild location with:

Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();

I've noticed different behavior depending on whether the host application is compiled against Net472 or against NetCoreApp31:

On Net472

On NetCoreApp3.1

So I believe this is the reason because it works on Net4 and not on NetCore.

Is there a way to solve this issue and make the "Microsoft.NET.Sdk.WindowsDesktop" projects to load while the host app is NetCoreApp?

ghelyar commented 2 years ago

This is not a particularly nice workaround because it makes the library pretty pointless, as you have to tell the "locator" library exactly where to look, but you can do this from a .NET Core app (on Windows), and it will be able to load both .NET Core and .NET Framework projects:

MSBuildLocator.RegisterMSBuildPath(@"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin");

Seems that DiscoveryType.DeveloperConsole and DiscoveryType.VisualStudioSetup only work on the .NET Framework version of this package, and DiscoveryType.DotNetSdk only works on the .NET Core version of this package, so this returns completely different results depending on the target framework of the project that is referencing the package:

foreach (var instance in MSBuildLocator.QueryVisualStudioInstances(
    new VisualStudioInstanceQueryOptions
    {
        DiscoveryTypes = DiscoveryType.DeveloperConsole | DiscoveryType.DotNetSdk | DiscoveryType.VisualStudioSetup
    }))
{
    Console.WriteLine($"{instance.DiscoveryType} {instance.MSBuildPath}");
}

But if you take the paths from either of them and just pass them to MSBuildLocator.RegisterMSBuildPath, they work in the other, so it's a bit annoying that it doesn't just find all the same paths regardless of target framework. You can specify DiscoveryType but it's mostly pointless, because it ignores it when finding them, and then only uses it to throw away results afterwards.

vpenades commented 2 years ago

The problem with that approach is that our project runs on different environments, and it's not guaranteed the path to pass to RegisterMSBuildPath is the same on all machines (specially those with different versions of VS)

ghelyar commented 2 years ago

Yes, it's not ideal, and I'm not making any excuses for this library. I'm just a consumer of this library, and I'm sharing my workaround.

Exactly how you get the path to the directory containing msbuild will depend on your environments. For my usage, I can just pass it in as an argument. You might be able to search your path environment variables, or use where.exe msbuild to find it, or save it to an environment variable on the machine, or you might be able to do something like this:

var msbuildDirectories = new[]
    {
        @"C:\Program Files (x86)\Microsoft Visual Studio",
        @"C:\Program Files\Microsoft Visual Studio"
    }
    .Where(Directory.Exists)
    .SelectMany(path => Directory.EnumerateFiles(path, "msbuild.exe", SearchOption.AllDirectories))
    .Select(Path.GetDirectoryName);

Two overloads exist:

So you could either .ToArray() this or .First() it or .Last() it etc.