Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1067,3 +1067,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...)
}
}
24 changes: 24 additions & 0 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error {
return nil
}

if t.If != "" {
if err := execext.RunCommand(ctx, &execext.RunCommandOptions{
Command: t.If,
Dir: t.Dir,
Env: env.Get(t),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes people ask to have Posix and Bash opt available on these commands. I don't know if that is a good or a bad idea, never the less please see #2538 where the idea is in draft form.

}); err != nil {
e.Logger.VerboseOutf(logger.Yellow, "task: %q if condition not met - skipped\n", call.Task)
return nil
}
}

if err := e.areTaskRequiredVarsSet(t); err != nil {
return err
}
Expand Down Expand Up @@ -295,6 +306,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 {
Expand All @@ -305,6 +317,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 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()
Expand Down
5 changes: 5 additions & 0 deletions taskfile/ast/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Cmd struct {
Cmd string
Task string
For *For
If string
Silent bool
Set []string
Shopt []string
Expand All @@ -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),
Expand All @@ -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
Expand Down Expand Up @@ -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
return nil
}
Expand All @@ -100,6 +104,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
Expand Down
4 changes: 4 additions & 0 deletions taskfile/ast/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Task struct {
IgnoreError bool
Run string
Platforms []*Platform
If string
Watch bool
Location *Location
Failfast bool
Expand Down Expand Up @@ -142,6 +143,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
Expand Down Expand Up @@ -181,6 +183,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
Expand Down Expand Up @@ -225,6 +228,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,
Expand Down
160 changes: 160 additions & 0 deletions testdata/if/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-cmd-if-false.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
this runs
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-cmd-if-true.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
executed
7 changes: 7 additions & 0 deletions testdata/if/testdata/TestIf-if-in-for-loop.golden
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions testdata/if/testdata/TestIf-task-call-if-false.golden
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 0 deletions testdata/if/testdata/TestIf-task-call-if-true.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
subtask ran
after task call
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-task-if-false.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task: "task-if-false" if condition not met - skipped
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-task-if-true.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task executed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task: "task-level-template-false" if condition not met - skipped
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-task-level-template.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
task runs in prod
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-and.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
both conditions met
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-bool-false.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feature was disabled
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-bool-true.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feature enabled
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-cli-var.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MY_VAR is yes
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-direct-false.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
direct false skipped correctly
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-direct-true.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
direct true works
5 changes: 5 additions & 0 deletions testdata/if/testdata/TestIf-template-eq-false.golden
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-eq-true.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
env is prod
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-ne.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
env is not dev
1 change: 1 addition & 0 deletions testdata/if/testdata/TestIf-template-or.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
at least one condition met
Loading
Loading