diff --git a/src/Terrabuild.Extensions/Cargo.fs b/src/Terrabuild.Extensions/Cargo.fs index 297c4c3f..d04068bf 100644 --- a/src/Terrabuild.Extensions/Cargo.fs +++ b/src/Terrabuild.Extensions/Cargo.fs @@ -45,8 +45,9 @@ type Cargo() = /// Arguments for command. static member __dispatch__ (context: ActionContext) (arguments: string option) = let arguments = arguments |> Option.defaultValue "" - - let ops = [ shellOp context.Command arguments ] + let arguments = $"{context.Command} {arguments}" + + let ops = [ shellOp "cargo" arguments ] execRequest Cacheability.Always ops diff --git a/src/Terrabuild.Extensions/Docker.fs b/src/Terrabuild.Extensions/Docker.fs index b65f235e..c8e15469 100644 --- a/src/Terrabuild.Extensions/Docker.fs +++ b/src/Terrabuild.Extensions/Docker.fs @@ -7,6 +7,19 @@ open Terrabuild.Extensibility /// type Docker() = + /// + /// Run a docker `command`. + /// + /// Example. + /// Arguments for command. + static member __dispatch__ (context: ActionContext) (arguments: string option) = + let arguments = arguments |> Option.defaultValue "" + let arguments = $"{context.Command} {arguments}" + + let ops = [ shellOp "docker" arguments ] + execRequest Cacheability.Always ops + + /// /// Build a Dockerfile. /// diff --git a/src/Terrabuild.Extensions/Dotnet.fs b/src/Terrabuild.Extensions/Dotnet.fs index c4262385..c3f0d087 100644 --- a/src/Terrabuild.Extensions/Dotnet.fs +++ b/src/Terrabuild.Extensions/Dotnet.fs @@ -75,6 +75,19 @@ type Dotnet() = projectInfo + /// + /// Run a dotnet `command`. + /// + /// Example. + /// Arguments for command. + static member __dispatch__ (context: ActionContext) (arguments: string option) = + let arguments = arguments |> Option.defaultValue "" + let arguments = $"{context.Command} {arguments}" + + let ops = [ shellOp "dotnet" arguments ] + execRequest Cacheability.Always ops + + /// /// Build project and ensure packages are available first. /// @@ -110,17 +123,6 @@ type Dotnet() = buildRequest context buildOps - /// - /// Run a dotnet `command`. - /// - /// Example. - /// Arguments for command. - static member __dispatch__ (context: ActionContext) (arguments: string option) = - let arguments = arguments |> Option.defaultValue "" - - let ops = [ shellOp context.Command arguments ] - execRequest Cacheability.Always ops - /// /// Pack a project. diff --git a/src/Terrabuild.Extensions/Gradle.fs b/src/Terrabuild.Extensions/Gradle.fs index 5e24af21..dcf4759d 100644 --- a/src/Terrabuild.Extensions/Gradle.fs +++ b/src/Terrabuild.Extensions/Gradle.fs @@ -21,6 +21,19 @@ type Gradle() = with Outputs = Set [ "build/classes/" ] } projectInfo + /// + /// Run a gradle `command`. + /// + /// Example. + /// Arguments for command. + static member __dispatch__ (context: ActionContext) (arguments: string option) = + let arguments = arguments |> Option.defaultValue "" + let arguments = $"{context.Command} {arguments}" + + let ops = [ shellOp "gradle" arguments ] + execRequest Cacheability.Always ops + + /// /// Invoke build task `assemble` for `configuration`. /// diff --git a/src/Terrabuild.Extensions/Npm.fs b/src/Terrabuild.Extensions/Npm.fs index 9f066fc1..98492622 100644 --- a/src/Terrabuild.Extensions/Npm.fs +++ b/src/Terrabuild.Extensions/Npm.fs @@ -22,6 +22,21 @@ type Npm() = projectInfo + /// + /// Run npm command. + /// + /// Arguments to pass to target. + static member __dispatch__ (context: ActionContext) (arguments: string option) = + let cmd = context.Command + let arguments = arguments |> Option.defaultValue "" + + let ops = [ + shellOp "npm" "ci" + shellOp "npm" $"run {cmd} -- {arguments}" + ] + execRequest Cacheability.Always ops + + /// /// Install packages using lock file. /// diff --git a/src/Terrabuild.Extensions/Terraform.fs b/src/Terrabuild.Extensions/Terraform.fs index cc910fc8..6e05a2a7 100644 --- a/src/Terrabuild.Extensions/Terraform.fs +++ b/src/Terrabuild.Extensions/Terraform.fs @@ -24,6 +24,20 @@ type Terraform() = Outputs = Set [ "*.planfile" ] } projectInfo + + /// + /// Run a terraform `command`. + /// + /// Example. + /// Arguments for command. + static member __dispatch__ (context: ActionContext) (arguments: string option) = + let arguments = arguments |> Option.defaultValue "" + let arguments = $"{context.Command} {arguments}" + + let ops = [ shellOp "terraform" arguments ] + execRequest Cacheability.Always ops + + /// /// Init Terraform. /// diff --git a/src/Terrabuild.Extensions/Yarn.fs b/src/Terrabuild.Extensions/Yarn.fs index 10ab6f5d..06c1abed 100644 --- a/src/Terrabuild.Extensions/Yarn.fs +++ b/src/Terrabuild.Extensions/Yarn.fs @@ -23,6 +23,21 @@ type Yarn() = projectInfo + /// + /// Run yarn `command`. + /// + /// Arguments to pass to target. + static member __dispatch__ (context: ActionContext) (arguments: string option) = + let arguments = arguments |> Option.defaultValue "" + let cmd = context.Command + + let ops = [ + shellOp "yarn" "install --frozen-lockfile" + shellOp "yarn" $"{cmd} -- {arguments}" + ] + execRequest Cacheability.Always ops + + /// /// Install packages using lock file. /// diff --git a/src/Terrabuild/CLI.fs b/src/Terrabuild/CLI.fs index f40cfc9b..f857f6d8 100644 --- a/src/Terrabuild/CLI.fs +++ b/src/Terrabuild/CLI.fs @@ -72,6 +72,24 @@ with | Container_Tool _ -> "Container Tool to use (docker or podman)." | WhatIf -> "Prepare the action but do not apply." +[] +type ServeArgs = + | [] Workspace of path:string + | [] Configuration of name:string + | [] Variable of variable:string * value:string + | [] Label of labels:string list + | [] Logs +with + interface IArgParserTemplate with + member this.Usage = + match this with + | Workspace _ -> "Root of workspace. If not specified, current directory is used." + | Configuration _ -> "Configuration to use." + | Variable _ -> "Set variable." + | Label _-> "Select projects based on labels." + | Logs -> "Output logs for impacted projects." + + [] type ClearArgs = | [] Cache @@ -110,6 +128,7 @@ type TerrabuildArgs = | [] Scaffold of ParseResults | [] Logs of ParseResults | [] Run of ParseResults + | [] Serve of ParseResults | [] Clear of ParseResults | [] Login of ParseResults | [] Logout of ParseResults @@ -122,6 +141,7 @@ with | Scaffold _ -> "Scaffold workspace." | Logs _ -> "dump logs." | Run _ -> "Run specified targets." + | Serve _ -> "Serve specified targets." | Clear _ -> "Clear specified caches." | Login _ -> "Connect to backend." | Logout _ -> "Disconnect from backend." diff --git a/src/Terrabuild/Core/Build.fs b/src/Terrabuild/Core/Build.fs index b30a2a0d..19d86fc4 100644 --- a/src/Terrabuild/Core/Build.fs +++ b/src/Terrabuild/Core/Build.fs @@ -115,7 +115,11 @@ let execCommands (node: GraphDef.Node) (cacheEntry: Cache.IEntry) (options: Conf Log.Debug("{Hash}: Running '{Command}' with '{Arguments}'", node.TargetHash, cmd, args) let logFile = cacheEntry.NextLogFile() - let exitCode = Exec.execCaptureTimestampedOutput workDir cmd args logFile + let exitCode = + if options.Targets |> Set.contains "serve" then + Exec.execConsole workDir cmd args + else + Exec.execCaptureTimestampedOutput workDir cmd args logFile cmdLastEndedAt <- DateTime.UtcNow let endedAt = cmdLastEndedAt let duration = endedAt - startedAt diff --git a/src/Terrabuild/Helpers/Exec.fs b/src/Terrabuild/Helpers/Exec.fs index 23303ee5..87ac0d36 100644 --- a/src/Terrabuild/Helpers/Exec.fs +++ b/src/Terrabuild/Helpers/Exec.fs @@ -9,17 +9,17 @@ type CaptureResult = | Success of string*int | Error of string*int -let private createProcess workingDir command args = +let private createProcess workingDir command args redirect = let psi = ProcessStartInfo (FileName = command, Arguments = args, UseShellExecute = false, WorkingDirectory = workingDir, - RedirectStandardOutput = true, - RedirectStandardError = true) + RedirectStandardOutput = redirect, + RedirectStandardError = redirect) new Process(StartInfo = psi) let execCaptureOutput (workingDir: string) (command: string) (args: string) = - use proc = createProcess workingDir command args + use proc = createProcess workingDir command args true proc.Start() |> ignore proc.WaitForExit() @@ -27,6 +27,15 @@ let execCaptureOutput (workingDir: string) (command: string) (args: string) = | 0 -> Success (proc.StandardOutput.ReadToEnd(), proc.ExitCode) | _ -> Error (proc.StandardError.ReadToEnd(), proc.ExitCode) +let execConsole (workingDir: string) (command: string) (args: string) = + try + use proc = createProcess workingDir command args false + proc.Start() |> ignore + proc.WaitForExit() + proc.ExitCode + with + | exn -> TerrabuildException.Raise($"Process '{command} {args} in directory '{workingDir}' failed", exn) + let execCaptureTimestampedOutput (workingDir: string) (command: string) (args: string) (logFile: string) = try use logWriter = new StreamWriter(logFile) @@ -35,14 +44,13 @@ let execCaptureTimestampedOutput (workingDir: string) (command: string) (args: s let inline lockWrite (from: string) (msg: string) = lock writeLock (fun () -> logWriter.WriteLine($"{DateTime.UtcNow} {from} {msg}")) - use proc = createProcess workingDir command args + use proc = createProcess workingDir command args true proc.OutputDataReceived.Add(fun e -> lockWrite "OUT" e.Data) proc.ErrorDataReceived.Add(fun e -> lockWrite "ERR" e.Data) proc.Start() |> ignore proc.BeginOutputReadLine() proc.BeginErrorReadLine() proc.WaitForExit() - proc.ExitCode with | exn -> TerrabuildException.Raise($"Process '{command} {args} in directory '{workingDir}' failed", exn) diff --git a/src/Terrabuild/Program.fs b/src/Terrabuild/Program.fs index f142f751..1e3ef4ad 100644 --- a/src/Terrabuild/Program.fs +++ b/src/Terrabuild/Program.fs @@ -174,7 +174,6 @@ let processCommandLine (parser: ArgumentParser) (result: ParseRe 0 let run (runArgs: ParseResults) = - let targets = runArgs.GetResult(RunArgs.Target) |> Seq.map String.toLower let wsDir = match runArgs.TryGetResult(RunArgs.Workspace) with | Some ws -> ws @@ -182,6 +181,7 @@ let processCommandLine (parser: ArgumentParser) (result: ParseRe match Environment.CurrentDirectory |> findWorkspace with | Some ws -> ws | _ -> TerrabuildException.Raise("Can't find workspace root directory. Check you are in a workspace.") + let targets = runArgs.GetResult(RunArgs.Target) |> Seq.map String.toLower let configuration = runArgs.TryGetResult(RunArgs.Configuration) |> Option.defaultValue "default" |> String.toLower let note = runArgs.TryGetResult(RunArgs.Note) let labels = runArgs.TryGetResult(RunArgs.Label) |> Option.map (fun labels -> labels |> Seq.map String.toLower |> Set) @@ -218,6 +218,36 @@ let processCommandLine (parser: ArgumentParser) (result: ParseRe RunTargetOptions.ContainerTool = containerTool } runTarget logs options + let serve (serveArgs: ParseResults) = + let wsDir = + match serveArgs.TryGetResult(ServeArgs.Workspace) with + | Some ws -> ws + | _ -> + match Environment.CurrentDirectory |> findWorkspace with + | Some ws -> ws + | _ -> TerrabuildException.Raise("Can't find workspace root directory. Check you are in a workspace.") + let configuration = serveArgs.TryGetResult(ServeArgs.Configuration) |> Option.defaultValue "default" |> String.toLower + let labels = serveArgs.TryGetResult(ServeArgs.Label) |> Option.map (fun labels -> labels |> Seq.map String.toLower |> Set) + let variables = serveArgs.GetResults(ServeArgs.Variable) |> Map + let options = { RunTargetOptions.Workspace = wsDir |> FS.fullPath + RunTargetOptions.WhatIf = false + RunTargetOptions.Debug = debug + RunTargetOptions.Force = false + RunTargetOptions.MaxConcurrency = Int32.MaxValue + RunTargetOptions.Retry = true + RunTargetOptions.StartedAt = DateTime.UtcNow + RunTargetOptions.IsLog = false + RunTargetOptions.Targets = Set [ "serve" ] + RunTargetOptions.LocalOnly = true + RunTargetOptions.CheckState = false + RunTargetOptions.Configuration = configuration + RunTargetOptions.Note = None + RunTargetOptions.Tag = None + RunTargetOptions.Labels = labels + RunTargetOptions.Variables = variables + RunTargetOptions.ContainerTool = None } + runTarget true options + let logs (logsArgs: ParseResults) = let targets = logsArgs.GetResult(LogsArgs.Target) |> Seq.map String.toLower let wsDir = @@ -279,6 +309,7 @@ let processCommandLine (parser: ArgumentParser) (result: ParseRe | p when p.Contains(TerrabuildArgs.Scaffold) -> p.GetResult(TerrabuildArgs.Scaffold) |> scaffold | p when p.Contains(TerrabuildArgs.Logs) -> p.GetResult(TerrabuildArgs.Logs) |> logs | p when p.Contains(TerrabuildArgs.Run) -> p.GetResult(TerrabuildArgs.Run) |> run + | p when p.Contains(TerrabuildArgs.Serve) -> p.GetResult(TerrabuildArgs.Serve) |> serve | p when p.Contains(TerrabuildArgs.Clear) -> p.GetResult(TerrabuildArgs.Clear) |> clear | p when p.Contains(TerrabuildArgs.Login) -> p.GetResult(TerrabuildArgs.Login) |> login | p when p.Contains(TerrabuildArgs.Logout) -> p.GetResult(TerrabuildArgs.Logout) |> logout