mirror of
https://github.com/go-task/task.git
synced 2025-06-06 23:46:46 +02:00
commit
cb72c404f5
53
README.md
53
README.md
@ -39,9 +39,7 @@ your `PATH`. DEB and RPM packages are also available.
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Create a file called `Taskfile.yml` in the root of the project.
|
Create a file called `Taskfile.yml` in the root of the project.
|
||||||
(`Taskfile.toml` and `Taskfile.json` are also supported, but YAML is used in
|
The `cmds` attribute should contains the commands of a task:
|
||||||
the documentation). The `cmds` attribute should contains the commands of a
|
|
||||||
task:
|
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
build:
|
build:
|
||||||
@ -167,22 +165,53 @@ The above will fail with the message: "cyclic dependency detected".
|
|||||||
|
|
||||||
When a task has many dependencies, they are executed concurrently. This will
|
When a task has many dependencies, they are executed concurrently. This will
|
||||||
often result in a faster build pipeline. But in some situations you may need
|
often result in a faster build pipeline. But in some situations you may need
|
||||||
to call other tasks serially. For this just prefix a command with `^`:
|
to call other tasks serially. In this case, just use the following syntax:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
main-task:
|
||||||
|
cmds:
|
||||||
|
- task: task-to-be-called
|
||||||
|
- task: another-task
|
||||||
|
- echo "Both done"
|
||||||
|
|
||||||
|
task-to-be-called:
|
||||||
|
cmds:
|
||||||
|
- echo "Task to be called"
|
||||||
|
|
||||||
|
another-task:
|
||||||
|
cmds:
|
||||||
|
- echo "Another task"
|
||||||
|
```
|
||||||
|
|
||||||
|
Overriding variables in the called task is as simple as informing `vars`
|
||||||
|
attribute:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
main-task:
|
||||||
|
cmds:
|
||||||
|
- task: write-file
|
||||||
|
vars: {FILE: "hello.txt", CONTENT: "Hello!"}
|
||||||
|
- task: write-file
|
||||||
|
vars: {FILE: "world.txt", CONTENT: "World!"}
|
||||||
|
|
||||||
|
write-file:
|
||||||
|
cmds:
|
||||||
|
- echo "{{.CONTENT}}" > {{.FILE}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above syntax is also supported in `deps`.
|
||||||
|
|
||||||
|
> NOTE: It's also possible to call a task without any param prefixing it
|
||||||
|
with `^`, but this syntax is deprecaded:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
a-task:
|
a-task:
|
||||||
cmds:
|
cmds:
|
||||||
- ^another-task
|
- ^another-task
|
||||||
- ^even-another-task
|
|
||||||
- echo "Both done"
|
|
||||||
|
|
||||||
another-task:
|
another-task:
|
||||||
cmds:
|
cmds:
|
||||||
- ...
|
- echo "Another task"
|
||||||
|
|
||||||
even-another-task:
|
|
||||||
cmds:
|
|
||||||
- ...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Prevent unnecessary work
|
### Prevent unnecessary work
|
||||||
@ -256,7 +285,7 @@ setvar:
|
|||||||
The above sample saves the path into a new variable which is then again echoed.
|
The above sample saves the path into a new variable which is then again echoed.
|
||||||
|
|
||||||
You can use environment variables, task level variables and a file called
|
You can use environment variables, task level variables and a file called
|
||||||
`Taskvars.yml` (or `Taskvars.toml` or `Taskvars.json`) as source of variables.
|
`Taskvars.yml` as source of variables.
|
||||||
|
|
||||||
They are evaluated in the following order:
|
They are evaluated in the following order:
|
||||||
|
|
||||||
|
68
command.go
Normal file
68
command.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cmd is a task command
|
||||||
|
type Cmd struct {
|
||||||
|
Cmd string
|
||||||
|
Task string
|
||||||
|
Vars Vars
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dep is a task dependency
|
||||||
|
type Dep struct {
|
||||||
|
Task string
|
||||||
|
Vars Vars
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCantUnmarshalCmd is returned for invalid command YAML
|
||||||
|
ErrCantUnmarshalCmd = errors.New("task: can't unmarshal cmd value")
|
||||||
|
// ErrCantUnmarshalDep is returned for invalid dependency YAML
|
||||||
|
ErrCantUnmarshalDep = errors.New("task: can't unmarshal dep value")
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||||
|
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
|
||||||
|
Vars Vars
|
||||||
|
}
|
||||||
|
if err := unmarshal(&taskCall); err == nil {
|
||||||
|
c.Task = taskCall.Task
|
||||||
|
c.Vars = taskCall.Vars
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrCantUnmarshalCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaler interface
|
||||||
|
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
|
||||||
|
Vars Vars
|
||||||
|
}
|
||||||
|
if err := unmarshal(&taskCall); err == nil {
|
||||||
|
d.Task = taskCall.Task
|
||||||
|
d.Vars = taskCall.Vars
|
||||||
|
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
|
||||||
|
vars:
|
||||||
|
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", Vars: task.Vars{"PARAM1": "VALUE1", "PARAM2": "VALUE2"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
yamlDep,
|
||||||
|
&task.Dep{},
|
||||||
|
&task.Dep{Task: "task-name"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
yamlTaskCall,
|
||||||
|
&task.Dep{},
|
||||||
|
&task.Dep{Task: "another-task", Vars: task.Vars{"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)
|
defer delete(visits, name)
|
||||||
|
|
||||||
for _, d := range t.Deps {
|
for _, d := range t.Deps {
|
||||||
if !checkCyclicDep(d, e.Tasks[d]) {
|
if !checkCyclicDep(d.Task, e.Tasks[d.Task]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ func TestCyclicDepCheck(t *testing.T) {
|
|||||||
isCyclic := &task.Executor{
|
isCyclic := &task.Executor{
|
||||||
Tasks: task.Tasks{
|
Tasks: task.Tasks{
|
||||||
"task-a": &task.Task{
|
"task-a": &task.Task{
|
||||||
Deps: []string{"task-b"},
|
Deps: []*task.Dep{&task.Dep{Task: "task-b"}},
|
||||||
},
|
},
|
||||||
"task-b": &task.Task{
|
"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{
|
isNotCyclic := &task.Executor{
|
||||||
Tasks: task.Tasks{
|
Tasks: task.Tasks{
|
||||||
"task-a": &task.Task{
|
"task-a": &task.Task{
|
||||||
Deps: []string{"task-c"},
|
Deps: []*task.Dep{&task.Dep{Task: "task-c"}},
|
||||||
},
|
},
|
||||||
"task-b": &task.Task{
|
"task-b": &task.Task{
|
||||||
Deps: []string{"task-c"},
|
Deps: []*task.Dep{&task.Dep{Task: "task-c"}},
|
||||||
},
|
},
|
||||||
"task-c": &task.Task{},
|
"task-c": &task.Task{},
|
||||||
},
|
},
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"hello": {
|
|
||||||
"cmds": [
|
|
||||||
"echo \"I am going to write a file named 'output.txt' now.\"",
|
|
||||||
"echo \"hello\" > output.txt"
|
|
||||||
],
|
|
||||||
"generates": [
|
|
||||||
"output.txt"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
[hello]
|
|
||||||
cmds = [
|
|
||||||
"echo \"I am going to write a file named 'output.txt' now.\"",
|
|
||||||
"echo \"hello\" > output.txt"
|
|
||||||
]
|
|
||||||
generates = ["output.txt"]
|
|
@ -1,13 +1,11 @@
|
|||||||
package task
|
package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
@ -26,7 +24,6 @@ func (e *Executor) ReadTaskfile() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case taskFileNotFound:
|
case taskFileNotFound:
|
||||||
return nil
|
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -34,6 +31,9 @@ func (e *Executor) ReadTaskfile() error {
|
|||||||
if err := mergo.MapWithOverwrite(&e.Tasks, osTasks); err != nil {
|
if err := mergo.MapWithOverwrite(&e.Tasks, osTasks); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := e.readTaskvarsFile(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,11 +41,16 @@ func (e *Executor) readTaskfileData(path string) (tasks map[string]*Task, err er
|
|||||||
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
|
if b, err := ioutil.ReadFile(path + ".yml"); err == nil {
|
||||||
return tasks, yaml.Unmarshal(b, &tasks)
|
return tasks, yaml.Unmarshal(b, &tasks)
|
||||||
}
|
}
|
||||||
if b, err := ioutil.ReadFile(path + ".json"); err == nil {
|
|
||||||
return tasks, json.Unmarshal(b, &tasks)
|
|
||||||
}
|
|
||||||
if b, err := ioutil.ReadFile(path + ".toml"); err == nil {
|
|
||||||
return tasks, toml.Unmarshal(b, &tasks)
|
|
||||||
}
|
|
||||||
return nil, taskFileNotFound{path}
|
return nil, taskFileNotFound{path}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Executor) readTaskvarsFile() error {
|
||||||
|
file := filepath.Join(e.Dir, TaskvarsFilePath)
|
||||||
|
|
||||||
|
if b, err := ioutil.ReadFile(file + ".yml"); err == nil {
|
||||||
|
if err := yaml.Unmarshal(b, &e.taskvars); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
89
task.go
89
task.go
@ -30,24 +30,28 @@ type Executor struct {
|
|||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
|
taskvars Vars
|
||||||
watchingFiles map[string]struct{}
|
watchingFiles map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vars is a string[string] variables map
|
||||||
|
type Vars map[string]string
|
||||||
|
|
||||||
// Tasks representas a group of tasks
|
// Tasks representas a group of tasks
|
||||||
type Tasks map[string]*Task
|
type Tasks map[string]*Task
|
||||||
|
|
||||||
// Task represents a task
|
// Task represents a task
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Cmds []string
|
Cmds []*Cmd
|
||||||
Deps []string
|
Deps []*Dep
|
||||||
Desc string
|
Desc string
|
||||||
Sources []string
|
Sources []string
|
||||||
Generates []string
|
Generates []string
|
||||||
Status []string
|
Status []string
|
||||||
Dir string
|
Dir string
|
||||||
Vars map[string]string
|
Vars Vars
|
||||||
Set string
|
Set string
|
||||||
Env map[string]string
|
Env Vars
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs Task
|
// Run runs Task
|
||||||
@ -83,7 +87,7 @@ func (e *Executor) Run(args ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range args {
|
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 err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,18 +95,18 @@ func (e *Executor) Run(args ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunTask runs a task by its name
|
// 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, vars Vars) error {
|
||||||
t, ok := e.Tasks[name]
|
t, ok := e.Tasks[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return &taskNotFoundError{name}
|
return &taskNotFoundError{name}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := e.runDeps(ctx, name); err != nil {
|
if err := e.runDeps(ctx, name, vars); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !e.Force {
|
if !e.Force {
|
||||||
upToDate, err := e.isTaskUpToDate(ctx, name)
|
upToDate, err := e.isTaskUpToDate(ctx, name, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -113,27 +117,27 @@ func (e *Executor) RunTask(ctx context.Context, name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range t.Cmds {
|
for i := range t.Cmds {
|
||||||
if err := e.runCommand(ctx, name, i); err != nil {
|
if err := e.runCommand(ctx, name, i, vars); err != nil {
|
||||||
return &taskRunError{name, err}
|
return &taskRunError{name, err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) runDeps(ctx context.Context, task string) error {
|
func (e *Executor) runDeps(ctx context.Context, task string, vars Vars) error {
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
t := e.Tasks[task]
|
t := e.Tasks[task]
|
||||||
|
|
||||||
for _, d := range t.Deps {
|
for _, d := range t.Deps {
|
||||||
dep := d
|
d := d
|
||||||
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
dep, err := e.ReplaceVariables(task, dep)
|
dep, err := e.ReplaceVariables(d.Task, task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = e.RunTask(ctx, dep); err != nil {
|
if err = e.RunTask(ctx, dep, d.Vars); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -146,28 +150,32 @@ func (e *Executor) runDeps(ctx context.Context, task string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) isTaskUpToDate(ctx context.Context, task string) (bool, error) {
|
func (e *Executor) isTaskUpToDate(ctx context.Context, task string, vars Vars) (bool, error) {
|
||||||
t := e.Tasks[task]
|
t := e.Tasks[task]
|
||||||
|
|
||||||
if len(t.Status) > 0 {
|
if len(t.Status) > 0 {
|
||||||
return e.isUpToDateStatus(ctx, task)
|
return e.isUpToDateStatus(ctx, task, vars)
|
||||||
}
|
}
|
||||||
return e.isUpToDateTimestamp(ctx, task)
|
return e.isUpToDateTimestamp(ctx, task, vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) isUpToDateStatus(ctx context.Context, task string) (bool, error) {
|
func (e *Executor) isUpToDateStatus(ctx context.Context, task string, vars Vars) (bool, error) {
|
||||||
t := e.Tasks[task]
|
t := e.Tasks[task]
|
||||||
|
|
||||||
environ, err := e.getEnviron(task)
|
environ, err := e.getEnviron(task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
dir, err := e.getTaskDir(task)
|
dir, err := e.getTaskDir(task, vars)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
status, err := e.ReplaceSliceVariables(t.Status, task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range t.Status {
|
for _, s := range status {
|
||||||
err = execext.RunCommand(&execext.RunCommandOptions{
|
err = execext.RunCommand(&execext.RunCommandOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Command: s,
|
Command: s,
|
||||||
@ -181,23 +189,23 @@ func (e *Executor) isUpToDateStatus(ctx context.Context, task string) (bool, err
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string) (bool, error) {
|
func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string, vars Vars) (bool, error) {
|
||||||
t := e.Tasks[task]
|
t := e.Tasks[task]
|
||||||
|
|
||||||
if len(t.Sources) == 0 || len(t.Generates) == 0 {
|
if len(t.Sources) == 0 || len(t.Generates) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := e.getTaskDir(task)
|
dir, err := e.getTaskDir(task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sources, err := e.ReplaceSliceVariables(task, t.Sources)
|
sources, err := e.ReplaceSliceVariables(t.Sources, task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
generates, err := e.ReplaceSliceVariables(task, t.Generates)
|
generates, err := e.ReplaceSliceVariables(t.Generates, task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -215,28 +223,25 @@ func (e *Executor) isUpToDateTimestamp(ctx context.Context, task string) (bool,
|
|||||||
return generatesMinTime.After(sourcesMaxTime), nil
|
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, vars Vars) error {
|
||||||
t := e.Tasks[task]
|
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.Vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := e.ReplaceVariables(cmd.Cmd, task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(c, "^") {
|
dir, err := e.getTaskDir(task, vars)
|
||||||
c = strings.TrimPrefix(c, "^")
|
|
||||||
if err = e.RunTask(ctx, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := e.getTaskDir(task)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
envs, err := e.getEnviron(task)
|
envs, err := e.getEnviron(task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -266,14 +271,14 @@ func (e *Executor) runCommand(ctx context.Context, task string, i int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) getTaskDir(name string) (string, error) {
|
func (e *Executor) getTaskDir(task string, vars Vars) (string, error) {
|
||||||
t := e.Tasks[name]
|
t := e.Tasks[task]
|
||||||
|
|
||||||
exeDir, err := e.ReplaceVariables(name, e.Dir)
|
exeDir, err := e.ReplaceVariables(e.Dir, task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
taskDir, err := e.ReplaceVariables(name, t.Dir)
|
taskDir, err := e.ReplaceVariables(t.Dir, task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -281,7 +286,7 @@ func (e *Executor) getTaskDir(name string) (string, error) {
|
|||||||
return filepath.Join(exeDir, taskDir), nil
|
return filepath.Join(exeDir, taskDir), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) getEnviron(task string) ([]string, error) {
|
func (e *Executor) getEnviron(task string, vars Vars) ([]string, error) {
|
||||||
t := e.Tasks[task]
|
t := e.Tasks[task]
|
||||||
|
|
||||||
if t.Env == nil {
|
if t.Env == nil {
|
||||||
@ -291,7 +296,7 @@ func (e *Executor) getEnviron(task string) ([]string, error) {
|
|||||||
envs := os.Environ()
|
envs := os.Environ()
|
||||||
|
|
||||||
for k, v := range t.Env {
|
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, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
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
|
17
testdata/params/Taskfile.yml
vendored
Normal file
17
testdata/params/Taskfile.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
default:
|
||||||
|
deps:
|
||||||
|
- task: write-file
|
||||||
|
vars: {CONTENT: Dependence1, FILE: dep1.txt}
|
||||||
|
- task: write-file
|
||||||
|
vars: {CONTENT: Dependence2, FILE: dep2.txt}
|
||||||
|
cmds:
|
||||||
|
- task: write-file
|
||||||
|
vars: {CONTENT: Hello, FILE: hello.txt}
|
||||||
|
- task: write-file
|
||||||
|
vars: {CONTENT: "$echo 'World'", FILE: world.txt}
|
||||||
|
- task: write-file
|
||||||
|
vars: {CONTENT: "!", FILE: exclamation.txt}
|
||||||
|
|
||||||
|
write-file:
|
||||||
|
cmds:
|
||||||
|
- echo {{.CONTENT}} > {{.FILE}}
|
@ -2,9 +2,7 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -13,9 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-task/task/execext"
|
"github.com/go-task/task/execext"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
"github.com/Masterminds/sprig"
|
"github.com/Masterminds/sprig"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -52,7 +48,7 @@ func (e *Executor) handleDynamicVariableContent(value string) (string, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) getVariables(task string) (map[string]string, error) {
|
func (e *Executor) getVariables(task string, vars Vars) (map[string]string, error) {
|
||||||
t := e.Tasks[task]
|
t := e.Tasks[task]
|
||||||
|
|
||||||
localVariables := make(map[string]string)
|
localVariables := make(map[string]string)
|
||||||
@ -63,20 +59,27 @@ func (e *Executor) getVariables(task string) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
localVariables[key] = val
|
localVariables[key] = val
|
||||||
}
|
}
|
||||||
if fileVariables, err := e.readTaskvarsFile(); err == nil {
|
if e.taskvars != nil {
|
||||||
for key, value := range fileVariables {
|
for key, value := range e.taskvars {
|
||||||
val, err := e.handleDynamicVariableContent(value)
|
val, err := e.handleDynamicVariableContent(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
localVariables[key] = val
|
localVariables[key] = val
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
for key, value := range getEnvironmentVariables() {
|
for key, value := range getEnvironmentVariables() {
|
||||||
localVariables[key] = value
|
localVariables[key] = value
|
||||||
}
|
}
|
||||||
|
if vars != nil {
|
||||||
|
for k, v := range vars {
|
||||||
|
val, err := e.handleDynamicVariableContent(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
localVariables[k] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
return localVariables, nil
|
return localVariables, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,11 +112,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplaceSliceVariables writes vars into initial string slice
|
// 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, vars Vars) ([]string, error) {
|
||||||
result := make([]string, len(initials))
|
result := make([]string, len(initials))
|
||||||
for i, s := range initials {
|
for i, s := range initials {
|
||||||
var err error
|
var err error
|
||||||
result[i], err = e.ReplaceVariables(task, s)
|
result[i], err = e.ReplaceVariables(s, task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -122,8 +125,8 @@ func (e *Executor) ReplaceSliceVariables(task string, initials []string) ([]stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplaceVariables writes vars into initial string
|
// ReplaceVariables writes vars into initial string
|
||||||
func (e *Executor) ReplaceVariables(task, initial string) (string, error) {
|
func (e *Executor) ReplaceVariables(initial, task string, vars Vars) (string, error) {
|
||||||
vars, err := e.getVariables(task)
|
vars, err := e.getVariables(task, vars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -154,28 +157,3 @@ func getEnvironmentVariables() map[string]string {
|
|||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) readTaskvarsFile() (map[string]string, error) {
|
|
||||||
file := filepath.Join(e.Dir, TaskvarsFilePath)
|
|
||||||
|
|
||||||
var variables map[string]string
|
|
||||||
if b, err := ioutil.ReadFile(file + ".yml"); err == nil {
|
|
||||||
if err := yaml.Unmarshal(b, &variables); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return variables, nil
|
|
||||||
}
|
|
||||||
if b, err := ioutil.ReadFile(file + ".json"); err == nil {
|
|
||||||
if err := json.Unmarshal(b, &variables); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return variables, nil
|
|
||||||
}
|
|
||||||
if b, err := ioutil.ReadFile(file + ".toml"); err == nil {
|
|
||||||
if err := toml.Unmarshal(b, &variables); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return variables, nil
|
|
||||||
}
|
|
||||||
return variables, nil
|
|
||||||
}
|
|
||||||
|
10
watch.go
10
watch.go
@ -15,7 +15,7 @@ func (e *Executor) watchTasks(args ...string) error {
|
|||||||
|
|
||||||
// run tasks on init
|
// run tasks on init
|
||||||
for _, a := range args {
|
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)
|
e.println(err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ loop:
|
|||||||
select {
|
select {
|
||||||
case <-watcher.Events:
|
case <-watcher.Events:
|
||||||
for _, a := range args {
|
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)
|
e.println(err)
|
||||||
continue loop
|
continue loop
|
||||||
}
|
}
|
||||||
@ -68,7 +68,11 @@ func (e *Executor) registerWatchedFiles(w *fsnotify.Watcher, args []string) erro
|
|||||||
if !ok {
|
if !ok {
|
||||||
return &taskNotFoundError{a}
|
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
|
return err
|
||||||
}
|
}
|
||||||
for _, s := range task.Sources {
|
for _, s := range task.Sources {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user