如何检查在使用ContinueOnError=true时MSBuild任务是否失败

15

我正在使用 MSBuild 任务并设置 ContinueOnError=true

<MSBuild Projects="@(ComponentToDeploy)"
    Targets="$(DeploymentTargets)"
    Properties="$(CommonProperties);%(AdditionalProperties)"
    ContinueOnError="true" 
    Condition="%(Condition)"/>

所以我的构建总是成功的。

有没有办法找出是否发生了任何错误?

我找不到任何包含此信息的MSBuild任务的输出。我知道的唯一方法是解析日志文件以查找错误,但对我来说这似乎是一个变通方法。

(我正在使用MSBuild 4.0)


这是对@Ilya最后反馈的回答。
由于评论的长度和格式限制,我使用反馈/回答。

日志的作用域是单个目标或更具体地说是任务...

这确实是当我阅读您的评论并建议使用Log.HasLoggedErrors时首先引起疑问的第一个问题:“日志的作用域是什么?”。不幸的是,我没有找到适当的文档。MSND并没有帮助太多...
你怎么知道它的作用域是任务?
我完全不怀疑你的陈述!我只是想知道是否有适当的文档存在。 (我已经好几年没有使用MSBuild了;-)

无论如何,您正在构建什么项目?

我的测试项目非常简单。
MyTest.project

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="ElenasTarget" ToolsVersion="4.0">

    <UsingTask AssemblyFile="$(MSBuildProjectDirectory)\MyCompany.Tools.MSBuild.Tasks.dll" TaskName="MSBuildWithHasLoggedErrors" />

    <ItemGroup>
        <MyProjects Include="CopyNotExistingFile.proj" />
    </ItemGroup>

    <Target Name="ElenasTarget">
        <MSBuildWithHasLoggedErrors Projects="@(MyProjects)" ContinueOnError="true" >
            <Output TaskParameter="HasLoggedErrors" PropertyName="BuildFailed" />
         </MSBuildWithHasLoggedErrors>

         <Message Text="BuildFailed=$(BuildFailed)" />
  </Target>
</Project>
< p > < em > CopyNotExistingFile.proj 只会尝试复制一个不存在的文件:

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Target1" ToolsVersion="4.0">
    <Target Name="Target1"> 
         <Copy SourceFiles="C:\lalala.bum" DestinationFiles="C:\tralala.bam" />
  </Target>
</Project>

这是我的自定义任务 MSBuildWithHasLoggedErrors

namespace MyCompany.Tools.MSBuild.Tasks
{
    public class MSBuildWithHasLoggedErrors : Microsoft.Build.Tasks.MSBuild
    {
        [Output]
        public bool HasLoggedErrors { get; private set; }

        public override bool Execute()
        {
            try
            {
                base.Execute();
                HasLoggedErrors = Log.HasLoggedErrors;
            }
            catch (Exception e)
            {
                Log.LogErrorFromException(e, true);
                return false;
            }

            return true;
        }
    }
}

如果我构建MyTest.proj,即使在控制台记录了一个错误(MSB3021),HasLoggedErrors也会被设置为false

如果我构建MyTest.proj,即使在控制台记录了一个错误(MSB3021),HasLoggedErrors也会被设置为false。
Project "C:\Users\elena\mytest.proj" on node 1 (default targets).
Project "C:\Users\elena\mytest.proj" (1) is building "C:\Users\elena\CopyNotExistingFile.proj" (2) on node 1 (default targets).
Target1:
  Copying file from "C:\lalala.bum" to "C:\tralala.bam".
C:\Users\elena\CopyNotExistingFile.proj(5,4): error MSB3021: Unable to copy file "C:\lalala.bum" to "C:\tralala.bam". Could not find file 'C:\lalala.bum'.
Done Building Project "C:\Users\elena\CopyNotExistingFile.proj" (default targets) -- FAILED.
ElenasTarget:
  BuildFailed=False
Done Building Project "C:\Users\elena\mytest.proj" (default targets).

Build succeeded.

我期望 HasLoggedErrors 将被设置为 true



一种方法是构建自身,但使用不同的目标,例如您的 DefaultTargets 启动您的自定义 MSBuildWrapper 任务,指向自己(即 $(MSBuildProjectFile)),但使用其他构建、复制等不同的目标。

