Compare commits

...

11 Commits

Author SHA1 Message Date
Valentin Maerten
be4155ab54 update docs 2025-02-23 12:56:47 +01:00
Valentin Maerten
8bc8490485 lint 2025-02-23 12:42:16 +01:00
Valentin Maerten
dbe053277e preconditions cannot be in included taskfile 2025-02-23 12:40:55 +01:00
Valentin Maerten
37715657ae separe preconditions and precondition 2025-02-23 12:23:43 +01:00
Valentin Maerten
b72d1bbcfa use slices.concat 2025-02-23 12:20:27 +01:00
Valentin Maerten
c676b20385 modify json schema 2025-02-23 12:20:27 +01:00
Valentin Maerten
16ac79c561 merge preconditions 2025-02-23 12:20:27 +01:00
Valentin Maerten
596fd29cb2 add test 2025-02-23 12:20:27 +01:00
Valentin Maerten
d3e0fc9eea add docs 2025-02-23 12:20:27 +01:00
Valentin Maerten
08bd0d982a wip 2025-02-23 12:20:27 +01:00
Valentin Maerten
10d7123f60 wip 2025-02-23 12:20:27 +01:00
14 changed files with 218 additions and 34 deletions

View File

@@ -2,6 +2,7 @@ package task
import (
"context"
"slices"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/env"
@@ -14,7 +15,7 @@ import (
var ErrPreconditionFailed = errors.New("task: precondition not met")
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{
Command: p.Sh,
Dir: t.Dir,

View File

@@ -456,10 +456,10 @@ func TestStatus(t *testing.T) {
buff.Reset()
}
func TestPrecondition(t *testing.T) {
func TestPreconditionLocal(t *testing.T) {
t.Parallel()
const dir = "testdata/precondition"
const dir = "testdata/precondition/local"
var buff bytes.Buffer
e := &task.Executor{
@@ -499,6 +499,62 @@ func TestPrecondition(t *testing.T) {
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) {
t.Parallel()

View File

@@ -9,10 +9,12 @@ import (
)
// Precondition represents a precondition necessary for a task to run
type Precondition struct {
Sh string
Msg string
}
type (
Precondition struct {
Sh string
Msg string
}
)
func (p *Precondition) DeepCopy() *Precondition {
if p == nil {

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

View File

@@ -18,22 +18,26 @@ var V3 = semver.MustParse("3")
// 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")
// 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
type Taskfile struct {
Location string
Version *semver.Version
Output Output
Method string
Includes *Includes
Set []string
Shopt []string
Vars *Vars
Env *Vars
Tasks *Tasks
Silent bool
Dotenv []string
Run string
Interval time.Duration
Location string
Version *semver.Version
Output Output
Method string
Includes *Includes
Set []string
Shopt []string
Vars *Vars
Env *Vars
Preconditions *Preconditions
Tasks *Tasks
Silent bool
Dotenv []string
Run string
Interval time.Duration
}
// 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 {
return ErrIncludedTaskfilesCantHaveDotenvs
}
if len(t2.Preconditions.Values) > 0 {
return ErrIncludedTaskfilesCantHavePreconditions
}
if t2.Output.IsSet() {
t1.Output = t2.Output
}
@@ -59,6 +66,9 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error {
if t1.Tasks == nil {
t1.Tasks = NewTasks()
}
if t1.Preconditions == nil {
t1.Preconditions = NewPreconditions()
}
t1.Vars.Merge(t2.Vars, include)
t1.Env.Merge(t2.Env, include)
return t1.Tasks.Merge(t2.Tasks, include, t1.Vars)
@@ -68,19 +78,20 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.MappingNode:
var taskfile struct {
Version *semver.Version
Output Output
Method string
Includes *Includes
Set []string
Shopt []string
Vars *Vars
Env *Vars
Tasks *Tasks
Silent bool
Dotenv []string
Run string
Interval time.Duration
Version *semver.Version
Output Output
Method string
Includes *Includes
Preconditions *Preconditions
Set []string
Shopt []string
Vars *Vars
Env *Vars
Tasks *Tasks
Silent bool
Dotenv []string
Run string
Interval time.Duration
}
if err := node.Decode(&taskfile); err != nil {
return errors.NewTaskfileDecodeError(err, node)
@@ -98,6 +109,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
tf.Dotenv = taskfile.Dotenv
tf.Run = taskfile.Run
tf.Interval = taskfile.Interval
tf.Preconditions = taskfile.Preconditions
if tf.Includes == nil {
tf.Includes = NewIncludes()
}
@@ -110,6 +122,9 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
if tf.Tasks == nil {
tf.Tasks = NewTasks()
}
if tf.Preconditions == nil {
tf.Preconditions = NewPreconditions()
}
return nil
}

View File

@@ -0,0 +1,9 @@
version: '3'
preconditions:
- sh: "[ 1 = 0 ]"
msg: "1 != 0 obviously!"
tasks:
impossible:
cmd: echo "won't run"

View File

@@ -0,0 +1,8 @@
version: 3
includes:
included: included.yml
preconditions:
- sh: "[ 1 = 0 ]"
msg: "1 != 0 obviously!"

View File

@@ -0,0 +1,5 @@
version: 3
preconditions:
- sh: "[ 1 = 0 ]"
msg: "1 != 0 obviously!"

View 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
View File

View File

@@ -1019,6 +1019,28 @@ tasks:
- 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
If a task executed by multiple `cmds` or multiple `deps` you can control when it

View File

@@ -699,6 +699,13 @@
"description": "A set of global environment variables.",
"$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": {
"description": "A set of task definitions.",
"$ref": "#/definitions/tasks"