From 290c0ae83dcf76f5eb90d77a056eb6068fffd0df Mon Sep 17 00:00:00 2001 From: binwen Date: Wed, 31 Jan 2024 11:04:17 +0800 Subject: [PATCH] Support continueOnStepFailure for stage --- Fun.Build.Cli/Pipeline.fs | 7 +- Fun.Build.Tests/SequenceTests.fs | 127 ++++++++++++++++++++++++++++ Fun.Build/CHANGELOG.md | 4 + Fun.Build/ConditionsBuilder.fs | 6 +- Fun.Build/StageBuilder.fs | 9 ++ Fun.Build/StageContextExtensions.fs | 11 +-- Fun.Build/Types.Internal.fs | 2 + README.md | 7 +- build.fsx | 12 +-- 9 files changed, 162 insertions(+), 23 deletions(-) diff --git a/Fun.Build.Cli/Pipeline.fs b/Fun.Build.Cli/Pipeline.fs index 4779566..7822029 100644 --- a/Fun.Build.Cli/Pipeline.fs +++ b/Fun.Build.Cli/Pipeline.fs @@ -16,10 +16,11 @@ type Pipeline with static member Parse(str: string) = let lines = str.Split(Environment.NewLine) - if lines.Length = 0 then [] + if lines.Length = 0 then + [] else if lines[0].StartsWith("Description:") then - let name = lines[1].Split(" ") |> Seq.map (fun x -> x.Trim()) |> Seq.filter (String.IsNullOrEmpty >> not) |> Seq.item 1 + let name = lines[1].Split(" ") |> Seq.map (fun x -> x.Trim()) |> Seq.filter (String.IsNullOrEmpty >> not) |> Seq.item 1 let description = lines[2].Trim() [ name, description ] @@ -77,7 +78,7 @@ type Pipeline with psInfo.FileName <- Diagnostics.Process.GetQualifiedFileName "dotnet" psInfo.Arguments <- $"fsi \"{f}\" -- -h" - let! result = + let! result = Async.StartChild( Diagnostics.Process.StartAsync(psInfo, "", "", printOutput = false, captureOutput = true), millisecondsTimeout = 60_000 diff --git a/Fun.Build.Tests/SequenceTests.fs b/Fun.Build.Tests/SequenceTests.fs index 480028a..6a711c8 100644 --- a/Fun.Build.Tests/SequenceTests.fs +++ b/Fun.Build.Tests/SequenceTests.fs @@ -202,3 +202,130 @@ let ``for loop or yield! should work`` () = } Assert.Equal([ 1..6 ], list) + + + +[] +let ``continueOnStepFailure should work`` () = + let list = System.Collections.Generic.List() + pipeline "" { + stage "" { + continueOnStepFailure + run (fun _ -> list.Add(1)) + run (fun _ -> + list.Add(2) + Error "" + ) + run (fun _ -> list.Add(3)) + } + stage "" { run (fun _ -> list.Add(4)) } + runImmediate + } + Assert.Equal([ 1; 2; 3; 4 ], list) + + shouldBeCalled (fun fn -> + pipeline "" { + stage "" { + paralle + continueOnStepFailure + run (fun _ -> Ok()) + run (fun _ -> Error "") + run (fun _ -> Ok()) + } + stage "" { run fn } + runImmediate + } + ) + + list.Clear() + Assert.Throws(fun _ -> + shouldNotBeCalled (fun fn -> + pipeline "" { + stage "" { + continueOnStepFailure false + run (fun _ -> list.Add(1)) + run (fun _ -> + list.Add(2) + Error "" + ) + run (fun _ -> Ok()) + } + stage "" { run fn } + runImmediate + } + ) + ) + |> ignore + Assert.Equal([ 1; 2 ], list) + + Assert.Throws(fun _ -> + shouldNotBeCalled (fun fn -> + pipeline "" { + stage "" { + paralle + continueOnStepFailure false + run (fun _ -> Ok()) + run (fun _ -> Error "") + run (fun _ -> Ok()) + } + stage "" { run fn } + runImmediate + } + ) + ) + |> ignore + + list.Clear() + Assert.Throws(fun _ -> + shouldNotBeCalled (fun fn -> + pipeline "" { + stage "" { + run (fun _ -> list.Add(1)) + run (fun _ -> + list.Add(2) + Error "" + ) + run (fun _ -> Ok()) + } + stage "" { run fn } + runImmediate + } + ) + ) + |> ignore + Assert.Equal([ 1; 2 ], list) + + Assert.Throws(fun _ -> + shouldNotBeCalled (fun fn -> + pipeline "" { + stage "" { + paralle + run (fun _ -> Ok()) + run (fun _ -> Error "") + run (fun _ -> Ok()) + } + stage "" { run fn } + runImmediate + } + ) + ) + |> ignore + + list.Clear() + pipeline "" { + stage "" { + continueOnStepFailure + run (fun _ -> list.Add(1)) + stage "" { + run (fun _ -> + list.Add(2) + Error "" + ) + run (fun _ -> list.Add(5)) + } + run (fun _ -> list.Add(3)) + } + stage "" { run (fun _ -> list.Add(4)) } + runImmediate + } + Assert.Equal([ 1; 2; 3; 4 ], list) diff --git a/Fun.Build/CHANGELOG.md b/Fun.Build/CHANGELOG.md index bcd37f7..95bf097 100644 --- a/Fun.Build/CHANGELOG.md +++ b/Fun.Build/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +## [1.1.0] - 2024-01-31 + +- Support continueOnStepFailure for stage + ## [1.0.9] - 2024-01-22 - Add when' for ConditionsBuilder diff --git a/Fun.Build/ConditionsBuilder.fs b/Fun.Build/ConditionsBuilder.fs index 242c51f..79fd59a 100644 --- a/Fun.Build/ConditionsBuilder.fs +++ b/Fun.Build/ConditionsBuilder.fs @@ -17,15 +17,13 @@ module Internal = type StageContext with member ctx.When'(isTrue: bool) = - let getPrintInfo (prefix: string) = - makeCommandOption prefix (if isTrue then "always true" else "always false") "" + let getPrintInfo (prefix: string) = makeCommandOption prefix (if isTrue then "always true" else "always false") "" match ctx.GetMode() with | Mode.CommandHelp { Verbose = true } -> AnsiConsole.WriteLine(getPrintInfo (ctx.BuildIndent())) false - | Mode.CommandHelp _ -> - false + | Mode.CommandHelp _ -> false | Mode.Verification -> if isTrue then AnsiConsole.MarkupLineInterpolated($"[green]✓ always true[/]") diff --git a/Fun.Build/StageBuilder.fs b/Fun.Build/StageBuilder.fs index 7e567ac..f052213 100644 --- a/Fun.Build/StageBuilder.fs +++ b/Fun.Build/StageBuilder.fs @@ -132,6 +132,15 @@ type StageBuilder(name: string) = member inline this.failIfNoActiveSubStage([] build: BuildStage) = this.failIfNoActiveSubStage (build, true) + /// Continue pipeline execution (consider this stage as success) even if the stage's step is failed, default is true + [] + member inline _.continueOnStepFailure([] build: BuildStage, ?flag) = + BuildStage(fun ctx -> + let ctx = build.Invoke ctx + { ctx with ContinueOnStepFailure = defaultArg flag true } + ) + + /// Set timeout for every step under the current stage. /// Unit is second. [] diff --git a/Fun.Build/StageContextExtensions.fs b/Fun.Build/StageContextExtensions.fs index 810b07d..5a725b3 100644 --- a/Fun.Build/StageContextExtensions.fs +++ b/Fun.Build/StageContextExtensions.fs @@ -16,6 +16,7 @@ module StageContextExtensionsInternal = Name = name IsActive = fun _ -> true IsParallel = fun _ -> false + ContinueOnStepFailure = false Timeout = ValueNone TimeoutForStep = ValueNone WorkingDir = ValueNone @@ -249,7 +250,7 @@ module StageContextExtensionsInternal = ) if i = stage.Steps.Length - 1 then AnsiConsole.WriteLine() - if not isSuccess then stepErrorCTS.Cancel() + if not isSuccess && not stage.ContinueOnStepFailure then stepErrorCTS.Cancel() return isSuccess with @@ -265,7 +266,7 @@ module StageContextExtensionsInternal = AnsiConsole.MarkupLineInterpolated $"[red]{prefix} exception hanppened.[/]" AnsiConsole.WriteException ex stepExns.Add ex - stepErrorCTS.Cancel() + if not stage.ContinueOnStepFailure then stepErrorCTS.Cancel() return false }) @@ -280,7 +281,7 @@ module StageContextExtensionsInternal = completers.Add completer let mutable i = 0 - while i < completers.Count && isSuccess do + while i < completers.Count && (stage.ContinueOnStepFailure || isSuccess) do let! result = completers[i] i <- i + 1 isSuccess <- isSuccess && result @@ -289,7 +290,7 @@ module StageContextExtensionsInternal = async { let mutable i = 0 let length = Seq.length steps - while i < length && isSuccess do + while i < length && (stage.ContinueOnStepFailure || isSuccess) do let! completer = Async.StartChild(Seq.item i steps, timeoutForStep) let! result = completer i <- i + 1 @@ -338,7 +339,7 @@ module StageContextExtensionsInternal = finally pipeline |> Option.iter (fun x -> x.RunAfterEachStage stage) - isSuccess, stepExns + stage.ContinueOnStepFailure || isSuccess, stepExns let inline buildStageIsActive ([] build: BuildStage) ([] conditionFn) = diff --git a/Fun.Build/Types.Internal.fs b/Fun.Build/Types.Internal.fs index 5a62da7..cc548f7 100644 --- a/Fun.Build/Types.Internal.fs +++ b/Fun.Build/Types.Internal.fs @@ -51,6 +51,8 @@ type StageContext = { Name: string IsActive: StageContext -> bool IsParallel: StageContext -> bool + /// When this is ture, the stage's final execution result will also be considered as true, so the pipeline can continue too. + ContinueOnStepFailure: bool Timeout: TimeSpan voption TimeoutForStep: TimeSpan voption WorkingDir: string voption diff --git a/README.md b/README.md index 1823146..49f73f6 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Every **step** is just a **async>**, string is for the erro ## Minimal example and conventions ```fsharp -#r "nuget: Fun.Build, 1.0.9" +#r "nuget: Fun.Build, 1.1.0" open Fun.Build pipeline "demo" { @@ -67,7 +67,7 @@ dotnet fsi build.fsx -- -p your_pipeline -h Below example covered most of the apis and usage example, take it as the documents😊: ```fsharp -#r "nuget: Fun.Build, 1.0.9" +#r "nuget: Fun.Build, 1.1.0" open Fun.Result open Fun.Build @@ -172,6 +172,7 @@ pipeline "Fun.Build" { } stage "FailIfIgnored" { failIfIgnored // When set this, the stage cannot be ignored + continueOnStepFailure // When set this, the stage will be considered as success even if it's step is failed whenCmdArg "arg2" echo "Got here!" } @@ -259,6 +260,8 @@ pipeline "cmd-info" { } echo "here we are" run "dotnet --list-sdks" + // You can get the cmd from the context + run (fun ctx -> printfn "%A" (ctx.GetCmdArg("--build"))) } runIfOnlySpecified } diff --git a/build.fsx b/build.fsx index 155570e..5d3fcc0 100644 --- a/build.fsx +++ b/build.fsx @@ -1,4 +1,4 @@ -#r "nuget: Fun.Build, 1.0.8" +#r "nuget: Fun.Build" open System.IO open Fun.Result @@ -14,17 +14,11 @@ let options = {| |} -let stage_checkEnv = - stage "Check environment" { - run "dotnet tool restore" - } +let stage_checkEnv = stage "Check environment" { run "dotnet tool restore" } let stage_lint = stage "Lint" { - stage "Format" { - whenGithubAction - run "dotnet fantomas . -r" - } + stage "Format" { run "dotnet fantomas . -r" } stage "Check" { whenGithubAction run "dotnet fantomas . -r --check"