mirror of
https://github.com/go-task/task.git
synced 2025-08-08 22:36:57 +02:00
add custom Cmd and Dep types
This commit is contained in:
64
command.go
Normal file
64
command.go
Normal file
@@ -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
|
||||
}
|
54
command_test.go
Normal file
54
command_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
@@ -13,7 +13,7 @@ func (e *Executor) HasCyclicDep() bool {
|
||||
defer delete(visits, name)
|
||||
|
||||
for _, d := range t.Deps {
|
||||
if !checkCyclicDep(d, e.Tasks[d]) {
|
||||
if !checkCyclicDep(d.Task, e.Tasks[d.Task]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@@ -10,10 +10,10 @@ func TestCyclicDepCheck(t *testing.T) {
|
||||
isCyclic := &task.Executor{
|
||||
Tasks: task.Tasks{
|
||||
"task-a": &task.Task{
|
||||
Deps: []string{"task-b"},
|
||||
Deps: []*task.Dep{&task.Dep{Task: "task-b"}},
|
||||
},
|
||||
"task-b": &task.Task{
|
||||
Deps: []string{"task-a"},
|
||||
Deps: []*task.Dep{&task.Dep{Task: "task-a"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -25,10 +25,10 @@ func TestCyclicDepCheck(t *testing.T) {
|
||||
isNotCyclic := &task.Executor{
|
||||
Tasks: task.Tasks{
|
||||
"task-a": &task.Task{
|
||||
Deps: []string{"task-c"},
|
||||
Deps: []*task.Dep{&task.Dep{Task: "task-c"}},
|
||||
},
|
||||
"task-b": &task.Task{
|
||||
Deps: []string{"task-c"},
|
||||
Deps: []*task.Dep{&task.Dep{Task: "task-c"}},
|
||||
},
|
||||
"task-c": &task.Task{},
|
||||
},
|
||||
|
81
task.go
81
task.go
@@ -38,8 +38,8 @@ type Tasks map[string]*Task
|
||||
|
||||
// Task represents a task
|
||||
type Task struct {
|
||||
Cmds []string
|
||||
Deps []string
|
||||
Cmds []*Cmd
|
||||
Deps []*Dep
|
||||
Desc string
|
||||
Sources []string
|
||||
Generates []string
|
||||
@@ -83,7 +83,7 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -91,18 +91,18 @@ func (e *Executor) Run(args ...string) error {
|
||||
}
|
||||
|
||||
// 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]
|
||||
|
||||
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 cmd.Cmd == "" {
|
||||
return e.RunTask(ctx, cmd.Task, cmd.Params)
|
||||
}
|
||||
|
||||
c, err := e.ReplaceVariables(cmd.Cmd, task, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(c, "^") {
|
||||
c = strings.TrimPrefix(c, "^")
|
||||
if err = e.RunTask(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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,14 +267,14 @@ 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
|
||||
}
|
||||
@@ -281,7 +282,7 @@ func (e *Executor) getTaskDir(name string) (string, error) {
|
||||
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
|
||||
}
|
||||
|
32
task_test.go
32
task_test.go
@@ -166,3 +166,35 @@ func TestInit(t *testing.T) {
|
||||
t.Errorf("Taskfile.yml should exists")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParams(t *testing.T) {
|
||||
const dir = "testdata/params"
|
||||
var files = []struct {
|
||||
file string
|
||||
content string
|
||||
}{
|
||||
{"hello.txt", "Hello\n"},
|
||||
{"world.txt", "World\n"},
|
||||
{"exclamation.txt", "!\n"},
|
||||
{"dep1.txt", "Dependence1\n"},
|
||||
{"dep2.txt", "Dependence2\n"},
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
_ = os.Remove(filepath.Join(dir, f.file))
|
||||
}
|
||||
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: ioutil.Discard,
|
||||
Stderr: ioutil.Discard,
|
||||
}
|
||||
assert.NoError(t, e.ReadTaskfile())
|
||||
assert.NoError(t, e.Run("default"))
|
||||
|
||||
for _, f := range files {
|
||||
content, err := ioutil.ReadFile(filepath.Join(dir, f.file))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, f.content, string(content))
|
||||
}
|
||||
}
|
||||
|
1
testdata/params/.gitignore
vendored
Normal file
1
testdata/params/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.txt
|
27
testdata/params/Taskfile.yml
vendored
Normal file
27
testdata/params/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
default:
|
||||
deps:
|
||||
- task: write-file
|
||||
params:
|
||||
CONTENT: Dependence1
|
||||
FILE: dep1.txt
|
||||
- task: write-file
|
||||
params:
|
||||
CONTENT: Dependence2
|
||||
FILE: dep2.txt
|
||||
cmds:
|
||||
- task: write-file
|
||||
params:
|
||||
CONTENT: Hello
|
||||
FILE: hello.txt
|
||||
- task: write-file
|
||||
params:
|
||||
CONTENT: World
|
||||
FILE: world.txt
|
||||
- task: write-file
|
||||
params:
|
||||
CONTENT: "!"
|
||||
FILE: exclamation.txt
|
||||
|
||||
write-file:
|
||||
cmds:
|
||||
- echo {{.CONTENT}} > {{.FILE}}
|
@@ -52,7 +52,7 @@ func (e *Executor) handleDynamicVariableContent(value string) (string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (e *Executor) getVariables(task string) (map[string]string, error) {
|
||||
func (e *Executor) getVariables(task string, params Params) (map[string]string, error) {
|
||||
t := e.Tasks[task]
|
||||
|
||||
localVariables := make(map[string]string)
|
||||
@@ -77,6 +77,15 @@ func (e *Executor) getVariables(task string) (map[string]string, error) {
|
||||
for key, value := range getEnvironmentVariables() {
|
||||
localVariables[key] = value
|
||||
}
|
||||
if params != nil {
|
||||
for k, v := range params {
|
||||
val, err := e.handleDynamicVariableContent(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
localVariables[k] = val
|
||||
}
|
||||
}
|
||||
return localVariables, nil
|
||||
}
|
||||
|
||||
@@ -109,11 +118,11 @@ func init() {
|
||||
}
|
||||
|
||||
// ReplaceSliceVariables writes vars into initial string slice
|
||||
func (e *Executor) ReplaceSliceVariables(task string, initials []string) ([]string, error) {
|
||||
func (e *Executor) ReplaceSliceVariables(initials []string, task string, params Params) ([]string, error) {
|
||||
result := make([]string, len(initials))
|
||||
for i, s := range initials {
|
||||
var err error
|
||||
result[i], err = e.ReplaceVariables(task, s)
|
||||
result[i], err = e.ReplaceVariables(s, task, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -122,8 +131,8 @@ func (e *Executor) ReplaceSliceVariables(task string, initials []string) ([]stri
|
||||
}
|
||||
|
||||
// ReplaceVariables writes vars into initial string
|
||||
func (e *Executor) ReplaceVariables(task, initial string) (string, error) {
|
||||
vars, err := e.getVariables(task)
|
||||
func (e *Executor) ReplaceVariables(initial, task string, params Params) (string, error) {
|
||||
vars, err := e.getVariables(task, params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
10
watch.go
10
watch.go
@@ -15,7 +15,7 @@ func (e *Executor) watchTasks(args ...string) error {
|
||||
|
||||
// run tasks on init
|
||||
for _, a := range args {
|
||||
if err := e.RunTask(context.Background(), a); err != nil {
|
||||
if err := e.RunTask(context.Background(), a, nil); err != nil {
|
||||
e.println(err)
|
||||
break
|
||||
}
|
||||
@@ -41,7 +41,7 @@ loop:
|
||||
select {
|
||||
case <-watcher.Events:
|
||||
for _, a := range args {
|
||||
if err := e.RunTask(context.Background(), a); err != nil {
|
||||
if err := e.RunTask(context.Background(), a, nil); err != nil {
|
||||
e.println(err)
|
||||
continue loop
|
||||
}
|
||||
@@ -68,7 +68,11 @@ func (e *Executor) registerWatchedFiles(w *fsnotify.Watcher, args []string) erro
|
||||
if !ok {
|
||||
return &taskNotFoundError{a}
|
||||
}
|
||||
if err := e.registerWatchedFiles(w, task.Deps); err != nil {
|
||||
deps := make([]string, len(task.Deps))
|
||||
for i, d := range task.Deps {
|
||||
deps[i] = d.Task
|
||||
}
|
||||
if err := e.registerWatchedFiles(w, deps); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range task.Sources {
|
||||
|
Reference in New Issue
Block a user