-
-
Notifications
You must be signed in to change notification settings - Fork 640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add task parameters #32
Changes from 1 commit
196d3cb
b8fe8d4
e1fc3aa
06d80e9
313d708
a52a66e
01b9bf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package task | ||
|
||
import ( | ||
"errors" | ||
"strings" | ||
) | ||
|
||
type Params map[string]string | ||
|
||
type Cmd struct { | ||
Cmd string | ||
Task string | ||
Params Params | ||
} | ||
|
||
type Dep struct { | ||
Task string | ||
Params Params | ||
} | ||
|
||
var ( | ||
ErrCantUnmarshalCmd = errors.New("task: can't unmarshal cmd value") | ||
ErrCantUnmarshalDep = errors.New("task: can't unmarshal dep value") | ||
) | ||
|
||
func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||
var cmd string | ||
if err := unmarshal(&cmd); err == nil { | ||
if strings.HasPrefix(cmd, "^") { | ||
c.Task = strings.TrimPrefix(cmd, "^") | ||
} else { | ||
c.Cmd = cmd | ||
} | ||
return nil | ||
} | ||
var taskCall struct { | ||
Task string | ||
Params Params | ||
} | ||
if err := unmarshal(&taskCall); err == nil { | ||
c.Task = taskCall.Task | ||
c.Params = taskCall.Params | ||
return nil | ||
} | ||
return ErrCantUnmarshalCmd | ||
} | ||
|
||
func (d *Dep) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||
var task string | ||
if err := unmarshal(&task); err == nil { | ||
d.Task = task | ||
return nil | ||
} | ||
var taskCall struct { | ||
Task string | ||
Params Params | ||
} | ||
if err := unmarshal(&taskCall); err == nil { | ||
d.Task = taskCall.Task | ||
d.Params = taskCall.Params | ||
return nil | ||
} | ||
return ErrCantUnmarshalDep | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package task_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/go-task/task" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
func TestCmdParse(t *testing.T) { | ||
const ( | ||
yamlCmd = `echo "a string command"` | ||
yamlDep = `"task-name"` | ||
yamlTaskCall = ` | ||
task: another-task | ||
params: | ||
PARAM1: VALUE1 | ||
PARAM2: VALUE2 | ||
` | ||
) | ||
tests := []struct { | ||
content string | ||
v interface{} | ||
expected interface{} | ||
}{ | ||
{ | ||
yamlCmd, | ||
&task.Cmd{}, | ||
&task.Cmd{Cmd: `echo "a string command"`}, | ||
}, | ||
{ | ||
yamlTaskCall, | ||
&task.Cmd{}, | ||
&task.Cmd{Task: "another-task", Params: task.Params{"PARAM1": "VALUE1", "PARAM2": "VALUE2"}}, | ||
}, | ||
{ | ||
yamlDep, | ||
&task.Dep{}, | ||
&task.Dep{Task: "task-name"}, | ||
}, | ||
{ | ||
yamlTaskCall, | ||
&task.Dep{}, | ||
&task.Dep{Task: "another-task", Params: task.Params{"PARAM1": "VALUE1", "PARAM2": "VALUE2"}}, | ||
}, | ||
} | ||
for _, test := range tests { | ||
err := yaml.Unmarshal([]byte(test.content), test.v) | ||
assert.NoError(t, err) | ||
assert.Equal(t, test.expected, test.v) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,8 +38,8 @@ type Tasks map[string]*Task | |
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless the semantics of a nil task is needed, or unless you often need to retrieve a task pointer, you could concider to use map[string]Task. It will have some consequences for how you can write to the tasks though, as this will then fetch out a copy: t, ok := e.tasks[name] |
||
// Task represents a task | ||
type Task struct { | ||
Cmds []string | ||
Deps []string | ||
Cmds []*Cmd | ||
Deps []*Dep | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless there is any case where it makes sense to have Why? Code wise it's usually cleaner. As an FYI: Memory allocation / performance wise, it's usually better to have all the actual structs within the slice, so that all the memory for these objects will be allocated in one big slice of bytes and are more likely to be accessed faster in for-loops due to pipelining. Also there is less memory fractions and less garbage-collection work to do. This only really matters in tight loops for huge programs though; it's not going to matter for task. |
||
Desc string | ||
Sources []string | ||
Generates []string | ||
|
@@ -83,26 +83,26 @@ func (e *Executor) Run(args ...string) error { | |
} | ||
|
||
for _, a := range args { | ||
if err := e.RunTask(context.Background(), a); err != nil { | ||
if err := e.RunTask(context.Background(), a, nil); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// RunTask runs a task by its name | ||
func (e *Executor) RunTask(ctx context.Context, name string) error { | ||
func (e *Executor) RunTask(ctx context.Context, name string, params Params) error { | ||
t, ok := e.Tasks[name] | ||
if !ok { | ||
return &taskNotFoundError{name} | ||
} | ||
|
||
if err := e.runDeps(ctx, name); err != nil { | ||
if err := e.runDeps(ctx, name, params); err != nil { | ||
return err | ||
} | ||
|
||
if !e.Force { | ||
upToDate, err := e.isTaskUpToDate(ctx, name) | ||
upToDate, err := e.isTaskUpToDate(ctx, name, params) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -113,27 +113,27 @@ func (e *Executor) RunTask(ctx context.Context, name string) error { | |
} | ||
|
||
for i := range t.Cmds { | ||
if err := e.runCommand(ctx, name, i); err != nil { | ||
if err := e.runCommand(ctx, name, i, params); err != nil { | ||
return &taskRunError{name, err} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (e *Executor) runDeps(ctx context.Context, task string) error { | ||
func (e *Executor) runDeps(ctx context.Context, task string, params Params) error { | ||
g, ctx := errgroup.WithContext(ctx) | ||
t := e.Tasks[task] | ||
|
||
for _, d := range t.Deps { | ||
dep := d | ||
d := d | ||
|
||
g.Go(func() error { | ||
dep, err := e.ReplaceVariables(task, dep) | ||
dep, err := e.ReplaceVariables(d.Task, task, params) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err = e.RunTask(ctx, dep); err != nil { | ||
if err = e.RunTask(ctx, dep, d.Params); err != nil { | ||
return err | ||
} | ||
return nil | ||
|
@@ -146,28 +146,32 @@ func (e *Executor) runDeps(ctx context.Context, task string) error { | |
return nil | ||
} | ||
|
||
func (e *Executor) isTaskUpToDate(ctx context.Context, task string) (bool, error) { | ||
func (e *Executor) isTaskUpToDate(ctx context.Context, task string, params Params) (bool, error) { | ||
t := e.Tasks[task] | ||
|
||
if len(t.Status) > 0 { | ||
return e.isUpToDateStatus(ctx, task) | ||
return e.isUpToDateStatus(ctx, task, params) | ||
} | ||
return e.isUpToDateTimestamp(ctx, task) | ||
return e.isUpToDateTimestamp(ctx, task, params) | ||
} | ||
|
||
func (e *Executor) isUpToDateStatus(ctx context.Context, task string) (bool, error) { | ||
func (e *Executor) isUpToDateStatus(ctx context.Context, task string, params Params) (bool, error) { | ||
t := e.Tasks[task] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's considered clean code (by some author called Robert C. Martin) to try to keep the number of positional parameters in a function to a minimum. Often just 1, or maybe 2, or in worst case 3. Generally I think you here handle the trade-offs well, but I will give some tips that you could consider if you feel it's necessary, or otherwise feel free to ignore for now. Since all the helper methods that need params, also needs the task name, one way of reducing the number of positional parameters, could be to use the Something similar could be done for when you need to reference a Cmd, where you basically use the same struct but include and type TaskReference struct{
Task string
Params Params
}
// Task returns a copy of the task in e with Vars updated with values from Params,
// or an error if the task don't exist.
func (tr *TaskReference) Task(e *Executor) (*Task, error) {
}
type CmdReference struct{
TaskReference
Cmd int
}
// Cmd returns a copy of the command in e with all template variables replaces by
// intended values.
func (tr *CmdReference) Cmd(e *Executor) (*Cmd, error) {
} |
||
|
||
environ, err := e.getEnviron(task) | ||
environ, err := e.getEnviron(task, params) | ||
if err != nil { | ||
return false, err | ||
} | ||
dir, err := e.getTaskDir(task) | ||
dir, err := e.getTaskDir(task, params) | ||
if err != nil { | ||
return false, err | ||
} | ||
status, err := e.ReplaceSliceVariables(t.Status, task, params) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
for _, s := range t.Status { | ||
for _, s := range status { | ||
err = execext.RunCommand(&execext.RunCommandOptions{ | ||
Context: ctx, | ||
Command: s, | ||
|
@@ -181,23 +185,23 @@ func (e *Executor) isUpToDateStatus(ctx context.Context, task string) (bool, err | |
return true, nil | ||
} | ||
|
||
func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string) (bool, error) { | ||
func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string, params Params) (bool, error) { | ||
t := e.Tasks[task] | ||
|
||
if len(t.Sources) == 0 || len(t.Generates) == 0 { | ||
return false, nil | ||
} | ||
|
||
dir, err := e.getTaskDir(task) | ||
dir, err := e.getTaskDir(task, params) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
sources, err := e.ReplaceSliceVariables(task, t.Sources) | ||
sources, err := e.ReplaceSliceVariables(t.Sources, task, params) | ||
if err != nil { | ||
return false, err | ||
} | ||
generates, err := e.ReplaceSliceVariables(task, t.Generates) | ||
generates, err := e.ReplaceSliceVariables(t.Generates, task, params) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
@@ -215,28 +219,25 @@ func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string) (bool, | |
return generatesMinTime.After(sourcesMaxTime), nil | ||
} | ||
|
||
func (e *Executor) runCommand(ctx context.Context, task string, i int) error { | ||
func (e *Executor) runCommand(ctx context.Context, task string, i int, params Params) error { | ||
t := e.Tasks[task] | ||
cmd := t.Cmds[i] | ||
|
||
c, err := e.ReplaceVariables(task, t.Cmds[i]) | ||
if err != nil { | ||
return err | ||
if cmd.Cmd == "" { | ||
return e.RunTask(ctx, cmd.Task, cmd.Params) | ||
} | ||
|
||
if strings.HasPrefix(c, "^") { | ||
c = strings.TrimPrefix(c, "^") | ||
if err = e.RunTask(ctx, c); err != nil { | ||
return err | ||
} | ||
return nil | ||
c, err := e.ReplaceVariables(cmd.Cmd, task, params) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
dir, err := e.getTaskDir(task) | ||
dir, err := e.getTaskDir(task, params) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
envs, err := e.getEnviron(task) | ||
envs, err := e.getEnviron(task, params) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -266,22 +267,22 @@ func (e *Executor) runCommand(ctx context.Context, task string, i int) error { | |
return nil | ||
} | ||
|
||
func (e *Executor) getTaskDir(name string) (string, error) { | ||
t := e.Tasks[name] | ||
func (e *Executor) getTaskDir(task string, params Params) (string, error) { | ||
t := e.Tasks[task] | ||
|
||
exeDir, err := e.ReplaceVariables(name, e.Dir) | ||
exeDir, err := e.ReplaceVariables(e.Dir, task, params) | ||
if err != nil { | ||
return "", err | ||
} | ||
taskDir, err := e.ReplaceVariables(name, t.Dir) | ||
taskDir, err := e.ReplaceVariables(t.Dir, task, params) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return filepath.Join(exeDir, taskDir), nil | ||
} | ||
|
||
func (e *Executor) getEnviron(task string) ([]string, error) { | ||
func (e *Executor) getEnviron(task string, params Params) ([]string, error) { | ||
t := e.Tasks[task] | ||
|
||
if t.Env == nil { | ||
|
@@ -291,7 +292,7 @@ func (e *Executor) getEnviron(task string) ([]string, error) { | |
envs := os.Environ() | ||
|
||
for k, v := range t.Env { | ||
env, err := e.ReplaceVariables(task, fmt.Sprintf("%s=%s", k, v)) | ||
env, err := e.ReplaceVariables(fmt.Sprintf("%s=%s", k, v), task, params) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on what's easiest to document in the README.
If you need to rename "parms" -> "vars" for the end-user only, you could probably add a struct tag here and in dep, if your yaml lib supports it: