如何从命令行或C#应用程序检测msbuild的状态

30

我正在使用C#编写一个结账、构建和部署应用程序,并需要知道检测我对msbuild.exe的调用是否成功的最佳方法。我已经尝试使用进程的错误代码,但我不确定这是否总是准确的。

通过下面的代码,是否有一种方法可以告诉我msbuild.exe是否已成功完成?

try
{
    Process msbProcess = new Process();
    msbProcess.StartInfo.FileName = this.MSBuildPath;
    msbProcess.StartInfo.Arguments = msbArguments;
    msbProcess.Start();
    msbProcess.WaitForExit();

    if (msbProcess.ExitCode != 0)
    {
        //
    }
    else
    {
        //
    }

    msbProcess.Close();
}
catch (Exception ex)
{
    //
}

我会认为你的代码在检查退出代码时能够正常工作,难道不是吗?你考虑过使用nant脚本吗? - Orn Kristjansson
2个回答

35
据我所知,当MSBuild遇到错误时,它会返回大于零的退出代码。如果没有遇到任何错误,它将返回退出代码0。我从未见过它以小于0的代码退出。
我在批处理文件中使用它:
msbuild <args>
if errorlevel 1 goto errorDone

在这种使用方式下的四年时间里,我从未怀疑过这种做法的正确性。

MSDN论坛上有几个问题询问同样的事情

标准的回答实际上是,“如果错误级别为0,则没有错误”。


4
"错误"的意思是什么?是编译错误还是其他问题?我的 C++ 项目在 msbuild 中返回 ExitCode 0,并在控制台输出中写入:8个警告,7个错误。如何让一个 C# 应用程序获取错误级别? - Vertexwahn
1
Jim,如果项目编译仅出现警告,该怎么办?msbuild应该返回什么代码? - Farrukh Waheed
@FarrukhWaheed:我不知道。创建一个带有警告的程序,然后使用批处理文件进行编译。在批处理文件中,在msbuild命令之后添加一行echo Exit Code is %errorlevel% - Jim Mischel
当我从命令行调用msbuild并提供无效的解决方案文件路径时,%ERRORLEVEL%被设置为0。只有当编译失败时才会给出错误吗? - Developer Webs

9

很抱歉,如果我来晚了...但是在问题发布近7年后,我想看到一个完整的答案。我使用下面的代码进行了一些测试,以下是结论:


分析

msbuild.exe 当至少发生一个构建错误时返回 1,当构建成功完成时返回 0。目前,该程序不考虑警告,这意味着带有警告的成功构建仍会使 msbuild.exe 返回 0

其他错误,例如:尝试构建不存在的项目,或提供不正确的参数(例如/myInvalidArgument),也会导致 msbuild.exe 返回 1


源代码

以下C#代码是通过从命令行启动 msbuild.exe 来构建您最喜欢的项目的完整实现。在编译项目之前,不要忘记设置任何必要的环境设置。

您的 BuildControl 类:

using System;

namespace Example
{
    public sealed class BuildControl
    {
        // ...

        public bool BuildStuff()
        {
            MsBuilder builder = new MsBuilder(@"C:\...\project.csproj", "Release", "x86")
            {
                Target = "Rebuild", // for rebuilding instead of just building
            };
            bool success = builder.Build(out string buildOutput);
            Console.WriteLine(buildOutput);
            return success;
        }

        // ...
    }
}

