mirror of
https://github.com/go-task/task.git
synced 2025-06-17 00:17:51 +02:00
feat: remove v2 support (#1447)
* feat: remove v2 support * docs: update v2 schema docs
This commit is contained in:
30
args/args.go
30
args/args.go
@ -6,8 +6,8 @@ import (
|
|||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseV3 parses command line argument: tasks and global variables
|
// Parse parses command line argument: tasks and global variables
|
||||||
func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
func Parse(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
||||||
calls := []taskfile.Call{}
|
calls := []taskfile.Call{}
|
||||||
globals := &taskfile.Vars{}
|
globals := &taskfile.Vars{}
|
||||||
|
|
||||||
@ -24,32 +24,6 @@ func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
|||||||
return calls, globals
|
return calls, globals
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseV2 parses command line argument: tasks and vars of each task
|
|
||||||
func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) {
|
|
||||||
calls := []taskfile.Call{}
|
|
||||||
globals := &taskfile.Vars{}
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
if !strings.Contains(arg, "=") {
|
|
||||||
calls = append(calls, taskfile.Call{Task: arg, Direct: true})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(calls) < 1 {
|
|
||||||
name, value := splitVar(arg)
|
|
||||||
globals.Set(name, taskfile.Var{Value: value})
|
|
||||||
} else {
|
|
||||||
if calls[len(calls)-1].Vars == nil {
|
|
||||||
calls[len(calls)-1].Vars = &taskfile.Vars{}
|
|
||||||
}
|
|
||||||
name, value := splitVar(arg)
|
|
||||||
calls[len(calls)-1].Vars.Set(name, taskfile.Var{Value: value})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return calls, globals
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitVar(s string) (string, string) {
|
func splitVar(s string) (string, string) {
|
||||||
pair := strings.SplitN(s, "=", 2)
|
pair := strings.SplitN(s, "=", 2)
|
||||||
return pair[0], pair[1]
|
return pair[0], pair[1]
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestArgsV3(t *testing.T) {
|
func TestArgs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Args []string
|
Args []string
|
||||||
ExpectedCalls []taskfile.Call
|
ExpectedCalls []taskfile.Call
|
||||||
@ -97,7 +97,7 @@ func TestArgsV3(t *testing.T) {
|
|||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
||||||
calls, globals := args.ParseV3(test.Args...)
|
calls, globals := args.Parse(test.Args...)
|
||||||
assert.Equal(t, test.ExpectedCalls, calls)
|
assert.Equal(t, test.ExpectedCalls, calls)
|
||||||
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
||||||
assert.Equal(t, test.ExpectedGlobals.Keys(), globals.Keys())
|
assert.Equal(t, test.ExpectedGlobals.Keys(), globals.Keys())
|
||||||
@ -106,114 +106,3 @@ func TestArgsV3(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArgsV2(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
Args []string
|
|
||||||
ExpectedCalls []taskfile.Call
|
|
||||||
ExpectedGlobals *taskfile.Vars
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "task-b", "task-c"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "task-a", Direct: true},
|
|
||||||
{Task: "task-b", Direct: true},
|
|
||||||
{Task: "task-c", Direct: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{
|
|
||||||
Task: "task-a",
|
|
||||||
Direct: true,
|
|
||||||
Vars: &taskfile.Vars{
|
|
||||||
OrderedMap: orderedmap.FromMapWithOrder(
|
|
||||||
map[string]taskfile.Var{
|
|
||||||
"FOO": {Value: "bar"},
|
|
||||||
},
|
|
||||||
[]string{"FOO"},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{Task: "task-b", Direct: true},
|
|
||||||
{
|
|
||||||
Task: "task-c",
|
|
||||||
Direct: true,
|
|
||||||
Vars: &taskfile.Vars{
|
|
||||||
OrderedMap: orderedmap.FromMapWithOrder(
|
|
||||||
map[string]taskfile.Var{
|
|
||||||
"BAR": {Value: "baz"},
|
|
||||||
"BAZ": {Value: "foo"},
|
|
||||||
},
|
|
||||||
[]string{"BAR", "BAZ"},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"task-a", "CONTENT=with some spaces"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{
|
|
||||||
Task: "task-a",
|
|
||||||
Direct: true,
|
|
||||||
Vars: &taskfile.Vars{
|
|
||||||
OrderedMap: orderedmap.FromMapWithOrder(
|
|
||||||
map[string]taskfile.Var{
|
|
||||||
"CONTENT": {Value: "with some spaces"},
|
|
||||||
},
|
|
||||||
[]string{"CONTENT"},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"FOO=bar", "task-a", "task-b"},
|
|
||||||
ExpectedCalls: []taskfile.Call{
|
|
||||||
{Task: "task-a", Direct: true},
|
|
||||||
{Task: "task-b", Direct: true},
|
|
||||||
},
|
|
||||||
ExpectedGlobals: &taskfile.Vars{
|
|
||||||
OrderedMap: orderedmap.FromMapWithOrder(
|
|
||||||
map[string]taskfile.Var{
|
|
||||||
"FOO": {Value: "bar"},
|
|
||||||
},
|
|
||||||
[]string{"FOO"},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: nil,
|
|
||||||
ExpectedCalls: []taskfile.Call{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{},
|
|
||||||
ExpectedCalls: []taskfile.Call{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Args: []string{"FOO=bar", "BAR=baz"},
|
|
||||||
ExpectedCalls: []taskfile.Call{},
|
|
||||||
ExpectedGlobals: &taskfile.Vars{
|
|
||||||
OrderedMap: orderedmap.FromMapWithOrder(
|
|
||||||
map[string]taskfile.Var{
|
|
||||||
"FOO": {Value: "bar"},
|
|
||||||
"BAR": {Value: "baz"},
|
|
||||||
},
|
|
||||||
[]string{"FOO", "BAR"},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
t.Run(fmt.Sprintf("TestArgs%d", i+1), func(t *testing.T) {
|
|
||||||
calls, globals := args.ParseV2(test.Args...)
|
|
||||||
assert.Equal(t, test.ExpectedCalls, calls)
|
|
||||||
if test.ExpectedGlobals.Len() > 0 || globals.Len() > 0 {
|
|
||||||
assert.Equal(t, test.ExpectedGlobals, globals)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -300,11 +300,7 @@ func run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Taskfile.Version.Compare(taskfile.V3) >= 0 {
|
calls, globals = args.Parse(tasksAndVars...)
|
||||||
calls, globals = args.ParseV3(tasksAndVars...)
|
|
||||||
} else {
|
|
||||||
calls, globals = args.ParseV2(tasksAndVars...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no calls, run the default task instead
|
// If there are no calls, run the default task instead
|
||||||
if len(calls) == 0 {
|
if len(calls) == 0 {
|
||||||
|
@ -13,18 +13,21 @@ This deprecation breaks the following functionality:
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
The Taskfile v2 schema was introduced in March 2018 and replaced by version 3 in
|
The Taskfile version 2 schema was introduced in March 2018 and replaced by
|
||||||
August the following year. Users have had a long time to update and so we feel
|
version 3 in August 2019. In May 2023 [we published a deprecation
|
||||||
that it is time to tidy up the codebase and focus on new functionality instead.
|
notice][deprecation-notice] for the version 2 schema on the basis that the vast
|
||||||
|
majority of users had already upgraded to version 3 and removing support for
|
||||||
|
version 2 would allow us to tidy up the codebase and focus on new functionality
|
||||||
|
instead.
|
||||||
|
|
||||||
This notice does not mean that we are immediately removing support for version 2
|
In December 2023, the final version of Task that supports the version 2 schema
|
||||||
schemas. However, support will not be extended to future major releases and we
|
([v3.33.0][v3.33.0]) was published and all legacy code was removed from Task's
|
||||||
_strongly recommend_ that anybody still using a version 2 schema upgrades to
|
main branch. To use a more recent version of Task, you will need to ensure that
|
||||||
version 3 as soon as possible.
|
your Taskfile uses the version 3 schema instead. A list of changes between
|
||||||
|
version 2 and version 3 are available in the [Task v3 Release Notes][v3.0.0].
|
||||||
A list of changes between version 2 and version 3 are available in the [Task v3
|
|
||||||
Release Notes][version-3-release-notes].
|
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
[version-3-release-notes]: https://github.com/go-task/task/releases/tag/v3.0.0
|
[v3.0.0]: https://github.com/go-task/task/releases/tag/v3.0.0
|
||||||
|
[v3.33.0]: https://github.com/go-task/task/releases/tag/v3.33.0
|
||||||
|
[deprecation-notice]: https://github.com/go-task/task/issues/1197
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
@ -82,8 +82,8 @@ tasks:
|
|||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
|
|
||||||
v2 schema support is [deprecated][deprecate-version-2-schema] and will be
|
v2 schemas are [no longer supported by the latest version of
|
||||||
removed in a future release.
|
Task][deprecate-version-2-schema].
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@ -106,8 +106,8 @@ Please check the [documentation][includes]
|
|||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
|
|
||||||
v2 schema support is [deprecated][deprecate-version-2-schema] and will be
|
v2 schemas are [no longer supported by the latest version of
|
||||||
removed in a future release.
|
Task][deprecate-version-2-schema].
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@ -125,8 +125,8 @@ includes:
|
|||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
|
|
||||||
v2 schema support is [deprecated][deprecate-version-2-schema] and will be
|
v2 schemas are [no longer supported by the latest version of
|
||||||
removed in a future release.
|
Task][deprecate-version-2-schema].
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@ -170,8 +170,8 @@ tasks:
|
|||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
|
|
||||||
v2 schema support is [deprecated][deprecate-version-2-schema] and will be
|
v2 schemas are [no longer supported by the latest version of
|
||||||
removed in a future release.
|
Task][deprecate-version-2-schema].
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@ -256,7 +256,7 @@ The variable priority order was also different:
|
|||||||
4. `Taskvars.yml` variables
|
4. `Taskvars.yml` variables
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
[deprecate-version-2-schema]: https://github.com/go-task/task/issues/1197
|
[deprecate-version-2-schema]: /deprecations/version-2-schema/
|
||||||
[output]: /usage#output-syntax
|
[output]: /usage#output-syntax
|
||||||
[ignore_errors]: /usage#ignore-errors
|
[ignore_errors]: /usage#ignore-errors
|
||||||
[includes]: /usage#including-other-taskfiles
|
[includes]: /usage#including-other-taskfiles
|
||||||
|
@ -1,15 +1,207 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
|
"github.com/go-task/task/v3/internal/templater"
|
||||||
|
"github.com/go-task/task/v3/internal/version"
|
||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compiler handles compilation of a task before its execution.
|
type Compiler struct {
|
||||||
// E.g. variable merger, template processing, etc.
|
Dir string
|
||||||
type Compiler interface {
|
UserWorkingDir string
|
||||||
GetTaskfileVariables() (*taskfile.Vars, error)
|
|
||||||
GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
TaskfileEnv *taskfile.Vars
|
||||||
FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error)
|
TaskfileVars *taskfile.Vars
|
||||||
HandleDynamicVar(v taskfile.Var, dir string) (string, error)
|
|
||||||
ResetCache()
|
Logger *logger.Logger
|
||||||
|
|
||||||
|
dynamicCache map[string]string
|
||||||
|
muDynamicCache sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) GetTaskfileVariables() (*taskfile.Vars, error) {
|
||||||
|
return c.getVariables(nil, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||||
|
return c.getVariables(t, &call, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||||
|
return c.getVariables(t, &call, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) getVariables(t *taskfile.Task, call *taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
|
||||||
|
result := GetEnviron()
|
||||||
|
if t != nil {
|
||||||
|
specialVars, err := c.getSpecialVars(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range specialVars {
|
||||||
|
result.Set(k, taskfile.Var{Value: v})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
|
||||||
|
return func(k string, v taskfile.Var) error {
|
||||||
|
tr := templater.Templater{Vars: result}
|
||||||
|
// Replace values
|
||||||
|
newVar := taskfile.Var{}
|
||||||
|
switch value := v.Value.(type) {
|
||||||
|
case string:
|
||||||
|
newVar.Value = tr.Replace(value)
|
||||||
|
default:
|
||||||
|
newVar.Value = value
|
||||||
|
}
|
||||||
|
newVar.Sh = tr.Replace(v.Sh)
|
||||||
|
newVar.Dir = v.Dir
|
||||||
|
// If the variable should not be evaluated, but is nil, set it to an empty string
|
||||||
|
// This stops empty interface errors when using the templater to replace values later
|
||||||
|
if !evaluateShVars && newVar.Value == nil {
|
||||||
|
result.Set(k, taskfile.Var{Value: ""})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// If the variable should not be evaluated and it is set, we can set it and return
|
||||||
|
if !evaluateShVars {
|
||||||
|
result.Set(k, taskfile.Var{Value: newVar.Value})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
|
||||||
|
if err := tr.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// If the variable is not dynamic, we can set it and return
|
||||||
|
if newVar.Value != nil || newVar.Sh == "" {
|
||||||
|
result.Set(k, taskfile.Var{Value: newVar.Value})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// If the variable is dynamic, we need to resolve it first
|
||||||
|
static, err := c.HandleDynamicVar(newVar, dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result.Set(k, taskfile.Var{Value: static})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rangeFunc := getRangeFunc(c.Dir)
|
||||||
|
|
||||||
|
var taskRangeFunc func(k string, v taskfile.Var) error
|
||||||
|
if t != nil {
|
||||||
|
// NOTE(@andreynering): We're manually joining these paths here because
|
||||||
|
// this is the raw task, not the compiled one.
|
||||||
|
tr := templater.Templater{Vars: result}
|
||||||
|
dir := tr.Replace(t.Dir)
|
||||||
|
if err := tr.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dir = filepathext.SmartJoin(c.Dir, dir)
|
||||||
|
taskRangeFunc = getRangeFunc(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.TaskfileEnv.Range(rangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
if err := t.IncludedTaskfileVars.Range(taskRangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := t.IncludeVars.Range(rangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t == nil || call == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := call.Vars.Range(rangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := t.Vars.Range(taskRangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) HandleDynamicVar(v taskfile.Var, dir string) (string, error) {
|
||||||
|
c.muDynamicCache.Lock()
|
||||||
|
defer c.muDynamicCache.Unlock()
|
||||||
|
|
||||||
|
if c.dynamicCache == nil {
|
||||||
|
c.dynamicCache = make(map[string]string, 30)
|
||||||
|
}
|
||||||
|
if result, ok := c.dynamicCache[v.Sh]; ok {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(@andreynering): If a var have a specific dir, use this instead
|
||||||
|
if v.Dir != "" {
|
||||||
|
dir = v.Dir
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
opts := &execext.RunCommandOptions{
|
||||||
|
Command: v.Sh,
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &stdout,
|
||||||
|
Stderr: c.Logger.Stderr,
|
||||||
|
}
|
||||||
|
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
||||||
|
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim a single trailing newline from the result to make most command
|
||||||
|
// output easier to use in shell commands.
|
||||||
|
result := strings.TrimSuffix(stdout.String(), "\r\n")
|
||||||
|
result = strings.TrimSuffix(result, "\n")
|
||||||
|
|
||||||
|
c.dynamicCache[v.Sh] = result
|
||||||
|
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", v.Sh, result)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetCache clear the dymanic variables cache
|
||||||
|
func (c *Compiler) ResetCache() {
|
||||||
|
c.muDynamicCache.Lock()
|
||||||
|
defer c.muDynamicCache.Unlock()
|
||||||
|
|
||||||
|
c.dynamicCache = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) getSpecialVars(t *taskfile.Task) (map[string]string, error) {
|
||||||
|
taskfileDir, err := c.getTaskfileDir(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]string{
|
||||||
|
"TASK": t.Task,
|
||||||
|
"ROOT_DIR": c.Dir,
|
||||||
|
"TASKFILE_DIR": taskfileDir,
|
||||||
|
"USER_WORKING_DIR": c.UserWorkingDir,
|
||||||
|
"TASK_VERSION": version.GetVersion(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) getTaskfileDir(t *taskfile.Task) (string, error) {
|
||||||
|
if t.IncludedTaskfile != nil {
|
||||||
|
return t.IncludedTaskfile.FullDirPath()
|
||||||
|
}
|
||||||
|
return c.Dir, nil
|
||||||
}
|
}
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/compiler"
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ compiler.Compiler = &CompilerV2{}
|
|
||||||
|
|
||||||
type CompilerV2 struct {
|
|
||||||
Dir string
|
|
||||||
|
|
||||||
Taskvars *taskfile.Vars
|
|
||||||
TaskfileVars *taskfile.Vars
|
|
||||||
|
|
||||||
Expansions int
|
|
||||||
|
|
||||||
Logger *logger.Logger
|
|
||||||
|
|
||||||
dynamicCache map[string]string
|
|
||||||
muDynamicCache sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV2) GetTaskfileVariables() (*taskfile.Vars, error) {
|
|
||||||
return &taskfile.Vars{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FastGetVariables is a no-op on v2
|
|
||||||
func (c *CompilerV2) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
|
||||||
return c.GetVariables(t, call)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVariables returns fully resolved variables following the priority order:
|
|
||||||
// 1. Task variables
|
|
||||||
// 2. Call variables
|
|
||||||
// 3. Taskfile variables
|
|
||||||
// 4. Taskvars file variables
|
|
||||||
// 5. Environment variables
|
|
||||||
func (c *CompilerV2) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
|
||||||
vr := varResolver{
|
|
||||||
c: c,
|
|
||||||
vars: compiler.GetEnviron(),
|
|
||||||
}
|
|
||||||
vr.vars.Set("TASK", taskfile.Var{Value: t.Task})
|
|
||||||
|
|
||||||
for _, vars := range []*taskfile.Vars{c.Taskvars, c.TaskfileVars, call.Vars, t.Vars} {
|
|
||||||
for i := 0; i < c.Expansions; i++ {
|
|
||||||
vr.merge(vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vr.vars, vr.err
|
|
||||||
}
|
|
||||||
|
|
||||||
type varResolver struct {
|
|
||||||
c *CompilerV2
|
|
||||||
vars *taskfile.Vars
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vr *varResolver) merge(vars *taskfile.Vars) {
|
|
||||||
if vr.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tr := templater.Templater{Vars: vr.vars}
|
|
||||||
_ = vars.Range(func(k string, v taskfile.Var) error {
|
|
||||||
// Replace values
|
|
||||||
newVar := taskfile.Var{}
|
|
||||||
switch value := v.Value.(type) {
|
|
||||||
case string:
|
|
||||||
newVar.Value = tr.Replace(value)
|
|
||||||
default:
|
|
||||||
newVar.Value = value
|
|
||||||
}
|
|
||||||
newVar.Sh = tr.Replace(v.Sh)
|
|
||||||
// If the variable is not dynamic, we can set it and return
|
|
||||||
if newVar.Value != nil || newVar.Sh == "" {
|
|
||||||
vr.vars.Set(k, taskfile.Var{Value: newVar.Value})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// If the variable is dynamic, we need to resolve it first
|
|
||||||
static, err := vr.c.HandleDynamicVar(newVar, "")
|
|
||||||
if err != nil {
|
|
||||||
vr.err = err
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
vr.vars.Set(k, taskfile.Var{Value: static})
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
vr.err = tr.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV2) HandleDynamicVar(v taskfile.Var, _ string) (string, error) {
|
|
||||||
c.muDynamicCache.Lock()
|
|
||||||
defer c.muDynamicCache.Unlock()
|
|
||||||
|
|
||||||
if c.dynamicCache == nil {
|
|
||||||
c.dynamicCache = make(map[string]string, 30)
|
|
||||||
}
|
|
||||||
if result, ok := c.dynamicCache[v.Sh]; ok {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
opts := &execext.RunCommandOptions{
|
|
||||||
Command: v.Sh,
|
|
||||||
Stdout: &stdout,
|
|
||||||
Stderr: c.Logger.Stderr,
|
|
||||||
}
|
|
||||||
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
|
||||||
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim a single trailing newline from the result to make most command
|
|
||||||
// output easier to use in shell commands.
|
|
||||||
result := strings.TrimSuffix(stdout.String(), "\n")
|
|
||||||
|
|
||||||
c.dynamicCache[v.Sh] = result
|
|
||||||
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", v.Sh, result)
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetCache clear the dymanic variables cache
|
|
||||||
func (c *CompilerV2) ResetCache() {
|
|
||||||
c.muDynamicCache.Lock()
|
|
||||||
defer c.muDynamicCache.Unlock()
|
|
||||||
|
|
||||||
c.dynamicCache = nil
|
|
||||||
}
|
|
@ -1,210 +0,0 @@
|
|||||||
package v3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/compiler"
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
|
||||||
"github.com/go-task/task/v3/internal/templater"
|
|
||||||
"github.com/go-task/task/v3/internal/version"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ compiler.Compiler = &CompilerV3{}
|
|
||||||
|
|
||||||
type CompilerV3 struct {
|
|
||||||
Dir string
|
|
||||||
UserWorkingDir string
|
|
||||||
|
|
||||||
TaskfileEnv *taskfile.Vars
|
|
||||||
TaskfileVars *taskfile.Vars
|
|
||||||
|
|
||||||
Logger *logger.Logger
|
|
||||||
|
|
||||||
dynamicCache map[string]string
|
|
||||||
muDynamicCache sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) GetTaskfileVariables() (*taskfile.Vars, error) {
|
|
||||||
return c.getVariables(nil, nil, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
|
||||||
return c.getVariables(t, &call, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) FastGetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
|
||||||
return c.getVariables(t, &call, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) getVariables(t *taskfile.Task, call *taskfile.Call, evaluateShVars bool) (*taskfile.Vars, error) {
|
|
||||||
result := compiler.GetEnviron()
|
|
||||||
if t != nil {
|
|
||||||
specialVars, err := c.getSpecialVars(t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for k, v := range specialVars {
|
|
||||||
result.Set(k, taskfile.Var{Value: v})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getRangeFunc := func(dir string) func(k string, v taskfile.Var) error {
|
|
||||||
return func(k string, v taskfile.Var) error {
|
|
||||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
|
||||||
// Replace values
|
|
||||||
newVar := taskfile.Var{}
|
|
||||||
switch value := v.Value.(type) {
|
|
||||||
case string:
|
|
||||||
newVar.Value = tr.Replace(value)
|
|
||||||
default:
|
|
||||||
newVar.Value = value
|
|
||||||
}
|
|
||||||
newVar.Sh = tr.Replace(v.Sh)
|
|
||||||
newVar.Dir = v.Dir
|
|
||||||
// If the variable should not be evaluated, but is nil, set it to an empty string
|
|
||||||
// This stops empty interface errors when using the templater to replace values later
|
|
||||||
if !evaluateShVars && newVar.Value == nil {
|
|
||||||
result.Set(k, taskfile.Var{Value: ""})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// If the variable should not be evaluated and it is set, we can set it and return
|
|
||||||
if !evaluateShVars {
|
|
||||||
result.Set(k, taskfile.Var{Value: newVar.Value})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
|
|
||||||
if err := tr.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// If the variable is not dynamic, we can set it and return
|
|
||||||
if newVar.Value != nil || newVar.Sh == "" {
|
|
||||||
result.Set(k, taskfile.Var{Value: newVar.Value})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// If the variable is dynamic, we need to resolve it first
|
|
||||||
static, err := c.HandleDynamicVar(newVar, dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
result.Set(k, taskfile.Var{Value: static})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rangeFunc := getRangeFunc(c.Dir)
|
|
||||||
|
|
||||||
var taskRangeFunc func(k string, v taskfile.Var) error
|
|
||||||
if t != nil {
|
|
||||||
// NOTE(@andreynering): We're manually joining these paths here because
|
|
||||||
// this is the raw task, not the compiled one.
|
|
||||||
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
|
||||||
dir := tr.Replace(t.Dir)
|
|
||||||
if err := tr.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dir = filepathext.SmartJoin(c.Dir, dir)
|
|
||||||
taskRangeFunc = getRangeFunc(dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.TaskfileEnv.Range(rangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if t != nil {
|
|
||||||
if err := t.IncludedTaskfileVars.Range(taskRangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := t.IncludeVars.Range(rangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if t == nil || call == nil {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := call.Vars.Range(rangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := t.Vars.Range(taskRangeFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) HandleDynamicVar(v taskfile.Var, dir string) (string, error) {
|
|
||||||
c.muDynamicCache.Lock()
|
|
||||||
defer c.muDynamicCache.Unlock()
|
|
||||||
|
|
||||||
if c.dynamicCache == nil {
|
|
||||||
c.dynamicCache = make(map[string]string, 30)
|
|
||||||
}
|
|
||||||
if result, ok := c.dynamicCache[v.Sh]; ok {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(@andreynering): If a var have a specific dir, use this instead
|
|
||||||
if v.Dir != "" {
|
|
||||||
dir = v.Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
opts := &execext.RunCommandOptions{
|
|
||||||
Command: v.Sh,
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &stdout,
|
|
||||||
Stderr: c.Logger.Stderr,
|
|
||||||
}
|
|
||||||
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
|
||||||
return "", fmt.Errorf(`task: Command "%s" failed: %s`, opts.Command, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trim a single trailing newline from the result to make most command
|
|
||||||
// output easier to use in shell commands.
|
|
||||||
result := strings.TrimSuffix(stdout.String(), "\r\n")
|
|
||||||
result = strings.TrimSuffix(result, "\n")
|
|
||||||
|
|
||||||
c.dynamicCache[v.Sh] = result
|
|
||||||
c.Logger.VerboseErrf(logger.Magenta, "task: dynamic variable: %q result: %q\n", v.Sh, result)
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetCache clear the dymanic variables cache
|
|
||||||
func (c *CompilerV3) ResetCache() {
|
|
||||||
c.muDynamicCache.Lock()
|
|
||||||
defer c.muDynamicCache.Unlock()
|
|
||||||
|
|
||||||
c.dynamicCache = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) getSpecialVars(t *taskfile.Task) (map[string]string, error) {
|
|
||||||
taskfileDir, err := c.getTaskfileDir(t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]string{
|
|
||||||
"TASK": t.Task,
|
|
||||||
"ROOT_DIR": c.Dir,
|
|
||||||
"TASKFILE_DIR": taskfileDir,
|
|
||||||
"USER_WORKING_DIR": c.UserWorkingDir,
|
|
||||||
"TASK_VERSION": version.GetVersion(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CompilerV3) getTaskfileDir(t *taskfile.Task) (string, error) {
|
|
||||||
if t.IncludedTaskfile != nil {
|
|
||||||
return t.IncludedTaskfile.FullDirPath()
|
|
||||||
}
|
|
||||||
return c.Dir, nil
|
|
||||||
}
|
|
@ -15,8 +15,7 @@ import (
|
|||||||
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
// happen will be assigned to r.err, and consecutive calls to funcs will just
|
||||||
// return the zero value.
|
// return the zero value.
|
||||||
type Templater struct {
|
type Templater struct {
|
||||||
Vars *taskfile.Vars
|
Vars *taskfile.Vars
|
||||||
RemoveNoValue bool
|
|
||||||
|
|
||||||
cacheMap map[string]any
|
cacheMap map[string]any
|
||||||
err error
|
err error
|
||||||
@ -62,10 +61,7 @@ func (r *Templater) replace(str string, extra map[string]any) string {
|
|||||||
r.err = err
|
r.err = err
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if r.RemoveNoValue {
|
return strings.ReplaceAll(b.String(), "<no value>", "")
|
||||||
return strings.ReplaceAll(b.String(), "<no value>", "")
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Templater) ReplaceSlice(strs []string) []string {
|
func (r *Templater) ReplaceSlice(strs []string) []string {
|
||||||
|
103
setup.go
103
setup.go
@ -12,8 +12,7 @@ import (
|
|||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/sajari/fuzzy"
|
"github.com/sajari/fuzzy"
|
||||||
|
|
||||||
compilerv2 "github.com/go-task/task/v3/internal/compiler/v2"
|
"github.com/go-task/task/v3/internal/compiler"
|
||||||
compilerv3 "github.com/go-task/task/v3/internal/compiler/v3"
|
|
||||||
"github.com/go-task/task/v3/internal/execext"
|
"github.com/go-task/task/v3/internal/execext"
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
"github.com/go-task/task/v3/internal/filepathext"
|
||||||
"github.com/go-task/task/v3/internal/logger"
|
"github.com/go-task/task/v3/internal/logger"
|
||||||
@ -179,38 +178,21 @@ func (e *Executor) setupOutput() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) setupCompiler() error {
|
func (e *Executor) setupCompiler() error {
|
||||||
if e.Taskfile.Version.LessThan(taskfile.V3) {
|
if e.UserWorkingDir == "" {
|
||||||
var err error
|
var err error
|
||||||
e.taskvars, err = read.Taskvars(e.Dir)
|
e.UserWorkingDir, err = os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Compiler = &compilerv2.CompilerV2{
|
|
||||||
Dir: e.Dir,
|
|
||||||
Taskvars: e.taskvars,
|
|
||||||
TaskfileVars: e.Taskfile.Vars,
|
|
||||||
Expansions: e.Taskfile.Expansions,
|
|
||||||
Logger: e.Logger,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if e.UserWorkingDir == "" {
|
|
||||||
var err error
|
|
||||||
e.UserWorkingDir, err = os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Compiler = &compilerv3.CompilerV3{
|
|
||||||
Dir: e.Dir,
|
|
||||||
UserWorkingDir: e.UserWorkingDir,
|
|
||||||
TaskfileEnv: e.Taskfile.Env,
|
|
||||||
TaskfileVars: e.Taskfile.Vars,
|
|
||||||
Logger: e.Logger,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.Compiler = &compiler.Compiler{
|
||||||
|
Dir: e.Dir,
|
||||||
|
UserWorkingDir: e.UserWorkingDir,
|
||||||
|
TaskfileEnv: e.Taskfile.Env,
|
||||||
|
TaskfileVars: e.Taskfile.Vars,
|
||||||
|
Logger: e.Logger,
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,19 +216,9 @@ func (e *Executor) readDotEnvFiles() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) setupDefaults() {
|
func (e *Executor) setupDefaults() {
|
||||||
// Color available only on v3
|
|
||||||
if e.Taskfile.Version.LessThan(taskfile.V3) {
|
|
||||||
e.Logger.Color = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Taskfile.Method == "" {
|
if e.Taskfile.Method == "" {
|
||||||
if e.Taskfile.Version.Compare(taskfile.V3) >= 0 {
|
e.Taskfile.Method = "checksum"
|
||||||
e.Taskfile.Method = "checksum"
|
|
||||||
} else {
|
|
||||||
e.Taskfile.Method = "timestamp"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Taskfile.Run == "" {
|
if e.Taskfile.Run == "" {
|
||||||
e.Taskfile.Run = "always"
|
e.Taskfile.Run = "always"
|
||||||
}
|
}
|
||||||
@ -272,18 +244,11 @@ func (e *Executor) doVersionChecks() error {
|
|||||||
v := &semver.Version{}
|
v := &semver.Version{}
|
||||||
*v = *e.Taskfile.Version
|
*v = *e.Taskfile.Version
|
||||||
|
|
||||||
if v.LessThan(taskfile.V2) {
|
|
||||||
return fmt.Errorf(`task: version 1 schemas are no longer supported`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.LessThan(taskfile.V3) {
|
if v.LessThan(taskfile.V3) {
|
||||||
e.Logger.Errf(logger.Yellow, "task: version 2 schemas are deprecated and will be removed in a future release\nSee https://github.com/go-task/task/issues/1197 for more details\n")
|
return fmt.Errorf(`task: Taskfile schemas prior to v3 are no longer supported`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// consider as equal to the greater version if round
|
// consider as equal to the greater version if round
|
||||||
if v.Equal(taskfile.V2) {
|
|
||||||
v = semver.MustParse("2.6")
|
|
||||||
}
|
|
||||||
if v.Equal(taskfile.V3) {
|
if v.Equal(taskfile.V3) {
|
||||||
v = semver.MustParse("3.8")
|
v = semver.MustParse("3.8")
|
||||||
}
|
}
|
||||||
@ -292,54 +257,10 @@ func (e *Executor) doVersionChecks() error {
|
|||||||
return fmt.Errorf(`task: Taskfile versions greater than v3.8 not implemented in the version of Task`)
|
return fmt.Errorf(`task: Taskfile versions greater than v3.8 not implemented in the version of Task`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.LessThan(semver.MustParse("2.1")) && !e.Taskfile.Output.IsSet() {
|
|
||||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
|
||||||
}
|
|
||||||
if v.LessThan(semver.MustParse("2.2")) && e.Taskfile.Includes.Len() > 0 {
|
|
||||||
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
|
|
||||||
}
|
|
||||||
if v.Compare(taskfile.V3) >= 0 && e.Taskfile.Expansions > 2 {
|
|
||||||
return fmt.Errorf(`task: The "expansions" setting is not available anymore on v3.0`)
|
|
||||||
}
|
|
||||||
if v.LessThan(semver.MustParse("3.8")) && e.Taskfile.Output.Group.IsSet() {
|
if v.LessThan(semver.MustParse("3.8")) && e.Taskfile.Output.Group.IsSet() {
|
||||||
return fmt.Errorf(`task: Taskfile option "output.group" is only available starting on Taskfile version v3.8`)
|
return fmt.Errorf(`task: Taskfile option "output.group" is only available starting on Taskfile version v3.8`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Compare(semver.MustParse("2.1")) <= 0 {
|
|
||||||
err := errors.New(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)
|
|
||||||
|
|
||||||
for _, task := range e.Taskfile.Tasks.Values() {
|
|
||||||
if task.IgnoreError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, cmd := range task.Cmds {
|
|
||||||
if cmd.IgnoreError {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.LessThan(semver.MustParse("2.6")) {
|
|
||||||
for _, task := range e.Taskfile.Tasks.Values() {
|
|
||||||
if len(task.Preconditions) > 0 {
|
|
||||||
return errors.New(`task: Task option "preconditions" is only available starting on Taskfile version v2.6`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.LessThan(taskfile.V3) {
|
|
||||||
err := e.Taskfile.Includes.Range(func(_ string, taskfile taskfile.IncludedTaskfile) error {
|
|
||||||
if taskfile.AdvancedImport {
|
|
||||||
return errors.New(`task: Import with additional parameters is only available starting on Taskfile version v3`)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.LessThan(semver.MustParse("3.7")) {
|
if v.LessThan(semver.MustParse("3.7")) {
|
||||||
if e.Taskfile.Run != "" {
|
if e.Taskfile.Run != "" {
|
||||||
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
|
return errors.New(`task: Setting the "run" type is only available starting on Taskfile version v3.7`)
|
||||||
|
5
task.go
5
task.go
@ -64,13 +64,12 @@ type Executor struct {
|
|||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
Logger *logger.Logger
|
Logger *logger.Logger
|
||||||
Compiler compiler.Compiler
|
Compiler *compiler.Compiler
|
||||||
Output output.Output
|
Output output.Output
|
||||||
OutputStyle taskfile.Output
|
OutputStyle taskfile.Output
|
||||||
TaskSorter sort.TaskSorter
|
TaskSorter sort.TaskSorter
|
||||||
UserWorkingDir string
|
UserWorkingDir string
|
||||||
|
|
||||||
taskvars *taskfile.Vars
|
|
||||||
fuzzyModel *fuzzy.Model
|
fuzzyModel *fuzzy.Model
|
||||||
|
|
||||||
concurrencySemaphore chan struct{}
|
concurrencySemaphore chan struct{}
|
||||||
@ -349,7 +348,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
|
|||||||
outputWrapper = output.Interleaved{}
|
outputWrapper = output.Interleaved{}
|
||||||
}
|
}
|
||||||
vars, err := e.Compiler.FastGetVariables(t, call)
|
vars, err := e.Compiler.FastGetVariables(t, call)
|
||||||
outputTemplater := &templater.Templater{Vars: vars, RemoveNoValue: true}
|
outputTemplater := &templater.Templater{Vars: vars}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("task: failed to get variables: %w", err)
|
return fmt.Errorf("task: failed to get variables: %w", err)
|
||||||
}
|
}
|
||||||
|
116
task_test.go
116
task_test.go
@ -91,47 +91,9 @@ func TestEnv(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVarsV2(t *testing.T) {
|
func TestVars(t *testing.T) {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
Dir: "testdata/vars/v2",
|
Dir: "testdata/vars",
|
||||||
Target: "default",
|
|
||||||
TrimSpace: true,
|
|
||||||
Files: map[string]string{
|
|
||||||
"foo.txt": "foo",
|
|
||||||
"bar.txt": "bar",
|
|
||||||
"baz.txt": "baz",
|
|
||||||
"tmpl_foo.txt": "foo",
|
|
||||||
"tmpl_bar.txt": "bar",
|
|
||||||
"tmpl_foo2.txt": "foo2",
|
|
||||||
"tmpl_bar2.txt": "bar2",
|
|
||||||
"shtmpl_foo.txt": "foo",
|
|
||||||
"shtmpl_foo2.txt": "foo2",
|
|
||||||
"nestedtmpl_foo.txt": "<no value>",
|
|
||||||
"nestedtmpl_foo2.txt": "foo2",
|
|
||||||
"foo2.txt": "foo2",
|
|
||||||
"bar2.txt": "bar2",
|
|
||||||
"baz2.txt": "baz2",
|
|
||||||
"tmpl2_foo.txt": "<no value>",
|
|
||||||
"tmpl2_foo2.txt": "foo2",
|
|
||||||
"tmpl2_bar.txt": "<no value>",
|
|
||||||
"tmpl2_bar2.txt": "bar2",
|
|
||||||
"shtmpl2_foo.txt": "<no value>",
|
|
||||||
"shtmpl2_foo2.txt": "foo2",
|
|
||||||
"nestedtmpl2_foo2.txt": "<no value>",
|
|
||||||
"override.txt": "bar",
|
|
||||||
"nested.txt": "Taskvars-TaskfileVars-TaskVars",
|
|
||||||
"task_name.txt": "hello",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
// Ensure identical results when running hello task directly.
|
|
||||||
tt.Target = "hello"
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVarsV3(t *testing.T) {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: "testdata/vars/v3",
|
|
||||||
Target: "default",
|
Target: "default",
|
||||||
Files: map[string]string{
|
Files: map[string]string{
|
||||||
"missing-var.txt": "\n",
|
"missing-var.txt": "\n",
|
||||||
@ -144,29 +106,6 @@ func TestVarsV3(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultilineVars(t *testing.T) {
|
|
||||||
for _, dir := range []string{"testdata/vars/v2/multiline"} {
|
|
||||||
tt := fileContentTest{
|
|
||||||
Dir: dir,
|
|
||||||
Target: "default",
|
|
||||||
TrimSpace: false,
|
|
||||||
Files: map[string]string{
|
|
||||||
// Note:
|
|
||||||
// - task does not strip a trailing newline from var entries
|
|
||||||
// - task strips one trailing newline from shell output
|
|
||||||
// - the cat command adds a trailing newline
|
|
||||||
"echo_foobar.txt": "foo\nbar\n",
|
|
||||||
"echo_n_foobar.txt": "foo\nbar\n",
|
|
||||||
"echo_n_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n",
|
|
||||||
"var_multiline.txt": "\n\nfoo\n bar\nfoobar\n\nbaz\n\n\n",
|
|
||||||
"var_catlines.txt": " foo bar foobar baz \n",
|
|
||||||
"var_enumfile.txt": "0:\n1:\n2:foo\n3: bar\n4:foobar\n5:\n6:baz\n7:\n8:\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tt.Run(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSpecialVars(t *testing.T) {
|
func TestSpecialVars(t *testing.T) {
|
||||||
const dir = "testdata/special_vars"
|
const dir = "testdata/special_vars"
|
||||||
const target = "default"
|
const target = "default"
|
||||||
@ -202,22 +141,6 @@ func TestSpecialVars(t *testing.T) {
|
|||||||
assert.Contains(t, output, "included/TASK_VERSION=unknown")
|
assert.Contains(t, output, "included/TASK_VERSION=unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVarsInvalidTmpl(t *testing.T) {
|
|
||||||
const (
|
|
||||||
dir = "testdata/vars/v2"
|
|
||||||
target = "invalid-var-tmpl"
|
|
||||||
expectError = "template: :1: unexpected EOF"
|
|
||||||
)
|
|
||||||
|
|
||||||
e := &task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: io.Discard,
|
|
||||||
Stderr: io.Discard,
|
|
||||||
}
|
|
||||||
require.NoError(t, e.Setup(), "e.Setup()")
|
|
||||||
assert.EqualError(t, e.Run(context.Background(), taskfile.Call{Task: target}), expectError, "e.Run(target)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConcurrency(t *testing.T) {
|
func TestConcurrency(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
dir = "testdata/concurrency"
|
dir = "testdata/concurrency"
|
||||||
@ -910,8 +833,11 @@ func TestTaskVersion(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
Dir string
|
Dir string
|
||||||
Version *semver.Version
|
Version *semver.Version
|
||||||
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{"testdata/version/v2", semver.MustParse("2")},
|
{"testdata/version/v1", semver.MustParse("1"), true},
|
||||||
|
{"testdata/version/v2", semver.MustParse("2"), true},
|
||||||
|
{"testdata/version/v3", semver.MustParse("3"), false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -921,7 +847,12 @@ func TestTaskVersion(t *testing.T) {
|
|||||||
Stdout: io.Discard,
|
Stdout: io.Discard,
|
||||||
Stderr: io.Discard,
|
Stderr: io.Discard,
|
||||||
}
|
}
|
||||||
require.NoError(t, e.Setup())
|
err := e.Setup()
|
||||||
|
if test.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
assert.Equal(t, test.Version, e.Taskfile.Version)
|
assert.Equal(t, test.Version, e.Taskfile.Version)
|
||||||
assert.Equal(t, 2, e.Taskfile.Tasks.Len())
|
assert.Equal(t, 2, e.Taskfile.Tasks.Len())
|
||||||
})
|
})
|
||||||
@ -1061,21 +992,6 @@ func TestIncludeCycle(t *testing.T) {
|
|||||||
assert.Contains(t, err.Error(), "task: include cycle detected between")
|
assert.Contains(t, err.Error(), "task: include cycle detected between")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIncorrectVersionIncludes(t *testing.T) {
|
|
||||||
const dir = "testdata/incorrect_includes"
|
|
||||||
expectedError := "task: Import with additional parameters is only available starting on Taskfile version v3"
|
|
||||||
|
|
||||||
var buff bytes.Buffer
|
|
||||||
e := task.Executor{
|
|
||||||
Dir: dir,
|
|
||||||
Stdout: &buff,
|
|
||||||
Stderr: &buff,
|
|
||||||
Silent: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.EqualError(t, e.Setup(), expectedError)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIncludesIncorrect(t *testing.T) {
|
func TestIncludesIncorrect(t *testing.T) {
|
||||||
const dir = "testdata/includes_incorrect"
|
const dir = "testdata/includes_incorrect"
|
||||||
|
|
||||||
@ -1500,10 +1416,10 @@ func TestDisplaysErrorOnVersion1Schema(t *testing.T) {
|
|||||||
}
|
}
|
||||||
err := e.Setup()
|
err := e.Setup()
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, "task: version 1 schemas are no longer supported", err.Error())
|
assert.Equal(t, "task: Taskfile schemas prior to v3 are no longer supported", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDisplaysWarningOnVersion2Schema(t *testing.T) {
|
func TestDisplaysErrorOnVersion2Schema(t *testing.T) {
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
e := task.Executor{
|
e := task.Executor{
|
||||||
Dir: "testdata/version/v2",
|
Dir: "testdata/version/v2",
|
||||||
@ -1511,8 +1427,8 @@ func TestDisplaysWarningOnVersion2Schema(t *testing.T) {
|
|||||||
Stderr: &buff,
|
Stderr: &buff,
|
||||||
}
|
}
|
||||||
err := e.Setup()
|
err := e.Setup()
|
||||||
require.NoError(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, "task: version 2 schemas are deprecated and will be removed in a future release\nSee https://github.com/go-task/task/issues/1197 for more details\n", buff.String())
|
assert.Equal(t, "task: Taskfile schemas prior to v3 are no longer supported", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShortTaskNotation(t *testing.T) {
|
func TestShortTaskNotation(t *testing.T) {
|
||||||
|
@ -13,10 +13,6 @@ func Merge(t1, t2 *Taskfile, includedTaskfile *IncludedTaskfile, namespaces ...s
|
|||||||
if !t1.Version.Equal(t2.Version) {
|
if !t1.Version.Equal(t2.Version) {
|
||||||
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t2.Expansions != 0 && t2.Expansions != 2 {
|
|
||||||
t1.Expansions = t2.Expansions
|
|
||||||
}
|
|
||||||
if t2.Output.IsSet() {
|
if t2.Output.IsSet() {
|
||||||
t1.Output = t2.Output
|
t1.Output = t2.Output
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/go-task/task/v3/taskfile"
|
"github.com/go-task/task/v3/taskfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Dotenv(c compiler.Compiler, tf *taskfile.Taskfile, dir string) (*taskfile.Vars, error) {
|
func Dotenv(c *compiler.Compiler, tf *taskfile.Taskfile, dir string) (*taskfile.Vars, error) {
|
||||||
if len(tf.Dotenv) == 0 {
|
if len(tf.Dotenv) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ func Dotenv(c compiler.Compiler, tf *taskfile.Taskfile, dir string) (*taskfile.V
|
|||||||
|
|
||||||
env := &taskfile.Vars{}
|
env := &taskfile.Vars{}
|
||||||
|
|
||||||
tr := templater.Templater{Vars: vars, RemoveNoValue: true}
|
tr := templater.Templater{Vars: vars}
|
||||||
|
|
||||||
for _, dotEnvPath := range tf.Dotenv {
|
for _, dotEnvPath := range tf.Dotenv {
|
||||||
dotEnvPath = tr.Replace(dotEnvPath)
|
dotEnvPath = tr.Replace(dotEnvPath)
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@ -174,21 +173,19 @@ func Taskfile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
|
err = t.Includes.Range(func(namespace string, includedTask taskfile.IncludedTaskfile) error {
|
||||||
if t.Version.Compare(taskfile.V3) >= 0 {
|
tr := templater.Templater{Vars: t.Vars}
|
||||||
tr := templater.Templater{Vars: t.Vars, RemoveNoValue: true}
|
includedTask = taskfile.IncludedTaskfile{
|
||||||
includedTask = taskfile.IncludedTaskfile{
|
Taskfile: tr.Replace(includedTask.Taskfile),
|
||||||
Taskfile: tr.Replace(includedTask.Taskfile),
|
Dir: tr.Replace(includedTask.Dir),
|
||||||
Dir: tr.Replace(includedTask.Dir),
|
Optional: includedTask.Optional,
|
||||||
Optional: includedTask.Optional,
|
Internal: includedTask.Internal,
|
||||||
Internal: includedTask.Internal,
|
Aliases: includedTask.Aliases,
|
||||||
Aliases: includedTask.Aliases,
|
AdvancedImport: includedTask.AdvancedImport,
|
||||||
AdvancedImport: includedTask.AdvancedImport,
|
Vars: includedTask.Vars,
|
||||||
Vars: includedTask.Vars,
|
BaseDir: includedTask.BaseDir,
|
||||||
BaseDir: includedTask.BaseDir,
|
}
|
||||||
}
|
if err := tr.Err(); err != nil {
|
||||||
if err := tr.Err(); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uri, err := includedTask.FullTaskfilePath()
|
uri, err := includedTask.FullTaskfilePath()
|
||||||
@ -219,7 +216,7 @@ func Taskfile(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Version.Compare(taskfile.V3) >= 0 && len(includedTaskfile.Dotenv) > 0 {
|
if len(includedTaskfile.Dotenv) > 0 {
|
||||||
return ErrIncludedTaskfilesCantHaveDotenvs
|
return ErrIncludedTaskfilesCantHaveDotenvs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,31 +270,6 @@ func Taskfile(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Version.Compare(taskfile.V3) < 0 {
|
|
||||||
if node, isFileNode := node.(*FileNode); isFileNode {
|
|
||||||
path := filepathext.SmartJoin(node.Dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
|
|
||||||
if _, err = os.Stat(path); err == nil {
|
|
||||||
osNode := &FileNode{
|
|
||||||
BaseNode: NewBaseNode(WithParent(node)),
|
|
||||||
Entrypoint: path,
|
|
||||||
Dir: node.Dir,
|
|
||||||
}
|
|
||||||
b, err := osNode.Read(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var osTaskfile *taskfile.Taskfile
|
|
||||||
if err := yaml.Unmarshal(b, &osTaskfile); err != nil {
|
|
||||||
return nil, &errors.TaskfileInvalidError{URI: filepathext.TryAbsToRel(node.Location()), Err: err}
|
|
||||||
}
|
|
||||||
t.Location = node.Location()
|
|
||||||
if err = taskfile.Merge(t, osTaskfile, nil); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, task := range t.Tasks.Values() {
|
for _, task := range t.Tasks.Values() {
|
||||||
// If the task is not defined, create a new one
|
// If the task is not defined, create a new one
|
||||||
if task == nil {
|
if task == nil {
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
package read
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
"github.com/go-task/task/v3/internal/filepathext"
|
|
||||||
"github.com/go-task/task/v3/taskfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Taskvars reads a Taskvars for a given directory
|
|
||||||
func Taskvars(dir string) (*taskfile.Vars, error) {
|
|
||||||
vars := &taskfile.Vars{}
|
|
||||||
|
|
||||||
path := filepathext.SmartJoin(dir, "Taskvars.yml")
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
vars, err = readTaskvars(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
path = filepathext.SmartJoin(dir, fmt.Sprintf("Taskvars_%s.yml", runtime.GOOS))
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
|
||||||
osVars, err := readTaskvars(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
vars.Merge(osVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
return vars, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTaskvars(file string) (*taskfile.Vars, error) {
|
|
||||||
f, err := os.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var vars taskfile.Vars
|
|
||||||
return &vars, yaml.NewDecoder(f).Decode(&vars)
|
|
||||||
}
|
|
@ -10,54 +10,48 @@ import (
|
|||||||
"github.com/go-task/task/v3/errors"
|
"github.com/go-task/task/v3/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var V3 = semver.MustParse("3")
|
||||||
V3 = semver.MustParse("3")
|
|
||||||
V2 = semver.MustParse("2")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Taskfile represents a Taskfile.yml
|
// Taskfile represents a Taskfile.yml
|
||||||
type Taskfile struct {
|
type Taskfile struct {
|
||||||
Location string
|
Location string
|
||||||
Version *semver.Version
|
Version *semver.Version
|
||||||
Expansions int
|
Output Output
|
||||||
Output Output
|
Method string
|
||||||
Method string
|
Includes *IncludedTaskfiles
|
||||||
Includes *IncludedTaskfiles
|
Set []string
|
||||||
Set []string
|
Shopt []string
|
||||||
Shopt []string
|
Vars *Vars
|
||||||
Vars *Vars
|
Env *Vars
|
||||||
Env *Vars
|
Tasks Tasks
|
||||||
Tasks Tasks
|
Silent bool
|
||||||
Silent bool
|
Dotenv []string
|
||||||
Dotenv []string
|
Run string
|
||||||
Run string
|
Interval time.Duration
|
||||||
Interval time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error {
|
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
|
||||||
Expansions int
|
Output Output
|
||||||
Output Output
|
Method string
|
||||||
Method string
|
Includes *IncludedTaskfiles
|
||||||
Includes *IncludedTaskfiles
|
Set []string
|
||||||
Set []string
|
Shopt []string
|
||||||
Shopt []string
|
Vars *Vars
|
||||||
Vars *Vars
|
Env *Vars
|
||||||
Env *Vars
|
Tasks Tasks
|
||||||
Tasks Tasks
|
Silent bool
|
||||||
Silent bool
|
Dotenv []string
|
||||||
Dotenv []string
|
Run string
|
||||||
Run string
|
Interval time.Duration
|
||||||
Interval time.Duration
|
|
||||||
}
|
}
|
||||||
if err := node.Decode(&taskfile); err != nil {
|
if err := node.Decode(&taskfile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tf.Version = taskfile.Version
|
tf.Version = taskfile.Version
|
||||||
tf.Expansions = taskfile.Expansions
|
|
||||||
tf.Output = taskfile.Output
|
tf.Output = taskfile.Output
|
||||||
tf.Method = taskfile.Method
|
tf.Method = taskfile.Method
|
||||||
tf.Includes = taskfile.Includes
|
tf.Includes = taskfile.Includes
|
||||||
@ -70,9 +64,6 @@ 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
|
||||||
if tf.Expansions <= 0 {
|
|
||||||
tf.Expansions = 2
|
|
||||||
}
|
|
||||||
if tf.Version == nil {
|
if tf.Version == nil {
|
||||||
return errors.New("task: 'version' is required")
|
return errors.New("task: 'version' is required")
|
||||||
}
|
}
|
||||||
|
2
testdata/concurrency/Taskfile.yml
vendored
2
testdata/concurrency/Taskfile.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: "3"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
default:
|
default:
|
||||||
|
10
testdata/incorrect_includes/Taskfile.yml
vendored
10
testdata/incorrect_includes/Taskfile.yml
vendored
@ -1,10 +0,0 @@
|
|||||||
version: '2.6'
|
|
||||||
|
|
||||||
includes:
|
|
||||||
included:
|
|
||||||
taskfile: ./included
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
cmds:
|
|
||||||
- task: gen
|
|
@ -1,6 +0,0 @@
|
|||||||
version: '2.6'
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
gen:
|
|
||||||
cmds:
|
|
||||||
- echo incorrect includes test
|
|
58
testdata/vars/v2/Taskfile.yml
vendored
58
testdata/vars/v2/Taskfile.yml
vendored
@ -1,58 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
|
|
||||||
vars:
|
|
||||||
NESTED2: "{{.NESTED1}}-TaskfileVars"
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
deps: [hello]
|
|
||||||
|
|
||||||
hello:
|
|
||||||
cmds:
|
|
||||||
- echo {{.FOO}} > foo.txt
|
|
||||||
- echo {{.BAR}} > bar.txt
|
|
||||||
- echo {{.BAZ}} > baz.txt
|
|
||||||
- echo '{{.TMPL_FOO}}' > tmpl_foo.txt
|
|
||||||
- echo '{{.TMPL_BAR}}' > tmpl_bar.txt
|
|
||||||
- echo '{{.TMPL_FOO2}}' > tmpl_foo2.txt
|
|
||||||
- echo '{{.TMPL_BAR2}}' > tmpl_bar2.txt
|
|
||||||
- echo '{{.SHTMPL_FOO}}' > shtmpl_foo.txt
|
|
||||||
- echo '{{.SHTMPL_FOO2}}' > shtmpl_foo2.txt
|
|
||||||
- echo '{{.NESTEDTMPL_FOO}}' > nestedtmpl_foo.txt
|
|
||||||
- echo '{{.NESTEDTMPL_FOO2}}' > nestedtmpl_foo2.txt
|
|
||||||
- echo {{.FOO2}} > foo2.txt
|
|
||||||
- echo {{.BAR2}} > bar2.txt
|
|
||||||
- echo {{.BAZ2}} > baz2.txt
|
|
||||||
- echo '{{.TMPL2_FOO}}' > tmpl2_foo.txt
|
|
||||||
- echo '{{.TMPL2_BAR}}' > tmpl2_bar.txt
|
|
||||||
- echo '{{.TMPL2_FOO2}}' > tmpl2_foo2.txt
|
|
||||||
- echo '{{.TMPL2_BAR2}}' > tmpl2_bar2.txt
|
|
||||||
- echo '{{.SHTMPL2_FOO}}' > shtmpl2_foo.txt
|
|
||||||
- echo '{{.SHTMPL2_FOO2}}' > shtmpl2_foo2.txt
|
|
||||||
- echo '{{.NESTEDTMPL2_FOO2}}' > nestedtmpl2_foo2.txt
|
|
||||||
- echo {{.OVERRIDE}} > override.txt
|
|
||||||
- echo '{{.NESTED3}}' > nested.txt
|
|
||||||
- echo '{{.TASK}}' > task_name.txt
|
|
||||||
vars:
|
|
||||||
FOO: foo
|
|
||||||
BAR:
|
|
||||||
sh: echo bar
|
|
||||||
BAZ:
|
|
||||||
sh: echo baz
|
|
||||||
TMPL_FOO: "{{.FOO}}"
|
|
||||||
TMPL_BAR: "{{.BAR}}"
|
|
||||||
TMPL_FOO2: "{{.FOO2}}"
|
|
||||||
TMPL_BAR2: "{{.BAR2}}"
|
|
||||||
SHTMPL_FOO:
|
|
||||||
sh: "echo '{{.FOO}}'"
|
|
||||||
SHTMPL_FOO2:
|
|
||||||
sh: "echo '{{.FOO2}}'"
|
|
||||||
NESTEDTMPL_FOO: "{{.TMPL_FOO}}"
|
|
||||||
NESTEDTMPL_FOO2: "{{.TMPL2_FOO2}}"
|
|
||||||
OVERRIDE: "bar"
|
|
||||||
NESTED3: "{{.NESTED2}}-TaskVars"
|
|
||||||
|
|
||||||
invalid-var-tmpl:
|
|
||||||
vars:
|
|
||||||
CHARS: "abcd"
|
|
||||||
INVALID: "{{range .CHARS}}no end"
|
|
14
testdata/vars/v2/Taskvars.yml
vendored
14
testdata/vars/v2/Taskvars.yml
vendored
@ -1,14 +0,0 @@
|
|||||||
FOO2: foo2
|
|
||||||
BAR2:
|
|
||||||
sh: echo bar2
|
|
||||||
BAZ2:
|
|
||||||
sh: echo baz2
|
|
||||||
TMPL2_FOO: "{{.FOO}}"
|
|
||||||
TMPL2_BAR: "{{.BAR}}"
|
|
||||||
TMPL2_FOO2: "{{.FOO2}}"
|
|
||||||
TMPL2_BAR2: "{{.BAR2}}"
|
|
||||||
SHTMPL2_FOO2:
|
|
||||||
sh: "echo '{{.FOO2}}'"
|
|
||||||
NESTEDTMPL2_FOO2: "{{.TMPL2_FOO2}}"
|
|
||||||
OVERRIDE: "foo"
|
|
||||||
NESTED1: "Taskvars"
|
|
45
testdata/vars/v2/multiline/Taskfile.yml
vendored
45
testdata/vars/v2/multiline/Taskfile.yml
vendored
@ -1,45 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
tasks:
|
|
||||||
default:
|
|
||||||
vars:
|
|
||||||
MULTILINE: "\n\nfoo\n bar\nfoobar\n\nbaz\n\n"
|
|
||||||
cmds:
|
|
||||||
- task: file
|
|
||||||
vars:
|
|
||||||
CONTENT:
|
|
||||||
sh: "echo 'foo\nbar'"
|
|
||||||
FILE: "echo_foobar.txt"
|
|
||||||
- task: file
|
|
||||||
vars:
|
|
||||||
CONTENT:
|
|
||||||
sh: "echo -n 'foo\nbar'"
|
|
||||||
FILE: "echo_n_foobar.txt"
|
|
||||||
- task: file
|
|
||||||
vars:
|
|
||||||
CONTENT:
|
|
||||||
sh: echo -n "{{.MULTILINE}}"
|
|
||||||
FILE: "echo_n_multiline.txt"
|
|
||||||
- task: file
|
|
||||||
vars:
|
|
||||||
CONTENT: "{{.MULTILINE}}"
|
|
||||||
FILE: "var_multiline.txt"
|
|
||||||
- task: file
|
|
||||||
vars:
|
|
||||||
CONTENT: "{{.MULTILINE | catLines}}"
|
|
||||||
FILE: "var_catlines.txt"
|
|
||||||
- task: enumfile
|
|
||||||
vars:
|
|
||||||
LINES: "{{.MULTILINE}}"
|
|
||||||
FILE: "var_enumfile.txt"
|
|
||||||
file:
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
cat << EOF > '{{.FILE}}'
|
|
||||||
{{.CONTENT}}
|
|
||||||
EOF
|
|
||||||
enumfile:
|
|
||||||
cmds:
|
|
||||||
- |
|
|
||||||
cat << EOF > '{{.FILE}}'
|
|
||||||
{{range $i, $line := .LINES| splitLines}}{{$i}}:{{$line}}
|
|
||||||
{{end}}EOF
|
|
1
testdata/vars/v3/.gitignore
vendored
1
testdata/vars/v3/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
*.txt
|
|
2
testdata/version/v1/Taskfile.yml
vendored
2
testdata/version/v1/Taskfile.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
version: 1
|
version: "1"
|
||||||
tasks:
|
tasks:
|
||||||
foo:
|
foo:
|
||||||
cmds:
|
cmds:
|
||||||
|
2
testdata/version/v2/Taskfile.yml
vendored
2
testdata/version/v2/Taskfile.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: "2"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
foo:
|
foo:
|
||||||
|
9
testdata/version/v3/Taskfile.yml
vendored
Normal file
9
testdata/version/v3/Taskfile.yml
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
foo:
|
||||||
|
cmds:
|
||||||
|
- echo "Foo"
|
||||||
|
bar:
|
||||||
|
cmds:
|
||||||
|
- echo "Bar"
|
@ -42,7 +42,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := templater.Templater{Vars: vars, RemoveNoValue: e.Taskfile.Version.Compare(taskfile.V3) >= 0}
|
r := templater.Templater{Vars: vars}
|
||||||
|
|
||||||
new := taskfile.Task{
|
new := taskfile.Task{
|
||||||
Task: origTask.Task,
|
Task: origTask.Task,
|
||||||
|
Reference in New Issue
Block a user