From 020b8f5e456ef4c09708e82697c98868a038420c Mon Sep 17 00:00:00 2001 From: Timothy Rule <34501912+trulede@users.noreply.github.com> Date: Sun, 30 Nov 2025 23:16:42 +0100 Subject: [PATCH 1/2] Add posix and bash opts for status and precondition commands. --- internal/fingerprint/status.go | 19 +++++++++++++------ internal/fingerprint/task.go | 18 +++++++++++++++++- precondition.go | 9 ++++++--- status.go | 2 ++ task.go | 2 ++ 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/internal/fingerprint/status.go b/internal/fingerprint/status.go index 3a7e21f924..886d8f362e 100644 --- a/internal/fingerprint/status.go +++ b/internal/fingerprint/status.go @@ -6,25 +6,32 @@ import ( "github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/logger" + "github.com/go-task/task/v3/internal/slicesext" "github.com/go-task/task/v3/taskfile/ast" ) type StatusChecker struct { - logger *logger.Logger + logger *logger.Logger + posixOpts []string + bashOpts []string } -func NewStatusChecker(logger *logger.Logger) StatusCheckable { +func NewStatusChecker(logger *logger.Logger, posixOpts []string, bashOpts []string) StatusCheckable { return &StatusChecker{ - logger: logger, + logger: logger, + posixOpts: posixOpts, + bashOpts: bashOpts, } } func (checker *StatusChecker) IsUpToDate(ctx context.Context, t *ast.Task) (bool, error) { for _, s := range t.Status { err := execext.RunCommand(ctx, &execext.RunCommandOptions{ - Command: s, - Dir: t.Dir, - Env: env.Get(t), + Command: s, + Dir: t.Dir, + Env: env.Get(t), + PosixOpts: slicesext.UniqueJoin(checker.posixOpts, t.Set), + BashOpts: slicesext.UniqueJoin(checker.bashOpts, t.Shopt), }) if err != nil { checker.logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s\n", s, err) diff --git a/internal/fingerprint/task.go b/internal/fingerprint/task.go index 2b48e114c9..8df437ff5b 100644 --- a/internal/fingerprint/task.go +++ b/internal/fingerprint/task.go @@ -16,6 +16,8 @@ type ( logger *logger.Logger statusChecker StatusCheckable sourcesChecker SourcesCheckable + posixOpts []string + bashOpts []string } ) @@ -55,6 +57,18 @@ func WithSourcesChecker(checker SourcesCheckable) CheckerOption { } } +func WithPosixOpts(posixOpts []string) CheckerOption { + return func(config *CheckerConfig) { + config.posixOpts = posixOpts + } +} + +func WithBashOpts(bashOpts []string) CheckerOption { + return func(config *CheckerConfig) { + config.bashOpts = bashOpts + } +} + func IsTaskUpToDate( ctx context.Context, t *ast.Task, @@ -72,6 +86,8 @@ func IsTaskUpToDate( logger: nil, statusChecker: nil, sourcesChecker: nil, + posixOpts: []string{}, + bashOpts: []string{}, } // Apply functional options @@ -81,7 +97,7 @@ func IsTaskUpToDate( // If no status checker was given, set up the default one if config.statusChecker == nil { - config.statusChecker = NewStatusChecker(config.logger) + config.statusChecker = NewStatusChecker(config.logger, config.posixOpts, config.bashOpts) } // If no sources checker was given, set up the default one diff --git a/precondition.go b/precondition.go index 1f25bd37f5..c0b340d9c6 100644 --- a/precondition.go +++ b/precondition.go @@ -7,6 +7,7 @@ import ( "github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/logger" + "github.com/go-task/task/v3/internal/slicesext" "github.com/go-task/task/v3/taskfile/ast" ) @@ -16,9 +17,11 @@ var ErrPreconditionFailed = errors.New("task: precondition not met") func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *ast.Task) (bool, error) { for _, p := range t.Preconditions { err := execext.RunCommand(ctx, &execext.RunCommandOptions{ - Command: p.Sh, - Dir: t.Dir, - Env: env.Get(t), + Command: p.Sh, + Dir: t.Dir, + Env: env.Get(t), + PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set), + BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt), }) if err != nil { if !errors.Is(err, context.Canceled) { diff --git a/status.go b/status.go index ae40f5ba5f..806238f547 100644 --- a/status.go +++ b/status.go @@ -30,6 +30,8 @@ func (e *Executor) Status(ctx context.Context, calls ...*Call) error { fingerprint.WithTempDir(e.TempDir.Fingerprint), fingerprint.WithDry(e.Dry), fingerprint.WithLogger(e.Logger), + fingerprint.WithPosixOpts(e.Taskfile.Set), + fingerprint.WithBashOpts(e.Taskfile.Shopt), ) if err != nil { return err diff --git a/task.go b/task.go index 54cda92762..0877c93716 100644 --- a/task.go +++ b/task.go @@ -231,6 +231,8 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error { fingerprint.WithTempDir(e.TempDir.Fingerprint), fingerprint.WithDry(e.Dry), fingerprint.WithLogger(e.Logger), + fingerprint.WithPosixOpts(e.Taskfile.Set), + fingerprint.WithBashOpts(e.Taskfile.Shopt), ) if err != nil { return err From dc9cfcd173b66bedcf3eef3bac51e2fd8f297b19 Mon Sep 17 00:00:00 2001 From: Timothy Rule <34501912+trulede@users.noreply.github.com> Date: Sun, 8 Feb 2026 10:45:00 +0100 Subject: [PATCH 2/2] Update handling for dynamic variables too. --- compiler.go | 18 +++++++++++------- task.go | 16 ++++++++++------ variables.go | 5 ++++- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/compiler.go b/compiler.go index 311fd58423..7f51d5f37b 100644 --- a/compiler.go +++ b/compiler.go @@ -81,7 +81,9 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (* return nil } // If the variable is dynamic, we need to resolve it first - static, err := c.HandleDynamicVar(newVar, dir, env.GetFromVars(result)) + + static, err := c.HandleDynamicVar(newVar, dir, env.GetFromVars(result), + []string{}, []string{}) if err != nil { return err } @@ -145,7 +147,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (* return result, nil } -func (c *Compiler) HandleDynamicVar(v ast.Var, dir string, e []string) (string, error) { +func (c *Compiler) HandleDynamicVar(v ast.Var, dir string, e []string, posixOpts []string, bashOpts []string) (string, error) { c.muDynamicCache.Lock() defer c.muDynamicCache.Unlock() @@ -168,11 +170,13 @@ func (c *Compiler) HandleDynamicVar(v ast.Var, dir string, e []string) (string, var stdout bytes.Buffer opts := &execext.RunCommandOptions{ - Command: *v.Sh, - Dir: dir, - Stdout: &stdout, - Stderr: c.Logger.Stderr, - Env: e, + Command: *v.Sh, + Dir: dir, + Stdout: &stdout, + Stderr: c.Logger.Stderr, + Env: e, + PosixOpts: posixOpts, + BashOpts: bashOpts, } if err := execext.RunCommand(context.Background(), opts); err != nil { return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err) diff --git a/task.go b/task.go index 0877c93716..cb7ec74dee 100644 --- a/task.go +++ b/task.go @@ -164,9 +164,11 @@ func (e *Executor) RunTask(ctx context.Context, call *Call) error { // Check if condition after CompiledTask so dynamic variables are resolved if strings.TrimSpace(t.If) != "" { if err := execext.RunCommand(ctx, &execext.RunCommandOptions{ - Command: t.If, - Dir: t.Dir, - Env: env.Get(t), + Command: t.If, + Dir: t.Dir, + Env: env.Get(t), + PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set), + BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt), }); err != nil { e.Logger.VerboseOutf(logger.Yellow, "task: if condition not met - skipped: %q\n", call.Task) return nil @@ -367,9 +369,11 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in // 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), + Command: cmd.If, + Dir: t.Dir, + Env: env.Get(t), + PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set, cmd.Set), + BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt, cmd.Shopt), }); err != nil { e.Logger.VerboseOutf(logger.Yellow, "task: [%s] if condition not met - skipped\n", t.Name()) return nil diff --git a/variables.go b/variables.go index c10bfcd576..2487aa72c1 100644 --- a/variables.go +++ b/variables.go @@ -15,6 +15,7 @@ import ( "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/fingerprint" + "github.com/go-task/task/v3/internal/slicesext" "github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/taskfile/ast" ) @@ -173,7 +174,9 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err new.Env.Set(k, ast.Var{Value: v.Value}) continue } - static, err := e.Compiler.HandleDynamicVar(v, new.Dir, env.GetFromVars(new.Env)) + static, err := e.Compiler.HandleDynamicVar(v, new.Dir, env.GetFromVars(new.Env), + slicesext.UniqueJoin(e.Taskfile.Set, origTask.Set), + slicesext.UniqueJoin(e.Taskfile.Shopt, origTask.Shopt)) if err != nil { return nil, err }