这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/hclext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/internal/version"
Expand Down Expand Up @@ -46,28 +47,62 @@ func (c *Compiler) FastGetVariables(t *ast.Task, call *Call) (*ast.Vars, error)

func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*ast.Vars, error) {
result := env.GetEnviron()
evaluator := hclext.NewHCLEvaluator(result)
specialVars, err := c.getSpecialVars(t, call)
if err != nil {
return nil, err
}
for k, v := range specialVars {
result.Set(k, ast.Var{Value: v})
evaluator.SetVar(k, v)
}

getRangeFunc := func(dir string) func(k string, v ast.Var) error {
return func(k string, v ast.Var) error {
if v.Expr != nil || v.ShExpr != nil {
if v.Expr != nil {
val, err := evaluator.EvalString(v.Expr)
if err != nil {
return err
}
result.Set(k, ast.Var{Value: val})
evaluator.SetVar(k, val)
return nil
}
if v.ShExpr != nil {
if !evaluateShVars {
result.Set(k, ast.Var{Value: ""})
evaluator.SetVar(k, "")
return nil
}
cmd, err := evaluator.EvalString(v.ShExpr)
if err != nil {
return err
}
static, err := c.HandleDynamicVar(ast.Var{Sh: &cmd}, dir, env.GetFromVars(result))
if err != nil {
return err
}
result.Set(k, ast.Var{Value: static})
evaluator.SetVar(k, static)
return nil
}
}

cache := &templater.Cache{Vars: result}
// Replace values
newVar := templater.ReplaceVar(v, cache)
// 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, ast.Var{Value: ""})
evaluator.SetVar(k, "")
return nil
}
// If the variable should not be evaluated and it is set, we can set it and return
if !evaluateShVars {
result.Set(k, ast.Var{Value: newVar.Value})
evaluator.SetVar(k, fmt.Sprint(newVar.Value))
return nil
}
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
Expand All @@ -77,6 +112,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
// If the variable is already set, we can set it and return
if newVar.Value != nil || newVar.Sh == nil {
result.Set(k, ast.Var{Value: newVar.Value})
evaluator.SetVar(k, fmt.Sprint(newVar.Value))
return nil
}
// If the variable is dynamic, we need to resolve it first
Expand All @@ -85,6 +121,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
return err
}
result.Set(k, ast.Var{Value: static})
evaluator.SetVar(k, static)
return nil
}
}
Expand Down
128 changes: 128 additions & 0 deletions internal/hclext/evaluator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package hclext

import (
"fmt"
"os"
"strings"

"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"

"github.com/go-task/task/v3/taskfile/ast"
)

type HCLEvaluator struct {
EvalCtx *hcl.EvalContext
}

func NewHCLEvaluator(vars *ast.Vars) *HCLEvaluator {
ctx := &hcl.EvalContext{
Variables: map[string]cty.Value{},
Functions: builtinFunctions(),
}
if vars != nil {
for k, v := range vars.All() {
if v.Value != nil {
ctx.Variables[k] = cty.StringVal(fmt.Sprint(v.Value))
}
}
}
return &HCLEvaluator{EvalCtx: ctx}
}

func builtinFunctions() map[string]function.Function {
return map[string]function.Function{
"upper": stringFunc(strings.ToUpper),
"lower": stringFunc(strings.ToLower),
"join": joinFunc(),
"split": splitFunc(),
"env": envFunc(),
}
}

func stringFunc(fn func(string) string) function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{{Name: "s", Type: cty.String}},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.StringVal(fn(args[0].AsString())), nil
},
})
}

func joinFunc() function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{Name: "list", Type: cty.List(cty.String)},
{Name: "delim", Type: cty.String},
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
vals := args[0].AsValueSlice()
parts := make([]string, len(vals))
for i, v := range vals {
parts[i] = v.AsString()
}
return cty.StringVal(strings.Join(parts, args[1].AsString())), nil
},
})
}

func splitFunc() function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{
{Name: "s", Type: cty.String},
{Name: "delim", Type: cty.String},
},
Type: func(args []cty.Value) (cty.Type, error) {
return cty.List(cty.String), nil
},
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
parts := strings.Split(args[0].AsString(), args[1].AsString())
vals := make([]cty.Value, len(parts))
for i, p := range parts {
vals[i] = cty.StringVal(p)
}
return cty.ListVal(vals), nil
},
})
}

func envFunc() function.Function {
return function.New(&function.Spec{
Params: []function.Parameter{{Name: "name", Type: cty.String}},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
return cty.StringVal(os.Getenv(args[0].AsString())), nil
},
})
}

