From 60952c2f11a6dbfaa93a748dccdd1df9f30a6694 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Thu, 2 Jan 2025 22:18:25 +0100 Subject: [PATCH 1/6] wip --- taskfile/ast/precondition.go | 45 +++++++++++++++++++++++++-- taskfile/ast/taskfile.go | 60 ++++++++++++++++++++---------------- 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/taskfile/ast/precondition.go b/taskfile/ast/precondition.go index 275144c917..07663f2b93 100644 --- a/taskfile/ast/precondition.go +++ b/taskfile/ast/precondition.go @@ -2,6 +2,8 @@ package ast import ( "fmt" + "github.com/go-task/task/v3/internal/deepcopy" + "sync" "gopkg.in/yaml.v3" @@ -9,9 +11,27 @@ import ( ) // Precondition represents a precondition necessary for a task to run -type Precondition struct { - Sh string - Msg string +type ( + Preconditions struct { + preconditions []*Precondition + mutex sync.RWMutex + } + + Precondition struct { + Sh string + Msg string + } +) + +func (p *Preconditions) DeepCopy() *Preconditions { + if p == nil { + return nil + } + defer p.mutex.RUnlock() + p.mutex.RLock() + return &Preconditions{ + preconditions: deepcopy.Slice(p.preconditions), + } } func (p *Precondition) DeepCopy() *Precondition { @@ -24,6 +44,12 @@ func (p *Precondition) DeepCopy() *Precondition { } } +func NewPreconditions() *Preconditions { + return &Preconditions{ + preconditions: make([]*Precondition, 0), + } +} + // UnmarshalYAML implements yaml.Unmarshaler interface. func (p *Precondition) UnmarshalYAML(node *yaml.Node) error { switch node.Kind { @@ -55,3 +81,16 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error { return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("precondition") } + +func (p *Preconditions) UnmarshalYAML(node *yaml.Node) error { + + if p == nil || p.preconditions == nil { + *p = *NewPreconditions() + } + + if err := node.Decode(&p.preconditions); err != nil { + return errors.NewTaskfileDecodeError(err, node).WithTypeMessage("precondition") + } + + return nil +} diff --git a/taskfile/ast/taskfile.go b/taskfile/ast/taskfile.go index 4aad932da7..f75d8761c2 100644 --- a/taskfile/ast/taskfile.go +++ b/taskfile/ast/taskfile.go @@ -20,20 +20,21 @@ var ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles c // 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 @@ -68,19 +69,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) @@ -110,6 +112,10 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error { if tf.Tasks == nil { tf.Tasks = NewTasks() } + + if tf.Preconditions == nil { + tf.Preconditions = NewPreconditions() + } return nil } From 2b77f0ad4ae8d2d5c3321202dc1fdaa31b0170a0 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Fri, 3 Jan 2025 14:03:16 +0100 Subject: [PATCH 2/6] wip --- precondition.go | 2 +- taskfile/ast/precondition.go | 17 +++++++++-------- taskfile/ast/taskfile.go | 6 +++++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/precondition.go b/precondition.go index 1f25bd37f5..b1f22cc6ba 100644 --- a/precondition.go +++ b/precondition.go @@ -14,7 +14,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 append(t.Preconditions, e.Taskfile.Preconditions.Preconditions...) { err := execext.RunCommand(ctx, &execext.RunCommandOptions{ Command: p.Sh, Dir: t.Dir, diff --git a/taskfile/ast/precondition.go b/taskfile/ast/precondition.go index 07663f2b93..ab48094ac3 100644 --- a/taskfile/ast/precondition.go +++ b/taskfile/ast/precondition.go @@ -2,9 +2,11 @@ package ast import ( "fmt" - "github.com/go-task/task/v3/internal/deepcopy" "sync" + + "github.com/go-task/task/v3/internal/deepcopy" + "gopkg.in/yaml.v3" "github.com/go-task/task/v3/errors" @@ -13,7 +15,7 @@ import ( // Precondition represents a precondition necessary for a task to run type ( Preconditions struct { - preconditions []*Precondition + Preconditions []*Precondition mutex sync.RWMutex } @@ -30,7 +32,7 @@ func (p *Preconditions) DeepCopy() *Preconditions { defer p.mutex.RUnlock() p.mutex.RLock() return &Preconditions{ - preconditions: deepcopy.Slice(p.preconditions), + Preconditions: deepcopy.Slice(p.Preconditions), } } @@ -46,7 +48,7 @@ func (p *Precondition) DeepCopy() *Precondition { func NewPreconditions() *Preconditions { return &Preconditions{ - preconditions: make([]*Precondition, 0), + Preconditions: make([]*Precondition, 0), } } @@ -83,13 +85,12 @@ func (p *Precondition) UnmarshalYAML(node *yaml.Node) error { } func (p *Preconditions) UnmarshalYAML(node *yaml.Node) error { - - if p == nil || p.preconditions == nil { + if p == nil || p.Preconditions == nil { *p = *NewPreconditions() } - if err := node.Decode(&p.preconditions); err != nil { - return errors.NewTaskfileDecodeError(err, node).WithTypeMessage("precondition") + if err := node.Decode(&p.Preconditions); err != nil { + return errors.NewTaskfileDecodeError(err, node).WithTypeMessage("preconditions") } return nil diff --git a/taskfile/ast/taskfile.go b/taskfile/ast/taskfile.go index f75d8761c2..98e2fce375 100644 --- a/taskfile/ast/taskfile.go +++ b/taskfile/ast/taskfile.go @@ -60,8 +60,12 @@ 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) + // TODO:@vmaerten Merge precondition return t1.Tasks.Merge(t2.Tasks, include, t1.Vars) } @@ -100,6 +104,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() } @@ -112,7 +117,6 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error { if tf.Tasks == nil { tf.Tasks = NewTasks() } - if tf.Preconditions == nil { tf.Preconditions = NewPreconditions() } From 739b0c32e567a500ecaa0dde6335d00942036274 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Sun, 5 Jan 2025 10:42:34 +0100 Subject: [PATCH 3/6] add docs --- taskfile/ast/precondition.go | 16 +++++++--------- website/docs/usage.mdx | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/taskfile/ast/precondition.go b/taskfile/ast/precondition.go index ab48094ac3..9d8ad80f5e 100644 --- a/taskfile/ast/precondition.go +++ b/taskfile/ast/precondition.go @@ -4,12 +4,10 @@ import ( "fmt" "sync" - + "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/deepcopy" "gopkg.in/yaml.v3" - - "github.com/go-task/task/v3/errors" ) // Precondition represents a precondition necessary for a task to run @@ -25,6 +23,12 @@ type ( } ) +func NewPreconditions() *Preconditions { + return &Preconditions{ + Preconditions: make([]*Precondition, 0), + } +} + func (p *Preconditions) DeepCopy() *Preconditions { if p == nil { return nil @@ -46,12 +50,6 @@ func (p *Precondition) DeepCopy() *Precondition { } } -func NewPreconditions() *Preconditions { - return &Preconditions{ - Preconditions: make([]*Precondition, 0), - } -} - // UnmarshalYAML implements yaml.Unmarshaler interface. func (p *Precondition) UnmarshalYAML(node *yaml.Node) error { switch node.Kind { diff --git a/website/docs/usage.mdx b/website/docs/usage.mdx index d082b446c2..b56713df59 100644 --- a/website/docs/usage.mdx +++ b/website/docs/usage.mdx @@ -1019,6 +1019,21 @@ 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" +``` + ### Limiting when tasks run If a task executed by multiple `cmds` or multiple `deps` you can control when it From 028883e69b238b18f2315d3ff00c3d68582a7eb5 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Sun, 5 Jan 2025 13:46:24 +0100 Subject: [PATCH 4/6] add test --- task_test.go | 46 ++++++++++++++++++- testdata/precondition/global/Taskfile.yml | 9 ++++ .../global/with_local/Taskfile.yml | 12 +++++ .../{ => global/with_local}/foo.txt | 0 .../precondition/{ => local}/Taskfile.yml | 0 testdata/precondition/local/foo.txt | 0 6 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 testdata/precondition/global/Taskfile.yml create mode 100644 testdata/precondition/global/with_local/Taskfile.yml rename testdata/precondition/{ => global/with_local}/foo.txt (100%) rename testdata/precondition/{ => local}/Taskfile.yml (100%) create mode 100644 testdata/precondition/local/foo.txt diff --git a/task_test.go b/task_test.go index 8920460b32..6b06c39f14 100644 --- a/task_test.go +++ b/task_test.go @@ -443,10 +443,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{ @@ -486,6 +486,48 @@ 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()) + } +} + func TestGenerates(t *testing.T) { t.Parallel() diff --git a/testdata/precondition/global/Taskfile.yml b/testdata/precondition/global/Taskfile.yml new file mode 100644 index 0000000000..cc2e5e5901 --- /dev/null +++ b/testdata/precondition/global/Taskfile.yml @@ -0,0 +1,9 @@ +version: '3' + +preconditions: + - sh: "[ 1 = 0 ]" + msg: "1 != 0 obviously!" + +tasks: + impossible: + cmd: echo "won't run" diff --git a/testdata/precondition/global/with_local/Taskfile.yml b/testdata/precondition/global/with_local/Taskfile.yml new file mode 100644 index 0000000000..d70d809f75 --- /dev/null +++ b/testdata/precondition/global/with_local/Taskfile.yml @@ -0,0 +1,12 @@ +version: '3' + +preconditions: + - test -f foo.txt + +tasks: + foo: + + impossible: + preconditions: + - sh: "[ 1 = 0 ]" + msg: "1 != 0 obviously!" diff --git a/testdata/precondition/foo.txt b/testdata/precondition/global/with_local/foo.txt similarity index 100% rename from testdata/precondition/foo.txt rename to testdata/precondition/global/with_local/foo.txt diff --git a/testdata/precondition/Taskfile.yml b/testdata/precondition/local/Taskfile.yml similarity index 100% rename from testdata/precondition/Taskfile.yml rename to testdata/precondition/local/Taskfile.yml diff --git a/testdata/precondition/local/foo.txt b/testdata/precondition/local/foo.txt new file mode 100644 index 0000000000..e69de29bb2 From 34943780154a5116770cc7e27e131187ac283534 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Tue, 7 Jan 2025 21:05:43 +0100 Subject: [PATCH 5/6] merge preconditions --- taskfile/ast/precondition.go | 14 ++++++++++++++ taskfile/ast/taskfile.go | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/taskfile/ast/precondition.go b/taskfile/ast/precondition.go index 9d8ad80f5e..69ca5f004a 100644 --- a/taskfile/ast/precondition.go +++ b/taskfile/ast/precondition.go @@ -40,6 +40,20 @@ func (p *Preconditions) DeepCopy() *Preconditions { } } +func (p *Preconditions) Merge(other *Preconditions) { + if p == nil || p.Preconditions == nil || other == nil { + return + } + + p.mutex.Lock() + defer p.mutex.Unlock() + + other.mutex.RLock() + defer other.mutex.RUnlock() + + p.Preconditions = append(p.Preconditions, deepcopy.Slice(other.Preconditions)...) +} + func (p *Precondition) DeepCopy() *Precondition { if p == nil { return nil diff --git a/taskfile/ast/taskfile.go b/taskfile/ast/taskfile.go index 98e2fce375..988ae21613 100644 --- a/taskfile/ast/taskfile.go +++ b/taskfile/ast/taskfile.go @@ -65,7 +65,7 @@ func (t1 *Taskfile) Merge(t2 *Taskfile, include *Include) error { } t1.Vars.Merge(t2.Vars, include) t1.Env.Merge(t2.Env, include) - // TODO:@vmaerten Merge precondition + t1.Preconditions.Merge(t2.Preconditions) return t1.Tasks.Merge(t2.Tasks, include, t1.Vars) } From 49af480ac4ac9396f09313033d8b1a17a55c2e94 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Tue, 7 Jan 2025 21:16:37 +0100 Subject: [PATCH 6/6] modify json schema --- website/static/schema.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/static/schema.json b/website/static/schema.json index da25a209f9..e4785c836b 100644 --- a/website/static/schema.json +++ b/website/static/schema.json @@ -678,6 +678,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"