通过 MSBuildLocator 加载时,MSBuild 16.0 找不到自己的依赖程序集

MSBuild 16.0 not finding its own dependent assemblies when loaded through MSBuildLocator

我正在尝试从 C# DLL(最终将从 PowerShell 加载)以编程方式 运行 MSBuild,作为命令行应用程序的第一步。我通过将其 NuGet 包安装到我的项目并将以下引用添加到我的测试项目来按照推荐(或者我认为)使用 Microsoft.Build.Locator:

    <Reference Include="Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <Private>False</Private>
    </Reference>
    <Reference Include="Microsoft.Build.Framework, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
      <Private>False</Private>
    </Reference>
    <Reference Include="Microsoft.Build.Locator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9dff12846e04bfbd, processorArchitecture=MSIL">
      <HintPath>..\packages\Microsoft.Build.Locator.1.2.6\lib\net46\Microsoft.Build.Locator.dll</HintPath>
    </Reference>

项目面向.NET Framework 4.8,源码如下:

using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Locator;
using System.Collections.Generic;

namespace nrm_testing
{
    class Program
    {
        static void Main(string[] args)
        {
            MSBuildLocator.RegisterDefaults();
            DoStuff();
        }
        
        static void DoStuff()
        {
            using (var projectCollection = new ProjectCollection())
            {
                var buildParameters = new BuildParameters
                {
                    MaxNodeCount = 1 // 
                };
                
                var buildRequestData = new BuildRequestData(
                    @"path\to\a\project.vcxproj",
                    new Dictionary<string, string>(),
                    null,
                    new string[0],
                    null
                );

                var result = BuildManager.DefaultBuildManager.Build(buildParameters, buildRequestData);
            }
        }
    }
}

进入 using 块后,我收到以下异常:

System.IO.FileNotFoundException: 'Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.'

模块 window 显示 MSBL 确实成功找到了我的 VS2019 安装:

Microsoft.Build.dll           16.07.0.37604 C:\Program Files (x86)\Microsoft Visual Studio19\Enterprise\MSBuild\Current\Bin\Microsoft.Build.dll
Microsoft.Build.Framework.dll 16.07.0.37604 C:\Program Files (x86)\Microsoft Visual Studio19\Enterprise\MSBuild\Current\Bin\Microsoft.Build.Framework.dll
Microsoft.Build.Locator.dll   1.02.6.49918  C:\dev\nrm3-tests\nrm\nrm-testing\.out\AnyCPU-Debug\Microsoft.Build.Locator.dll

System.Runtime.CompilerServices.Unsafe.dll 在 4.0.6.0 版(根据 DotPeek)中除了定位的 MSBuild 程序集之外确实存在。

什么可能导致此错误,我该如何解决?


我目前的尝试:

实际上,这对 packages.config nuget management format 来说是一个长期存在的问题。 Microsoft 针对此问题推荐的解决方案是添加一个 bindingRedirect。

通常,可以在xxx.csproj文件中使用该节点自动生成bindingredirect。

但是,对于某些特定的 dll,由于某些原因,此节点可能无法正常工作。正如你所说,这仍然是当前 packages.config nuget management format 的一个问题。

建议

作为建议,您可以使用 new PackageReference nuget manage format,因为 VS2017。这种格式简单、方便、高效。

另外,当你使用这种格式时,首先,你应该备份你的项目。

只需 right-click 在 packages.config 文件上 --> 单击 Migrate packages.config to PackageReference

除此之外,我也在DC论坛上反映过this issue,希望团队提供更好的建议。

经过多次摆弄不同的想法,我最终根据 manual assembly resolution.

编写了这个解决方法

RegisterMSBuildAssemblyPath 检测何时加载 Microsoft.Build.dll,并记住其目录。在随后的程序集加载失败时,RedirectMSBuildAssemblies 检查该路径中是否存在丢失的程序集,如果存在则加载它。

class Program
{
    private static string MSBuildAssemblyDir;

    static void Main(string[] args)
    {
        MSBuildLocator.RegisterDefaults();

        Thread.GetDomain().AssemblyLoad += RegisterMSBuildAssemblyPath;
        Thread.GetDomain().AssemblyResolve += RedirectMSBuildAssemblies;

        DoStuff();
    }

    private static void RegisterMSBuildAssemblyPath(object sender, AssemblyLoadEventArgs args)
    {
        var assemblyPath = args.LoadedAssembly.Location;

        if (Path.GetFileName(assemblyPath) == "Microsoft.Build.dll")
            MSBuildAssemblyDir = Path.GetDirectoryName(assemblyPath);
    }

    private static Assembly RedirectMSBuildAssemblies(object sender, ResolveEventArgs args)
    {
        if (MSBuildAssemblyDir == null)
            return null;

        try
        {
            var assemblyFilename = $"{args.Name.Split(',')[0]}.dll";
            var potentialAssemblyPath = Path.Combine(MSBuildAssemblyDir, assemblyFilename);

            return Assembly.LoadFrom(potentialAssemblyPath);
        }
        catch (Exception)
        {
            return null;
        }
    }

    static void DoStuff()
    {
        // Same as before
    }
}

我很确定有(很多)极端情况会导致此失败,但暂时可以。