MsBuilder类:通过从命令行调用MsBuild.exe来构建项目:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Example
{
    public sealed class MsBuilder
    {
        public string ProjectPath { get; }
        public string LogPath { get; set; }

        public string Configuration { get; }
        public string Platform { get; }

        public int MaxCpuCount { get; set; } = 1;
        public string Target { get; set; } = "Build";

        public string MsBuildPath { get; set; } =
            @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MsBuild.exe";

        public string BuildOutput { get; private set; }

        public MsBuilder(string projectPath, string configuration, string platform)
        {
            ProjectPath = !string.IsNullOrWhiteSpace(projectPath) ? projectPath : throw new ArgumentNullException(nameof(projectPath));
            if (!File.Exists(ProjectPath)) throw new FileNotFoundException(projectPath);
            Configuration = !string.IsNullOrWhiteSpace(configuration) ? configuration : throw new ArgumentNullException(nameof(configuration));
            Platform = !string.IsNullOrWhiteSpace(platform) ? platform : throw new ArgumentNullException(nameof(platform));
            LogPath = Path.Combine(Path.GetDirectoryName(ProjectPath), $"{Path.GetFileName(ProjectPath)}.{Configuration}-{Platform}.msbuild.log");
        }

        public bool Build(out string buildOutput)
        {
            List<string> arguments = new List<string>()
            {
                $"/nologo",
                $"\"{ProjectPath}\"",
                $"/p:Configuration={Configuration}",
                $"/p:Platform={Platform}",
                $"/t:{Target}",
                $"/maxcpucount:{(MaxCpuCount > 0 ? MaxCpuCount : 1)}",
                $"/fileLoggerParameters:LogFile=\"{LogPath}\";Append;Verbosity=diagnostic;Encoding=UTF-8",
            };

            using (CommandLineProcess cmd = new CommandLineProcess(MsBuildPath, string.Join(" ", arguments)))
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"Build started: Project: '{ProjectPath}', Configuration: {Configuration}, Platform: {Platform}");

                // Call MsBuild:
                int exitCode = cmd.Run(out string processOutput, out string processError);

                // Check result:
                sb.AppendLine(processOutput);
                if (exitCode == 0)
                {
                    sb.AppendLine("Build completed successfully!");
                    buildOutput = sb.ToString();
                    return true;
                }
                else
                {
                    if (!string.IsNullOrWhiteSpace(processError))
                        sb.AppendLine($"MSBUILD PROCESS ERROR: {processError}");
                    sb.AppendLine("Build failed!");
                    buildOutput = sb.ToString();
                    return false;
                }
            }
        }

    }
}

CommandLineProcess类 - 开始一个命令行进程并等待其完成。所有标准输出/错误都被捕获,不会为该进程启动单独的窗口:

using System;
using System.Diagnostics;
using System.IO;

namespace Example
{
    public sealed class CommandLineProcess : IDisposable
    {
        public string Path { get; }
        public string Arguments { get; }
        public bool IsRunning { get; private set; }
        public int? ExitCode { get; private set; }

        private Process Process;
        private readonly object Locker = new object();

        public CommandLineProcess(string path, string arguments)
        {
            Path = path ?? throw new ArgumentNullException(nameof(path));
            if (!File.Exists(path)) throw new ArgumentException($"Executable not found: {path}");
            Arguments = arguments;
        }

        public int Run(out string output, out string err)
        {
            lock (Locker)
            {
                if (IsRunning) throw new Exception("The process is already running");

                Process = new Process()
                {
                    EnableRaisingEvents = true,
                    StartInfo = new ProcessStartInfo()
                    {
                        FileName = Path,
                        Arguments = Arguments,
                        UseShellExecute = false,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                        CreateNoWindow = true,
                    },
                };

                if (!Process.Start()) throw new Exception("Process could not be started");
                output = Process.StandardOutput.ReadToEnd();
                err = Process.StandardError.ReadToEnd();
                Process.WaitForExit();
                try { Process.Refresh(); } catch { }
                return (ExitCode = Process.ExitCode).Value;
            }
        }

        public void Kill()
        {
            lock (Locker)
            {
                try { Process?.Kill(); }
                catch { }
                IsRunning = false;
                Process = null;
            }
        }

        public void Dispose()
        {
            try { Process?.Dispose(); }
            catch { }
        }
    }
}

PS: 我正在使用Visual Studio 2017 / .NET 4.7.2。


1
5年后,我可以确认,如果出现内部错误,如下所示,MSBuild可能会失败但仍然返回0: MSBUILD:错误MSB1025:运行MSBuild时发生内部故障。 Microsoft.Build.Framework.InternalErrorException:MSB0001:内部MSBuild错误:无法获取所需的节点数。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接