mirror of
https://github.com/go-task/task.git
synced 2026-06-22 04:05:53 +00:00
Compare commits
11 Commits
split-exec
...
feat/add-g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be4155ab54 | ||
|
|
8bc8490485 | ||
|
|
dbe053277e | ||
|
|
37715657ae | ||
|
|
b72d1bbcfa | ||
|
|
c676b20385 | ||
|
|
16ac79c561 | ||
|
|
596fd29cb2 | ||
|
|
d3e0fc9eea | ||
|
|
08bd0d982a | ||
|
|
10d7123f60 |
@@ -2,6 +2,7 @@ package task
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
"github.com/go-task/task/v3/internal/env"
|
"github.com/go-task/task/v3/internal/env"
|
||||||
@@ -14,7 +15,7 @@ import (
|
|||||||
var ErrPreconditionFailed = errors.New("task: precondition not met")
|
var ErrPreconditionFailed = errors.New("task: precondition not met")
|
||||||
|
|
||||||
func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *ast.Task) (bool, error) {
|
func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *ast.Task) (bool, error) {
|
||||||
for _, p := range t.Preconditions {
|
for _, p := range slices.Concat(e.Taskfile.Preconditions.Values, t.Preconditions) {
|
||||||
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
err := execext.RunCommand(ctx, &execext.RunCommandOptions{
|
||||||
Command: p.Sh,
|
Command: p.Sh,
|
||||||
Dir: t.Dir,
|
Dir: t.Dir,
|
||||||
|
|||||||
60
task_test.go
60
task_test.go
@@ -456,10 +456,10 @@ func TestStatus(t *testing.T) {
|
|||||||
buff.Reset()
|
buff.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrecondition(t *testing.T) {
|
func TestPreconditionLocal(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const dir = "testdata/precondition"
|
const dir = "testdata/precondition/local"
|
||||||
|
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
e := &task.Executor{
|
e := &task.Executor{
|
||||||
@@ -499,6 +499,62 @@ func TestPrecondition(t *testing.T) {
|
|||||||
buff.Reset()
|
buff.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreconditionGlobal(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := &task.Executor{
|
||||||
|
Dir: "testdata/precondition/global",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
// A global precondition that was not met
|
||||||
|
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "impossible"}))
|
||||||
|
|
||||||
|
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||||
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
e = &task.Executor{
|
||||||
|
Dir: "testdata/precondition/global/with_local",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
// A global precondition that was met
|
||||||
|
require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
|
||||||
|
if buff.String() != "" {
|
||||||
|
t.Errorf("Got Output when none was expected: %s", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A local precondition that was not met
|
||||||
|
require.Error(t, e.Run(context.Background(), &ast.Call{Task: "impossible"}))
|
||||||
|
|
||||||
|
if buff.String() != "task: 1 != 0 obviously!\n" {
|
||||||
|
t.Errorf("Wrong output message: %s", buff.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
e = &task.Executor{
|
||||||
|
Dir: "testdata/precondition/global/included",
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := e.Setup()
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "task: Included Taskfiles can't have preconditions declarations. Please, move the preconditions declaration to the main Taskfile", err.Error())
|
||||||
|
buff.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerates(t *testing.T) {
|
func TestGenerates(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Precondition represents a precondition necessary for a task to run
|
// Precondition represents a precondition necessary for a task to run
|
||||||
type Precondition struct {
|
type (
|
||||||
Sh string
|
Precondition struct {
|
||||||
Msg string
|
Sh string
|
||||||
}
|
Msg string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func (p *Precondition) DeepCopy() *Precondition {
|
func (p *Precondition) DeepCopy() *Precondition {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
|
|||||||
47
taskfile/ast/preconditions.go
Normal file
47
taskfile/ast/preconditions.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/errors"
|
||||||
|
"github.com/go-task/task/v3/internal/deepcopy"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Precondition represents a precondition necessary for a task to run
|
||||||
|
type (
|
||||||
|
Preconditions struct {
|
||||||
|
Values []*Precondition
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPreconditions() *Preconditions {
|
||||||
|
return &Preconditions{
|
||||||
|
Values: make([]*Precondition, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Preconditions) DeepCopy() *Preconditions {
|
||||||
|
if p == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer p.mutex.RUnlock()
|
||||||
|
p.mutex.RLock()
|
||||||
|
return &Preconditions{
|
||||||
|
Values: deepcopy.Slice(p.Values),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Preconditions) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
if p == nil || p.Values == nil {
|
||||||
|
*p = *NewPreconditions()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := node.Decode(&p.Values); err != nil {
|
||||||
|
return errors.NewTaskfileDecodeError(err, node).WithTypeMessage("preconditions")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -18,22 +18,26 @@ var V3 = semver.MustParse("3")
|
|||||||
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
|
||||||
var ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
var ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
|
||||||
|
|
||||||
|
// ErrIncludedTaskfilesCantHavePreconditions is returned when a included Taskfile contains Preconditions
|
||||||
|
var ErrIncludedTaskfilesCantHavePreconditions = errors.New("task: Included Taskfiles can't have preconditions declarations. Please, move the preconditions declaration to the main Taskfile")
|
||||||
|
|
||||||
// Taskfile is the abstract syntax tree for a Taskfile
|
// Taskfile is the abstract syntax tree for a Taskfile
|
||||||
type Taskfile struct {
|
type Taskfile struct {
|
||||||
Location string
|
Location string
|
||||||
Version *semver.Version
|
Version *semver.Version
|
||||||
Output Output
|
Output Output
|
||||||
Method string
|
Method string
|
||||||
Includes *Includes
|
Includes *Includes
|
||||||
Set []string
|
Set []string
|
||||||
Shopt []string
|
Shopt []string
|
||||||
Vars *Vars
|
Vars *Vars
|
||||||
Env *Vars
|
Env *Vars
|
||||||
Tasks *Tasks
|
Preconditions *Preconditions
|
||||||
Silent bool
|
Tasks *Tasks
|
||||||
Dotenv []string
|
Silent bool
|
||||||
Run string
|
Dotenv []string
|
||||||
Interval time.Duration
|
Run string
|
||||||
|
Interval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge merges the second Taskfile into the first
|
// Merge merges the second Taskfile into the first
|
||||||
@@ -44,6 +48,9 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
|||||||
if len(t2.Dotenv) > 0 {
|
if len(t2.Dotenv) > 0 {
|
||||||
return ErrIncludedTaskfilesCantHaveDotenvs
|
return ErrIncludedTaskfilesCantHaveDotenvs
|
||||||
}
|
}
|
||||||
|
if len(t2.Preconditions.Values) > 0 {
|
||||||
|
return ErrIncludedTaskfilesCantHavePreconditions
|
||||||
|
}
|
||||||
if t2.Output.IsSet() {
|
if t2.Output.IsSet() {
|
||||||
t1.Output = t2.Output
|
t1.Output = t2.Output
|
||||||
}
|
}
|
||||||
@@ -59,6 +66,9 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
|
|||||||
if t1.Tasks == nil {
|
if t1.Tasks == nil {
|
||||||
t1.Tasks = NewTasks()
|
t1.Tasks = NewTasks()
|
||||||
}
|
}
|
||||||
|
if t1.Preconditions == nil {
|
||||||
|
t1.Preconditions = NewPreconditions()
|
||||||
|
}
|
||||||
t1.Vars.Merge(t2.Vars, include)
|
t1.Vars.Merge(t2.Vars, include)
|
||||||
t1.Env.Merge(t2.Env, include)
|
t1.Env.Merge(t2.Env, include)
|
||||||
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
|
||||||
@@ -68,19 +78,20 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
switch node.Kind {
|
switch node.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
var taskfile struct {
|
var taskfile struct {
|
||||||
Version *semver.Version
|
Version *semver.Version
|
||||||
Output Output
|
Output Output
|
||||||
Method string
|
Method string
|
||||||
Includes *Includes
|
Includes *Includes
|
||||||
Set []string
|
Preconditions *Preconditions
|
||||||
Shopt []string
|
Set []string
|
||||||
Vars *Vars
|
Shopt []string
|
||||||
Env *Vars
|
Vars *Vars
|
||||||
Tasks *Tasks
|
Env *Vars
|
||||||
Silent bool
|
Tasks *Tasks
|
||||||
Dotenv []string
|
Silent bool
|
||||||
Run string
|
Dotenv []string
|
||||||
Interval time.Duration
|
Run string
|
||||||
|
Interval time.Duration
|
||||||
}
|
}
|
||||||
if err := node.Decode(&taskfile); err != nil {
|
if err := node.Decode(&taskfile); err != nil {
|
||||||
return errors.NewTaskfileDecodeError(err, node)
|
return errors.NewTaskfileDecodeError(err, node)
|
||||||
@@ -98,6 +109,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
tf.Dotenv = taskfile.Dotenv
|
tf.Dotenv = taskfile.Dotenv
|
||||||
tf.Run = taskfile.Run
|
tf.Run = taskfile.Run
|
||||||
tf.Interval = taskfile.Interval
|
tf.Interval = taskfile.Interval
|
||||||
|
tf.Preconditions = taskfile.Preconditions
|
||||||
if tf.Includes == nil {
|
if tf.Includes == nil {
|
||||||
tf.Includes = NewIncludes()
|
tf.Includes = NewIncludes()
|
||||||
}
|
}
|
||||||
@@ -110,6 +122,9 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
|||||||
if tf.Tasks == nil {
|
if tf.Tasks == nil {
|
||||||
tf.Tasks = NewTasks()
|
tf.Tasks = NewTasks()
|
||||||
}
|
}
|
||||||
|
if tf.Preconditions == nil {
|
||||||
|
tf.Preconditions = NewPreconditions()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
testdata/precondition/global/Taskfile.yml
vendored
Normal file
9
testdata/precondition/global/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
preconditions:
|
||||||
|
- sh: "[ 1 = 0 ]"
|
||||||
|
msg: "1 != 0 obviously!"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
impossible:
|
||||||
|
cmd: echo "won't run"
|
||||||
8
testdata/precondition/global/included/Taskfile.yml
vendored
Normal file
8
testdata/precondition/global/included/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
version: 3
|
||||||
|
|
||||||
|
includes:
|
||||||
|
included: included.yml
|
||||||
|
|
||||||
|
preconditions:
|
||||||
|
- sh: "[ 1 = 0 ]"
|
||||||
|
msg: "1 != 0 obviously!"
|
||||||
5
testdata/precondition/global/included/included.yml
vendored
Normal file
5
testdata/precondition/global/included/included.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
version: 3
|
||||||
|
|
||||||
|
preconditions:
|
||||||
|
- sh: "[ 1 = 0 ]"
|
||||||
|
msg: "1 != 0 obviously!"
|
||||||
12
testdata/precondition/global/with_local/Taskfile.yml
vendored
Normal file
12
testdata/precondition/global/with_local/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
preconditions:
|
||||||
|
- test -f foo.txt
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
|
||||||
|
impossible:
|
||||||
|
preconditions:
|
||||||
|
- sh: "[ 1 = 0 ]"
|
||||||
|
msg: "1 != 0 obviously!"
|
||||||
0
testdata/precondition/local/foo.txt
vendored
Normal file
0
testdata/precondition/local/foo.txt
vendored
Normal file
@@ -1019,6 +1019,28 @@ tasks:
|
|||||||
- echo "I will not run"
|
- echo "I will not run"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
They can be defined at two levels:
|
||||||
|
|
||||||
|
- Global Level: Applies to all tasks.
|
||||||
|
- Task Level: Applies only to a specific task.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
preconditions:
|
||||||
|
- sh: 'exit 1'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
task-will-fail: echo "I will not run"
|
||||||
|
```
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
Please note that you are not currently able to use the `preconditions` key inside
|
||||||
|
included Taskfiles. It'll produce an error.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
### Limiting when tasks run
|
### Limiting when tasks run
|
||||||
|
|
||||||
If a task executed by multiple `cmds` or multiple `deps` you can control when it
|
If a task executed by multiple `cmds` or multiple `deps` you can control when it
|
||||||
|
|||||||
@@ -699,6 +699,13 @@
|
|||||||
"description": "A set of global environment variables.",
|
"description": "A set of global environment variables.",
|
||||||
"$ref": "#/definitions/env"
|
"$ref": "#/definitions/env"
|
||||||
},
|
},
|
||||||
|
"preconditions": {
|
||||||
|
"description": "A list of commands to check if any task should run. If a condition is not met, the task will return an error.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/precondition"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"description": "A set of task definitions.",
|
"description": "A set of task definitions.",
|
||||||
"$ref": "#/definitions/tasks"
|
"$ref": "#/definitions/tasks"
|
||||||
|
|||||||
Reference in New Issue
Block a user