diff --git a/task.go b/task.go index 36160c77..2a276ea6 100644 --- a/task.go +++ b/task.go @@ -39,6 +39,8 @@ type Executor struct { taskvars Vars watchingFiles map[string]struct{} + taskCallCount map[string]*int32 + dynamicCache map[string]string muDynamicCache sync.Mutex } @@ -58,8 +60,6 @@ type Task struct { Vars Vars Set string Env Vars - - callCount int32 } // Run runs Task @@ -74,6 +74,11 @@ func (e *Executor) Run(args ...string) error { e.Stderr = os.Stderr } + e.taskCallCount = make(map[string]*int32, len(e.Tasks)) + for k := range e.Tasks { + e.taskCallCount[k] = new(int32) + } + if e.dynamicCache == nil { e.dynamicCache = make(map[string]string, 10) } @@ -104,35 +109,44 @@ func (e *Executor) Run(args ...string) error { // RunTask runs a task by its name func (e *Executor) RunTask(ctx context.Context, call Call) error { - t, ok := e.Tasks[call.Task] + task, ok := e.Tasks[call.Task] if !ok { return &taskNotFoundError{call.Task} } - if atomic.AddInt32(&t.callCount, 1) >= MaximumTaskCall { + if atomic.AddInt32(e.taskCallCount[call.Task], 1) >= MaximumTaskCall { return &MaximumTaskCallExceededError{task: call.Task} } var err error - call.Vars, err = e.getVariables(call) + call.Vars, err = e.getVariables(task, call) if err != nil { return err } - if err := e.runDeps(ctx, call); err != nil { + t, err := task.ReplaceVariables(call.Vars) + if err != nil { + return err + } + + if err := e.runDeps(ctx, t); err != nil { return err } // FIXME: doing again, since a var may have been overriden // using the `set:` attribute of a dependecy. // Remove this when `set` (that is deprecated) be removed - call.Vars, err = e.getVariables(call) + call.Vars, err = e.getVariables(task, call) + if err != nil { + return err + } + t, err = task.ReplaceVariables(call.Vars) if err != nil { return err } if !e.Force { - upToDate, err := e.isTaskUpToDate(ctx, call) + upToDate, err := e.isTaskUpToDate(ctx, t) if err != nil { return err } @@ -143,63 +157,41 @@ func (e *Executor) RunTask(ctx context.Context, call Call) error { } for i := range t.Cmds { - if err := e.runCommand(ctx, call, i); err != nil { + if err := e.runCommand(ctx, t, call, i); err != nil { return &taskRunError{call.Task, err} } } return nil } -func (e *Executor) runDeps(ctx context.Context, call Call) error { +func (e *Executor) runDeps(ctx context.Context, t *Task) error { g, ctx := errgroup.WithContext(ctx) - t := e.Tasks[call.Task] for _, d := range t.Deps { d := d g.Go(func() error { - c, err := e.toCall(d.Task, d.Vars, call) - if err != nil { - return err - } - return e.RunTask(ctx, c) + return e.RunTask(ctx, Call{Task: d.Task, Vars: d.Vars}) }) } return g.Wait() } -func (e *Executor) isTaskUpToDate(ctx context.Context, call Call) (bool, error) { - t := e.Tasks[call.Task] - +func (e *Executor) isTaskUpToDate(ctx context.Context, t *Task) (bool, error) { if len(t.Status) > 0 { - return e.isUpToDateStatus(ctx, call) + return e.isUpToDateStatus(ctx, t) } - return e.isUpToDateTimestamp(ctx, call) + return e.isUpToDateTimestamp(ctx, t) } -func (e *Executor) isUpToDateStatus(ctx context.Context, call Call) (bool, error) { - t := e.Tasks[call.Task] - - environ, err := e.getEnviron(call) - if err != nil { - return false, err - } - dir, err := e.getTaskDir(call) - if err != nil { - return false, err - } - status, err := e.ReplaceSliceVariables(t.Status, call) - if err != nil { - return false, err - } - - for _, s := range status { - err = execext.RunCommand(&execext.RunCommandOptions{ +func (e *Executor) isUpToDateStatus(ctx context.Context, t *Task) (bool, error) { + for _, s := range t.Status { + err := execext.RunCommand(&execext.RunCommandOptions{ Context: ctx, Command: s, - Dir: dir, - Env: environ, + Dir: e.getTaskDir(t), + Env: e.getEnviron(t), }) if err != nil { return false, nil @@ -208,79 +200,46 @@ func (e *Executor) isUpToDateStatus(ctx context.Context, call Call) (bool, error return true, nil } -func (e *Executor) isUpToDateTimestamp(ctx context.Context, call Call) (bool, error) { - t := e.Tasks[call.Task] - +func (e *Executor) isUpToDateTimestamp(ctx context.Context, t *Task) (bool, error) { if len(t.Sources) == 0 || len(t.Generates) == 0 { return false, nil } - dir, err := e.getTaskDir(call) - if err != nil { - return false, err - } + dir := e.getTaskDir(t) - sources, err := e.ReplaceSliceVariables(t.Sources, call) - if err != nil { - return false, err - } - generates, err := e.ReplaceSliceVariables(t.Generates, call) - if err != nil { - return false, err - } - - sourcesMaxTime, err := getPatternsMaxTime(dir, sources) + sourcesMaxTime, err := getPatternsMaxTime(dir, t.Sources) if err != nil || sourcesMaxTime.IsZero() { return false, nil } - generatesMinTime, err := getPatternsMinTime(dir, generates) + generatesMinTime, err := getPatternsMinTime(dir, t.Generates) if err != nil || generatesMinTime.IsZero() { return false, nil } return !generatesMinTime.Before(sourcesMaxTime), nil } -func (e *Executor) runCommand(ctx context.Context, call Call, i int) error { - t := e.Tasks[call.Task] +func (e *Executor) runCommand(ctx context.Context, t *Task, call Call, i int) error { cmd := t.Cmds[i] if cmd.Cmd == "" { - c, err := e.toCall(cmd.Task, cmd.Vars, call) - if err != nil { - return err - } - return e.RunTask(ctx, c) + return e.RunTask(ctx, Call{Task: cmd.Task, Vars: cmd.Vars}) } - c, err := e.ReplaceVariables(cmd.Cmd, call) - if err != nil { - return err - } - - dir, err := e.getTaskDir(call) - if err != nil { - return err - } - - envs, err := e.getEnviron(call) - if err != nil { - return err - } opts := &execext.RunCommandOptions{ Context: ctx, - Command: c, - Dir: dir, - Env: envs, + Command: cmd.Cmd, + Dir: e.getTaskDir(t), + Env: e.getEnviron(t), Stdin: e.Stdin, Stderr: e.Stderr, } - e.println(c) + e.println(cmd.Cmd) if t.Set != "" { var stdout bytes.Buffer opts.Stdout = &stdout - if err = execext.RunCommand(opts); err != nil { + if err := execext.RunCommand(opts); err != nil { return err } return os.Setenv(t.Set, strings.TrimSpace(stdout.String())) @@ -290,53 +249,21 @@ func (e *Executor) runCommand(ctx context.Context, call Call, i int) error { return execext.RunCommand(opts) } -func (e *Executor) toCall(task string, vs Vars, call Call) (Call, error) { - task, err := e.ReplaceVariables(task, call) - if err != nil { - return Call{}, err +func (e *Executor) getTaskDir(t *Task) string { + if filepath.IsAbs(t.Dir) { + return t.Dir } - - newVars := make(Vars, len(vs)) - for k, v := range vs { - static, err := e.ReplaceVariables(v.Static, call) - if err != nil { - return Call{}, err - } - sh, err := e.ReplaceVariables(v.Sh, call) - if err != nil { - return Call{}, err - } - newVars[k] = Var{Static: static, Sh: sh} - } - return Call{Task: task, Vars: newVars}, nil + return filepath.Join(e.Dir, t.Dir) } -func (e *Executor) getTaskDir(call Call) (string, error) { - t := e.Tasks[call.Task] - - taskDir, err := e.ReplaceVariables(t.Dir, call) - if err != nil { - return "", err - } - - return filepath.Join(e.Dir, taskDir), nil -} - -func (e *Executor) getEnviron(call Call) ([]string, error) { - t := e.Tasks[call.Task] - +func (e *Executor) getEnviron(t *Task) []string { if t.Env == nil { - return nil, nil + return nil } envs := os.Environ() - for k, v := range t.Env { - env, err := e.ReplaceVariables(fmt.Sprintf("%s=%s", k, v), call) - if err != nil { - return nil, err - } - envs = append(envs, env) + envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } - return envs, nil + return envs } diff --git a/variables.go b/variables.go index 3bd60dfc..013c1f3e 100644 --- a/variables.go +++ b/variables.go @@ -95,82 +95,36 @@ func init() { } } -// ReplaceVariables writes vars into initial string -func (e *Executor) ReplaceVariables(initial string, call Call) (string, error) { - if initial == "" { - return initial, nil - } - - templ, err := template.New("").Funcs(templateFuncs).Parse(initial) - if err != nil { - return "", err - } - - var b bytes.Buffer - if err = templ.Execute(&b, call.Vars.toStringMap()); err != nil { - return "", err - } - return b.String(), nil -} - -// ReplaceSliceVariables writes vars into initial string slice -func (e *Executor) ReplaceSliceVariables(initials []string, call Call) ([]string, error) { - result := make([]string, len(initials)) - for i, s := range initials { - var err error - result[i], err = e.ReplaceVariables(s, call) - if err != nil { - return nil, err - } - } - return result, nil -} - -func (e *Executor) getVariables(call Call) (Vars, error) { - t := e.Tasks[call.Task] - +func (e *Executor) getVariables(t *Task, call Call) (Vars, error) { result := make(Vars, len(t.Vars)+len(e.taskvars)+len(call.Vars)) - merge := func(vars Vars, runTemplate bool) error { - for k, v := range vars { - if runTemplate { - var err error - v.Static, err = e.ReplaceVariables(v.Static, call) - if err != nil { - return err - } - v.Sh, err = e.ReplaceVariables(v.Sh, call) - if err != nil { - return err - } - } + merge := func(vars Vars) error { + for k, v := range vars { v, err := e.handleDynamicVariableContent(v) if err != nil { return err } - result[k] = Var{Static: v} } return nil } - if err := merge(e.taskvars, false); err != nil { + if err := merge(e.taskvars); err != nil { return nil, err } - if err := merge(t.Vars, true); err != nil { + if err := merge(t.Vars); err != nil { return nil, err } - if err := merge(getEnvironmentVariables(), false); err != nil { + if err := merge(getEnvironmentVariables()); err != nil { return nil, err } - if err := merge(call.Vars, false); err != nil { + if err := merge(call.Vars); err != nil { return nil, err } return result, nil } -// GetEnvironmentVariables returns environment variables as map func getEnvironmentVariables() Vars { var ( env = os.Environ() @@ -217,3 +171,102 @@ func (e *Executor) handleDynamicVariableContent(v Var) (string, error) { e.dynamicCache[v.Sh] = result return result, nil } + +// ReplaceVariables returns a copy of a task, but replacing +// variables in almost all properties using the Go template package +func (t *Task) ReplaceVariables(vars Vars) (*Task, error) { + r := varReplacer{vars: vars} + new := Task{ + Desc: r.replace(t.Desc), + Sources: r.replaceSlice(t.Sources), + Generates: r.replaceSlice(t.Generates), + Status: r.replaceSlice(t.Status), + Dir: r.replace(t.Dir), + Vars: r.replaceVars(t.Vars), + Set: r.replace(t.Set), + Env: r.replaceVars(t.Env), + } + + if len(t.Cmds) > 0 { + new.Cmds = make([]*Cmd, len(t.Cmds)) + for i, cmd := range t.Cmds { + new.Cmds[i] = &Cmd{ + Task: r.replace(cmd.Task), + Cmd: r.replace(cmd.Cmd), + Vars: r.replaceVars(cmd.Vars), + } + + } + } + if len(t.Deps) > 0 { + new.Deps = make([]*Dep, len(t.Deps)) + for i, dep := range t.Deps { + new.Deps[i] = &Dep{ + Task: r.replace(dep.Task), + Vars: r.replaceVars(dep.Vars), + } + } + } + + return &new, r.err +} + +// varReplacer is a help struct that allow us to call "replaceX" funcs multiple +// times, without having to check for error each time. +// The first error that happen will be assigned to r.err, and consecutive +// calls to funcs will just return the zero value. +type varReplacer struct { + vars Vars + strMap map[string]string + err error +} + +func (r *varReplacer) replace(str string) string { + if r.err != nil || str == "" { + return "" + } + + templ, err := template.New("").Funcs(templateFuncs).Parse(str) + if err != nil { + r.err = err + return "" + } + + if r.strMap == nil { + r.strMap = r.vars.toStringMap() + } + + var b bytes.Buffer + if err = templ.Execute(&b, r.strMap); err != nil { + r.err = err + return "" + } + return b.String() +} + +func (r *varReplacer) replaceSlice(strs []string) []string { + if r.err != nil || len(strs) == 0 { + return nil + } + + new := make([]string, len(strs)) + for i, str := range strs { + new[i] = r.replace(str) + } + return new +} + +func (r *varReplacer) replaceVars(vars Vars) Vars { + if r.err != nil || len(vars) == 0 { + return nil + } + + new := make(Vars, len(vars)) + for k, v := range vars { + new[k] = Var{ + Static: r.replace(v.Static), + Sh: r.replace(v.Sh), + } + } + return new +}