func (e *HCLEvaluator) SetVar(name, value string) {
if e.EvalCtx.Variables == nil {
e.EvalCtx.Variables = map[string]cty.Value{}
}
e.EvalCtx.Variables[name] = cty.StringVal(value)
}

func (e *HCLEvaluator) EvalString(expr hcl.Expression) (string, error) {
val, diags := expr.Value(e.EvalCtx)
if diags.HasErrors() {
return "", diags
}
switch {
case val.Type() == cty.String:
return val.AsString(), nil
case val.Type() == cty.Number:
bf := val.AsBigFloat()
return bf.Text('f', -1), nil
case val.Type() == cty.Bool:
if val.True() {
return "true", nil
}
return "false", nil
default:
return "", fmt.Errorf("unsupported value type %s", val.Type().FriendlyName())
}
}
16 changes: 13 additions & 3 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/fingerprint"
"github.com/go-task/task/v3/internal/hclext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/slicesext"
Expand Down Expand Up @@ -290,15 +291,24 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, deferredExitCode
cmd := t.Cmds[i]
vars, _ := e.Compiler.GetVariables(origTask, call)
cache := &templater.Cache{Vars: vars}
hclEval := hclext.NewHCLEvaluator(vars)
extra := map[string]any{}

if deferredExitCode != nil && *deferredExitCode > 0 {
extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode)
}

cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
cmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
cmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
if origTask.IsHCL && cmd.Expr != nil {
val, err := hclEval.EvalString(cmd.Expr)
if err != nil {
return
}
cmd.Cmd = val
} else {
cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra)
cmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra)
cmd.Vars = templater.ReplaceVarsWithExtra(cmd.Vars, cache, extra)
}

if err := e.runCommand(ctx, t, call, i); err != nil {
e.Logger.VerboseErrf(logger.Yellow, "task: ignored error in deferred cmd: %s\n", err.Error())
Expand Down
37 changes: 37 additions & 0 deletions taskfile/hcl_evaluator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package taskfile

import (
"testing"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/stretchr/testify/require"

"github.com/go-task/task/v3/internal/hclext"
"github.com/go-task/task/v3/taskfile/ast"
)

func TestHCLEvaluatorExpressions(t *testing.T) {
t.Setenv("HOME", "/home/test")
vars := ast.NewVars()
vars.Set("FOO", ast.Var{Value: "bar"})
eval := hclext.NewHCLEvaluator(vars)

expr1, diags := hclsyntax.ParseTemplate([]byte("${FOO}"), "test.hcl", hcl.InitialPos)
require.False(t, diags.HasErrors())
v, err := eval.EvalString(expr1)
require.NoError(t, err)
require.Equal(t, "bar", v)

expr2, diags := hclsyntax.ParseTemplate([]byte("${upper(FOO)}"), "test.hcl", hcl.InitialPos)
require.False(t, diags.HasErrors())
v, err = eval.EvalString(expr2)
require.NoError(t, err)
require.Equal(t, "BAR", v)

expr3, diags := hclsyntax.ParseTemplate([]byte("${env(\"HOME\")}"), "test.hcl", hcl.InitialPos)
require.False(t, diags.HasErrors())
v, err = eval.EvalString(expr3)
require.NoError(t, err)
require.Equal(t, "/home/test", v)
}
27 changes: 0 additions & 27 deletions taskfile/hcl_integration_test.go

This file was deleted.

21 changes: 13 additions & 8 deletions taskfile/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,16 +373,21 @@ func (r *Reader) readNode(ctx context.Context, node Node) (*ast.Taskfile, error)
return nil, &errors.TaskfileVersionCheckError{URI: node.Location()}
}

if tf == nil {
return nil, &errors.TaskfileInvalidError{URI: node.Location(), Err: fmt.Errorf("empty taskfile")}
}
// Set the taskfile/task's locations
tf.Location = node.Location()
for task := range tf.Tasks.Values(nil) {
// If the task is not defined, create a new one
if task == nil {
task = &ast.Task{}
}
// Set the location of the taskfile for each task
if task.Location.Taskfile == "" {
task.Location.Taskfile = tf.Location
if tf.Tasks != nil {
for task := range tf.Tasks.Values(nil) {
// If the task is not defined, create a new one
if task == nil {
task = &ast.Task{}
}
// Set the location of the taskfile for each task
if task.Location.Taskfile == "" {
task.Location.Taskfile = tf.Location
}
}
}

Expand Down
Loading