mirror of
https://github.com/go-task/task.git
synced 2026-02-24 12:10:47 +01:00
feat: add conditional execution for tasks and commands (#2564)
This commit is contained in:
@@ -1105,3 +1105,65 @@ func TestFailfast(t *testing.T) {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
task string
|
||||
vars map[string]any
|
||||
verbose bool
|
||||
}{
|
||||
// Basic command-level if
|
||||
{name: "cmd-if-true", task: "cmd-if-true"},
|
||||
{name: "cmd-if-false", task: "cmd-if-false"},
|
||||
|
||||
// Task-level if
|
||||
{name: "task-if-true", task: "task-if-true"},
|
||||
{name: "task-if-false", task: "task-if-false", verbose: true},
|
||||
|
||||
// Task call with if
|
||||
{name: "task-call-if-true", task: "task-call-if-true"},
|
||||
{name: "task-call-if-false", task: "task-call-if-false", verbose: true},
|
||||
|
||||
// Go template conditions
|
||||
{name: "template-eq-true", task: "template-eq-true"},
|
||||
{name: "template-eq-false", task: "template-eq-false", verbose: true},
|
||||
{name: "template-ne", task: "template-ne"},
|
||||
{name: "template-bool-true", task: "template-bool-true"},
|
||||
{name: "template-bool-false", task: "template-bool-false"},
|
||||
{name: "template-direct-true", task: "template-direct-true"},
|
||||
{name: "template-direct-false", task: "template-direct-false"},
|
||||
{name: "template-and", task: "template-and"},
|
||||
{name: "template-or", task: "template-or"},
|
||||
|
||||
// CLI variable override
|
||||
{name: "template-cli-var", task: "template-cli-var", vars: map[string]any{"MY_VAR": "yes"}},
|
||||
|
||||
// Task-level if with template
|
||||
{name: "task-level-template", task: "task-level-template"},
|
||||
{name: "task-level-template-false", task: "task-level-template-false", verbose: true},
|
||||
|
||||
// For loop with if
|
||||
{name: "if-in-for-loop", task: "if-in-for-loop", verbose: true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
opts := []ExecutorTestOption{
|
||||
WithName(test.name),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/if"),
|
||||
task.WithSilent(true),
|
||||
task.WithVerbose(test.verbose),
|
||||
),
|
||||
WithTask(test.task),
|
||||
}
|
||||
if test.vars != nil {
|
||||
for k, v := range test.vars {
|
||||
opts = append(opts, WithVar(k, v))
|
||||
}
|
||||
}
|
||||
NewExecutorTest(t, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
25
task.go
25
task.go
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -129,6 +130,17 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.TrimSpace(t.If) != "" {
|
||||
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: t.If,
|
||||
Dir: t.Dir,
|
||||
Env: env.Get(t),
|
||||
}); err != nil {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: if condition not met - skipped: %q\n", call.Task)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.areTaskRequiredVarsSet(t); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -299,6 +311,7 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, d
|
||||
|
||||
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
|
||||
cmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
|
||||
cmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
|
||||
cmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
|
||||
|
||||
if err := e.runCommand(ctx, t, call, i); err != nil {
|
||||
@@ -309,6 +322,18 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, d
|
||||
func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i int) error {
|
||||
cmd := t.Cmds[i]
|
||||
|
||||
// Check if condition for any command type
|
||||
if strings.TrimSpace(cmd.If) != "" {
|
||||
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||
Command: cmd.If,
|
||||
Dir: t.Dir,
|
||||
Env: env.Get(t),
|
||||
}); err != nil {
|
||||
e.Logger.VerboseOutf(logger.Yellow, "task: [%s] if condition not met - skipped\n", t.Name())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case cmd.Task != "":
|
||||
reacquire := e.releaseConcurrencyLimit()
|
||||
|
||||
@@ -12,6 +12,7 @@ type Cmd struct {
|
||||
Cmd string
|
||||
Task string
|
||||
For *For
|
||||
If string
|
||||
Silent bool
|
||||
Set []string
|
||||
Shopt []string
|
||||
@@ -29,6 +30,7 @@ func (c *Cmd) DeepCopy() *Cmd {
|
||||
Cmd: c.Cmd,
|
||||
Task: c.Task,
|
||||
For: c.For.DeepCopy(),
|
||||
If: c.If,
|
||||
Silent: c.Silent,
|
||||
Set: deepcopy.Slice(c.Set),
|
||||
Shopt: deepcopy.Slice(c.Shopt),
|
||||
@@ -55,6 +57,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
Cmd string
|
||||
Task string
|
||||
For *For
|
||||
If string
|
||||
Silent bool
|
||||
Set []string
|
||||
Shopt []string
|
||||
@@ -92,6 +95,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
c.Task = cmdStruct.Task
|
||||
c.Vars = cmdStruct.Vars
|
||||
c.For = cmdStruct.For
|
||||
c.If = cmdStruct.If
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.IgnoreError = cmdStruct.IgnoreError
|
||||
return nil
|
||||
@@ -101,6 +105,7 @@ func (c *Cmd) UnmarshalYAML(node *yaml.Node) error {
|
||||
if cmdStruct.Cmd != "" {
|
||||
c.Cmd = cmdStruct.Cmd
|
||||
c.For = cmdStruct.For
|
||||
c.If = cmdStruct.If
|
||||
c.Silent = cmdStruct.Silent
|
||||
c.Set = cmdStruct.Set
|
||||
c.Shopt = cmdStruct.Shopt
|
||||
|
||||
@@ -40,6 +40,7 @@ type Task struct {
|
||||
IgnoreError bool
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
If string
|
||||
Watch bool
|
||||
Location *Location
|
||||
Failfast bool
|
||||
@@ -145,6 +146,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
IgnoreError bool `yaml:"ignore_error"`
|
||||
Run string
|
||||
Platforms []*Platform
|
||||
If string
|
||||
Requires *Requires
|
||||
Watch bool
|
||||
Failfast bool
|
||||
@@ -184,6 +186,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
||||
t.IgnoreError = task.IgnoreError
|
||||
t.Run = task.Run
|
||||
t.Platforms = task.Platforms
|
||||
t.If = task.If
|
||||
t.Requires = task.Requires
|
||||
t.Watch = task.Watch
|
||||
t.Failfast = task.Failfast
|
||||
@@ -228,6 +231,7 @@ func (t *Task) DeepCopy() *Task {
|
||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||
Platforms: deepcopy.Slice(t.Platforms),
|
||||
If: t.If,
|
||||
Location: t.Location.DeepCopy(),
|
||||
Requires: t.Requires.DeepCopy(),
|
||||
Namespace: t.Namespace,
|
||||
|
||||
160
testdata/if/Taskfile.yml
vendored
Normal file
160
testdata/if/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
SHOULD_RUN: "yes"
|
||||
ENV: "prod"
|
||||
FEATURE_ENABLED: "true"
|
||||
FEATURE_DISABLED: "false"
|
||||
|
||||
tasks:
|
||||
# Basic command-level if (condition met)
|
||||
cmd-if-true:
|
||||
cmds:
|
||||
- cmd: echo "executed"
|
||||
if: "true"
|
||||
|
||||
# Basic command-level if (condition not met)
|
||||
cmd-if-false:
|
||||
cmds:
|
||||
- cmd: echo "should not appear"
|
||||
if: "false"
|
||||
- echo "this runs"
|
||||
|
||||
# Task-level if (condition met)
|
||||
task-if-true:
|
||||
if: "true"
|
||||
cmds:
|
||||
- echo "task executed"
|
||||
|
||||
# Task-level if (condition not met)
|
||||
task-if-false:
|
||||
if: "false"
|
||||
cmds:
|
||||
- echo "should not appear"
|
||||
|
||||
# With template variables
|
||||
if-with-template:
|
||||
cmds:
|
||||
- cmd: echo "Running because SHOULD_RUN={{.SHOULD_RUN}}"
|
||||
if: '[ "{{.SHOULD_RUN}}" = "yes" ]'
|
||||
|
||||
# If inside for loop
|
||||
if-in-for-loop:
|
||||
cmds:
|
||||
- for: ["a", "b", "c"]
|
||||
cmd: echo "processing {{.ITEM}}"
|
||||
if: '[ "{{.ITEM}}" != "b" ]'
|
||||
|
||||
# If on task call
|
||||
if-on-task-call:
|
||||
cmds:
|
||||
- task: subtask
|
||||
if: "true"
|
||||
|
||||
subtask:
|
||||
internal: true
|
||||
cmds:
|
||||
- echo "subtask ran"
|
||||
|
||||
# If combined with platforms (both must pass)
|
||||
if-with-platforms:
|
||||
cmds:
|
||||
- cmd: echo "condition and platform met"
|
||||
platforms: [linux, darwin, windows]
|
||||
if: "true"
|
||||
|
||||
# Skip task call
|
||||
skip-task-call:
|
||||
cmds:
|
||||
- task: subtask
|
||||
if: "false"
|
||||
- echo "after skipped task call"
|
||||
|
||||
# Task call in cmds with if condition met
|
||||
task-call-if-true:
|
||||
cmds:
|
||||
- task: subtask
|
||||
if: "true"
|
||||
- echo "after task call"
|
||||
|
||||
# Task call in cmds with if condition not met
|
||||
task-call-if-false:
|
||||
cmds:
|
||||
- task: subtask
|
||||
if: "false"
|
||||
- echo "continues after skipped task"
|
||||
|
||||
# Template eq - condition met
|
||||
template-eq-true:
|
||||
cmds:
|
||||
- cmd: echo "env is prod"
|
||||
if: '{{ eq .ENV "prod" }}'
|
||||
|
||||
# Template eq - condition not met
|
||||
template-eq-false:
|
||||
cmds:
|
||||
- cmd: echo "should not appear"
|
||||
if: '{{ eq .ENV "dev" }}'
|
||||
- echo "this runs"
|
||||
|
||||
# Template ne (not equal)
|
||||
template-ne:
|
||||
cmds:
|
||||
- cmd: echo "env is not dev"
|
||||
if: '{{ ne .ENV "dev" }}'
|
||||
|
||||
# Template with boolean-like variable
|
||||
template-bool-true:
|
||||
cmds:
|
||||
- cmd: echo "feature enabled"
|
||||
if: '{{ eq .FEATURE_ENABLED "true" }}'
|
||||
|
||||
# Template with boolean-like variable (false)
|
||||
template-bool-false:
|
||||
cmds:
|
||||
- cmd: echo "should not appear"
|
||||
if: '{{ eq .FEATURE_DISABLED "true" }}'
|
||||
- echo "feature was disabled"
|
||||
|
||||
# Direct true/false from template
|
||||
template-direct-true:
|
||||
cmds:
|
||||
- cmd: echo "direct true works"
|
||||
if: '{{ .FEATURE_ENABLED }}'
|
||||
|
||||
# Direct true/false from template (false case)
|
||||
template-direct-false:
|
||||
cmds:
|
||||
- cmd: echo "should not appear"
|
||||
if: '{{ .FEATURE_DISABLED }}'
|
||||
- echo "direct false skipped correctly"
|
||||
|
||||
# Template with CLI variable override
|
||||
template-cli-var:
|
||||
cmds:
|
||||
- cmd: echo "MY_VAR is yes"
|
||||
if: '{{ eq .MY_VAR "yes" }}'
|
||||
|
||||
# Combined template conditions with and
|
||||
template-and:
|
||||
cmds:
|
||||
- cmd: echo "both conditions met"
|
||||
if: '{{ and (eq .ENV "prod") (eq .FEATURE_ENABLED "true") }}'
|
||||
|
||||
# Combined template conditions with or
|
||||
template-or:
|
||||
cmds:
|
||||
- cmd: echo "at least one condition met"
|
||||
if: '{{ or (eq .ENV "dev") (eq .ENV "prod") }}'
|
||||
|
||||
# Task-level if with template
|
||||
task-level-template:
|
||||
if: '{{ eq .ENV "prod" }}'
|
||||
cmds:
|
||||
- echo "task runs in prod"
|
||||
|
||||
# Task-level if with template (not met)
|
||||
task-level-template-false:
|
||||
if: '{{ eq .ENV "dev" }}'
|
||||
cmds:
|
||||
- echo "should not appear"
|
||||
1
testdata/if/testdata/TestIf-cmd-if-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-cmd-if-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
this runs
|
||||
1
testdata/if/testdata/TestIf-cmd-if-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-cmd-if-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
executed
|
||||
7
testdata/if/testdata/TestIf-if-in-for-loop.golden
vendored
Normal file
7
testdata/if/testdata/TestIf-if-in-for-loop.golden
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
task: "if-in-for-loop" started
|
||||
task: [if-in-for-loop] echo "processing a"
|
||||
processing a
|
||||
task: [if-in-for-loop] if condition not met - skipped
|
||||
task: [if-in-for-loop] echo "processing c"
|
||||
processing c
|
||||
task: "if-in-for-loop" finished
|
||||
5
testdata/if/testdata/TestIf-task-call-if-false.golden
vendored
Normal file
5
testdata/if/testdata/TestIf-task-call-if-false.golden
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
task: "task-call-if-false" started
|
||||
task: [task-call-if-false] if condition not met - skipped
|
||||
task: [task-call-if-false] echo "continues after skipped task"
|
||||
continues after skipped task
|
||||
task: "task-call-if-false" finished
|
||||
2
testdata/if/testdata/TestIf-task-call-if-true.golden
vendored
Normal file
2
testdata/if/testdata/TestIf-task-call-if-true.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
subtask ran
|
||||
after task call
|
||||
1
testdata/if/testdata/TestIf-task-if-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-if-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: if condition not met - skipped: "task-if-false"
|
||||
1
testdata/if/testdata/TestIf-task-if-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-if-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task executed
|
||||
1
testdata/if/testdata/TestIf-task-level-template-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-level-template-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task: if condition not met - skipped: "task-level-template-false"
|
||||
1
testdata/if/testdata/TestIf-task-level-template.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-task-level-template.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
task runs in prod
|
||||
1
testdata/if/testdata/TestIf-template-and.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-and.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
both conditions met
|
||||
1
testdata/if/testdata/TestIf-template-bool-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-bool-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
feature was disabled
|
||||
1
testdata/if/testdata/TestIf-template-bool-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-bool-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
feature enabled
|
||||
1
testdata/if/testdata/TestIf-template-cli-var.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-cli-var.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
MY_VAR is yes
|
||||
1
testdata/if/testdata/TestIf-template-direct-false.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-direct-false.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
direct false skipped correctly
|
||||
1
testdata/if/testdata/TestIf-template-direct-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-direct-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
direct true works
|
||||
5
testdata/if/testdata/TestIf-template-eq-false.golden
vendored
Normal file
5
testdata/if/testdata/TestIf-template-eq-false.golden
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
task: "template-eq-false" started
|
||||
task: [template-eq-false] if condition not met - skipped
|
||||
task: [template-eq-false] echo "this runs"
|
||||
this runs
|
||||
task: "template-eq-false" finished
|
||||
1
testdata/if/testdata/TestIf-template-eq-true.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-eq-true.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
env is prod
|
||||
1
testdata/if/testdata/TestIf-template-ne.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-ne.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
env is not dev
|
||||
1
testdata/if/testdata/TestIf-template-or.golden
vendored
Normal file
1
testdata/if/testdata/TestIf-template-or.golden
vendored
Normal file
@@ -0,0 +1 @@
|
||||
at least one condition met
|
||||
@@ -123,6 +123,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
IncludeVars: origTask.IncludeVars,
|
||||
IncludedTaskfileVars: origTask.IncludedTaskfileVars,
|
||||
Platforms: origTask.Platforms,
|
||||
If: templater.Replace(origTask.If, cache),
|
||||
Location: origTask.Location,
|
||||
Requires: origTask.Requires,
|
||||
Watch: origTask.Watch,
|
||||
@@ -228,6 +229,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
newCmd := cmd.DeepCopy()
|
||||
newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
|
||||
newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
|
||||
newCmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra)
|
||||
newCmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
|
||||
new.Cmds = append(new.Cmds, newCmd)
|
||||
}
|
||||
@@ -242,6 +244,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
newCmd := cmd.DeepCopy()
|
||||
newCmd.Cmd = templater.Replace(cmd.Cmd, cache)
|
||||
newCmd.Task = templater.Replace(cmd.Task, cache)
|
||||
newCmd.If = templater.Replace(cmd.If, cache)
|
||||
newCmd.Vars = templater.ReplaceVars(cmd.Vars, cache)
|
||||
new.Cmds = append(new.Cmds, newCmd)
|
||||
}
|
||||
|
||||
@@ -1020,6 +1020,99 @@ tasks:
|
||||
- echo "I will not run"
|
||||
```
|
||||
|
||||
### Conditional execution with `if`
|
||||
|
||||
The `if` attribute allows you to conditionally skip tasks or commands based on a
|
||||
shell command's exit code. Unlike `preconditions` which fail and stop execution,
|
||||
`if` simply skips the task or command when the condition is not met and continues
|
||||
with the rest of the Taskfile.
|
||||
|
||||
#### Task-level `if`
|
||||
|
||||
When `if` is set on a task, the entire task is skipped if the condition fails:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
deploy:
|
||||
if: '[ "$CI" = "true" ]'
|
||||
cmds:
|
||||
- echo "Deploying..."
|
||||
- ./deploy.sh
|
||||
```
|
||||
|
||||
#### Command-level `if`
|
||||
|
||||
When `if` is set on a command, only that specific command is skipped:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- cmd: echo "Building for production"
|
||||
if: '[ "$ENV" = "production" ]'
|
||||
- cmd: echo "Building for development"
|
||||
if: '[ "$ENV" = "development" ]'
|
||||
- go build ./...
|
||||
```
|
||||
|
||||
#### Using templates in `if` conditions
|
||||
|
||||
You can use Go template expressions in `if` conditions. Template expressions like
|
||||
<span v-pre>`{{eq .VAR "value"}}`</span> evaluate to `true` or `false`, which are valid shell
|
||||
commands (`true` exits with 0, `false` exits with 1):
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
conditional:
|
||||
vars:
|
||||
ENABLE_FEATURE: "true"
|
||||
cmds:
|
||||
- cmd: echo "Feature is enabled"
|
||||
if: '{{eq .ENABLE_FEATURE "true"}}'
|
||||
- cmd: echo "Feature is disabled"
|
||||
if: '{{ne .ENABLE_FEATURE "true"}}'
|
||||
```
|
||||
|
||||
#### Using `if` with `for` loops
|
||||
|
||||
When used inside a `for` loop, the `if` condition is evaluated for each iteration:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
process-items:
|
||||
cmds:
|
||||
- for: ['a', 'b', 'c']
|
||||
cmd: echo "processing {{.ITEM}}"
|
||||
if: '[ "{{.ITEM}}" != "b" ]'
|
||||
```
|
||||
|
||||
This will output:
|
||||
|
||||
```
|
||||
processing a
|
||||
processing c
|
||||
```
|
||||
|
||||
#### `if` vs `preconditions`
|
||||
|
||||
| Aspect | `if` | `preconditions` |
|
||||
|--------|------|-----------------|
|
||||
| On failure | Skips (continues) | Fails (stops) |
|
||||
| Message | Only in verbose mode | Always shown |
|
||||
| Use case | "Run if possible" | "Must be true" |
|
||||
|
||||
Use `if` when you want optional conditional execution that shouldn't stop the
|
||||
workflow. Use `preconditions` when the condition must be met for the task to
|
||||
make sense.
|
||||
|
||||
### Limiting when tasks run
|
||||
|
||||
If a task executed by multiple `cmds` or multiple `deps` you can control when it
|
||||
|
||||
@@ -616,6 +616,27 @@ tasks:
|
||||
- ./deploy.sh
|
||||
```
|
||||
|
||||
#### `if`
|
||||
|
||||
- **Type**: `string`
|
||||
- **Description**: Shell command to conditionally execute the task. If the
|
||||
command exits with a non-zero code, the task is skipped (not failed).
|
||||
|
||||
```yaml
|
||||
tasks:
|
||||
# Task only runs in CI environment
|
||||
deploy:
|
||||
if: '[ "$CI" = "true" ]'
|
||||
cmds:
|
||||
- ./deploy.sh
|
||||
|
||||
# Using Go template expressions
|
||||
build-prod:
|
||||
if: '{{eq .ENV "production"}}'
|
||||
cmds:
|
||||
- go build -ldflags="-s -w" ./...
|
||||
```
|
||||
|
||||
### `dir`
|
||||
|
||||
- **Type**: `string`
|
||||
@@ -812,6 +833,27 @@ tasks:
|
||||
SERVICE: '{{.ITEM}}'
|
||||
```
|
||||
|
||||
### Conditional Commands
|
||||
|
||||
Use `if` to conditionally execute a command. If the shell command exits with a
|
||||
non-zero code, the command is skipped.
|
||||
|
||||
```yaml
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
# Only run in production
|
||||
- cmd: echo "Optimizing for production"
|
||||
if: '[ "$ENV" = "production" ]'
|
||||
# Using Go templates
|
||||
- cmd: echo "Feature enabled"
|
||||
if: '{{eq .ENABLE_FEATURE "true"}}'
|
||||
# Inside for loops (evaluated per iteration)
|
||||
- for: [a, b, c]
|
||||
cmd: echo "processing {{.ITEM}}"
|
||||
if: '[ "{{.ITEM}}" != "b" ]'
|
||||
```
|
||||
|
||||
## Shell Options
|
||||
|
||||
### Set Options
|
||||
|
||||
@@ -193,6 +193,10 @@
|
||||
"description": "Specifies which platforms the task should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
},
|
||||
"if": {
|
||||
"description": "A shell command to evaluate. If the exit code is non-zero, the task is skipped.",
|
||||
"type": "string"
|
||||
},
|
||||
"requires": {
|
||||
"description": "A list of variables which should be set if this task is to run, if any of these variables are unset the task will error and not run",
|
||||
"$ref": "#/definitions/requires_obj"
|
||||
@@ -332,6 +336,10 @@
|
||||
"silent": {
|
||||
"description": "Hides task name and command from output. The command's output will still be redirected to `STDOUT` and `STDERR`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"if": {
|
||||
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -369,6 +377,10 @@
|
||||
"platforms": {
|
||||
"description": "Specifies which platforms the command should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
},
|
||||
"if": {
|
||||
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -447,6 +459,10 @@
|
||||
"platforms": {
|
||||
"description": "Specifies which platforms the command should be run on.",
|
||||
"$ref": "#/definitions/platforms"
|
||||
},
|
||||
"if": {
|
||||
"description": "A shell command to evaluate. If the exit code is non-zero, the command is skipped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
Reference in New Issue
Block a user