以编程方式构建 msbuild 15 项目

Building msbuild 15 project programmatically

我正在尝试构建一个使用 VS2017 创建的简单 C# 7 class 库项目。

来自框架程序集的 MSBuild 已过时,因此我从 visual studio (C:\Program Files (x86)\Microsoft Visual Studio17\Community\MSBuild.0\Bin) 的 MSBuild 文件夹中引用 Microsoft.BuildMicrosoft.Build.EngineMicrosoft.Build.Framework .

不过,当我这样做时:

using (var collection = new ProjectCollection())
{
    var proj = collection.LoadProject(@"c:\projects\Sample\Sample.csproj"); // <-- exception
    proj.Build(new[] {new ConsoleLogger()});
}

我得到 InvalidProjectFileException: The tools version "15.0" is unrecognized. Available tools versions are "4.0", "2.0".

是否有使用最新构建工具和 C# 7 编译器调用构建的编程方式?

我的团队也有类似的需求,我为 C# 编写了一个支持多个版本 Visual Studio 的 Builder 库。 我无法使Project.Build功能正常运行,所以我直接执行MsBuild.exe。

我是如何构建它的:

使用 NuGet

中的 Microsoft.Build.Framework

创建一个包含 1 个名为 Build 的目标的新项目对象

定义正确的工具版本

根据 Visual Studio 版本的项目:

  • 2010, 2012 => 4.0
  • 2013 => 12.0
  • 2015 => 14.0
  • 2017 => 15.0

添加 MsBuild 类型的新任务

With Projects 属性 包含我需要构建的所有项目

定义工具版本

与项目具有相同值的 MsBuild 任务

将项目序列化为临时文件

根据ToolsVersion

找到合适的MsBuild.exe

4.0、12.0、14.0

在注册表中找到:

Registry.LocalMachine.OpenSubKey($@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\{msBuildVersion}")

15.0

注册表中没有了,需要使用Nuget包Microsoft.VisualStudio.Setup.Configuration.Interop

        var query = new SetupConfiguration();

        var query2 = (ISetupConfiguration2)query;

        var e = query2.EnumAllInstances();

        var helper = (ISetupHelper)query;

        int fetched;

        var instances = new ISetupInstance[1];

        do
        {
            e.Next(1, instances, out fetched);
            if (fetched > 0)
            {
                var instance = instances[0];

                var instance2 = (ISetupInstance2)instance;

                var state = instance2.GetState();

                // Skip non-complete instance, I guess?
                // Skip non-local instance, I guess?
                // Skip unregistered products?
                if (state != InstanceState.Complete
                    || (state & InstanceState.Local) != InstanceState.Local
                    || (state & InstanceState.Registered) != InstanceState.Registered)
                {
                    continue;
                }

                var msBuildComponent =
                    instance2.GetPackages()
                        .FirstOrDefault(
                            p =>
                                p.GetId()
                                    .Equals("Microsoft.Component.MSBuild",
                                        StringComparison.InvariantCultureIgnoreCase));

                if (msBuildComponent == null)
                {
                    continue;
                }

                var instanceRootDirectory = instance2.GetInstallationPath();

                var msbuildPathInInstance = Path.Combine(instanceRootDirectory, "MSBuild", msBuildVersion, "Bin", "msbuild.exe");

                if (File.Exists(msbuildPathInInstance))
                {
                    return msbuildPathInInstance;
                }
            }
        } while (fetched > 0);

执行MsBuild.exe

并使用自定义 XML 记录器构建序列化项目 - 您可以使用 MsBuildExtensionPack

提供的记录器

阅读结果摘要

反序列化 Xml 的结果摘要,并用它来确定构建是否失败,发生了哪些错误和警告等。

我在我的项目中遇到了完全相同的问题。
就我而言,我想以编程方式构建一个 SSDT 项目,但我也尝试了其他项目类型。

有趣的是,它在 VS2017 的版本 26228.04(发布版本)中运行良好,在版本 26228.09 中停止工作。
昨天发布了 build 26228.10,所以我决定再试一次。

令人惊讶的是,以下对我有用:

  1. 使用 Visual Studio 安装程序更新到最新的 VS2017 build 26228.10。
  2. 我一直无法让 collection.LoadProject(...) 在不出现奇怪错误的情况下工作,但您可以使用此代码进行构建:

    BuildResult result = null;
    
    using (var pc = new ProjectCollection())
        result = BuildManager.DefaultBuildManager.Build(
            new BuildParameters(pc) { Loggers = new[] { new ConsoleLogger() } },
            // Change this path to your .sln file instead of the .csproj.
            // (It won't work with the .csproj.)
            new BuildRequestData(@"c:\projects\Sample.sln",
                // Change the parameters as you need them,
                // e.g. if you want to just Build the Debug (not Rebuild the Release).
                new Dictionary<string, string>
                {
                    { "Configuration", "Release" },
                    { "Platform", "Any CPU" }
                }, null, new[] { "Rebuild" }, null));
    
    if (result.OverallResult == BuildResultCode.Failure)
        // Something bad happened...
    
  3. 确保您已将所有 MSBuild 程序集绑定重定向复制到您的 .config 文件。
    如果您还没有这样做,请打开文件 C:\Program Files (x86)\Microsoft Visual Studio17\Enterprise\MSBuild.0\Bin\MSBuild.exe.config 并将整个 <runtime> 元素复制到使用 MSBuild 的项目中您自己的 .config 文件的 <configuration> 元素以编程方式。
    如果您不重定向 MSBuild 程序集版本,您会从 MSBuild 收到一些非常奇怪的错误消息,这些消息会因您的 MSBuild 版本而异。
    但是可以肯定地说:没有程序集绑定重定向肯定行不通。

  4. 如果要构建 SSDT 项目,还需要一个步骤:
    .sqlproj 文件中的以下两行更改为

    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
    <VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
    

    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
    <VisualStudioVersion Condition="'$(SSDTExists)' == ''">15.0</VisualStudioVersion>
    

我不是 100% 确定为什么,但这些步骤对我来说非常有效!
但是因为 MSBuild 有点 "difficult" 以编程方式使用(在我看来它有时像天气预报一样稳定),我的方法很可能不适用于您的情况。

如果您只需要最新 MSBuild.exe 的路径,请使用 Nuget 中的 Microsoft.Build.Utilities.Core 并使用以下代码:

ToolLocationHelper.GetPathToBuildToolsFile("msbuild.exe", ToolLocationHelper.CurrentToolsVersion);

无论是只安装构建工具还是完整 Visual Studio 安装,这都有效。

这已在 PreRelease 版本中解决 (15.5.0-preview-000072-0942130) of msbuild, see MsBuild issue #2369: msbuild nuget package unable to open vs2017 csproj files。 所以将来不需要进一步的黑客攻击

现在更容易了,因为 Microsoft 已经发布了他们自己的软件包。

设置包引用as described here。请注意除 Locator 包之外的所有包上的 ExcludeAssets="runtime"

<PackageReference Include="Microsoft.Build.Locator" Version="1.4.1" />
<PackageReference Include="Microsoft.Build" Version="16.10.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10.0" ExcludeAssets="runtime" />
public Init()
{
    MSBuildLocator.RegisterDefaults();
    var project = LoadProject(@"C:\Path\To\Project.csproj");
}

public Project LoadProject(string csprojPath)
{
    var context = new ProjectCollection();
    return context.LoadProject(csprojPath);
}

根据上面的文档 link,对 Microsoft.Build 程序集的调用需要以不同于 MSBuildLocator.RegisterDefaults().

的方法进行