Skip to content
Open
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
134 changes: 134 additions & 0 deletions taskfile/ast/fingerprint.go
Original file line number Diff line number Diff line change
@@ -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 changes: 103 additions & 0 deletions taskfile/ast/fingerprint_test.go
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 8 deletions variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading