mirror of
https://github.com/go-task/task.git
synced 2026-08-29 10:08:27 +02:00
perf: avoid eager fingerprint variable evaluation (#2883)
This commit is contained in:
134
taskfile/ast/fingerprint.go
Normal file
134
taskfile/ast/fingerprint.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ReferencesFingerprintVar reports whether the task references the variable
|
||||
// produced by the given fingerprint method in fields compiled after it.
|
||||
func (t *Task) ReferencesFingerprintVar(kind string) bool {
|
||||
name := strings.ToUpper(kind)
|
||||
if t == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, status := range t.Status {
|
||||
if stringReferencesFingerprintVar(status, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, cmd := range t.Cmds {
|
||||
if cmdReferencesFingerprintVar(cmd, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, dep := range t.Deps {
|
||||
if depReferencesFingerprintVar(dep, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, precondition := range t.Preconditions {
|
||||
if preconditionReferencesFingerprintVar(precondition, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmdReferencesFingerprintVar(cmd *Cmd, name string) bool {
|
||||
if cmd == nil {
|
||||
return false
|
||||
}
|
||||
return stringReferencesFingerprintVar(cmd.Cmd, name) ||
|
||||
stringReferencesFingerprintVar(cmd.Task, name) ||
|
||||
stringReferencesFingerprintVar(cmd.If, name) ||
|
||||
forReferencesFingerprintVar(cmd.For, name) ||
|
||||
varsReferenceFingerprintVar(cmd.Vars, name)
|
||||
}
|
||||
|
||||
func depReferencesFingerprintVar(dep *Dep, name string) bool {
|
||||
if dep == nil {
|
||||
return false
|
||||
}
|
||||
return stringReferencesFingerprintVar(dep.Task, name) ||
|
||||
forReferencesFingerprintVar(dep.For, name) ||
|
||||
varsReferenceFingerprintVar(dep.Vars, name)
|
||||
}
|
||||
|
||||
func preconditionReferencesFingerprintVar(precondition *Precondition, name string) bool {
|
||||
if precondition == nil {
|
||||
return false
|
||||
}
|
||||
return stringReferencesFingerprintVar(precondition.Sh, name) ||
|
||||
stringReferencesFingerprintVar(precondition.Msg, name)
|
||||
}
|
||||
|
||||
func forReferencesFingerprintVar(f *For, name string) bool {
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
if valueReferencesFingerprintVar(f.List, name) {
|
||||
return true
|
||||
}
|
||||
for _, row := range f.Matrix.All() {
|
||||
if row != nil && (stringReferencesFingerprintVar(row.Ref, name) ||
|
||||
valueReferencesFingerprintVar(row.Value, name)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func varsReferenceFingerprintVar(vars *Vars, name string) bool {
|
||||
for _, v := range vars.All() {
|
||||
if valueReferencesFingerprintVar(v.Value, name) ||
|
||||
valueReferencesFingerprintVar(v.Live, name) ||
|
||||
stringPointerReferencesFingerprintVar(v.Sh, name) ||
|
||||
stringReferencesFingerprintVar(v.Ref, name) ||
|
||||
stringReferencesFingerprintVar(v.Dir, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func valueReferencesFingerprintVar(value any, name string) bool {
|
||||
if value == nil {
|
||||
return false
|
||||
}
|
||||
if s, ok := value.(string); ok {
|
||||
return stringReferencesFingerprintVar(s, name)
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(value)
|
||||
switch rv.Kind() {
|
||||
case reflect.Pointer, reflect.Interface:
|
||||
if rv.IsNil() {
|
||||
return false
|
||||
}
|
||||
return valueReferencesFingerprintVar(rv.Elem().Interface(), name)
|
||||
case reflect.Array, reflect.Slice:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if valueReferencesFingerprintVar(rv.Index(i).Interface(), name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, key := range rv.MapKeys() {
|
||||
if valueReferencesFingerprintVar(key.Interface(), name) ||
|
||||
valueReferencesFingerprintVar(rv.MapIndex(key).Interface(), name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringPointerReferencesFingerprintVar(s *string, name string) bool {
|
||||
return s != nil && stringReferencesFingerprintVar(*s, name)
|
||||
}
|
||||
|
||||
func stringReferencesFingerprintVar(s string, name string) bool {
|
||||
return strings.Contains(s, name)
|
||||
}
|
||||
103
taskfile/ast/fingerprint_test.go
Normal file
103
taskfile/ast/fingerprint_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTaskReferencesFingerprintVar(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fingerprintRef := "{{.CHECKSUM}}"
|
||||
tests := []struct {
|
||||
name string
|
||||
task *Task
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil task",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "status",
|
||||
task: &Task{Status: []string{"test -n " + fingerprintRef}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "command",
|
||||
task: &Task{Cmds: []*Cmd{{Cmd: "echo " + fingerprintRef}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "task call",
|
||||
task: &Task{Cmds: []*Cmd{{Task: "build-" + fingerprintRef}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "command condition",
|
||||
task: &Task{Cmds: []*Cmd{{If: "test -n " + fingerprintRef}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "loop list nested value",
|
||||
task: &Task{Cmds: []*Cmd{{For: &For{List: []any{map[string]any{"value": fingerprintRef}}}}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "loop matrix reference",
|
||||
task: &Task{Cmds: []*Cmd{{For: &For{Matrix: NewMatrix(
|
||||
&MatrixElement{Key: "item", Value: &MatrixRow{Ref: fingerprintRef}},
|
||||
)}}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "command variable",
|
||||
task: &Task{Cmds: []*Cmd{{Vars: NewVars(
|
||||
&VarElement{Key: "VALUE", Value: Var{Value: []any{fingerprintRef}}},
|
||||
)}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "dynamic command variable",
|
||||
task: &Task{Cmds: []*Cmd{{Vars: NewVars(
|
||||
&VarElement{Key: "VALUE", Value: Var{Sh: &fingerprintRef}},
|
||||
)}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "dependency",
|
||||
task: &Task{Deps: []*Dep{{Task: "build-" + fingerprintRef}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "dependency variable",
|
||||
task: &Task{Deps: []*Dep{{Vars: NewVars(
|
||||
&VarElement{Key: "VALUE", Value: Var{Ref: fingerprintRef}},
|
||||
)}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "precondition",
|
||||
task: &Task{Preconditions: []*Precondition{{Msg: fingerprintRef}}},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "unrelated fields and nil entries",
|
||||
task: &Task{
|
||||
Cmds: []*Cmd{nil, {Cmd: "echo ok"}},
|
||||
Deps: []*Dep{nil, {Task: "build"}},
|
||||
Preconditions: []*Precondition{nil, {Sh: "test -f output"}},
|
||||
Status: []string{"test -f output"},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equal(t, tt.want, tt.task.ReferencesFingerprintVar("checksum"))
|
||||
})
|
||||
}
|
||||
}
|
||||
18
variables.go
18
variables.go
@@ -217,15 +217,17 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
checker = fingerprint.NewChecksumChecker(e.TempDir.Fingerprint, e.Dry)
|
||||
}
|
||||
|
||||
value, err := checker.Value(&new)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vars.Set(strings.ToUpper(checker.Kind()), ast.Var{Live: value})
|
||||
if origTask.ReferencesFingerprintVar(checker.Kind()) {
|
||||
value, err := checker.Value(&new)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vars.Set(strings.ToUpper(checker.Kind()), ast.Var{Live: value})
|
||||
|
||||
// Adding new variables, requires us to refresh the templaters
|
||||
// cache of the the values manually
|
||||
cache.ResetCache()
|
||||
// Adding new variables, requires us to refresh the templaters
|
||||
// cache of the the values manually
|
||||
cache.ResetCache()
|
||||
}
|
||||
}
|
||||
|
||||
if len(origTask.Cmds) > 0 {
|
||||
|
||||
Reference in New Issue
Block a user