From 0838d48ee355eff9fc314a971c7242ae04804232 Mon Sep 17 00:00:00 2001 From: Pete Davison Date: Fri, 10 Mar 2023 18:27:30 +0000 Subject: [PATCH] refactor: decouple fingerprinting from executor (#1039) --- CHANGELOG.md | 1 + Taskfile.yml | 18 ++ go.mod | 1 + go.sum | 27 ++- help.go | 13 +- internal/env/env.go | 31 ++++ internal/fingerprint/checker.go | 20 ++ internal/fingerprint/checker_mock.go | 132 +++++++++++++ internal/{status => fingerprint}/glob.go | 2 +- internal/fingerprint/sources.go | 16 ++ .../sources_checksum.go} | 84 ++++----- .../sources_checksum_test.go} | 2 +- internal/fingerprint/sources_none.go | 23 +++ .../sources_timestamp.go} | 41 +++-- internal/fingerprint/status.go | 36 ++++ internal/fingerprint/task.go | 132 +++++++++++++ internal/fingerprint/task_test.go | 174 ++++++++++++++++++ internal/status/none.go | 23 --- internal/status/status.go | 15 -- precondition.go | 3 +- status.go | 128 +++---------- task.go | 40 ++-- variables.go | 9 +- watch.go | 4 +- 24 files changed, 734 insertions(+), 241 deletions(-) create mode 100644 internal/env/env.go create mode 100644 internal/fingerprint/checker.go create mode 100644 internal/fingerprint/checker_mock.go rename internal/{status => fingerprint}/glob.go (97%) create mode 100644 internal/fingerprint/sources.go rename internal/{status/checksum.go => fingerprint/sources_checksum.go} (50%) rename internal/{status/checksum_test.go => fingerprint/sources_checksum_test.go} (94%) create mode 100644 internal/fingerprint/sources_none.go rename internal/{status/timestamp.go => fingerprint/sources_timestamp.go} (75%) create mode 100644 internal/fingerprint/status.go create mode 100644 internal/fingerprint/task.go create mode 100644 internal/fingerprint/task_test.go delete mode 100644 internal/status/none.go delete mode 100644 internal/status/status.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 79ea82a7..476e98f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Fixed bug where `.task/checksum` file was sometimes not being created when task also declares a `status:` ([#840](https://github.com/go-task/task/issues/840), [#1035](https://github.com/go-task/task/pull/1035) by @harelwa, [#1037](https://github.com/go-task/task/pull/1037) by @pd93). +- Refactored and decoupled fingerprinting from the main Task executor ([#1039](https://github.com/go-task/task/issues/1039) by @pd93). - Fixed deadlock issue when using `run: once` ([#715](https://github.com/go-task/task/issues/715), [#1025](https://github.com/go-task/task/pull/1025) by @theunrepentantgeek). diff --git a/Taskfile.yml b/Taskfile.yml index 2c9bc973..073191f8 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -27,6 +27,24 @@ tasks: GIT_COMMIT: sh: git log -n 1 --format=%h + generate: + desc: Runs Go generate to create mocks + aliases: [gen, g] + deps: [install:mockgen] + sources: + - "internal/fingerprint/checker.go" + generates: + - "internal/fingerprint/checker_mock.go" + cmds: + - mockgen -source=internal/fingerprint/checker.go -destination=internal/fingerprint/checker_mock.go -package=fingerprint + + install:mockgen: + desc: Installs mockgen; a tool to generate mock files + status: + - command -v mockgen &>/dev/null + cmds: + - go install github.com/golang/mock/mockgen@latest + mod: desc: Downloads and tidy Go modules cmds: diff --git a/go.mod b/go.mod index aac2386c..6f7b7243 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 github.com/fatih/color v1.14.1 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 + github.com/golang/mock v1.6.0 github.com/joho/godotenv v1.5.1 github.com/mattn/go-zglob v0.0.4 github.com/mitchellh/hashstructure/v2 v2.0.2 diff --git a/go.sum b/go.sum index 0e919f34..613e8b1b 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8Wlg github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= @@ -40,17 +42,38 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= -golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20230212135524-a684f29349b6 h1:Ic9KukPQ7PegFzHckNiMTQXGgEszA7mY2Fn4ZMtnMbw= golang.org/x/exp v0.0.0-20230212135524-a684f29349b6/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/help.go b/help.go index d0eca8f9..31ff1771 100644 --- a/help.go +++ b/help.go @@ -12,6 +12,7 @@ import ( "text/tabwriter" "github.com/go-task/task/v3/internal/editors" + "github.com/go-task/task/v3/internal/fingerprint" "github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/taskfile" ) @@ -148,7 +149,17 @@ func (e *Executor) ToEditorOutput(tasks []*taskfile.Task) (*editors.Output, erro Tasks: make([]editors.Task, len(tasks)), } for i, t := range tasks { - upToDate, err := e.isTaskUpToDate(context.Background(), t) + // Get the fingerprinting method to use + method := e.Taskfile.Method + if t.Method != "" { + method = t.Method + } + upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), t, + fingerprint.WithMethod(method), + fingerprint.WithTempDir(e.TempDir), + fingerprint.WithDry(e.Dry), + fingerprint.WithLogger(e.Logger), + ) if err != nil { return nil, err } diff --git a/internal/env/env.go b/internal/env/env.go new file mode 100644 index 00000000..b18279c5 --- /dev/null +++ b/internal/env/env.go @@ -0,0 +1,31 @@ +package env + +import ( + "fmt" + "os" + + "github.com/go-task/task/v3/taskfile" +) + +func Get(t *taskfile.Task) []string { + if t.Env == nil { + return nil + } + + environ := os.Environ() + + for k, v := range t.Env.ToCacheMap() { + str, isString := v.(string) + if !isString { + continue + } + + if _, alreadySet := os.LookupEnv(k); alreadySet { + continue + } + + environ = append(environ, fmt.Sprintf("%s=%s", k, str)) + } + + return environ +} diff --git a/internal/fingerprint/checker.go b/internal/fingerprint/checker.go new file mode 100644 index 00000000..ba1a5289 --- /dev/null +++ b/internal/fingerprint/checker.go @@ -0,0 +1,20 @@ +package fingerprint + +import ( + "context" + + "github.com/go-task/task/v3/taskfile" +) + +// StatusCheckable defines any type that can check if the status of a task is up-to-date. +type StatusCheckable interface { + IsUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) +} + +// SourcesCheckable defines any type that can check if the sources of a task are up-to-date. +type SourcesCheckable interface { + IsUpToDate(t *taskfile.Task) (bool, error) + Value(t *taskfile.Task) (interface{}, error) + OnError(t *taskfile.Task) error + Kind() string +} diff --git a/internal/fingerprint/checker_mock.go b/internal/fingerprint/checker_mock.go new file mode 100644 index 00000000..eb10670f --- /dev/null +++ b/internal/fingerprint/checker_mock.go @@ -0,0 +1,132 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: checker.go + +// Package fingerprint is a generated GoMock package. +package fingerprint + +import ( + context "context" + reflect "reflect" + + taskfile "github.com/go-task/task/v3/taskfile" + gomock "github.com/golang/mock/gomock" +) + +// MockStatusCheckable is a mock of StatusCheckable interface. +type MockStatusCheckable struct { + ctrl *gomock.Controller + recorder *MockStatusCheckableMockRecorder +} + +// MockStatusCheckableMockRecorder is the mock recorder for MockStatusCheckable. +type MockStatusCheckableMockRecorder struct { + mock *MockStatusCheckable +} + +// NewMockStatusCheckable creates a new mock instance. +func NewMockStatusCheckable(ctrl *gomock.Controller) *MockStatusCheckable { + mock := &MockStatusCheckable{ctrl: ctrl} + mock.recorder = &MockStatusCheckableMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStatusCheckable) EXPECT() *MockStatusCheckableMockRecorder { + return m.recorder +} + +// IsUpToDate mocks base method. +func (m *MockStatusCheckable) IsUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsUpToDate", ctx, t) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsUpToDate indicates an expected call of IsUpToDate. +func (mr *MockStatusCheckableMockRecorder) IsUpToDate(ctx, t interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUpToDate", reflect.TypeOf((*MockStatusCheckable)(nil).IsUpToDate), ctx, t) +} + +// MockSourcesCheckable is a mock of SourcesCheckable interface. +type MockSourcesCheckable struct { + ctrl *gomock.Controller + recorder *MockSourcesCheckableMockRecorder +} + +// MockSourcesCheckableMockRecorder is the mock recorder for MockSourcesCheckable. +type MockSourcesCheckableMockRecorder struct { + mock *MockSourcesCheckable +} + +// NewMockSourcesCheckable creates a new mock instance. +func NewMockSourcesCheckable(ctrl *gomock.Controller) *MockSourcesCheckable { + mock := &MockSourcesCheckable{ctrl: ctrl} + mock.recorder = &MockSourcesCheckableMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSourcesCheckable) EXPECT() *MockSourcesCheckableMockRecorder { + return m.recorder +} + +// IsUpToDate mocks base method. +func (m *MockSourcesCheckable) IsUpToDate(t *taskfile.Task) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsUpToDate", t) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsUpToDate indicates an expected call of IsUpToDate. +func (mr *MockSourcesCheckableMockRecorder) IsUpToDate(t interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUpToDate", reflect.TypeOf((*MockSourcesCheckable)(nil).IsUpToDate), t) +} + +// Kind mocks base method. +func (m *MockSourcesCheckable) Kind() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Kind") + ret0, _ := ret[0].(string) + return ret0 +} + +// Kind indicates an expected call of Kind. +func (mr *MockSourcesCheckableMockRecorder) Kind() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Kind", reflect.TypeOf((*MockSourcesCheckable)(nil).Kind)) +} + +// OnError mocks base method. +func (m *MockSourcesCheckable) OnError(t *taskfile.Task) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OnError", t) + ret0, _ := ret[0].(error) + return ret0 +} + +// OnError indicates an expected call of OnError. +func (mr *MockSourcesCheckableMockRecorder) OnError(t interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnError", reflect.TypeOf((*MockSourcesCheckable)(nil).OnError), t) +} + +// Value mocks base method. +func (m *MockSourcesCheckable) Value(t *taskfile.Task) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Value", t) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Value indicates an expected call of Value. +func (mr *MockSourcesCheckableMockRecorder) Value(t interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Value", reflect.TypeOf((*MockSourcesCheckable)(nil).Value), t) +} diff --git a/internal/status/glob.go b/internal/fingerprint/glob.go similarity index 97% rename from internal/status/glob.go rename to internal/fingerprint/glob.go index d04214be..0b7fc837 100644 --- a/internal/status/glob.go +++ b/internal/fingerprint/glob.go @@ -1,4 +1,4 @@ -package status +package fingerprint import ( "os" diff --git a/internal/fingerprint/sources.go b/internal/fingerprint/sources.go new file mode 100644 index 00000000..34d3a04b --- /dev/null +++ b/internal/fingerprint/sources.go @@ -0,0 +1,16 @@ +package fingerprint + +import "fmt" + +func NewSourcesChecker(method, tempDir string, dry bool) (SourcesCheckable, error) { + switch method { + case "timestamp": + return NewTimestampChecker(tempDir, dry), nil + case "checksum": + return NewChecksumChecker(tempDir, dry), nil + case "none": + return NoneChecker{}, nil + default: + return nil, fmt.Errorf(`task: invalid method "%s"`, method) + } +} diff --git a/internal/status/checksum.go b/internal/fingerprint/sources_checksum.go similarity index 50% rename from internal/status/checksum.go rename to internal/fingerprint/sources_checksum.go index d1ed712e..85680da5 100644 --- a/internal/status/checksum.go +++ b/internal/fingerprint/sources_checksum.go @@ -1,4 +1,4 @@ -package status +package fingerprint import ( "crypto/md5" @@ -10,51 +10,54 @@ import ( "strings" "github.com/go-task/task/v3/internal/filepathext" + "github.com/go-task/task/v3/taskfile" ) -// Checksum validades if a task is up to date by calculating its source +// ChecksumChecker validates if a task is up to date by calculating its source // files checksum -type Checksum struct { - TempDir string - TaskDir string - Task string - Sources []string - Generates []string - Dry bool +type ChecksumChecker struct { + tempDir string + dry bool } -// IsUpToDate implements the Checker interface -func (c *Checksum) IsUpToDate() (bool, error) { - if len(c.Sources) == 0 { +func NewChecksumChecker(tempDir string, dry bool) *ChecksumChecker { + return &ChecksumChecker{ + tempDir: tempDir, + dry: dry, + } +} + +func (checker *ChecksumChecker) IsUpToDate(t *taskfile.Task) (bool, error) { + if len(t.Sources) == 0 { return false, nil } - checksumFile := c.checksumFilePath() + checksumFile := checker.checksumFilePath(t) data, _ := os.ReadFile(checksumFile) oldMd5 := strings.TrimSpace(string(data)) - sources, err := globs(c.TaskDir, c.Sources) + sources, err := globs(t.Dir, t.Sources) if err != nil { return false, err } - newMd5, err := c.checksum(sources...) + newMd5, err := checker.checksum(sources...) if err != nil { return false, nil } - if !c.Dry { - _ = os.MkdirAll(filepathext.SmartJoin(c.TempDir, "checksum"), 0o755) + if !checker.dry { + _ = os.MkdirAll(filepathext.SmartJoin(checker.tempDir, "checksum"), 0o755) if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0o644); err != nil { return false, err } } - if len(c.Generates) > 0 { + if len(t.Generates) > 0 { // For each specified 'generates' field, check whether the files actually exist - for _, g := range c.Generates { - generates, err := Glob(c.TaskDir, g) + for _, g := range t.Generates { + generates, err := Glob(t.Dir, g) if os.IsNotExist(err) { return false, nil } @@ -70,7 +73,22 @@ func (c *Checksum) IsUpToDate() (bool, error) { return oldMd5 == newMd5, nil } -func (c *Checksum) checksum(files ...string) (string, error) { +func (checker *ChecksumChecker) Value(t *taskfile.Task) (interface{}, error) { + return checker.checksum() +} + +func (checker *ChecksumChecker) OnError(t *taskfile.Task) error { + if len(t.Sources) == 0 { + return nil + } + return os.Remove(checker.checksumFilePath(t)) +} + +func (*ChecksumChecker) Kind() string { + return "checksum" +} + +func (c *ChecksumChecker) checksum(files ...string) (string, error) { h := md5.New() for _, f := range files { @@ -91,31 +109,13 @@ func (c *Checksum) checksum(files ...string) (string, error) { return fmt.Sprintf("%x", h.Sum(nil)), nil } -// Value implements the Checker Interface -func (c *Checksum) Value() (interface{}, error) { - return c.checksum() -} - -// OnError implements the Checker interface -func (c *Checksum) OnError() error { - if len(c.Sources) == 0 { - return nil - } - return os.Remove(c.checksumFilePath()) -} - -// Kind implements the Checker Interface -func (*Checksum) Kind() string { - return "checksum" -} - -func (c *Checksum) checksumFilePath() string { - return filepath.Join(c.TempDir, "checksum", normalizeFilename(c.Task)) +func (checker *ChecksumChecker) checksumFilePath(t *taskfile.Task) string { + return filepath.Join(checker.tempDir, "checksum", normalizeFilename(t.Name())) } var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]") -// replaces invalid caracters on filenames with "-" +// replaces invalid characters on filenames with "-" func normalizeFilename(f string) string { return checksumFilenameRegexp.ReplaceAllString(f, "-") } diff --git a/internal/status/checksum_test.go b/internal/fingerprint/sources_checksum_test.go similarity index 94% rename from internal/status/checksum_test.go rename to internal/fingerprint/sources_checksum_test.go index 9d616685..c706cf06 100644 --- a/internal/status/checksum_test.go +++ b/internal/fingerprint/sources_checksum_test.go @@ -1,4 +1,4 @@ -package status +package fingerprint import ( "testing" diff --git a/internal/fingerprint/sources_none.go b/internal/fingerprint/sources_none.go new file mode 100644 index 00000000..8fd955fb --- /dev/null +++ b/internal/fingerprint/sources_none.go @@ -0,0 +1,23 @@ +package fingerprint + +import "github.com/go-task/task/v3/taskfile" + +// NoneChecker is a no-op Checker. +// It will always report that the task is not up-to-date. +type NoneChecker struct{} + +func (NoneChecker) IsUpToDate(t *taskfile.Task) (bool, error) { + return false, nil +} + +func (NoneChecker) Value(t *taskfile.Task) (interface{}, error) { + return "", nil +} + +func (NoneChecker) OnError(t *taskfile.Task) error { + return nil +} + +func (NoneChecker) Kind() string { + return "none" +} diff --git a/internal/status/timestamp.go b/internal/fingerprint/sources_timestamp.go similarity index 75% rename from internal/status/timestamp.go rename to internal/fingerprint/sources_timestamp.go index 708b8668..92dd4540 100644 --- a/internal/status/timestamp.go +++ b/internal/fingerprint/sources_timestamp.go @@ -1,24 +1,29 @@ -package status +package fingerprint import ( "os" "path/filepath" "time" + + "github.com/go-task/task/v3/taskfile" ) -// Timestamp checks if any source change compared with the generated files, +// TimestampChecker checks if any source change compared with the generated files, // using file modifications timestamps. -type Timestamp struct { - TempDir string - Task string - Dir string - Sources []string - Generates []string - Dry bool +type TimestampChecker struct { + tempDir string + dry bool +} + +func NewTimestampChecker(tempDir string, dry bool) *TimestampChecker { + return &TimestampChecker{ + tempDir: tempDir, + dry: dry, + } } // IsUpToDate implements the Checker interface -func (t *Timestamp) IsUpToDate() (bool, error) { +func (checker *TimestampChecker) IsUpToDate(t *taskfile.Task) (bool, error) { if len(t.Sources) == 0 { return false, nil } @@ -32,7 +37,7 @@ func (t *Timestamp) IsUpToDate() (bool, error) { return false, nil } - timestampFile := t.timestampFilePath() + timestampFile := checker.timestampFilePath(t) // If the file exists, add the file path to the generates. // If the generate file is old, the task will be executed. @@ -41,7 +46,7 @@ func (t *Timestamp) IsUpToDate() (bool, error) { generates = append(generates, timestampFile) } else { // Create the timestamp file for the next execution when the file does not exist. - if !t.Dry { + if !checker.dry { if err := os.MkdirAll(filepath.Dir(timestampFile), 0o755); err != nil { return false, err } @@ -70,7 +75,7 @@ func (t *Timestamp) IsUpToDate() (bool, error) { } // Modify the metadata of the file to the the current time. - if !t.Dry { + if !checker.dry { if err := os.Chtimes(timestampFile, taskTime, taskTime); err != nil { return false, err } @@ -79,12 +84,12 @@ func (t *Timestamp) IsUpToDate() (bool, error) { return !shouldUpdate, nil } -func (t *Timestamp) Kind() string { +func (checker *TimestampChecker) Kind() string { return "timestamp" } // Value implements the Checker Interface -func (t *Timestamp) Value() (interface{}, error) { +func (checker *TimestampChecker) Value(t *taskfile.Task) (interface{}, error) { sources, err := globs(t.Dir, t.Sources) if err != nil { return time.Now(), err @@ -137,10 +142,10 @@ func anyFileNewerThan(files []string, givenTime time.Time) (bool, error) { } // OnError implements the Checker interface -func (*Timestamp) OnError() error { +func (*TimestampChecker) OnError(t *taskfile.Task) error { return nil } -func (t *Timestamp) timestampFilePath() string { - return filepath.Join(t.TempDir, "timestamp", normalizeFilename(t.Task)) +func (checker *TimestampChecker) timestampFilePath(t *taskfile.Task) string { + return filepath.Join(checker.tempDir, "timestamp", normalizeFilename(t.Task)) } diff --git a/internal/fingerprint/status.go b/internal/fingerprint/status.go new file mode 100644 index 00000000..a6398ef5 --- /dev/null +++ b/internal/fingerprint/status.go @@ -0,0 +1,36 @@ +package fingerprint + +import ( + "context" + + "github.com/go-task/task/v3/internal/env" + "github.com/go-task/task/v3/internal/execext" + "github.com/go-task/task/v3/internal/logger" + "github.com/go-task/task/v3/taskfile" +) + +type StatusChecker struct { + logger *logger.Logger +} + +func NewStatusChecker(logger *logger.Logger) StatusCheckable { + return &StatusChecker{ + logger: logger, + } +} + +func (checker *StatusChecker) IsUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) { + for _, s := range t.Status { + err := execext.RunCommand(ctx, &execext.RunCommandOptions{ + Command: s, + Dir: t.Dir, + Env: env.Get(t), + }) + if err != nil { + checker.logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err) + return false, nil + } + checker.logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s) + } + return true, nil +} diff --git a/internal/fingerprint/task.go b/internal/fingerprint/task.go new file mode 100644 index 00000000..6156c386 --- /dev/null +++ b/internal/fingerprint/task.go @@ -0,0 +1,132 @@ +package fingerprint + +import ( + "context" + + "github.com/go-task/task/v3/internal/logger" + "github.com/go-task/task/v3/taskfile" +) + +type ( + CheckerOption func(*CheckerConfig) + CheckerConfig struct { + method string + dry bool + tempDir string + logger *logger.Logger + statusChecker StatusCheckable + sourcesChecker SourcesCheckable + } +) + +func WithMethod(method string) CheckerOption { + return func(config *CheckerConfig) { + config.method = method + } +} + +func WithDry(dry bool) CheckerOption { + return func(config *CheckerConfig) { + config.dry = dry + } +} + +func WithTempDir(tempDir string) CheckerOption { + return func(config *CheckerConfig) { + config.tempDir = tempDir + } +} + +func WithLogger(logger *logger.Logger) CheckerOption { + return func(config *CheckerConfig) { + config.logger = logger + } +} + +func WithStatusChecker(checker StatusCheckable) CheckerOption { + return func(config *CheckerConfig) { + config.statusChecker = checker + } +} + +func WithSourcesChecker(checker SourcesCheckable) CheckerOption { + return func(config *CheckerConfig) { + config.sourcesChecker = checker + } +} + +func IsTaskUpToDate( + ctx context.Context, + t *taskfile.Task, + opts ...CheckerOption, +) (bool, error) { + var statusUpToDate bool + var sourcesUpToDate bool + var err error + + // Default config + config := &CheckerConfig{ + method: "none", + tempDir: "", + dry: false, + logger: nil, + statusChecker: nil, + sourcesChecker: nil, + } + + // Apply functional options + for _, opt := range opts { + opt(config) + } + + // If no status checker was given, set up the default one + if config.statusChecker == nil { + config.statusChecker = NewStatusChecker(config.logger) + } + + // If no sources checker was given, set up the default one + if config.sourcesChecker == nil { + config.sourcesChecker, err = NewSourcesChecker(config.method, config.tempDir, config.dry) + if err != nil { + return false, err + } + } + + statusIsSet := len(t.Status) != 0 + sourcesIsSet := len(t.Sources) != 0 + + // If status is set, check if it is up-to-date + if statusIsSet { + statusUpToDate, err = config.statusChecker.IsUpToDate(ctx, t) + if err != nil { + return false, err + } + } + + // If sources is set, check if they are up-to-date + if sourcesIsSet { + sourcesUpToDate, err = config.sourcesChecker.IsUpToDate(t) + if err != nil { + return false, err + } + } + + // If both status and sources are set, the task is up-to-date if both are up-to-date + if statusIsSet && sourcesIsSet { + return statusUpToDate && sourcesUpToDate, nil + } + + // If only status is set, the task is up-to-date if the status is up-to-date + if statusIsSet { + return statusUpToDate, nil + } + + // If only sources is set, the task is up-to-date if the sources are up-to-date + if sourcesIsSet { + return sourcesUpToDate, nil + } + + // If no status or sources are set, the task should always run + // i.e. it is never considered "up-to-date" + return false, nil +} diff --git a/internal/fingerprint/task_test.go b/internal/fingerprint/task_test.go new file mode 100644 index 00000000..a6a38167 --- /dev/null +++ b/internal/fingerprint/task_test.go @@ -0,0 +1,174 @@ +package fingerprint + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/go-task/task/v3/taskfile" +) + +// TruthTable +// +// | Status up-to-date | Sources up-to-date | Task is up-to-date | +// | ----------------- | ------------------ | ------------------ | +// | not set | not set | false | +// | not set | true | true | +// | not set | false | false | +// | true | not set | true | +// | true | true | true | +// | true | false | false | +// | false | not set | false | +// | false | true | false | +// | false | false | false | +func TestIsTaskUpToDate(t *testing.T) { + tests := []struct { + name string + task *taskfile.Task + setupMockStatusChecker func(m *MockStatusCheckable) + setupMockSourcesChecker func(m *MockSourcesCheckable) + expected bool + }{ + { + name: "expect FALSE when no status or sources are defined", + task: &taskfile.Task{ + Status: nil, + Sources: nil, + }, + setupMockStatusChecker: nil, + setupMockSourcesChecker: nil, + expected: false, + }, + { + name: "expect TRUE when no status is defined and sources are up-to-date", + task: &taskfile.Task{ + Status: nil, + Sources: []string{"sources"}, + }, + setupMockStatusChecker: nil, + setupMockSourcesChecker: func(m *MockSourcesCheckable) { + m.EXPECT().IsUpToDate(gomock.Any()).Return(true, nil) + }, + expected: true, + }, + { + name: "expect FALSE when no status is defined and sources are NOT up-to-date", + task: &taskfile.Task{ + Status: nil, + Sources: []string{"sources"}, + }, + setupMockStatusChecker: nil, + setupMockSourcesChecker: func(m *MockSourcesCheckable) { + m.EXPECT().IsUpToDate(gomock.Any()).Return(false, nil) + }, + expected: false, + }, + { + name: "expect TRUE when status is up-to-date and sources are not defined", + task: &taskfile.Task{ + Status: []string{"status"}, + Sources: nil, + }, + setupMockStatusChecker: func(m *MockStatusCheckable) { + m.EXPECT().IsUpToDate(gomock.Any(), gomock.Any()).Return(true, nil) + }, + setupMockSourcesChecker: nil, + expected: true, + }, + { + name: "expect TRUE when status and sources are up-to-date", + task: &taskfile.Task{ + Status: []string{"status"}, + Sources: []string{"sources"}, + }, + setupMockStatusChecker: func(m *MockStatusCheckable) { + m.EXPECT().IsUpToDate(gomock.Any(), gomock.Any()).Return(true, nil) + }, + setupMockSourcesChecker: func(m *MockSourcesCheckable) { + m.EXPECT().IsUpToDate(gomock.Any()).Return(true, nil) + }, + expected: true, + }, + { + name: "expect FALSE when status is up-to-date, but sources are NOT up-to-date", + task: &taskfile.Task{ + Status: []string{"status"}, + Sources: []string{"sources"}, + }, + setupMockStatusChecker: func(m *MockStatusCheckable) { + m.EXPECT().IsUpToDate(gomock.Any(), gomock.Any()).Return(true, nil) + }, + setupMockSourcesChecker: func(m *MockSourcesCheckable) { + m.EXPECT().IsUpToDate(gomock.Any()).Return(false, nil) + }, + expected: false, + }, + { + name: "expect FALSE when status is NOT up-to-date and sources are not defined", + task: &taskfile.Task{ + Status: []string{"status"}, + Sources: nil, + }, + setupMockStatusChecker: func(m *MockStatusCheckable) { + m.EXPECT().IsUpToDate(gomock.Any(), gomock.Any()).Return(false, nil) + }, + setupMockSourcesChecker: nil, + expected: false, + }, + { + name: "expect FALSE when status is NOT up-to-date, but sources are up-to-date", + task: &taskfile.Task{ + Status: []string{"status"}, + Sources: []string{"sources"}, + }, + setupMockStatusChecker: func(m *MockStatusCheckable) { + m.EXPECT().IsUpToDate(gomock.Any(), gomock.Any()).Return(false, nil) + }, + setupMockSourcesChecker: func(m *MockSourcesCheckable) { + m.EXPECT().IsUpToDate(gomock.Any()).Return(true, nil) + }, + expected: false, + }, + { + name: "expect FALSE when status and sources are NOT up-to-date", + task: &taskfile.Task{ + Status: []string{"status"}, + Sources: []string{"sources"}, + }, + setupMockStatusChecker: func(m *MockStatusCheckable) { + m.EXPECT().IsUpToDate(gomock.Any(), gomock.Any()).Return(false, nil) + }, + setupMockSourcesChecker: func(m *MockSourcesCheckable) { + m.EXPECT().IsUpToDate(gomock.Any()).Return(false, nil) + }, + expected: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + + mockStatusChecker := NewMockStatusCheckable(ctrl) + if tt.setupMockStatusChecker != nil { + tt.setupMockStatusChecker(mockStatusChecker) + } + + mockSourcesChecker := NewMockSourcesCheckable(ctrl) + if tt.setupMockSourcesChecker != nil { + tt.setupMockSourcesChecker(mockSourcesChecker) + } + + result, err := IsTaskUpToDate( + context.Background(), + tt.task, + WithStatusChecker(mockStatusChecker), + WithSourcesChecker(mockSourcesChecker), + ) + require.NoError(t, err) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/internal/status/none.go b/internal/status/none.go deleted file mode 100644 index cca3888b..00000000 --- a/internal/status/none.go +++ /dev/null @@ -1,23 +0,0 @@ -package status - -// None is a no-op Checker -type None struct{} - -// IsUpToDate implements the Checker interface -func (None) IsUpToDate() (bool, error) { - return false, nil -} - -// Value implements the Checker interface -func (None) Value() (interface{}, error) { - return "", nil -} - -func (None) Kind() string { - return "none" -} - -// OnError implements the Checker interface -func (None) OnError() error { - return nil -} diff --git a/internal/status/status.go b/internal/status/status.go deleted file mode 100644 index 2648d27c..00000000 --- a/internal/status/status.go +++ /dev/null @@ -1,15 +0,0 @@ -package status - -var ( - _ Checker = &Timestamp{} - _ Checker = &Checksum{} - _ Checker = None{} -) - -// Checker is an interface that checks if the status is up-to-date -type Checker interface { - IsUpToDate() (bool, error) - Value() (interface{}, error) - OnError() error - Kind() string -} diff --git a/precondition.go b/precondition.go index aa739f9a..3c9b6038 100644 --- a/precondition.go +++ b/precondition.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/taskfile" @@ -19,7 +20,7 @@ func (e *Executor) areTaskPreconditionsMet(ctx context.Context, t *taskfile.Task err := execext.RunCommand(ctx, &execext.RunCommandOptions{ Command: p.Sh, Dir: t.Dir, - Env: getEnviron(t), + Env: env.Get(t), }) if err != nil { diff --git a/status.go b/status.go index 2a104add..1a515ce1 100644 --- a/status.go +++ b/status.go @@ -4,20 +4,33 @@ import ( "context" "fmt" - "github.com/go-task/task/v3/internal/execext" - "github.com/go-task/task/v3/internal/logger" - "github.com/go-task/task/v3/internal/status" + "github.com/go-task/task/v3/internal/fingerprint" "github.com/go-task/task/v3/taskfile" ) // Status returns an error if any the of given tasks is not up-to-date func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error { for _, call := range calls { + + // Compile the task t, err := e.CompiledTask(call) if err != nil { return err } - isUpToDate, err := e.isTaskUpToDate(ctx, t) + + // Get the fingerprinting method to use + method := e.Taskfile.Method + if t.Method != "" { + method = t.Method + } + + // Check if the task is up-to-date + isUpToDate, err := fingerprint.IsTaskUpToDate(ctx, t, + fingerprint.WithMethod(method), + fingerprint.WithTempDir(e.TempDir), + fingerprint.WithDry(e.Dry), + fingerprint.WithLogger(e.Logger), + ) if err != nil { return err } @@ -28,113 +41,14 @@ func (e *Executor) Status(ctx context.Context, calls ...taskfile.Call) error { return nil } -func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, error) { - var statusUpToDate bool - var sourcesUpToDate bool - var err error - - statusIsSet := len(t.Status) != 0 - sourcesIsSet := len(t.Sources) != 0 - - // If status is set, check if it is up-to-date - if statusIsSet { - statusUpToDate, err = e.isTaskUpToDateStatus(ctx, t) - if err != nil { - return false, err - } - } - - // If sources is set, check if they are up-to-date - if sourcesIsSet { - checker, err := e.getStatusChecker(t) - if err != nil { - return false, err - } - sourcesUpToDate, err = checker.IsUpToDate() - if err != nil { - return false, err - } - } - - // If both status and sources are set, the task is up-to-date if both are up-to-date - if statusIsSet && sourcesIsSet { - return statusUpToDate && sourcesUpToDate, nil - } - - // If only status is set, the task is up-to-date if the status is up-to-date - if statusIsSet { - return statusUpToDate, nil - } - - // If only sources is set, the task is up-to-date if the sources are up-to-date - if sourcesIsSet { - return sourcesUpToDate, nil - } - - // If no status or sources are set, the task should always run - // i.e. it is never considered "up-to-date" - return false, nil -} - func (e *Executor) statusOnError(t *taskfile.Task) error { - checker, err := e.getStatusChecker(t) - if err != nil { - return err - } - return checker.OnError() -} - -func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) { method := t.Method if method == "" { method = e.Taskfile.Method } - switch method { - case "timestamp": - return e.timestampChecker(t), nil - case "checksum": - return e.checksumChecker(t), nil - case "none": - return status.None{}, nil - default: - return nil, fmt.Errorf(`task: invalid method "%s"`, method) + checker, err := fingerprint.NewSourcesChecker(method, e.TempDir, e.Dry) + if err != nil { + return err } -} - -func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker { - return &status.Timestamp{ - TempDir: e.TempDir, - Task: t.Name(), - Dir: t.Dir, - Sources: t.Sources, - Generates: t.Generates, - Dry: e.Dry, - } -} - -func (e *Executor) checksumChecker(t *taskfile.Task) status.Checker { - return &status.Checksum{ - TempDir: e.TempDir, - TaskDir: t.Dir, - Task: t.Name(), - Sources: t.Sources, - Generates: t.Generates, - Dry: e.Dry, - } -} - -func (e *Executor) isTaskUpToDateStatus(ctx context.Context, t *taskfile.Task) (bool, error) { - for _, s := range t.Status { - err := execext.RunCommand(ctx, &execext.RunCommandOptions{ - Command: s, - Dir: t.Dir, - Env: getEnviron(t), - }) - if err != nil { - e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited non-zero: %s", s, err) - return false, nil - } - e.Logger.VerboseOutf(logger.Yellow, "task: status command %s exited zero", s) - } - return true, nil + return checker.OnError(t) } diff --git a/task.go b/task.go index c6710835..1244f8c3 100644 --- a/task.go +++ b/task.go @@ -13,7 +13,9 @@ import ( "time" "github.com/go-task/task/v3/internal/compiler" + "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/logger" "github.com/go-task/task/v3/internal/output" "github.com/go-task/task/v3/internal/slicesext" @@ -157,7 +159,18 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { return err } - upToDate, err := e.isTaskUpToDate(ctx, t) + // Get the fingerprinting method to use + method := e.Taskfile.Method + if t.Method != "" { + method = t.Method + } + + upToDate, err := fingerprint.IsTaskUpToDate(ctx, t, + fingerprint.WithMethod(method), + fingerprint.WithTempDir(e.TempDir), + fingerprint.WithDry(e.Dry), + fingerprint.WithLogger(e.Logger), + ) if err != nil { return err } @@ -286,7 +299,7 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi err = execext.RunCommand(ctx, &execext.RunCommandOptions{ Command: cmd.Cmd, Dir: t.Dir, - Env: getEnviron(t), + Env: env.Get(t), PosixOpts: slicesext.UniqueJoin(e.Taskfile.Set, t.Set, cmd.Set), BashOpts: slicesext.UniqueJoin(e.Taskfile.Shopt, t.Shopt, cmd.Shopt), Stdin: e.Stdin, @@ -306,29 +319,6 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi } } -func getEnviron(t *taskfile.Task) []string { - if t.Env == nil { - return nil - } - - environ := os.Environ() - - for k, v := range t.Env.ToCacheMap() { - str, isString := v.(string) - if !isString { - continue - } - - if _, alreadySet := os.LookupEnv(k); alreadySet { - continue - } - - environ = append(environ, fmt.Sprintf("%s=%s", k, str)) - } - - return environ -} - func (e *Executor) startExecution(ctx context.Context, t *taskfile.Task, execute func(ctx context.Context) error) error { h, err := e.GetHash(t) if err != nil { diff --git a/variables.go b/variables.go index 4bb34927..77c8fc43 100644 --- a/variables.go +++ b/variables.go @@ -8,7 +8,7 @@ import ( "github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/filepathext" - "github.com/go-task/task/v3/internal/status" + "github.com/go-task/task/v3/internal/fingerprint" "github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/taskfile" ) @@ -161,8 +161,11 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf } if len(origTask.Status) > 0 { - for _, checker := range []status.Checker{e.timestampChecker(&new), e.checksumChecker(&new)} { - value, err := checker.Value() + timestampChecker := fingerprint.NewTimestampChecker(e.TempDir, e.Dry) + checksumChecker := fingerprint.NewChecksumChecker(e.TempDir, e.Dry) + + for _, checker := range []fingerprint.SourcesCheckable{timestampChecker, checksumChecker} { + value, err := checker.Value(&new) if err != nil { return nil, err } diff --git a/watch.go b/watch.go index 15773a4a..b180c6f8 100644 --- a/watch.go +++ b/watch.go @@ -12,8 +12,8 @@ import ( "github.com/radovskyb/watcher" + "github.com/go-task/task/v3/internal/fingerprint" "github.com/go-task/task/v3/internal/logger" - "github.com/go-task/task/v3/internal/status" "github.com/go-task/task/v3/taskfile" ) @@ -142,7 +142,7 @@ func (e *Executor) registerWatchedFiles(w *watcher.Watcher, calls ...taskfile.Ca } for _, s := range task.Sources { - files, err := status.Glob(task.Dir, s) + files, err := fingerprint.Glob(task.Dir, s) if err != nil { return fmt.Errorf("task: %s: %w", s, err) }