diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index f3cff6b420c..89b00452150 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -203,6 +203,10 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, { System.Collections.Generic.IReadOnlyDictionary GetGlobalProperties(); } + public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 + { + bool AllowFailureWithoutError { get; set; } + } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { void Cancel(); diff --git a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs index 91a1c9be424..cb14afa8c1c 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -203,6 +203,10 @@ public partial interface IBuildEngine6 : Microsoft.Build.Framework.IBuildEngine, { System.Collections.Generic.IReadOnlyDictionary GetGlobalProperties(); } + public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6 + { + bool AllowFailureWithoutError { get; set; } + } public partial interface ICancelableTask : Microsoft.Build.Framework.ITask { void Cancel(); diff --git a/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs b/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs index d739b45082b..146d06ca8bc 100644 --- a/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs +++ b/ref/Microsoft.Build.Utilities.Core/net/Microsoft.Build.Utilities.Core.cs @@ -352,6 +352,7 @@ protected Task(System.Resources.ResourceManager taskResources, string helpKeywor public Microsoft.Build.Framework.IBuildEngine4 BuildEngine4 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine5 BuildEngine5 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine6 BuildEngine6 { get { throw null; } } + public Microsoft.Build.Framework.IBuildEngine7 BuildEngine7 { get { throw null; } } protected string HelpKeywordPrefix { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskHost HostObject { get { throw null; } set { } } public Microsoft.Build.Utilities.TaskLoggingHelper Log { get { throw null; } } diff --git a/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs b/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs index 1263a5949a1..fbc4b28ef38 100644 --- a/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs +++ b/ref/Microsoft.Build.Utilities.Core/netstandard/Microsoft.Build.Utilities.Core.cs @@ -197,6 +197,7 @@ protected Task(System.Resources.ResourceManager taskResources, string helpKeywor public Microsoft.Build.Framework.IBuildEngine4 BuildEngine4 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine5 BuildEngine5 { get { throw null; } } public Microsoft.Build.Framework.IBuildEngine6 BuildEngine6 { get { throw null; } } + public Microsoft.Build.Framework.IBuildEngine7 BuildEngine7 { get { throw null; } } protected string HelpKeywordPrefix { get { throw null; } set { } } public Microsoft.Build.Framework.ITaskHost HostObject { get { throw null; } set { } } public Microsoft.Build.Utilities.TaskLoggingHelper Log { get { throw null; } } diff --git a/ref/Microsoft.Build/net/Microsoft.Build.cs b/ref/Microsoft.Build/net/Microsoft.Build.cs index b8440d4530d..7a8e01c74d0 100644 --- a/ref/Microsoft.Build/net/Microsoft.Build.cs +++ b/ref/Microsoft.Build/net/Microsoft.Build.cs @@ -967,6 +967,7 @@ public partial class BuildParameters { public BuildParameters() { } public BuildParameters(Microsoft.Build.Evaluation.ProjectCollection projectCollection) { } + public bool AllowFailureWithoutError { get { throw null; } set { } } public System.Collections.Generic.IDictionary BuildProcessEnvironment { get { throw null; } } public System.Threading.ThreadPriority BuildThreadPriority { get { throw null; } set { } } public System.Globalization.CultureInfo Culture { get { throw null; } set { } } diff --git a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs index 5a215a8c307..355ee5fbd83 100644 --- a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs +++ b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs @@ -963,6 +963,7 @@ public partial class BuildParameters { public BuildParameters() { } public BuildParameters(Microsoft.Build.Evaluation.ProjectCollection projectCollection) { } + public bool AllowFailureWithoutError { get { throw null; } set { } } public System.Collections.Generic.IDictionary BuildProcessEnvironment { get { throw null; } } public System.Globalization.CultureInfo Culture { get { throw null; } set { } } public string DefaultToolsVersion { get { throw null; } set { } } diff --git a/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs b/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs index 0db697ff651..af9bf647c5d 100644 --- a/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs @@ -10,8 +10,8 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Unittest; +using Shouldly; using Xunit; - using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; namespace Microsoft.Build.UnitTests.BackEnd diff --git a/src/Build.UnitTests/BackEnd/FailingTask.cs b/src/Build.UnitTests/BackEnd/FailingTask.cs new file mode 100644 index 00000000000..c42f912d679 --- /dev/null +++ b/src/Build.UnitTests/BackEnd/FailingTask.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.Build.UnitTests +{ + public class FailingTask : Task + { + public override bool Execute() + { + BuildEngine.GetType().GetProperty("AllowFailureWithoutError").SetValue(BuildEngine, EnableDefaultFailure); + return false; + } + + [Required] + public bool EnableDefaultFailure { get; set; } + } +} diff --git a/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs index 861d10cd4b6..338cc793c8e 100644 --- a/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs @@ -4,8 +4,10 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; +using Shouldly; using System; using Xunit; +using Xunit.Abstractions; namespace Microsoft.Build.UnitTests.BackEnd { @@ -14,29 +16,36 @@ namespace Microsoft.Build.UnitTests.BackEnd /// public class LoggingContext_Tests { + private readonly ITestOutputHelper _output; + + public LoggingContext_Tests(ITestOutputHelper outputHelper) + { + _output = outputHelper; + } + /// /// A few simple tests for NodeLoggingContexts. /// [Fact] public void CreateValidNodeLoggingContexts() { - NodeLoggingContext context = new NodeLoggingContext(new MockLoggingService(), 1, true); - Assert.True(context.IsInProcNode); - Assert.True(context.IsValid); + NodeLoggingContext context = new NodeLoggingContext(new MockLoggingService(_output.WriteLine), 1, true); + context.IsInProcNode.ShouldBeTrue(); + context.IsValid.ShouldBeTrue(); context.LogBuildFinished(true); - Assert.False(context.IsValid); + context.IsValid.ShouldBeFalse(); - Assert.Equal(1, context.BuildEventContext.NodeId); + context.BuildEventContext.NodeId.ShouldBe(1); - NodeLoggingContext context2 = new NodeLoggingContext(new MockLoggingService(), 2, false); - Assert.False(context2.IsInProcNode); - Assert.True(context2.IsValid); + NodeLoggingContext context2 = new NodeLoggingContext(new MockLoggingService(_output.WriteLine), 2, false); + context2.IsInProcNode.ShouldBeFalse(); + context2.IsValid.ShouldBeTrue(); context2.LogBuildFinished(true); - Assert.False(context2.IsValid); + context2.IsValid.ShouldBeFalse(); - Assert.Equal(2, context2.BuildEventContext.NodeId); + context2.BuildEventContext.NodeId.ShouldBe(2); } /// @@ -49,9 +58,25 @@ public void InvalidNodeIdOnNodeLoggingContext() { Assert.Throws(() => { - NodeLoggingContext context = new NodeLoggingContext(new MockLoggingService(), -2, true); + _ = new NodeLoggingContext(new MockLoggingService(), -2, true); } ); } + + [Fact] + public void HasLoggedErrors() + { + NodeLoggingContext context = new NodeLoggingContext(new MockLoggingService(_output.WriteLine), 1, true); + context.HasLoggedErrors.ShouldBeFalse(); + + context.LogCommentFromText(Framework.MessageImportance.High, "Test message"); + context.HasLoggedErrors.ShouldBeFalse(); + + context.LogWarningFromText(null, null, null, null, "Test warning"); + context.HasLoggedErrors.ShouldBeFalse(); + + context.LogErrorFromText(null, null, null, null, "Test error"); + context.HasLoggedErrors.ShouldBeTrue(); + } } -} \ No newline at end of file +} diff --git a/src/Build.UnitTests/BackEnd/OnError_Tests.cs b/src/Build.UnitTests/BackEnd/OnError_Tests.cs index 191f8573567..745a3e91b04 100644 --- a/src/Build.UnitTests/BackEnd/OnError_Tests.cs +++ b/src/Build.UnitTests/BackEnd/OnError_Tests.cs @@ -11,6 +11,9 @@ using System.Xml; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; using Xunit; +using System.Reflection; +using Shouldly; +using System.Linq; namespace Microsoft.Build.UnitTests.BackEnd { @@ -559,6 +562,29 @@ public void OutOfOrderOnError() ); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ErrorWhenTaskFailsWithoutLoggingErrorEscapeHatch(bool emitError) + { + MockLogger logger = ObjectModelHelpers.BuildProjectExpectFailure($@" + + + + + +"); + if (emitError) + { + logger.ErrorCount.ShouldBe(1); + logger.Errors.First().Code.ShouldBe("MSB4181"); + } + else + { + logger.ErrorCount.ShouldBe(0); + } + } + #region Postbuild /* * Method: PostBuildBasic diff --git a/src/Build.UnitTests/BackEnd/ReturnFailureWithoutLoggingErrorTask.cs b/src/Build.UnitTests/BackEnd/ReturnFailureWithoutLoggingErrorTask.cs new file mode 100644 index 00000000000..8d9b0b8b436 --- /dev/null +++ b/src/Build.UnitTests/BackEnd/ReturnFailureWithoutLoggingErrorTask.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Build.BackEnd.Logging; +using Microsoft.Build.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +namespace Microsoft.Build.UnitTests +{ + /// + /// This task was created for https://github.com/microsoft/msbuild/issues/2036 + /// + public class ReturnFailureWithoutLoggingErrorTask : Task + { + /// + /// Intentionally return false without logging an error to test proper error catching. + /// + /// + public override bool Execute() + { + return false; + } + } +} diff --git a/src/Build.UnitTests/WarningsAsMessagesAndErrors_Tests.cs b/src/Build.UnitTests/WarningsAsMessagesAndErrors_Tests.cs index 0c8aa4a2da8..6cde0e901df 100644 --- a/src/Build.UnitTests/WarningsAsMessagesAndErrors_Tests.cs +++ b/src/Build.UnitTests/WarningsAsMessagesAndErrors_Tests.cs @@ -4,7 +4,9 @@ using System.Linq; using Microsoft.Build.Framework; using Microsoft.Build.UnitTests; +using Shouldly; using Xunit; +using Xunit.Abstractions; namespace Microsoft.Build.Engine.UnitTests { @@ -13,6 +15,13 @@ public sealed class WarningsAsMessagesAndErrorsTests private const string ExpectedEventMessage = "03767942CDB147B98D0ECDBDE1436DA3"; private const string ExpectedEventCode = "0BF68998"; + ITestOutputHelper _output; + + public WarningsAsMessagesAndErrorsTests(ITestOutputHelper output) + { + _output = output; + } + [Fact] public void TreatAllWarningsAsErrors() { @@ -29,7 +38,7 @@ public void TreatAllWarningsAsErrors() [Fact] public void TreatWarningsAsErrorsWhenBuildingSameProjectMultipleTimes() { - using (TestEnvironment testEnvironment = TestEnvironment.Create()) + using (TestEnvironment testEnvironment = TestEnvironment.Create(_output)) { TransientTestProjectWithFiles project2 = testEnvironment.CreateTestProjectWithFiles($@" @@ -123,7 +132,7 @@ public void TreatWarningsAsMessagesWhenSpecified() [Fact] public void TreatWarningsAsMessagesWhenBuildingSameProjectMultipleTimes() { - using (TestEnvironment testEnvironment = TestEnvironment.Create()) + using (TestEnvironment testEnvironment = TestEnvironment.Create(_output)) { TransientTestProjectWithFiles project2 = testEnvironment.CreateTestProjectWithFiles($@" @@ -263,5 +272,111 @@ private string GetTestProject(bool? treatAllWarningsAsErrors = null, string warn "; } + + [Fact] + public void TaskReturnsFailureButDoesNotLogError_ShouldCauseBuildFailure() + { + + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectFailure(); + + logger.AssertLogContains("MSB4181"); + } + } + + [Fact] + public void TaskReturnsFailureButDoesNotLogError_ContinueOnError_WarnAndContinue() + { + + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectSuccess(); + + logger.WarningCount.ShouldBe(1); + + logger.AssertLogContains("MSB4181"); + } + } + + [Fact] + public void TaskReturnsFailureButDoesNotLogError_ContinueOnError_True() + { + + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectSuccess(); + + logger.AssertLogContains("MSB4181"); + } + } + + [Fact] + public void TaskReturnsFailureButDoesNotLogError_ContinueOnError_ErrorAndStop() + { + + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectFailure(); + + logger.AssertLogContains("MSB4181"); + } + } + + [Fact] + public void TaskReturnsFailureButDoesNotLogError_ContinueOnError_False() + { + + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectFailure(); + + logger.AssertLogContains("MSB4181"); + } + } } } diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 44a8f200613..f542c41e721 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -317,6 +317,12 @@ public bool UseSynchronousLogging set => _useSynchronousLogging = value; } + + /// + /// Indicates whether to emit a default error if a task returns false without logging an error. + /// + public bool AllowFailureWithoutError { get; set; } = true; + /// /// Gets the environment variables which were set when this build was created. /// diff --git a/src/Build/BackEnd/Components/Logging/BuildLoggingContext.cs b/src/Build/BackEnd/Components/Logging/BuildLoggingContext.cs index 49f31354b1c..888ba5a557c 100644 --- a/src/Build/BackEnd/Components/Logging/BuildLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/BuildLoggingContext.cs @@ -65,6 +65,7 @@ internal void LogFatalTaskError(Exception exception, BuildEventFileInfo file, st { ErrorUtilities.VerifyThrow(IsValid, "must be valid"); LoggingService.LogFatalTaskError(BuildEventContext, exception, file, taskName); + _hasLoggedErrors = true; } } } diff --git a/src/Build/BackEnd/Components/Logging/LoggingContext.cs b/src/Build/BackEnd/Components/Logging/LoggingContext.cs index 51edfe927d6..2ba4ef2be8c 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingContext.cs @@ -28,6 +28,8 @@ internal class LoggingContext /// private bool _isValid; + protected bool _hasLoggedErrors; + /// /// Constructs the logging context from a logging service and an event context. /// @@ -41,6 +43,7 @@ public LoggingContext(ILoggingService loggingService, BuildEventContext eventCon _loggingService = loggingService; _eventContext = eventContext; _isValid = false; + _hasLoggedErrors = false; } /// @@ -106,6 +109,8 @@ protected set } } + internal bool HasLoggedErrors { get { return _hasLoggedErrors; } set { _hasLoggedErrors = value; } } + /// /// Helper method to create a message build event from a string resource and some parameters /// @@ -139,6 +144,7 @@ internal void LogError(BuildEventFileInfo file, string messageResourceName, para { ErrorUtilities.VerifyThrow(_isValid, "must be valid"); _loggingService.LogError(_eventContext, file, messageResourceName, messageArgs); + _hasLoggedErrors = true; } /// @@ -152,6 +158,7 @@ internal void LogErrorWithSubcategory(string subcategoryResourceName, BuildEvent { ErrorUtilities.VerifyThrow(_isValid, "must be valid"); _loggingService.LogError(_eventContext, subcategoryResourceName, file, messageResourceName, messageArgs); + _hasLoggedErrors = true; } /// @@ -166,6 +173,7 @@ internal void LogErrorFromText(string subcategoryResourceName, string errorCode, { ErrorUtilities.VerifyThrow(_isValid, "must be valid"); _loggingService.LogErrorFromText(_eventContext, subcategoryResourceName, errorCode, helpKeyword, file, message); + _hasLoggedErrors = true; } /// @@ -176,6 +184,7 @@ internal void LogInvalidProjectFileError(InvalidProjectFileException invalidProj { ErrorUtilities.VerifyThrow(_isValid, "must be valid"); _loggingService.LogInvalidProjectFileError(_eventContext, invalidProjectFileException); + _hasLoggedErrors = true; } /// @@ -189,6 +198,7 @@ internal void LogFatalError(Exception exception, BuildEventFileInfo file, string { ErrorUtilities.VerifyThrow(_isValid, "must be valid"); _loggingService.LogFatalError(_eventContext, exception, file, messageResourceName, messageArgs); + _hasLoggedErrors = true; } /// @@ -237,6 +247,7 @@ internal void LogFatalBuildError(Exception exception, BuildEventFileInfo file) { ErrorUtilities.VerifyThrow(IsValid, "must be valid"); LoggingService.LogFatalBuildError(BuildEventContext, exception, file); + _hasLoggedErrors = true; } } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index eba54a525eb..7469b45d780 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -768,6 +768,7 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta if (taskType == typeof(MSBuild)) { MSBuild msbuildTask = host.TaskInstance as MSBuild; + ErrorUtilities.VerifyThrow(msbuildTask != null, "Unexpected MSBuild internal task."); var undeclaredProjects = GetUndeclaredProjects(msbuildTask); @@ -943,6 +944,29 @@ private async Task ExecuteInstantiatedTask(ITaskExecutionHost ta } } + // When a task fails it must log an error. If a task fails to do so, + // that is logged as an error. MSBuild tasks are an exception because + // errors are not logged directly from them, but the tasks spawned by them. + IBuildEngine be = host.TaskInstance.BuildEngine; + if (taskReturned && !taskResult && !taskLoggingContext.HasLoggedErrors && (be is TaskHost th ? th.BuildRequestsSucceeded : false) && (be is IBuildEngine7 be7 ? be7.AllowFailureWithoutError : true)) + { + if (_continueOnError == ContinueOnError.WarnAndContinue) + { + taskLoggingContext.LogWarning(null, + new BuildEventFileInfo(_targetChildInstance.Location), + "TaskReturnedFalseButDidNotLogError", + _taskNode.Name); + + taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning"); + } + else + { + taskLoggingContext.LogError(new BuildEventFileInfo(_targetChildInstance.Location), + "TaskReturnedFalseButDidNotLogError", + _taskNode.Name); + } + } + // If the task returned attempt to gather its outputs. If gathering outputs fails set the taskResults // to false if (taskReturned) diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 4aac30ee2ff..c752657bfcb 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -34,7 +34,7 @@ internal class TaskHost : #if FEATURE_APPDOMAIN MarshalByRefObject, #endif - IBuildEngine6 + IBuildEngine7 { /// /// True if the "secret" environment variable MSBUILDNOINPROCNODE is set. @@ -445,6 +445,7 @@ public void LogErrorEvent(Microsoft.Build.Framework.BuildErrorEventArgs e) { e.BuildEventContext = _taskLoggingContext.BuildEventContext; _taskLoggingContext.LoggingService.LogBuildEvent(e); + _taskLoggingContext.HasLoggedErrors = true; } } } @@ -669,6 +670,13 @@ public IReadOnlyDictionary GetGlobalProperties() #endregion + #region IBuildEngine7 Members + /// + /// Enables or disables emitting a default error when a task fails without logging errors + /// + public bool AllowFailureWithoutError { get; set; } = true; + #endregion + /// /// Called by the internal MSBuild task. /// Does not take the lock because it is called by another request builder thread. diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 4b0fe42ac90..71ee7552cfa 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1101,9 +1101,9 @@ LOCALIZATION: "{2}" is a localized message from a CLR/FX exception. Also, Microsoft.Build.Framework should not be localized - - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + + MSB4181: The "{0}" task returned false but did not log an error. + {StrBegin="MSB4181: "} MSB1021: Cannot create an instance of the logger. {0} diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 262a0f64993..f43f4b17f33 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: Úloha {0} vrátila false, ale do protokolu se nezaznamenala chyba. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 64073a3496c..2cde397ce9a 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: Die Aufgabe "{0}" hat FALSE zurückgegeben, jedoch keinen Fehler protokolliert. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf index 1891ae50769..5aba65a0ddd 100644 --- a/src/Build/Resources/xlf/Strings.en.xlf +++ b/src/Build/Resources/xlf/Strings.en.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: The "{0}" task returned false but did not log an error. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 6211b592508..8f29bb2aed3 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: La tarea "{0}" devolvió false, pero no registró un error. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 10c3dc7f551..f1cd9cee144 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: la tâche "{0}" a retourné false mais n'a pas journalisé d'erreur. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 30f528b6661..60fc203b109 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: l'attività "{0}" ha restituito false, ma non è stato registrato alcun errore. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 50fc9bd8263..b5bddf4c851 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: "{0}" タスクから false が返されましたが、エラーがログに記録されませんでした。 + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index f3d92995264..33b37e07a1f 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: "{0}" 작업이 false를 반환했지만 오류를 기록하지 않았습니다. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index b9c95988257..051d1c3f027 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: Zadanie „{0}” zwróciło wartość false, ale nie zarejestrowało błędu. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index f857a832d90..b1103174c05 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: a tarefa "{0}" retornou false, mas não registrou um erro. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 2ae481c61d9..cc805f334fa 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: задача "{0}" возвратила значение false, но не зарегистрировала ошибку. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 248b8565c6f..cf5c74a8811 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: "{0}" görevi false değerini döndürdü ancak günlüğe hata kaydetmedi. + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index fd1afbf8728..0966373fbba 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: “{0}”任务返回了 false,但未记录错误。 + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 6c6f3879ab2..4bd39b6e142 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -200,9 +200,9 @@ - MSB4132: The "{0}" task returned false but did not log an error. - MSB4132: The "{0}" task returned false but did not log an error. - {StrBegin="MSB4132: "} + MSB4181: The "{0}" task returned false but did not log an error. + MSB4181: "{0}" 工作傳回了 false,但並未記錄錯誤。 + {StrBegin="MSB4181: "} MSB4254: The MSBuild task is building project(s) "{0}" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "{1}" diff --git a/src/Framework/IBuildEngine7.cs b/src/Framework/IBuildEngine7.cs new file mode 100644 index 00000000000..7f9200da1ab --- /dev/null +++ b/src/Framework/IBuildEngine7.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Build.Framework +{ + /// + /// This interface extends to allow tasks to set whether they want to + /// log an error when a task returns without logging an error. + /// + public interface IBuildEngine7 : IBuildEngine6 + { + public bool AllowFailureWithoutError { get; set; } + } +} diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index f29eff427fb..9050a35fd48 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -37,7 +37,7 @@ internal class OutOfProcTaskHostNode : #if CLR2COMPATIBILITY IBuildEngine3 #else - IBuildEngine6 + IBuildEngine7 #endif { /// @@ -268,6 +268,13 @@ public bool IsRunningMultipleNodes #endregion // IBuildEngine2 Implementation (Properties) + #region IBuildEngine7 Implementation + /// + /// Enables or disables emitting a default error when a task fails without logging errors + /// + public bool AllowFailureWithoutError { get; set; } = true; + #endregion + #region IBuildEngine Implementation (Methods) /// @@ -455,7 +462,6 @@ public IReadOnlyDictionary GetGlobalProperties() } #endregion - #endif #region INodePacketFactory Members diff --git a/src/Shared/UnitTests/MockEngine.cs b/src/Shared/UnitTests/MockEngine.cs index 4e8bea7d34c..466e3d639ce 100644 --- a/src/Shared/UnitTests/MockEngine.cs +++ b/src/Shared/UnitTests/MockEngine.cs @@ -31,7 +31,7 @@ namespace Microsoft.Build.UnitTests * is somewhat of a no-no for task assemblies. * **************************************************************************/ - internal sealed class MockEngine : IBuildEngine6 + internal sealed class MockEngine : IBuildEngine7 { private readonly object _lockObj = new object(); // Protects _log, _output private readonly ITestOutputHelper _output; @@ -51,6 +51,8 @@ internal MockEngine() : this(false) internal int Errors { get; set; } + public bool AllowFailureWithoutError { get; set; } = true; + public BuildErrorEventArgs[] ErrorEvents => _errorEvents.ToArray(); public Dictionary GlobalProperties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/Tasks.UnitTests/AssemblyDependency/AssemblyFoldersFromConfig_Tests.cs b/src/Tasks.UnitTests/AssemblyDependency/AssemblyFoldersFromConfig_Tests.cs index 8dd512df8e7..4cf2b992ef4 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/AssemblyFoldersFromConfig_Tests.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/AssemblyFoldersFromConfig_Tests.cs @@ -93,7 +93,7 @@ public void AssemblyFoldersFromConfigPlatformSpecificAssemblyFirstTest() } [Fact] - public void AasemblyFoldersFromConfigNormalizeNetFrameworkVersion() + public void AssemblyFoldersFromConfigNormalizeNetFrameworkVersion() { var assemblyConfig = Path.GetTempFileName(); File.WriteAllText(assemblyConfig, TestFile); diff --git a/src/Utilities/Task.cs b/src/Utilities/Task.cs index 76c2d030bd8..39846721d3c 100644 --- a/src/Utilities/Task.cs +++ b/src/Utilities/Task.cs @@ -89,6 +89,11 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// public IBuildEngine6 BuildEngine6 => (IBuildEngine6)BuildEngine; + /// + /// Retrieves the version of the build engine interface provided by the host. + /// + public IBuildEngine7 BuildEngine7 => (IBuildEngine7)BuildEngine; + /// /// The build engine sets this property if the host IDE has associated a host object with this particular task. ///