diff --git a/CHANGELOG.md b/CHANGELOG.md index fffcd84e..c53f53d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - Fixed `ROOT_TASKFILE` variable pointing to directory instead of the actual Taskfile path when no explicit `-t` flag is provided (#2635, #1706 by @trulede). +- Included Taskfiles with `silent: true` now properly propagate silence to their + tasks, while still allowing individual tasks to override with `silent: false` + (#2640, #1319 by @trulede). ## v3.47.0 - 2026-01-24 diff --git a/executor_test.go b/executor_test.go index d311e422..d810e652 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1060,6 +1060,18 @@ func TestIncludeChecksum(t *testing.T) { ) } +func TestIncludeSilent(t *testing.T) { + t.Parallel() + + NewExecutorTest(t, + WithName("include-taskfile-silent"), + WithExecutorOptions( + task.WithDir("testdata/includes_silent"), + ), + WithTask("default"), + ) +} + func TestFailfast(t *testing.T) { t.Parallel() diff --git a/internal/deepcopy/deepcopy.go b/internal/deepcopy/deepcopy.go index ecd36180..48329c13 100644 --- a/internal/deepcopy/deepcopy.go +++ b/internal/deepcopy/deepcopy.go @@ -10,6 +10,15 @@ type Copier[T any] interface { DeepCopy() T } +func Scalar[T any](orig *T) *T { + if orig == nil { + return nil + } else { + v := *orig + return &v + } +} + func Slice[T any](orig []T) []T { if orig == nil { return nil diff --git a/task.go b/task.go index 10365ea7..0184793c 100644 --- a/task.go +++ b/task.go @@ -228,7 +228,7 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error { } if upToDate && preCondMet { - if e.Verbose || (!call.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) { + if e.Verbose || (!call.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) { name := t.Name() if e.OutputStyle.Name == "prefixed" { name = t.Prefix @@ -383,7 +383,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in return nil } - if e.Verbose || (!call.Silent && !cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) { + if e.Verbose || (!call.Silent && !cmd.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) { e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.Cmd) } diff --git a/taskfile/ast/task.go b/taskfile/ast/task.go index 443ab111..3d71ce71 100644 --- a/taskfile/ast/task.go +++ b/taskfile/ast/task.go @@ -32,7 +32,7 @@ type Task struct { Vars *Vars Env *Vars Dotenv []string - Silent bool + Silent *bool Interactive bool Internal bool Method string @@ -69,6 +69,12 @@ func (t *Task) LocalName() string { return name } +// IsSilent returns true if the task has silent mode explicitly enabled. +// Returns false if Silent is nil (not set) or explicitly set to false. +func (t *Task) IsSilent() bool { + return t.Silent != nil && *t.Silent +} + // WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values. func (t *Task) WildcardMatch(name string) (bool, []string) { names := append([]string{t.Task}, t.Aliases...) @@ -138,7 +144,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { Vars *Vars Env *Vars Dotenv []string - Silent bool + Silent *bool `yaml:"silent,omitempty"` Interactive bool Internal bool Method string @@ -178,7 +184,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { t.Vars = task.Vars t.Env = task.Env t.Dotenv = task.Dotenv - t.Silent = task.Silent + t.Silent = deepcopy.Scalar(task.Silent) t.Interactive = task.Interactive t.Internal = task.Internal t.Method = task.Method @@ -221,7 +227,7 @@ func (t *Task) DeepCopy() *Task { Vars: t.Vars.DeepCopy(), Env: t.Env.DeepCopy(), Dotenv: deepcopy.Slice(t.Dotenv), - Silent: t.Silent, + Silent: deepcopy.Scalar(t.Silent), Interactive: t.Interactive, Internal: t.Internal, Method: t.Method, diff --git a/taskfile/ast/taskfile.go b/taskfile/ast/taskfile.go index 8085e41b..4ae1dbac 100644 --- a/taskfile/ast/taskfile.go +++ b/taskfile/ast/taskfile.go @@ -59,6 +59,14 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error { if t1.Tasks == nil { t1.Tasks = NewTasks() } + if t2.Silent { + for _, t := range t2.Tasks.All(nil) { + if t.Silent == nil { + v := true + t.Silent = &v + } + } + } t1.Vars.Merge(t2.Vars, include) t1.Env.Merge(t2.Env, include) return t1.Tasks.Merge(t2.Tasks, include, t1.Vars) diff --git a/testdata/includes_silent/Taskfile-inc.yml b/testdata/includes_silent/Taskfile-inc.yml new file mode 100644 index 00000000..5245dbbf --- /dev/null +++ b/testdata/includes_silent/Taskfile-inc.yml @@ -0,0 +1,22 @@ +version: '3' + +silent: true + +tasks: + hello: + cmds: + - echo "Hello from include" + + hello-silent: + silent: true + cmds: + - echo "Hello from include silent task" + + hello-silent-not-set: + cmds: + - echo "Hello from include silent not set task" + + hello-silent-set-false: + silent: false + cmds: + - echo "Hello from include silent false task" diff --git a/testdata/includes_silent/Taskfile.yml b/testdata/includes_silent/Taskfile.yml new file mode 100644 index 00000000..fa56ce72 --- /dev/null +++ b/testdata/includes_silent/Taskfile.yml @@ -0,0 +1,13 @@ +version: '3' + +includes: + inc: Taskfile-inc.yml + +tasks: + default: + cmds: + - echo "Hello from root Taskfile" + - task: inc:hello + - task: inc:hello-silent + - task: inc:hello-silent-not-set + - task: inc:hello-silent-set-false diff --git a/testdata/includes_silent/testdata/TestIncludeSilent-include-taskfile-silent.golden b/testdata/includes_silent/testdata/TestIncludeSilent-include-taskfile-silent.golden new file mode 100644 index 00000000..88094628 --- /dev/null +++ b/testdata/includes_silent/testdata/TestIncludeSilent-include-taskfile-silent.golden @@ -0,0 +1,7 @@ +task: [default] echo "Hello from root Taskfile" +Hello from root Taskfile +Hello from include +Hello from include silent task +Hello from include silent not set task +task: [inc:hello-silent-set-false] echo "Hello from include silent false task" +Hello from include silent false task diff --git a/variables.go b/variables.go index f2d23aea..c10bfcd5 100644 --- a/variables.go +++ b/variables.go @@ -10,6 +10,7 @@ import ( "github.com/joho/godotenv" "github.com/go-task/task/v3/errors" + "github.com/go-task/task/v3/internal/deepcopy" "github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/filepathext" @@ -57,7 +58,7 @@ func (e *Executor) CompiledTaskForTaskList(call *Call) (*ast.Task, error) { Vars: vars, Env: nil, Dotenv: origTask.Dotenv, - Silent: origTask.Silent, + Silent: deepcopy.Scalar(origTask.Silent), Interactive: origTask.Interactive, Internal: origTask.Internal, Method: origTask.Method, @@ -113,7 +114,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err Vars: vars, Env: nil, Dotenv: templater.Replace(origTask.Dotenv, cache), - Silent: origTask.Silent, + Silent: deepcopy.Scalar(origTask.Silent), Interactive: origTask.Interactive, Internal: origTask.Internal, Method: templater.Replace(origTask.Method, cache),