1
0
mirror of https://github.com/go-task/task.git synced 2025-01-04 03:48:02 +02:00

replace variables in a task once, instead of mixing these calls with unrelated code

this is the first big step of #45

now t.ReplaceVariable will return a copy of the task, but with variables
replaced

now we don't need to replace variables everywhere in the code, and will
make other refactorings easier
This commit is contained in:
Andrey Nering 2017-07-16 16:09:55 -03:00
parent 726130be09
commit 72250b32d3
2 changed files with 158 additions and 178 deletions

177
task.go
View File

@ -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
}

View File

@ -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
}