diff --git a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs index b018cb552ce..ffb69a0b99f 100644 --- a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs @@ -1208,6 +1208,42 @@ public void TestCancelWithNoEntriesAfterBuild() } } + [Fact] + public void SkippedTargetWithFailedDependenciesStopsBuild() + { + string projectContents = @" + + + + + + + + +"; + + var project = CreateTestProject(projectContents, string.Empty, "Build"); + TargetBuilder builder = (TargetBuilder)_host.GetComponent(BuildComponentType.TargetBuilder); + MockTaskBuilder taskBuilder = (MockTaskBuilder)_host.GetComponent(BuildComponentType.TaskBuilder); + // Fail the first task + taskBuilder.FailTaskNumber = 1; + + IConfigCache cache = (IConfigCache)_host.GetComponent(BuildComponentType.ConfigCache); + BuildRequestEntry entry = new BuildRequestEntry(CreateNewBuildRequest(1, new[] { "Build" }), cache[1]); + + var buildResult = builder.BuildTargets(GetProjectLoggingContext(entry), entry, this, entry.Request.Targets.ToArray(), CreateStandardLookup(project), CancellationToken.None).Result; + + IResultsCache resultsCache = (IResultsCache)_host.GetComponent(BuildComponentType.ResultsCache); + Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("Build")); + Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("ProduceError1")); + Assert.False(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("ProduceError2")); + Assert.True(resultsCache.GetResultForRequest(entry.Request).HasResultsForTarget("_Error1")); + + Assert.Equal(TargetResultCode.Failure, resultsCache.GetResultForRequest(entry.Request)["Build"].ResultCode); + Assert.Equal(TargetResultCode.Skipped, resultsCache.GetResultForRequest(entry.Request)["ProduceError1"].ResultCode); + Assert.Equal(TargetResultCode.Failure, resultsCache.GetResultForRequest(entry.Request)["_Error1"].ResultCode); + } + #region IRequestBuilderCallback Members /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs index ade4ad0c3da..abf5b5b6418 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs @@ -609,11 +609,17 @@ private void PopDependencyTargetsOnTargetFailure(TargetEntry topEntry, TargetRes } } - // Mark our parent for error execution + // Mark our parent for error execution when it is not Completed (e.g. Executing) if (topEntry.ParentEntry != null && topEntry.ParentEntry.State != TargetEntryState.Completed) { topEntry.ParentEntry.MarkForError(); } + // In cases where we need to indicate a failure but the ParentEntry was Skipped (due to condition) it must be + // marked stop to prevent other targets from executing. + else if (topEntry.ParentEntry?.Result?.ResultCode == TargetResultCode.Skipped) + { + topEntry.ParentEntry.MarkForStop(); + } } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs index b1d7d0a331f..684656e136a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs @@ -752,6 +752,20 @@ internal void MarkForError() _state = TargetEntryState.ErrorExecution; } + /// + /// This method is used by the Target Builder to indicate that a child of this target has failed and that work should not + /// continue in Completed / Skipped mode. We do not want to mark the state to run in ErrorExecution mode so that the + /// OnError targets do not run (the target was skipped due to condition so OnError targets should not run). + /// + internal void MarkForStop() + { + ErrorUtilities.VerifyThrow(_state == TargetEntryState.Completed, "State must be Completed. State is {0}.", _state); + ErrorUtilities.VerifyThrow(_targetResult.ResultCode == TargetResultCode.Skipped, "ResultCode must be Skipped. ResultCode is {0}.", _state); + ErrorUtilities.VerifyThrow(_targetResult.WorkUnitResult.ActionCode == WorkUnitActionCode.Continue, "ActionCode must be Continue. ActionCode is {0}.", _state); + + _targetResult.WorkUnitResult.ActionCode = WorkUnitActionCode.Stop; + } + /// /// Leaves all the call target scopes in the order they were entered. /// diff --git a/src/Build/BackEnd/Shared/WorkUnitResult.cs b/src/Build/BackEnd/Shared/WorkUnitResult.cs index 9f2ac36a7b7..dcf9969bbbd 100644 --- a/src/Build/BackEnd/Shared/WorkUnitResult.cs +++ b/src/Build/BackEnd/Shared/WorkUnitResult.cs @@ -115,6 +115,7 @@ internal WorkUnitResultCode ResultCode internal WorkUnitActionCode ActionCode { get { return _actionCode; } + set { _actionCode = value; } } ///