diff --git a/help/markdown/core-targets.md b/help/markdown/core-targets.md index 9027088e3ae..e1293a4b0d9 100644 --- a/help/markdown/core-targets.md +++ b/help/markdown/core-targets.md @@ -126,6 +126,43 @@ Everything after the target will be interpreted as argument for the target: You can access the arguments from every target executed along the way. +## Setting build status + +You can set the build status automatically using `Target.updateBuildStatus` + +Example: + +```fsharp +#r "paket: +nuget Fake.Core.Target //" + +open Fake.Core + +// *** Define Targets *** +Target.create "Clean" (fun p -> + Trace.trace " --- Cleaning stuff --- " +) + +Target.create "Build" (fun _ -> + Trace.trace " --- Building the app --- " +) + +Target.create "Deploy" (fun _ -> + Trace.trace " --- Deploying app --- " +) + +open Fake.Core.TargetOperators + +// *** Define Dependencies *** +"Clean" + ==> "Build" + ==> "Deploy" + +// *** Start Build *** +Target.runOrDefaultAndGetContext "Deploy" //Could also use: Target.runAndGetOptionalContext "Deploy" +|> Target.raiseIfError +``` + ## Final targets Final targets can be used for TearDown functionality. diff --git a/src/app/Fake.Core.Target/Target.fs b/src/app/Fake.Core.Target/Target.fs index 5431c5c0418..65165b741aa 100644 --- a/src/app/Fake.Core.Target/Target.fs +++ b/src/app/Fake.Core.Target/Target.fs @@ -54,6 +54,10 @@ and [] [] TargetContext = x.PreviousTargets |> List.tryFind (fun t -> t.Target.Name = name) member x.TryFindTarget name = x.AllExecutingTargets |> List.tryFind (fun t -> t.Name = name) + member x.ErrorTargets = + x.PreviousTargets |> List.choose (fun tres -> match tres.Error with + | Some er -> Some (er, tres.Target) + | None -> None) and [] [] TargetParameter = { TargetInfo : Target @@ -446,17 +450,13 @@ module Target = aligned "Total:" total null if not context.HasError then aligned "Status:" "Ok" null - //Trace.setBuildState TagStatus.Success else alignedError "Status:" "Failure" null - //Trace.setBuildState TagStatus.Failed else Trace.traceError "No target was successfully completed" - //Trace.setBuildState TagStatus.Warning Trace.traceLine() - /// Determines a parallel build order for the given set of targets let internal determineBuildOrder (target : string) = let _ = get target @@ -652,6 +652,11 @@ module Target = |> Observable.subscribe (fun _ -> Environment.Exit 1) Process.killAllCreatedProcesses() |> ignore cts.Cancel() + + /// Optional `TargetContext` + type OptionalTargetContext = + | Set of TargetContext + | MaybeSet of TargetContext option /// Runs a target and its dependencies. let internal runInternal singleTarget parallelJobs targetName args = @@ -705,29 +710,12 @@ module Target = if context.HasError && not context.CancellationToken.IsCancellationRequested then runBuildFailureTargets context - else context + else + context let context = runFinalTargets {context with IsRunningFinalTargets=true} writeTaskTimeSummary watch.Elapsed context - if context.HasError && not context.CancellationToken.IsCancellationRequested then - let errorTargets = - context.PreviousTargets - |> List.choose (fun tres -> - match tres.Error with - | Some er -> Some (er, tres.Target) - | None -> None) - let targets = errorTargets |> Seq.map (fun (_er, target) -> target.Name) |> Seq.distinct - let targetStr = String.Join(", ", targets) - let errorMsg = - if errorTargets.Length = 1 then - sprintf "Target '%s' failed." targetStr - else - sprintf "Targets '%s' failed." targetStr - let inner = AggregateException(AggregateException().Message, errorTargets |> Seq.map fst) - BuildFailedException(context, errorMsg, inner) - |> raise - - context + context /// Creates a target in case of build failure (not activated). let createBuildFailure name body = @@ -759,13 +747,40 @@ module Target = let t = get name // test if target is defined getFinalTargets().[name] <- false - /// Runs a target and its dependencies, used for testing - usually not called in scripts. + let internal getBuildFailedException (context:TargetContext) = + let targets = context.ErrorTargets |> Seq.map (fun (_er, target) -> target.Name) |> Seq.distinct + let targetStr = String.Join(", ", targets) + let errorMsg = + if context.ErrorTargets.Length = 1 then + sprintf "Target '%s' failed." targetStr + else + sprintf "Targets '%s' failed." targetStr + let inner = AggregateException(AggregateException().Message, context.ErrorTargets |> Seq.map fst) + BuildFailedException(context, errorMsg, inner) + + let private getTargetContext (context:OptionalTargetContext) = + match context with + | Set c -> Some(c) + | MaybeSet c -> c + + /// If `TargetContext option` is Some and has error, raise it as a BuildFailedException + let raiseIfError (context:OptionalTargetContext) = + let c = getTargetContext(context) + if c.IsSome && c.Value.HasError && not c.Value.CancellationToken.IsCancellationRequested then + getBuildFailedException c.Value + |> raise + context + + /// Runs a target and its dependencies and returns a `TargetContext` let runAndGetContext parallelJobs targetName args = runInternal false parallelJobs targetName args - /// Runs a target and its dependencies - let run parallelJobs targetName args = runInternal false parallelJobs targetName args |> ignore + /// Runs a target and its dependencies and returns an `OptionalTargetContext` + let runAndGetOptionalContext parallelJobs targetName args = runAndGetContext parallelJobs targetName args |> OptionalTargetContext.Set - let internal runWithDefault allowArgs fDefault = + /// Runs a target and its dependencies + let run parallelJobs targetName args = runAndGetOptionalContext parallelJobs targetName args |> raiseIfError |> ignore + + let internal getRunFunction allowArgs defaultTarget = let ctx = Fake.Core.Context.forceFakeContext () let trySplitEnvArg (arg:string) = let idx = arg.IndexOf('=') @@ -790,11 +805,14 @@ module Target = if DocoptResult.hasFlag "--list" results then listAvailable() + None elif DocoptResult.hasFlag "-h" results || DocoptResult.hasFlag "--help" results then printfn "%s" TargetCli.targetCli printfn "Hint: Run 'fake run target --help' to get help from your target." + None elif DocoptResult.hasFlag "--version" results then printfn "Target Module Version: %s" AssemblyVersionInformation.AssemblyInformationalVersion + None else let target = match DocoptResult.tryGetArgument "" results with @@ -833,23 +851,40 @@ module Target = | None -> [] if not allowArgs && arguments <> [] then failwithf "The following arguments could not be parsed: %A\nTo forward arguments to your targets you need to use \nTarget.runOrDefaultWithArguments instead of Target.runOrDefault" arguments - match target with - | Some t -> runInternal singleTarget parallelJobs t arguments |> ignore - | None -> fDefault singleTarget parallelJobs arguments + match target, defaultTarget with + | Some t, _ -> Some(fun () -> Some(runInternal singleTarget parallelJobs t arguments)) + | None, Some t -> Some(fun () -> Some(runInternal singleTarget parallelJobs t arguments)) + | None, None -> Some (fun () -> listAvailable() + None) | Choice2Of2 e -> // To ensure exit code. raise <| exn (sprintf "Usage error: %s\n%s" e.Message TargetCli.targetCli, e) + let private runFunction (targetFunction:(unit -> TargetContext option) Option) = + match targetFunction with + | Some f -> OptionalTargetContext.MaybeSet(f()) + | _ -> OptionalTargetContext.MaybeSet(None) + + /// Runs the command given on the command line or the given target when no target is given & get context + let runOrDefaultAndGetContext defaultTarget = + getRunFunction false (Some(defaultTarget)) |> runFunction + /// Runs the command given on the command line or the given target when no target is given let runOrDefault defaultTarget = - runWithDefault false (fun singleTarget parallelJobs arguments -> - runInternal singleTarget parallelJobs defaultTarget arguments |> ignore) + runOrDefaultAndGetContext defaultTarget |> raiseIfError |> ignore + + /// Runs the command given on the command line or the given target when no target is given & get context + let runOrDefaultWithArgumentsAndGetContext defaultTarget = + getRunFunction true (Some(defaultTarget)) |> runFunction /// Runs the command given on the command line or the given target when no target is given let runOrDefaultWithArguments defaultTarget = - runWithDefault true (fun singleTarget parallelJobs arguments -> - runInternal singleTarget parallelJobs defaultTarget arguments |> ignore) + runOrDefaultWithArgumentsAndGetContext defaultTarget |> raiseIfError |> ignore + + /// Runs the target given by the target parameter or lists the available targets & get context + let runOrListAndGetContext() = + getRunFunction false None |> runFunction /// Runs the target given by the target parameter or lists the available targets let runOrList() = - runWithDefault false (fun _ _ _ -> listAvailable()) + runOrListAndGetContext() |> raiseIfError |> ignore diff --git a/src/test/Fake.Core.UnitTests/Fake.Core.Target.fs b/src/test/Fake.Core.UnitTests/Fake.Core.Target.fs index f44f3454755..84172d6380d 100644 --- a/src/test/Fake.Core.UnitTests/Fake.Core.Target.fs +++ b/src/test/Fake.Core.UnitTests/Fake.Core.Target.fs @@ -4,18 +4,10 @@ open Fake.Core open Expecto let run targetName = - try Target.runAndGetContext 1 targetName [] - with | :? BuildFailedException as bfe -> - match bfe.Info with - | Some context -> context - | None -> failwithf "No context given!" + Target.runAndGetContext 1 targetName [] let runParallel targetName = - try Target.runAndGetContext 3 targetName [] - with | :? BuildFailedException as bfe -> - match bfe.Info with - | Some context -> context - | None -> failwithf "No context given!" + Target.runAndGetContext 3 targetName [] open Fake.Core.TargetOperators