mirror of
https://github.com/go-task/task.git
synced 2025-11-25 22:32:55 +02:00
Create v3 compiler which respects declaration order of variables
Also, fix "<no value>" been printed when a non-existing variable is printed.
This commit is contained in:
98
internal/compiler/v3/compiler_v3.go
Normal file
98
internal/compiler/v3/compiler_v3.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package v3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-task/task/v2/internal/compiler"
|
||||||
|
"github.com/go-task/task/v2/internal/execext"
|
||||||
|
"github.com/go-task/task/v2/internal/logger"
|
||||||
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
|
"github.com/go-task/task/v2/internal/templater"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ compiler.Compiler = &CompilerV3{}
|
||||||
|
|
||||||
|
type CompilerV3 struct {
|
||||||
|
Dir string
|
||||||
|
|
||||||
|
TaskfileVars *taskfile.Vars
|
||||||
|
|
||||||
|
Logger *logger.Logger
|
||||||
|
|
||||||
|
dynamicCache map[string]string
|
||||||
|
muDynamicCache sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompilerV3) GetVariables(t *taskfile.Task, call taskfile.Call) (*taskfile.Vars, error) {
|
||||||
|
result := compiler.GetEnviron()
|
||||||
|
result.Set("TASK", taskfile.Var{Static: t.Task})
|
||||||
|
|
||||||
|
rangeFunc := func(k string, v taskfile.Var) error {
|
||||||
|
tr := templater.Templater{Vars: result, RemoveNoValue: true}
|
||||||
|
v = taskfile.Var{
|
||||||
|
Static: tr.Replace(v.Static),
|
||||||
|
Sh: tr.Replace(v.Sh),
|
||||||
|
}
|
||||||
|
if err := tr.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
static, err := c.HandleDynamicVar(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result.Set(k, taskfile.Var{Static: static})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.TaskfileVars.Range(rangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := call.Vars.Range(rangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := t.Vars.Range(rangeFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompilerV3) HandleDynamicVar(v taskfile.Var) (string, error) {
|
||||||
|
if v.Static != "" || v.Sh == "" {
|
||||||
|
return v.Static, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
Dir: c.Dir,
|
||||||
|
Stdout: &stdout,
|
||||||
|
Stderr: c.Logger.Stderr,
|
||||||
|
}
|
||||||
|
if err := execext.RunCommand(context.Background(), opts); err != nil {
|
||||||
|
return "", fmt.Errorf(`task: Command "%s" in taskvars file 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: '%s' result: '%s'`, v.Sh, result)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package templater
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/go-task/task/v2/internal/taskfile"
|
"github.com/go-task/task/v2/internal/taskfile"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
// 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]interface{}
|
cacheMap map[string]interface{}
|
||||||
err error
|
err error
|
||||||
@@ -42,6 +44,9 @@ func (r *Templater) Replace(str string) string {
|
|||||||
r.err = err
|
r.err = err
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
if r.RemoveNoValue {
|
||||||
|
return strings.ReplaceAll(b.String(), "<no value>", "")
|
||||||
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
task.go
21
task.go
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-task/task/v2/internal/compiler"
|
"github.com/go-task/task/v2/internal/compiler"
|
||||||
compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
|
compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
|
||||||
|
compilerv3 "github.com/go-task/task/v2/internal/compiler/v3"
|
||||||
"github.com/go-task/task/v2/internal/execext"
|
"github.com/go-task/task/v2/internal/execext"
|
||||||
"github.com/go-task/task/v2/internal/logger"
|
"github.com/go-task/task/v2/internal/logger"
|
||||||
"github.com/go-task/task/v2/internal/output"
|
"github.com/go-task/task/v2/internal/output"
|
||||||
@@ -131,9 +132,9 @@ func (e *Executor) Setup() error {
|
|||||||
Color: e.Color,
|
Color: e.Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
|
v, err := e.parsedVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v < 2 {
|
if v < 2 {
|
||||||
@@ -154,6 +155,7 @@ func (e *Executor) Setup() error {
|
|||||||
e.Logger.Color = false
|
e.Logger.Color = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v < 3 {
|
||||||
e.Compiler = &compilerv2.CompilerV2{
|
e.Compiler = &compilerv2.CompilerV2{
|
||||||
Dir: e.Dir,
|
Dir: e.Dir,
|
||||||
Taskvars: e.taskvars,
|
Taskvars: e.taskvars,
|
||||||
@@ -161,6 +163,13 @@ func (e *Executor) Setup() error {
|
|||||||
Expansions: e.Taskfile.Expansions,
|
Expansions: e.Taskfile.Expansions,
|
||||||
Logger: e.Logger,
|
Logger: e.Logger,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
e.Compiler = &compilerv3.CompilerV3{
|
||||||
|
Dir: e.Dir,
|
||||||
|
TaskfileVars: e.Taskfile.Vars,
|
||||||
|
Logger: e.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if v < 2.1 && e.Taskfile.Output != "" {
|
if v < 2.1 && e.Taskfile.Output != "" {
|
||||||
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
|
||||||
@@ -231,6 +240,14 @@ func (e *Executor) Setup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Executor) parsedVersion() (float64, error) {
|
||||||
|
v, err := strconv.ParseFloat(e.Taskfile.Version, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf(`task: Could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RunTask runs a task by its name
|
// RunTask runs a task by its name
|
||||||
func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||||
t, err := e.CompiledTask(call)
|
t, err := e.CompiledTask(call)
|
||||||
|
|||||||
14
task_test.go
14
task_test.go
@@ -107,6 +107,20 @@ func TestVarsV2(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVarsV3(t *testing.T) {
|
||||||
|
tt := fileContentTest{
|
||||||
|
Dir: "testdata/vars/v3",
|
||||||
|
Target: "default",
|
||||||
|
Files: map[string]string{
|
||||||
|
"missing-var.txt": "\n",
|
||||||
|
"var-order.txt": "ABCDEF\n",
|
||||||
|
"dependent-sh.txt": "123456\n",
|
||||||
|
"with-call.txt": "Hi, ABC123!\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMultilineVars(t *testing.T) {
|
func TestMultilineVars(t *testing.T) {
|
||||||
for _, dir := range []string{"testdata/vars/v2/multiline"} {
|
for _, dir := range []string{"testdata/vars/v2/multiline"} {
|
||||||
tt := fileContentTest{
|
tt := fileContentTest{
|
||||||
|
|||||||
2
testdata/checksum/Taskfile.yml
vendored
2
testdata/checksum/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
build:
|
build:
|
||||||
|
|||||||
2
testdata/cyclic/Taskfile.yml
vendored
2
testdata/cyclic/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
task-1:
|
task-1:
|
||||||
|
|||||||
2
testdata/deps/Taskfile.yml
vendored
2
testdata/deps/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
default:
|
default:
|
||||||
|
|||||||
2
testdata/dir/Taskfile.yml
vendored
2
testdata/dir/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
whereami:
|
whereami:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
whereami:
|
whereami:
|
||||||
|
|||||||
2
testdata/dir/explicit_exists/Taskfile.yml
vendored
2
testdata/dir/explicit_exists/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
whereami:
|
whereami:
|
||||||
|
|||||||
2
testdata/dry/Taskfile.yml
vendored
2
testdata/dry/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
build:
|
build:
|
||||||
|
|||||||
2
testdata/dry_checksum/Taskfile.yml
vendored
2
testdata/dry_checksum/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
default:
|
default:
|
||||||
|
|||||||
2
testdata/env/Taskfile.yml
vendored
2
testdata/env/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
BAZ:
|
BAZ:
|
||||||
|
|||||||
2
testdata/expand/Taskfile.yml
vendored
2
testdata/expand/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
pwd:
|
pwd:
|
||||||
|
|||||||
5
testdata/generates/Taskfile.yml
vendored
5
testdata/generates/Taskfile.yml
vendored
@@ -1,4 +1,7 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
BUILD_DIR: $pwd
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
abs.txt:
|
abs.txt:
|
||||||
|
|||||||
1
testdata/generates/Taskvars.yml
vendored
1
testdata/generates/Taskvars.yml
vendored
@@ -1 +0,0 @@
|
|||||||
BUILD_DIR: $pwd
|
|
||||||
2
testdata/ignore_errors/Taskfile.yml
vendored
2
testdata/ignore_errors/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
task-should-pass:
|
task-should-pass:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
included: Taskfile2.yml
|
included: Taskfile2.yml
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
call-root:
|
call-root:
|
||||||
|
|||||||
2
testdata/includes_deps/Taskfile.yml
vendored
2
testdata/includes_deps/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
included: Taskfile2.yml
|
included: Taskfile2.yml
|
||||||
|
|||||||
2
testdata/includes_deps/Taskfile2.yml
vendored
2
testdata/includes_deps/Taskfile2.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
default:
|
default:
|
||||||
|
|||||||
2
testdata/includes_empty/Taskfile.yml
vendored
2
testdata/includes_empty/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
included: Taskfile2.yml
|
included: Taskfile2.yml
|
||||||
|
|||||||
2
testdata/includes_empty/Taskfile2.yml
vendored
2
testdata/includes_empty/Taskfile2.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
vars:
|
vars:
|
||||||
FILE: file.txt
|
FILE: file.txt
|
||||||
|
|||||||
6
testdata/params/Taskfile.yml
vendored
6
testdata/params/Taskfile.yml
vendored
@@ -1,4 +1,8 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
PORTUGUESE_HELLO_WORLD: Olá, mundo!
|
||||||
|
GERMAN: Hello
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
default:
|
default:
|
||||||
|
|||||||
2
testdata/params/Taskvars.yml
vendored
2
testdata/params/Taskvars.yml
vendored
@@ -1,2 +0,0 @@
|
|||||||
PORTUGUESE_HELLO_WORLD: Olá, mundo!
|
|
||||||
GERMAN: "Hello"
|
|
||||||
2
testdata/precondition/Taskfile.yml
vendored
2
testdata/precondition/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
foo:
|
foo:
|
||||||
|
|||||||
2
testdata/status/Taskfile.yml
vendored
2
testdata/status/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
gen-foo:
|
gen-foo:
|
||||||
|
|||||||
2
testdata/summary/Taskfile.yml
vendored
2
testdata/summary/Taskfile.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
version: '2'
|
version: '3'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
task-with-summary:
|
task-with-summary:
|
||||||
|
|||||||
1
testdata/vars/v3/.gitignore
vendored
Normal file
1
testdata/vars/v3/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.txt
|
||||||
46
testdata/vars/v3/Taskfile.yml
vendored
Normal file
46
testdata/vars/v3/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
VAR_A: A
|
||||||
|
VAR_B: '{{.VAR_A}}B'
|
||||||
|
VAR_C: '{{.VAR_B}}C'
|
||||||
|
|
||||||
|
VAR_1: {sh: echo 1}
|
||||||
|
VAR_2: {sh: 'echo "{{.VAR_1}}2"'}
|
||||||
|
VAR_3: {sh: 'echo "{{.VAR_2}}3"'}
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
- task: missing-var
|
||||||
|
- task: var-order
|
||||||
|
- task: dependent-sh
|
||||||
|
- task: with-call
|
||||||
|
|
||||||
|
missing-var: echo '{{.NON_EXISTING_VAR}}' > missing-var.txt
|
||||||
|
|
||||||
|
var-order:
|
||||||
|
vars:
|
||||||
|
VAR_D: '{{.VAR_C}}D'
|
||||||
|
VAR_E: '{{.VAR_D}}E'
|
||||||
|
VAR_F: '{{.VAR_E}}F'
|
||||||
|
cmds:
|
||||||
|
- echo '{{.VAR_F}}' > var-order.txt
|
||||||
|
|
||||||
|
dependent-sh:
|
||||||
|
vars:
|
||||||
|
VAR_4: {sh: 'echo "{{.VAR_3}}4"'}
|
||||||
|
VAR_5: {sh: 'echo "{{.VAR_4}}5"'}
|
||||||
|
VAR_6: {sh: 'echo "{{.VAR_5}}6"'}
|
||||||
|
cmds:
|
||||||
|
- echo '{{.VAR_6}}' > dependent-sh.txt
|
||||||
|
|
||||||
|
with-call:
|
||||||
|
- task: called-task
|
||||||
|
vars:
|
||||||
|
ABC123: '{{.VAR_C}}{{.VAR_3}}'
|
||||||
|
|
||||||
|
called-task:
|
||||||
|
vars:
|
||||||
|
MESSAGE: Hi, {{.ABC123}}!
|
||||||
|
cmds:
|
||||||
|
- echo "{{.MESSAGE}}" > with-call.txt
|
||||||
@@ -23,7 +23,12 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := templater.Templater{Vars: vars}
|
v, err := e.parsedVersion()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := templater.Templater{Vars: vars, RemoveNoValue: v >= 3.0}
|
||||||
|
|
||||||
new := taskfile.Task{
|
new := taskfile.Task{
|
||||||
Task: origTask.Task,
|
Task: origTask.Task,
|
||||||
|
|||||||
Reference in New Issue
Block a user