package v2 import ( "bytes" "context" "fmt" "strings" "sync" "github.com/go-task/task/v3/internal/compiler" "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/taskfile" ) var _ compiler.Compiler = &CompilerV2{} type CompilerV2 struct { Dir string Taskvars *taskfile.Vars TaskfileVars *taskfile.Vars Expansions int Logger *logger.Logger dynamicCache map[string]string muDynamicCache sync.Mutex } func (c *CompilerV2) GetTaskfileVariables() (*taskfile.Vars, error) { return &taskfile.Vars{}, nil } // FastGetVariables is a no-op on v2 func (c *CompilerV2) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) { return c.GetVariables(t, call) } // GetVariables returns fully resolved variables following the priority order: // 1. Task variables // 2. Call variables // 3. Taskfile variables // 4. Taskvars file variables // 5. Environment variables func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) { vr := varResolver{ c: c, vars: compiler.GetEnviron(), } vr.vars.Set("TASK", taskfile.Var{Static: t.Task}) for _, vars := range []*taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} { for i := 0; i < c.Expansions; i++ { vr.merge(vars) } } return vr.vars, vr.err } type varResolver struct { c *CompilerV2 vars *taskfile.Vars err error } func (vr *varResolver) merge(vars *taskfile.Vars) { if vr.err != nil { return } tr := templater.Templater{Vars: vr.vars} _ = vars.Range(func(k string, v taskfile.Var) error { v = taskfile.Var{ Static: tr.Replace(v.Static), Sh: tr.Replace(v.Sh), } static, err := vr.c.HandleDynamicVar(v, "") if err != nil { vr.err = err return err } vr.vars.Set(k, taskfile.Var{Static: static}) return nil }) vr.err = tr.Err() } func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ string) (string, error) { if v.Static != "" || v.Sh == "" { return v.Static, nil } c.muDynamicCache.Lock() defer c.muDynamicCache.Unlock() if c.dynamicCache == nil { c.dynamicCache = make(map[string]string, 30) } if result, ok := c.dynamicCache[v.Sh]; ok { return result, nil } var stdout bytes.Buffer opts := &execext.RunCommandOptions{ Command: v.Sh, Stdout: &stdout, Stderr: c.Logger.Stderr, } if err := execext.RunCommand(context.Background(), opts); err != nil { return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err) } // Trim a single trailing newline from the result to make most command // output easier to use in shell commands. result := strings.TrimSuffix(stdout.String(), "\n") c.dynamicCache[v.Sh] = result c.Logger.VerboseErrf(logger.Magenta, `task: dynamic variable: '%s' result: '%s'`, v.Sh, result) return result, nil } // ResetCache clear the dymanic variables cache func (c *CompilerV2) ResetCache() { c.muDynamicCache.Lock() defer c.muDynamicCache.Unlock() c.dynamicCache = nil }