diff --git a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs index 7a67734775b..5710084ae53 100644 --- a/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs @@ -211,6 +211,10 @@ public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, { bool AllowFailureWithoutError { get; set; } } + public partial interface IBuildEngine8 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7 + { + bool ShouldTreatWarningAsError(string warningCode); + } 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 9e3004af4bb..3ce966850da 100644 --- a/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs +++ b/ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs @@ -211,6 +211,10 @@ public partial interface IBuildEngine7 : Microsoft.Build.Framework.IBuildEngine, { bool AllowFailureWithoutError { get; set; } } + public partial interface IBuildEngine8 : Microsoft.Build.Framework.IBuildEngine, Microsoft.Build.Framework.IBuildEngine2, Microsoft.Build.Framework.IBuildEngine3, Microsoft.Build.Framework.IBuildEngine4, Microsoft.Build.Framework.IBuildEngine5, Microsoft.Build.Framework.IBuildEngine6, Microsoft.Build.Framework.IBuildEngine7 + { + bool ShouldTreatWarningAsError(string warningCode); + } 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 40abd53b294..56a49e69207 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 @@ -353,6 +353,7 @@ protected Task(System.Resources.ResourceManager taskResources, string helpKeywor 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; } } + public Microsoft.Build.Framework.IBuildEngine8 BuildEngine8 { 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 e6cc6f3fa50..ae04054e92b 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 @@ -198,6 +198,7 @@ protected Task(System.Resources.ResourceManager taskResources, string helpKeywor 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; } } + public Microsoft.Build.Framework.IBuildEngine8 BuildEngine8 { 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/src/Build.UnitTests/BackEnd/CustomLogAndReturnTask.cs b/src/Build.UnitTests/BackEnd/CustomLogAndReturnTask.cs new file mode 100644 index 00000000000..e6f05dff154 --- /dev/null +++ b/src/Build.UnitTests/BackEnd/CustomLogAndReturnTask.cs @@ -0,0 +1,39 @@ +// 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 CustomLogAndReturnTask : Task + { + public string WarningCode { get; set; } + + public string ErrorCode { get; set; } + + public bool ReturnHasLoggedErrors { get; set; } + + [Required] + public bool Return { get; set; } + + // Unused for now, created for task batching. + public ITaskItem[] Sources { get; set; } + + /// + /// This task returns and logs what you want based on the running test. + /// + public override bool Execute() + { + if(!string.IsNullOrEmpty(WarningCode)) + { + Log.LogWarning(null, WarningCode, null, null, 0, 0, 0, 0, "Warning Logged!", null); + } + + if(!string.IsNullOrEmpty(ErrorCode)) + { + Log.LogError(null, ErrorCode, null, null, 0, 0, 0, 0, "Error Logged!", null); + } + return ReturnHasLoggedErrors ? !Log.HasLoggedErrors : Return; + } + } +} diff --git a/src/Build.UnitTests/BackEnd/MockLoggingService.cs b/src/Build.UnitTests/BackEnd/MockLoggingService.cs index 8c7c480a220..cfd53b89220 100644 --- a/src/Build.UnitTests/BackEnd/MockLoggingService.cs +++ b/src/Build.UnitTests/BackEnd/MockLoggingService.cs @@ -557,6 +557,11 @@ public bool HasBuildSubmissionLoggedErrors(int submissionId) return false; } + public ICollection GetWarningsToBeLoggedAsErrorsByProject(BuildEventContext context) + { + throw new NotImplementedException(); + } + #endregion } } diff --git a/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs index 62a6e1a3d56..d892c47a917 100644 --- a/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; @@ -12,7 +13,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Utilities; - +using Shouldly; using Xunit; namespace Microsoft.Build.UnitTests.BackEnd @@ -36,22 +37,27 @@ public void ConstructorWithNullName() Assert.Throws(() => { TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - null, - @"c:\my tasks\mytask.dll", - null, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: null, + taskLocation: @"c:\my tasks\mytask.dll", + taskParameters: null, + globalParameters: null, + warningsAsErrors: null); } ); } @@ -64,22 +70,27 @@ public void ConstructorWithEmptyName() Assert.Throws(() => { TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - String.Empty, - @"c:\my tasks\mytask.dll", - null, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: String.Empty, + taskLocation: @"c:\my tasks\mytask.dll", + taskParameters: null, + globalParameters: null, + warningsAsErrors: null); } ); } @@ -92,22 +103,27 @@ public void ConstructorWithNullLocation() Assert.Throws(() => { TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - null, - null, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: null, + taskParameters: null, + globalParameters: null, + warningsAsErrors: null); } ); } @@ -122,22 +138,27 @@ public void ConstructorWithEmptyLocation() Assert.Throws(() => { TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - String.Empty, - null, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: String.Empty, + taskParameters: null, + globalParameters: null, + warningsAsErrors: null); } ); } @@ -150,22 +171,27 @@ public void ConstructorWithEmptyLocation() public void TestValidConstructors() { TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - @"c:\MyTasks\MyTask.dll", - null, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: null, + globalParameters: null, + warningsAsErrors: null); TaskHostConfiguration config2 = new TaskHostConfiguration( 1, @@ -183,26 +209,32 @@ public void TestValidConstructors() "TaskName", @"c:\MyTasks\MyTask.dll", null, + null, null); IDictionary parameters = new Dictionary(); TaskHostConfiguration config3 = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - @"c:\MyTasks\MyTask.dll", - parameters, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: parameters, + globalParameters: null, + warningsAsErrors: null); IDictionary parameters2 = new Dictionary(); parameters2.Add("Text", "Hello!"); @@ -211,22 +243,56 @@ public void TestValidConstructors() parameters2.Add("ItemArray", new ITaskItem[] { new TaskItem("DEF"), new TaskItem("GHI"), new TaskItem("JKL") }); TaskHostConfiguration config4 = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: parameters2, + globalParameters: null, + warningsAsErrors: null); + + HashSet WarningsAsErrors = new HashSet(); + WarningsAsErrors.Add("MSB1234"); + WarningsAsErrors.Add("MSB1235"); + WarningsAsErrors.Add("MSB1236"); + WarningsAsErrors.Add("MSB1237"); + + TaskHostConfiguration config5 = new TaskHostConfiguration( + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: +#if FEATURE_APPDOMAIN + null, +#endif + lineNumberOfTask: +#endif 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - @"c:\MyTasks\MyTask.dll", - parameters2, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: parameters2, + globalParameters: null, + warningsAsErrors: WarningsAsErrors); } /// @@ -242,22 +308,27 @@ public void TestTranslationWithNullDictionary() }; TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - @"c:\MyTasks\MyTask.dll", - null, - expectedGlobalProperties); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: null, + globalParameters: expectedGlobalProperties, + warningsAsErrors: null); ((ITranslatable)config).Translate(TranslationHelpers.GetWriteTranslator()); INodePacket packet = TaskHostConfiguration.FactoryForDeserialization(TranslationHelpers.GetReadTranslator()); @@ -280,22 +351,27 @@ public void TestTranslationWithNullDictionary() public void TestTranslationWithEmptyDictionary() { TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - @"c:\MyTasks\MyTask.dll", - new Dictionary(), - new Dictionary()); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: new Dictionary(), + globalParameters: new Dictionary(), + warningsAsErrors: null); ((ITranslatable)config).Translate(TranslationHelpers.GetWriteTranslator()); INodePacket packet = TaskHostConfiguration.FactoryForDeserialization(TranslationHelpers.GetReadTranslator()); @@ -323,22 +399,27 @@ public void TestTranslationWithValueTypesInDictionary() parameters.Add("Text", "Foo"); parameters.Add("BoolValue", false); TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - @"c:\MyTasks\MyTask.dll", - parameters, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: parameters, + globalParameters: null, + warningsAsErrors: null); ((ITranslatable)config).Translate(TranslationHelpers.GetWriteTranslator()); INodePacket packet = TaskHostConfiguration.FactoryForDeserialization(TranslationHelpers.GetReadTranslator()); @@ -364,22 +445,27 @@ public void TestTranslationWithITaskItemInDictionary() IDictionary parameters = new Dictionary(); parameters.Add("TaskItemValue", new TaskItem("Foo")); TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - @"c:\MyTasks\MyTask.dll", - parameters, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: parameters, + globalParameters: null, + warningsAsErrors: null); ((ITranslatable)config).Translate(TranslationHelpers.GetWriteTranslator()); INodePacket packet = TaskHostConfiguration.FactoryForDeserialization(TranslationHelpers.GetReadTranslator()); @@ -404,22 +490,27 @@ public void TestTranslationWithITaskItemArrayInDictionary() IDictionary parameters = new Dictionary(); parameters.Add("TaskItemArrayValue", new ITaskItem[] { new TaskItem("Foo"), new TaskItem("Baz") }); TaskHostConfiguration config = new TaskHostConfiguration( - 1, - Directory.GetCurrentDirectory(), - null, - Thread.CurrentThread.CurrentCulture, - Thread.CurrentThread.CurrentUICulture, + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: #if FEATURE_APPDOMAIN null, +#endif + lineNumberOfTask: #endif 1, - 1, - @"c:\my project\myproj.proj", - _continueOnErrorDefault, - "TaskName", - @"c:\MyTasks\MyTask.dll", - parameters, - null); + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: parameters, + globalParameters: null, + warningsAsErrors: null); ((ITranslatable)config).Translate(TranslationHelpers.GetWriteTranslator()); INodePacket packet = TaskHostConfiguration.FactoryForDeserialization(TranslationHelpers.GetReadTranslator()); @@ -439,6 +530,54 @@ public void TestTranslationWithITaskItemArrayInDictionary() TaskHostPacketHelpers.AreEqual(itemArray, deserializedItemArray); } + /// + /// Test serialization / deserialization when the parameter dictionary contains an ITaskItem array. + /// + [Fact] + public void TestTranslationWithWarningsAsErrors() + { + HashSet WarningsAsErrors = new HashSet(); + WarningsAsErrors.Add("MSB1234"); + WarningsAsErrors.Add("MSB1235"); + WarningsAsErrors.Add("MSB1236"); + WarningsAsErrors.Add("MSB1237"); + TaskHostConfiguration config = new TaskHostConfiguration( + nodeId: 1, + startupDirectory: Directory.GetCurrentDirectory(), + buildProcessEnvironment: null, + culture: Thread.CurrentThread.CurrentCulture, + uiCulture: Thread.CurrentThread.CurrentUICulture, +#if FEATURE_APPDOMAIN + appDomainSetup: +#if FEATURE_APPDOMAIN + null, +#endif + lineNumberOfTask: +#endif + 1, + columnNumberOfTask: 1, + projectFileOfTask: @"c:\my project\myproj.proj", + continueOnError: _continueOnErrorDefault, + taskName: "TaskName", + taskLocation: @"c:\MyTasks\MyTask.dll", + taskParameters: null, + globalParameters: null, + warningsAsErrors: WarningsAsErrors); + + ((ITranslatable)config).Translate(TranslationHelpers.GetWriteTranslator()); + INodePacket packet = TaskHostConfiguration.FactoryForDeserialization(TranslationHelpers.GetReadTranslator()); + + TaskHostConfiguration deserializedConfig = packet as TaskHostConfiguration; + + Assert.Equal(config.TaskName, deserializedConfig.TaskName); +#if !FEATURE_ASSEMBLYLOADCONTEXT + Assert.Equal(config.TaskLocation, deserializedConfig.TaskLocation); +#endif + Assert.NotNull(deserializedConfig.WarningsAsErrors); + config.WarningsAsErrors.SequenceEqual(deserializedConfig.WarningsAsErrors, StringComparer.Ordinal).ShouldBeTrue(); + + } + /// /// Helper methods for testing the task host-related packets. /// diff --git a/src/Build.UnitTests/WarningsAsMessagesAndErrors_Tests.cs b/src/Build.UnitTests/WarningsAsMessagesAndErrors_Tests.cs index eb56df3a007..e2973629106 100644 --- a/src/Build.UnitTests/WarningsAsMessagesAndErrors_Tests.cs +++ b/src/Build.UnitTests/WarningsAsMessagesAndErrors_Tests.cs @@ -272,6 +272,197 @@ private string GetTestProject(bool? treatAllWarningsAsErrors = null, string warn "; } + /// + /// We have a unique task host per bucket. Show that in these scenarios the build will stop if one sees an error. + /// + [Fact] + public void TaskReturnsHasLoggedErrorAndLogsWarningAsError_BuildShouldStopAndFail_BatchedBuild() + { + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + MSB1234 + + + + true + true + MSB1234 + + + true + true + MSB1235 + + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectFailure(); + + logger.WarningCount.ShouldBe(0); + logger.ErrorCount.ShouldBe(1); + + // The build should STOP when a task logs an error, make sure ReturnFailureWithoutLoggingErrorTask doesn't run. + logger.AssertLogDoesntContain("MSB4181"); + } + } + + /// + /// Item1 and Item2 log warnings and continue, item 3 logs a warn-> error and prevents item 4 from running in the batched build. + /// + [Fact] + public void TaskReturnsHasLoggedErrorAndLogsWarningAsError_BuildShouldStopOnceItLogsWarningAsErrorAndFail_BatchedBuild() + { + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + MSB1234 + + + + true + true + MSB1235 + + + true + true + MSB1236 + + + true + true + MSB1234 + + + true + true + MSB1237 + + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectFailure(); + + logger.WarningCount.ShouldBe(2); + logger.ErrorCount.ShouldBe(1); + + // The build should STOP when a task logs an error, make sure ReturnFailureWithoutLoggingErrorTask doesn't run. + logger.AssertLogDoesntContain("MSB1237"); + } + } + + [Fact] + public void TaskReturnsHasLoggedErrorAndLogsWarningAsError_BuildShouldFinishAndFail() + { + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + MSB1234 + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectFailure(); + + logger.WarningCount.ShouldBe(0); + logger.ErrorCount.ShouldBe(1); + + // The build should STOP when a task logs an error, make sure ReturnFailureWithoutLoggingErrorTask doesn't run. + logger.AssertLogDoesntContain("MSB4181"); + } + } + + /// + /// MSBuild behavior as of 16.10: As long as a task returns true, the build will continue despite logging a warning as error. + /// This tests MSBuildWarningsAsErrors + /// + [Fact] + public void TaskReturnsTrueButLogsWarningAsError_BuildShouldFinishAndFail() + { + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + + MSB1234 + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectFailure(); + + logger.WarningCount.ShouldBe(1); + logger.ErrorCount.ShouldBe(1); + + // The build will continue so we should see the warning MSB1235 + logger.AssertLogContains("MSB1235"); + } + } + + + /// + /// MSBuild behavior as of 16.10: As long as a task returns true, the build will continue despite logging warning as error. + /// This test specifically tests the MSBuildTreatWarningsAsErrors flag as opposed to MSBuildWarningsAsErrors + /// + [Fact] + public void TaskReturnsTrueButLogsWarning_TreatWarningsAsErrors_BuildShouldFinishAndFail() + { + using (TestEnvironment env = TestEnvironment.Create(_output)) + { + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles($@" + + + + + + true + + + + + + "); + + MockLogger logger = proj.BuildProjectExpectFailure(); + + logger.WarningCount.ShouldBe(0); + logger.ErrorCount.ShouldBe(2); + + // The build will continue so we should see the error MSB1235 + logger.AssertLogContains("MSB1235"); + } + } + [Fact] public void TaskReturnsFailureButDoesNotLogError_ShouldCauseBuildFailure() { diff --git a/src/Build/BackEnd/Components/Logging/ILoggingService.cs b/src/Build/BackEnd/Components/Logging/ILoggingService.cs index 42feb90220b..e8257651162 100644 --- a/src/Build/BackEnd/Components/Logging/ILoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/ILoggingService.cs @@ -219,6 +219,14 @@ bool IncludeTaskInputs /// true if the build submission logged an errors, otherwise false. bool HasBuildSubmissionLoggedErrors(int submissionId); + /// + /// Returns a hashset of warnings to be logged as errors for the specified project instance ID. + /// Note that WarningsAsMessages takes priority over WarningsAsErrors and are excluded from the set. + /// + /// The build context through which warnings will be logged as errors. + /// A Hashset containing warning codes that should be treated as warnings that will not be treated as messages. + ICollection GetWarningsToBeLoggedAsErrorsByProject(BuildEventContext context); + #region Register /// diff --git a/src/Build/BackEnd/Components/Logging/LoggingService.cs b/src/Build/BackEnd/Components/Logging/LoggingService.cs index 85c95d728d1..cd2b92bb029 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingService.cs @@ -515,6 +515,44 @@ public bool HasBuildSubmissionLoggedErrors(int submissionId) return _buildSubmissionIdsThatHaveLoggedErrors?.Contains(submissionId) == true; } + /// + /// Returns a hashset of warnings to be logged as errors for the specified build context. + /// Note that WarningsAsMessages takes priority over WarningsAsErrors and are excluded from the set. + /// + /// If all warnings to be treated as errors should also be treated as messages, return null. + /// This is to avoid all warnings being treated as errors. + /// + /// The build context through which warnings will be logged as errors. + /// + /// An empty set if all warnings should be treated as errors. + /// A set containing warning codes to be logged as errors. + /// Null if no warnings should be treated as errors. + /// + public ICollection GetWarningsToBeLoggedAsErrorsByProject(BuildEventContext context) + { + if (_warningsAsErrorsByProject == null) + { + return null; + } + + int key = GetWarningsAsErrorOrMessageKey(context); + + HashSet warningsAsErrorsExcludingMessages = new HashSet(_warningsAsErrorsByProject[key]); + + if (_warningsAsMessagesByProject != null) + { + warningsAsErrorsExcludingMessages.ExceptWith(_warningsAsMessagesByProject[key]); + + // A non-null empty set means all warnings are errors. Avoid this. + if (warningsAsErrorsExcludingMessages.Count == 0) + { + warningsAsErrorsExcludingMessages = null; + } + } + + return warningsAsErrorsExcludingMessages; + } + public void AddWarningsAsErrors(BuildEventContext buildEventContext, ISet codes) { lock (_lockObject) diff --git a/src/Build/BackEnd/Components/Logging/TaskLoggingContext.cs b/src/Build/BackEnd/Components/Logging/TaskLoggingContext.cs index c953ec6f226..2f4758b2196 100644 --- a/src/Build/BackEnd/Components/Logging/TaskLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/TaskLoggingContext.cs @@ -1,10 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Execution; +using System; +using System.Collections.Generic; namespace Microsoft.Build.BackEnd.Logging { @@ -144,5 +145,10 @@ internal void LogTaskWarningFromException(Exception exception, BuildEventFileInf ErrorUtilities.VerifyThrow(IsValid, "must be valid"); LoggingService.LogTaskWarningFromException(BuildEventContext, exception, file, taskName); } + + internal ICollection GetWarningsAsErrors() + { + return LoggingService.GetWarningsToBeLoggedAsErrorsByProject(BuildEventContext); + } } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 8d830c38a0b..3bc78d7159a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -33,7 +33,7 @@ internal class TaskHost : #if FEATURE_APPDOMAIN MarshalByRefObject, #endif - IBuildEngine7 + IBuildEngine8 { /// /// True if the "secret" environment variable MSBUILDNOINPROCNODE is set. @@ -676,6 +676,44 @@ public IReadOnlyDictionary GetGlobalProperties() public bool AllowFailureWithoutError { get; set; } = false; #endregion + #region IBuildEngine8 Members + private ICollection _warningsAsErrors; + + /// + /// Contains all warnings that should be logged as errors. + /// Non-null empty set when all warnings should be treated as errors. + /// + private ICollection WarningsAsErrors + { + get + { + // Test compatibility + if(_taskLoggingContext == null) + { + return null; + } + + return _warningsAsErrors ??= _taskLoggingContext.GetWarningsAsErrors(); + } + } + + /// + /// Determines if the given warning should be treated as an error. + /// + /// + /// True if WarningsAsErrors is an empty set or contains the given warning code. + public bool ShouldTreatWarningAsError(string warningCode) + { + if (WarningsAsErrors == null) + { + return false; + } + + // An empty set means all warnings are errors. + return WarningsAsErrors.Count == 0 || WarningsAsErrors.Contains(warningCode); + } + #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/Instance/TaskFactories/TaskHostTask.cs b/src/Build/Instance/TaskFactories/TaskHostTask.cs index 07f716a438f..4956e1383cd 100644 --- a/src/Build/Instance/TaskFactories/TaskHostTask.cs +++ b/src/Build/Instance/TaskFactories/TaskHostTask.cs @@ -271,7 +271,9 @@ public bool Execute() _taskType.Type.FullName, AssemblyUtilities.GetAssemblyLocation(_taskType.Type.GetTypeInfo().Assembly), _setParameters, - new Dictionary(_buildComponentHost.BuildParameters.GlobalProperties) + new Dictionary(_buildComponentHost.BuildParameters.GlobalProperties), + _taskLoggingContext.GetWarningsAsErrors() + ); try diff --git a/src/Framework/IBuildEngine8.cs b/src/Framework/IBuildEngine8.cs new file mode 100644 index 00000000000..bce28a4cfcf --- /dev/null +++ b/src/Framework/IBuildEngine8.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace Microsoft.Build.Framework +{ + /// + /// This interface extends to let tasks know if a warning + /// they are about to log will be converted into an error. + /// + public interface IBuildEngine8 : IBuildEngine7 + { + /// + /// Determines whether the logging service will convert the specified + /// warning code into an error. + /// + /// The warning code to check. + /// A boolean to determine whether the warning should be treated as an error. + public bool ShouldTreatWarningAsError(string warningCode); + } +} diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index e7604d0970b..7339ac36cd0 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -33,7 +33,7 @@ internal class OutOfProcTaskHostNode : #if CLR2COMPATIBILITY IBuildEngine3 #else - IBuildEngine7 + IBuildEngine8 #endif { /// @@ -267,6 +267,20 @@ public bool IsRunningMultipleNodes public bool AllowFailureWithoutError { get; set; } = false; #endregion + #region IBuildEngine8 Implementation + + /// + /// Contains all warnings that should be logged as errors. + /// Non-null empty set when all warnings should be treated as errors. + /// + private ICollection WarningsAsErrors { get; set; } + + public bool ShouldTreatWarningAsError(string warningCode) + { + return WarningsAsErrors != null && (WarningsAsErrors.Count == 0 || WarningsAsErrors.Contains(warningCode)); + } + #endregion + #region IBuildEngine Implementation (Methods) /// @@ -793,7 +807,7 @@ private void RunTask(object state) _debugCommunications = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBUILDDEBUGCOMM", "1", StringComparison.OrdinalIgnoreCase); _updateEnvironment = !taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostDoNotUpdateEnvironment", "1", StringComparison.OrdinalIgnoreCase); _updateEnvironmentAndLog = taskConfiguration.BuildProcessEnvironment.ContainsValueAndIsEqual("MSBuildTaskHostUpdateEnvironmentAndLog", "1", StringComparison.OrdinalIgnoreCase); - + WarningsAsErrors = taskConfiguration.WarningsAsErrors; try { // Change to the startup directory diff --git a/src/Shared/BinaryTranslator.cs b/src/Shared/BinaryTranslator.cs index 1a6c9e3c39c..6c2b6337393 100644 --- a/src/Shared/BinaryTranslator.cs +++ b/src/Shared/BinaryTranslator.cs @@ -293,6 +293,32 @@ public void Translate(ref IList list, ObjectTranslator objectTransla } } + /// + /// Translates a collection of T into the specified type using an and + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + public void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection + { + if (!TranslateNullable(collection)) + { + return; + } + + int count = _reader.ReadInt32(); + collection = collectionFactory(count); + + for (int i = 0; i < count; i++) + { + T value = default(T); + objectTranslator(this, ref value); + collection.Add(value); + } + } + /// /// Translates a DateTime. /// @@ -883,6 +909,30 @@ public void Translate(ref IList list, ObjectTranslator objectTransla } } + /// + /// Translates a collection of T into the specified type using an and + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + public void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection + { + if (!TranslateNullable(collection)) + { + return; + } + + _writer.Write(collection.Count); + + foreach (T item in collection) + { + T value = item; + objectTranslator(this, ref value); + } + } + /// /// Translates a DateTime. /// diff --git a/src/Shared/ITranslator.cs b/src/Shared/ITranslator.cs index 6fec218805e..b1acb85ec2f 100644 --- a/src/Shared/ITranslator.cs +++ b/src/Shared/ITranslator.cs @@ -180,6 +180,16 @@ BinaryWriter Writer /// factory to create a collection void Translate(ref IList list, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : IList; + /// + /// Translates a collection of T into the specified type using an and + /// + /// The collection to be translated. + /// The translator to use for the values in the collection. + /// The factory to create the ICollection. + /// The type contained in the collection. + /// The type of collection to be created. + void Translate(ref ICollection collection, ObjectTranslator objectTranslator, NodePacketCollectionCreator collectionFactory) where L : ICollection; + /// /// Translates a DateTime. /// diff --git a/src/Shared/TaskHostConfiguration.cs b/src/Shared/TaskHostConfiguration.cs index 45d007d25b3..367e2845823 100644 --- a/src/Shared/TaskHostConfiguration.cs +++ b/src/Shared/TaskHostConfiguration.cs @@ -85,6 +85,8 @@ internal class TaskHostConfiguration : INodePacket private Dictionary _globalParameters; + private ICollection _warningsAsErrors; + #if FEATURE_APPDOMAIN /// /// Constructor @@ -103,6 +105,7 @@ internal class TaskHostConfiguration : INodePacket /// Location of the assembly the task is to be loaded from. /// Parameters to apply to the task. /// global properties for the current project. + /// Warning codes to be thrown as errors for the current project. #else /// /// Constructor @@ -120,6 +123,7 @@ internal class TaskHostConfiguration : INodePacket /// Location of the assembly the task is to be loaded from. /// Parameters to apply to the task. /// global properties for the current project. + /// Warning codes to be logged as errors for the current project. #endif public TaskHostConfiguration ( @@ -138,7 +142,8 @@ public TaskHostConfiguration string taskName, string taskLocation, IDictionary taskParameters, - Dictionary globalParameters + Dictionary globalParameters, + ICollection warningsAsErrors ) { ErrorUtilities.VerifyThrowInternalLength(taskName, nameof(taskName)); @@ -168,6 +173,7 @@ Dictionary globalParameters _continueOnError = continueOnError; _taskName = taskName; _taskLocation = taskLocation; + _warningsAsErrors = warningsAsErrors; if (taskParameters != null) { @@ -342,6 +348,15 @@ public NodePacketType Type { return NodePacketType.TaskHostConfiguration; } } + public ICollection WarningsAsErrors + { + [DebuggerStepThrough] + get + { + return _warningsAsErrors; + } + } + /// /// Translates the packet to/from binary form. /// @@ -364,6 +379,13 @@ public void Translate(ITranslator translator) translator.TranslateDictionary(ref _taskParameters, StringComparer.OrdinalIgnoreCase, TaskParameter.FactoryForDeserialization); translator.Translate(ref _continueOnError); translator.TranslateDictionary(ref _globalParameters, StringComparer.OrdinalIgnoreCase); + translator.Translate(collection: ref _warningsAsErrors, + objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), +#if CLR2COMPATIBILITY + collectionFactory: count => new HashSet()); +#else + collectionFactory: count => new HashSet(count, StringComparer.OrdinalIgnoreCase)); +#endif } /// diff --git a/src/Shared/TaskLoggingHelper.cs b/src/Shared/TaskLoggingHelper.cs index 7592c784fc0..a4f4a164193 100644 --- a/src/Shared/TaskLoggingHelper.cs +++ b/src/Shared/TaskLoggingHelper.cs @@ -1016,6 +1016,28 @@ params object[] messageArgs // that gives the user something. bool fillInLocation = (String.IsNullOrEmpty(file) && (lineNumber == 0) && (columnNumber == 0)); + // This warning will be converted to an error if: + // 1. Its code exists within WarningsAsErrors + // 2. If WarningsAsErrors is a non-null empty set (treat all warnings as errors) + if (BuildEngine is IBuildEngine8 be8 && be8.ShouldTreatWarningAsError(warningCode)) + { + LogError + ( + subcategory: subcategory, + errorCode: warningCode, + helpKeyword: helpKeyword, + helpLink: helpLink, + file: fillInLocation ? BuildEngine.ProjectFileOfTaskNode : file, + lineNumber: fillInLocation ? BuildEngine.LineNumberOfTaskNode : lineNumber, + columnNumber: fillInLocation ? BuildEngine.ColumnNumberOfTaskNode : columnNumber, + endLineNumber: endLineNumber, + endColumnNumber: endColumnNumber, + message: message, + messageArgs: messageArgs + ); + return; + } + var e = new BuildWarningEventArgs ( subcategory, diff --git a/src/Utilities/Task.cs b/src/Utilities/Task.cs index 39846721d3c..a9f3de6ff36 100644 --- a/src/Utilities/Task.cs +++ b/src/Utilities/Task.cs @@ -94,6 +94,11 @@ protected Task(ResourceManager taskResources, string helpKeywordPrefix) /// public IBuildEngine7 BuildEngine7 => (IBuildEngine7)BuildEngine; + /// + /// Retrieves the version of the build engine interface provided by the host. + /// + public IBuildEngine8 BuildEngine8 => (IBuildEngine8)BuildEngine; + /// /// The build engine sets this property if the host IDE has associated a host object with this particular task. ///