我已经尝试过了(那就是我在帖子中提到的调查)。不幸的是它也不起作用 :-(
(我知道你说“理论上”可以)。 我的新的单个项目看起来像这样:

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="ElenasTarget" ToolsVersion="4.0">

    <UsingTask AssemblyFile="$(MSBuildProjectDirectory)\MyCompany.Tools.MSBuild.Tasks.dll" TaskName="MSBuildWithHasLoggedErrors" />

    <Target Name="ElenasTarget">
        <MSBuildWithHasLoggedErrors Projects="$(MSBuildProjectFile)" Targets="CopyNotExistingFile" ContinueOnError="true" >
            <Output TaskParameter="HasLoggedErrors" PropertyName="BuildFailed" />
         </MSBuildWithHasLoggedErrors>

         <Message Text="BuildFailed=$(BuildFailed)" />
  </Target>

  <Target Name="CopyNotExistingFile" >
         <Copy SourceFiles="C:\lalala.bum" DestinationFiles="C:\tralala.bam" />
  </Target>
</Project>
如果我构建这个项目,HasLoggedErrors 仍将被设置为 false
(此外,我正在维护的“真实”构建更为复杂,包含多个带有目标的项目文件...因此我无法将它们全部打包到一个项目文件中)。

或编写自定义记录器并通过命令行传递。
那是我的最后希望!
我的“真实”构建通过命令行传递了自定义记录器(出于简单起见,我没有在我的测试项目中使用它)。 这实际上正在生成日志(XML 文件),我将解析该日志以查找是否已记录任何错误。
顺便说一下,我认为控制台记录器是一种“全局”记录器。 我错了吗?
无论如何,自定义记录器也没有帮助,Log.HasLoggedErrors 仍然被设置为 false。
我不知道是否有某种方式可以引用特定的记录器(例如我的自定义记录器)以查询是否已记录任何错误?
它看起来确实像 Log 是针对各个目标作用域的。
嗯... 如果反射生成实例是最后的手段,我仍然更喜欢解析日志。
(别责备我!:-))

我的决定
经过一些调查,我决定坚持我的最初解决方案:解析日志以查找构建是否失败。
请检查我的评论,了解为什么我更喜欢这个建议。
如果有人有其他想法,请不要犹豫分享 :-)
(否则,我想可以关闭此问题...)
6个回答

22

MSBuildLastTaskResult 保留属性 将会被设置为 True 如果最后一个任务成功,否则将被设置为 False:

<MSBuild Projects="@(ComponentToDeploy)"
         Targets="$(DeploymentTargets)"
         Properties="$(CommonProperties);%(AdditionalProperties)"
         ContinueOnError="true" 
         Condition="%(Condition)" />
<Message Text="MSBuild failed!" Condition="'$(MSBuildLastTaskResult)' == 'False'" />

我相信这是在MSBuild v4.0中引入的。


谢谢你的提示,让我知道了一个在MSDN网站上还未列出的新保留属性!我之前并不知道它的存在。这就是我一直在寻找的解决方案,非常感谢! - Elena
如果您查看MSDN保留属性页面底部,您会发现社区中的某个人友好地记录了几个缺失的属性。令人困惑的是,官方文档从未更新过。 - Aaron Jensen
是的,我今天刚看到这条评论,而且我甚至在桌子上放着Sayed Ibrahim Hashimi的最新版书籍;-) 但我之前并不知道这个属性,再次感谢! - Elena
使用MSBuild 17.5进行测试,$(MSBuildLastTaskResult)仅在上一个任务失败时才为False。这听起来很琐碎,但是对于任务批处理来说并非如此。也就是说,如果任务批处理执行两次MSBuild任务,而只有第一个失败了,整个构建将会失败,但该属性将被设置为True - tm1
相关的是,如果您使用CallTarget而不是MSBuild并使用任务批处理,则整个构建将成功,并且当第一个任务失败且最后一个任务成功时,$(MSBuildLastTaskResult)设置为True。但是,如果不使用任务批处理,则CallTarget任务将在第一个失败的任务处停止,将$(MSBuildLastTaskResult)设置为False并使构建失败。这些不同的行为可以视为不一致性或强大的功能,具体取决于观点。 - tm1

