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)
|
checker = fingerprint.NewChecksumChecker(e.TempDir.Fingerprint, e.Dry)
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := checker.Value(&new)
|
if origTask.ReferencesFingerprintVar(checker.Kind()) {
|
||||||
if err != nil {
|
value, err := checker.Value(&new)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
vars.Set(strings.ToUpper(checker.Kind()), ast.Var{Live: value})
|
}
|
||||||
|
vars.Set(strings.ToUpper(checker.Kind()), ast.Var{Live: value})
|
||||||
|
|
||||||
// Adding new variables, requires us to refresh the templaters
|
// Adding new variables, requires us to refresh the templaters
|
||||||
// cache of the the values manually
|
// cache of the the values manually
|
||||||
cache.ResetCache()
|
cache.ResetCache()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(origTask.Cmds) > 0 {
|
if len(origTask.Cmds) > 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user