4

我知道这个帖子有点旧了,但是另一个可能的解决方案是,我假设你需要知道构建失败以执行一些“最终任务”,那么可以使用:

<OnError ExecuteTargets="FinalReportTarget;CleanupTarget" />

如果出现错误,将导致构建失败,但会执行“FinalReportTarget”和“CleanupTarget”。

在这种情况下,不需要使用ContinueOnError="true"


1
Aleksey,谢谢你的建议,但我的重点是我想使用"ContinueOnError="true""。我在项目中有几个MSBuild任务,我想运行它们所有,不管其中一些是否失败。但最终我希望能够检查是否有一些任务失败。使用您的方法,当其中一个任务失败时,构建将立即终止。 - Elena
这可能不是最优雅的解决方案,但你可以在每个单独的标签中链接 OnError,以便在预期出现错误的地方发生。此外,您可以拥有一些常见的 PrintError 任务,例如 <OnError ExecuteTargets="PrintError;NextTask">,其中在 PrintError 中您将检查 MSBuildLastTaskResult。 - Alex M

0

从.NET Framework 4.5开始,您可以使用ContinueOnError="ErrorAndContinue"来让整个构建失败。


0
你可以捕获TargetOutputs,然后检查它们是否存在错误条件,但这仍然相当不规范。

嗯...如果没有更好的方法,我最好创建一个自定义任务来解析我的日志文件并计算错误(和警告)数量。这样可以让我的项目文件看起来更清晰。@skolima,无论如何感谢你的快速回答! - Elena
4
无需解析日志,只需继承自Microsoft.Build.Tasks.MSBuild并公开一个输出,该输出返回Log.HasLoggedErrors。 - Ilya Kozhevnikov
嗯...我已经实现了一个简单的例子,但它并没有按预期工作。如果MSBuild任务本身记录了错误(例如,如果找不到项目文件),则Log.HasLoggedErrors返回true。但是,如果某个目标内部的另一个任务(例如Copy任务)引发了错误,则Log.HasLoggedErrors返回false。此外,MSDN关于Log属性的说明是:“此API支持.NET Framework基础结构,不建议直接从您的代码中使用。”这让我很紧张...听起来好像随时都可能被删除... - Elena

0

如果您只想检查MSBuild任务是否失败,请使用Exec任务。将IgnoreExitCode设置为true并检查ExitCode输出值。如果不是零,则表示出现了问题。

如果您需要构建错误列表,请使用/fileloggerparameters命令行开关仅将错误记录到某个特定文件中:

/flp1:logfile=errors.txt;errorsonly


жҲ‘еҸӘжғіжЈҖжҹҘMSBuildд»»еҠЎжҳҜеҗҰеӨұиҙҘпјҢдҪҶжҲ‘дёҚжғідҪҝз”ЁExecд»»еҠЎжӣҝд»ЈMSBuildд»»еҠЎпјҢеӣ дёәеҗҺиҖ…жңүе…¶дјҳеҠҝгҖӮ - Elena

0
但是,如果某个目标(例如Copytask)内部的另一个任务引发了错误,则Log.HasLoggedErrors返回false。
Log作用于单个目标或更具体地说是任务,并且(据我所知)没有办法获得“全局”日志,可能是通过对buildengine实例进行反射或编写自定义记录器并通过命令行传递它。无论如何,您正在构建什么项目?HasLoggedErrors按预期工作(并且多年来一直未更改),它显示正在构建的项目是否记录了任何错误。它不应该对其他任务的日志记录(可能使用其他类型的记录器)有任何控制。如果您想要全局的日志记录器,一种方法是构建自身,但使用不同的目标,例如您的DefaultTargets启动您的自定义MSBuildWrapper任务,指向自己(即$(MSBuildProjectFile)),但使用执行其他构建、复制等操作的不同目标,在理论上它应该模拟全局HasLoggedErrors...

我承认我应该详细描述我的“调查”。 我已经完成了,看看我的答案。 无论如何,我非常感谢您的建议和帮助的意愿! :-) - Elena

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