From 1ee684b7c067365811fd1de0966395448a9f2ac8 Mon Sep 17 00:00:00 2001 From: Stephen Prater Date: Tue, 11 Jun 2019 11:49:37 -0700 Subject: [PATCH 1/5] Expose timestamp and checksum to status --- docs/usage.md | 10 ++++++++++ internal/status/checksum.go | 9 +++++++++ internal/status/none.go | 9 +++++++++ internal/status/status.go | 2 ++ internal/status/timestamp.go | 24 ++++++++++++++++++++++++ status.go | 6 +++--- task_test.go | 21 +++++++++++++++++++++ testdata/checksum/Taskfile.yml | 13 +++++++++++++ variables.go | 29 ++++++++++++++++++++++++++++- 9 files changed, 119 insertions(+), 4 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index d8ffbd90..4e657149 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -340,6 +340,16 @@ tasks: - test -f directory/file2.txt ``` +Normally, you would use either `status` or `sources` in combination with +`generates` - but for tasks that generate remote artifacts (docker images, +deploys, CD releases) the checksum source and timestamps require either +access to the artifact or for an out-of-band refresh of the `.checksum` +fingerprint file. + +Two special variables `{{.CHECKSUM}}` and `{{.TIMESTAMP}}` are available +for interpolation within `status` commands, depending on the method assigned +to fingerprint the sources. Only `source` globs are fingerprinted. + You can use `--force` or `-f` if you want to force a task to run even when up-to-date. diff --git a/internal/status/checksum.go b/internal/status/checksum.go index 44331047..f33c1d22 100644 --- a/internal/status/checksum.go +++ b/internal/status/checksum.go @@ -46,6 +46,10 @@ func (c *Checksum) IsUpToDate() (bool, error) { return oldMd5 == newMd5, nil } +func (t *Checksum) Kind() string { + return "checksum" +} + func (c *Checksum) checksum(files ...string) (string, error) { h := md5.New() @@ -73,6 +77,11 @@ func (c *Checksum) checksum(files ...string) (string, error) { return fmt.Sprintf("%x", h.Sum(nil)), nil } +// Value implements the Chcker Interface +func (c *Checksum) Value() (string, error) { + return c.checksum() +} + // OnError implements the Checker interface func (c *Checksum) OnError() error { return os.Remove(c.checksumFilePath()) diff --git a/internal/status/none.go b/internal/status/none.go index 01e35060..c67dc437 100644 --- a/internal/status/none.go +++ b/internal/status/none.go @@ -8,6 +8,15 @@ func (None) IsUpToDate() (bool, error) { return false, nil } +// Value implements the Checker interface +func (None) Value() (string, 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 index 320ca8a6..45388b49 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -9,5 +9,7 @@ var ( // Checker is an interface that checks if the status is up-to-date type Checker interface { IsUpToDate() (bool, error) + Value() (string, error) OnError() error + Kind() string } diff --git a/internal/status/timestamp.go b/internal/status/timestamp.go index 62b9aafb..a97549fc 100644 --- a/internal/status/timestamp.go +++ b/internal/status/timestamp.go @@ -1,6 +1,7 @@ package status import ( + "fmt" "os" "time" ) @@ -41,6 +42,29 @@ func (t *Timestamp) IsUpToDate() (bool, error) { return !generatesMinTime.Before(sourcesMaxTime), nil } +func (t *Timestamp) Kind() string { + return "timestamp" +} + +// Value implements the Checker Interface +func (t *Timestamp) Value() (string, error) { + sources, err := glob(t.Dir, t.Sources) + if err != nil { + return "", err + } + + sourcesMaxTime, err := getMaxTime(sources...) + if err != nil { + return "", err + } + + if sourcesMaxTime.IsZero() { + return "0", nil + } + + return fmt.Sprintf("%d", sourcesMaxTime.Unix()), nil +} + func getMinTime(files ...string) (time.Time, error) { var t time.Time for _, f := range files { diff --git a/status.go b/status.go index 7ebb5f84..74f25489 100644 --- a/status.go +++ b/status.go @@ -32,7 +32,7 @@ func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, return e.isTaskUpToDateStatus(ctx, t) } - checker, err := e.getStatusChecker(t) + checker, err := e.GetStatusChecker(t) if err != nil { return false, err } @@ -41,14 +41,14 @@ func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, } func (e *Executor) statusOnError(t *taskfile.Task) error { - checker, err := e.getStatusChecker(t) + checker, err := e.GetStatusChecker(t) if err != nil { return err } return checker.OnError() } -func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) { +func (e *Executor) GetStatusChecker(t *taskfile.Task) (status.Checker, error) { switch t.Method { case "", "timestamp": return &status.Timestamp{ diff --git a/task_test.go b/task_test.go index 1b97fbb6..b732bfe5 100644 --- a/task_test.go +++ b/task_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/go-task/task/v2" + "github.com/go-task/task/v2/internal/logger" "github.com/go-task/task/v2/internal/taskfile" "github.com/mitchellh/go-homedir" @@ -388,12 +389,20 @@ func TestStatusChecksum(t *testing.T) { } var buff bytes.Buffer + + logCapturer := logger.Logger{ + Stdout: &buff, + Stderr: &buff, + Verbose: true, + } + e := task.Executor{ Dir: dir, Stdout: &buff, Stderr: &buff, } assert.NoError(t, e.Setup()) + e.Logger = &logCapturer assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"})) for _, f := range files { @@ -404,6 +413,18 @@ func TestStatusChecksum(t *testing.T) { buff.Reset() assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build"})) assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String()) + + buff.Reset() + e.Silent = false + e.Verbose = true + assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build-with-checksum"})) + assert.Contains(t, buff.String(), "d41d8cd98f00b204e9800998ecf8427e") + + buff.Reset() + inf, _ := os.Stat(filepath.Join(dir, "source.txt")) + ts := fmt.Sprintf("%d", inf.ModTime().Unix()) + assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build-with-timestamp"})) + assert.Contains(t, buff.String(), ts) } func TestInit(t *testing.T) { diff --git a/testdata/checksum/Taskfile.yml b/testdata/checksum/Taskfile.yml index 4f4a2362..b9d5f6e9 100644 --- a/testdata/checksum/Taskfile.yml +++ b/testdata/checksum/Taskfile.yml @@ -7,3 +7,16 @@ build: generates: - ./generated.txt method: checksum + +build-with-checksum: + sources: + - ./source.txt + method: checksum + status: + - echo "{{.CHECKSUM}}" + +build-with-timestamp: + sources: + - ./source.txt + status: + - echo "{{.TIMESTAMP}}" diff --git a/variables.go b/variables.go index 9c227372..59b1ac81 100644 --- a/variables.go +++ b/variables.go @@ -1,7 +1,9 @@ package task import ( + "io/ioutil" "path/filepath" + "strings" "github.com/go-task/task/v2/internal/execext" "github.com/go-task/task/v2/internal/taskfile" @@ -20,6 +22,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { if err != nil { return nil, err } + r := templater.Templater{Vars: vars} new := taskfile.Task{ @@ -27,7 +30,6 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { Desc: r.Replace(origTask.Desc), Sources: r.ReplaceSlice(origTask.Sources), Generates: r.ReplaceSlice(origTask.Generates), - Status: r.ReplaceSlice(origTask.Status), Dir: r.Replace(origTask.Dir), Vars: nil, Env: nil, @@ -62,6 +64,31 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { new.Env[k] = taskfile.Var{Static: static} } + if len(origTask.Status) > 0 { + + e := &Executor{ + Dir: new.Dir, + Stdout: ioutil.Discard, + Stderr: ioutil.Discard, + Dry: true, + } + + checker, err := e.GetStatusChecker(&new) + if err != nil { + return nil, err + } + + value, err := checker.Value() + if err != nil { + return nil, err + } + + vars[strings.ToUpper(checker.Kind())] = taskfile.Var{Static: value} + + statusTemplater := templater.Templater{Vars: vars} + new.Status = statusTemplater.ReplaceSlice(origTask.Status) + } + if len(origTask.Cmds) > 0 { new.Cmds = make([]*taskfile.Cmd, len(origTask.Cmds)) for i, cmd := range origTask.Cmds { From e0f72a6193612ee850c45345b18d878553d2b909 Mon Sep 17 00:00:00 2001 From: Stephen Prater Date: Mon, 17 Jun 2019 18:57:25 -0700 Subject: [PATCH 2/5] Apply suggestions from code review Co-Authored-By: Andrey Nering --- docs/usage.md | 2 +- variables.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 4e657149..48f52242 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -341,7 +341,7 @@ tasks: ``` Normally, you would use either `status` or `sources` in combination with -`generates` - but for tasks that generate remote artifacts (docker images, +`generates` - but for tasks that generate remote artifacts (Docker images, deploys, CD releases) the checksum source and timestamps require either access to the artifact or for an out-of-band refresh of the `.checksum` fingerprint file. diff --git a/variables.go b/variables.go index 59b1ac81..d90b331a 100644 --- a/variables.go +++ b/variables.go @@ -65,7 +65,6 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { } if len(origTask.Status) > 0 { - e := &Executor{ Dir: new.Dir, Stdout: ioutil.Discard, From db36bc67f18f4f104b52d5e3aa767a7141bcbcdc Mon Sep 17 00:00:00 2001 From: Stephen Prater Date: Sun, 25 Aug 2019 10:30:00 -0700 Subject: [PATCH 3/5] Changes per feedback --- docs/usage.md | 18 +++++++++++--- go.mod | 6 ++--- go.sum | 12 +++++++++ internal/status/checksum.go | 11 +++++---- internal/templater/templater.go | 4 +++ status.go | 6 ++--- variables.go | 44 ++++++++++++++------------------- 7 files changed, 60 insertions(+), 41 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 48f52242..5e23d236 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -266,6 +266,8 @@ The above syntax is also supported in `deps`. ## Prevent unnecessary work +### By fingerprinting locally generated files and their sources + If a task generates something, you can inform Task the source and generated files, so Task will prevent to run them if not necessary. @@ -321,6 +323,9 @@ tasks: > TIP: method `none` skips any validation and always run the task. +### Using programmatic checks to indicate a task is up to date. + + Alternatively, you can inform a sequence of tests as `status`. If no error is returned (exit status 0), the task is considered up-to-date: @@ -340,7 +345,8 @@ tasks: - test -f directory/file2.txt ``` -Normally, you would use either `status` or `sources` in combination with + +Normally, you would use `sources` in combination with `generates` - but for tasks that generate remote artifacts (Docker images, deploys, CD releases) the checksum source and timestamps require either access to the artifact or for an out-of-band refresh of the `.checksum` @@ -356,9 +362,13 @@ up-to-date. Also, `task --status [tasks]...` will exit with a non-zero exit code if any of the tasks are not up-to-date. -If you need a certain set of conditions to be _true_ you can use the -`preconditions` stanza. `preconditions` are very similar to `status` -lines except they support `sh` expansion and they SHOULD all return 0. +### Using programmatic checks to cancel execution of an task and it's dependencies + +In addition to `status` checks, there are also `preconditions` checks, which are +the logical inverse of `status` checks. That is, if you need a certain set of +conditions to be _true_ you can use the `preconditions` stanza. +`preconditions` are similar to `status` lines except they support `sh` +expansion and they SHOULD all return 0. ```yaml version: '2' diff --git a/go.mod b/go.mod index 1b479dd7..abcc7eb8 100644 --- a/go.mod +++ b/go.mod @@ -14,10 +14,8 @@ require ( github.com/radovskyb/watcher v1.0.5 github.com/spf13/pflag v1.0.3 github.com/stretchr/testify v1.3.0 - golang.org/x/crypto v0.0.0-20180830192347-182538f80094 // indirect - golang.org/x/net v0.0.0-20180826012351-8a410e7b638d // indirect - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f - golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 // indirect + golang.org/x/sync v0.0.0-20190423024810-112230192c58 + golang.org/x/tools/gopls v0.1.3 // indirect gopkg.in/yaml.v2 v2.2.1 mvdan.cc/sh v2.6.4+incompatible ) diff --git a/go.sum b/go.sum index 18f0ea37..c298de7f 100644 --- a/go.sum +++ b/go.sum @@ -33,12 +33,24 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg= golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789 h1:T8D7l6WB3tLu+VpKvw06ieD/OhBi1XpJmG1U/FtttZg= golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190710153321-831012c29e42 h1:4IOeC7p+OItq3+O5BWkcmVu2uBe3jekXau5S4QZX9DU= +golang.org/x/tools v0.0.0-20190710153321-831012c29e42/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools/gopls v0.1.3 h1:CB5ECiPysqZrwxcyRjN+exyZpY0gODTZvNiqQi3lpeo= +golang.org/x/tools/gopls v0.1.3/go.mod h1:vrCQzOKxvuiZLjCKSmbbov04oeBQQOb4VQqwYK2PWIY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= diff --git a/internal/status/checksum.go b/internal/status/checksum.go index f33c1d22..bdd2aff9 100644 --- a/internal/status/checksum.go +++ b/internal/status/checksum.go @@ -46,10 +46,6 @@ func (c *Checksum) IsUpToDate() (bool, error) { return oldMd5 == newMd5, nil } -func (t *Checksum) Kind() string { - return "checksum" -} - func (c *Checksum) checksum(files ...string) (string, error) { h := md5.New() @@ -77,7 +73,7 @@ func (c *Checksum) checksum(files ...string) (string, error) { return fmt.Sprintf("%x", h.Sum(nil)), nil } -// Value implements the Chcker Interface +// Value implements the Checker Interface func (c *Checksum) Value() (string, error) { return c.checksum() } @@ -87,6 +83,11 @@ func (c *Checksum) OnError() error { return os.Remove(c.checksumFilePath()) } +// Kind implements the Checker Interface +func (t *Checksum) Kind() string { + return "checksum" +} + func (c *Checksum) checksumFilePath() string { return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task)) } diff --git a/internal/templater/templater.go b/internal/templater/templater.go index 65a2fde2..d601c99a 100644 --- a/internal/templater/templater.go +++ b/internal/templater/templater.go @@ -18,6 +18,10 @@ type Templater struct { err error } +func (r *Templater) RefreshStringMap() { + r.strMap = r.Vars.ToStringMap() +} + func (r *Templater) Replace(str string) string { if r.err != nil || str == "" { return "" diff --git a/status.go b/status.go index 74f25489..7ebb5f84 100644 --- a/status.go +++ b/status.go @@ -32,7 +32,7 @@ func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, return e.isTaskUpToDateStatus(ctx, t) } - checker, err := e.GetStatusChecker(t) + checker, err := e.getStatusChecker(t) if err != nil { return false, err } @@ -41,14 +41,14 @@ func (e *Executor) isTaskUpToDate(ctx context.Context, t *taskfile.Task) (bool, } func (e *Executor) statusOnError(t *taskfile.Task) error { - checker, err := e.GetStatusChecker(t) + checker, err := e.getStatusChecker(t) if err != nil { return err } return checker.OnError() } -func (e *Executor) GetStatusChecker(t *taskfile.Task) (status.Checker, error) { +func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) { switch t.Method { case "", "timestamp": return &status.Timestamp{ diff --git a/variables.go b/variables.go index d90b331a..57e7ae78 100644 --- a/variables.go +++ b/variables.go @@ -1,7 +1,6 @@ package task import ( - "io/ioutil" "path/filepath" "strings" @@ -64,30 +63,6 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { new.Env[k] = taskfile.Var{Static: static} } - if len(origTask.Status) > 0 { - e := &Executor{ - Dir: new.Dir, - Stdout: ioutil.Discard, - Stderr: ioutil.Discard, - Dry: true, - } - - checker, err := e.GetStatusChecker(&new) - if err != nil { - return nil, err - } - - value, err := checker.Value() - if err != nil { - return nil, err - } - - vars[strings.ToUpper(checker.Kind())] = taskfile.Var{Static: value} - - statusTemplater := templater.Templater{Vars: vars} - new.Status = statusTemplater.ReplaceSlice(origTask.Status) - } - if len(origTask.Cmds) > 0 { new.Cmds = make([]*taskfile.Cmd, len(origTask.Cmds)) for i, cmd := range origTask.Cmds { @@ -120,5 +95,24 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { } } + if len(origTask.Status) > 0 { + checker, err := e.getStatusChecker(&new) + if err != nil { + return nil, err + } + + value, err := checker.Value() + if err != nil { + return nil, err + } + + vars[strings.ToUpper(checker.Kind())] = taskfile.Var{Static: value} + // Adding new static variables, requires us to refresh the templaters + // cache of the the static values + r.RefreshStringMap() + + new.Status = r.ReplaceSlice(origTask.Status) + } + return &new, r.Err() } From a1aec8178a917c46278c9d989a1eb64c6fc9c3e8 Mon Sep 17 00:00:00 2001 From: Stephen Prater Date: Sun, 25 Aug 2019 13:16:59 -0700 Subject: [PATCH 4/5] Export Time Struct to Template --- docs/usage.md | 5 +++++ internal/status/checksum.go | 2 +- internal/status/none.go | 2 +- internal/status/status.go | 2 +- internal/status/timestamp.go | 11 +++++------ internal/taskfile/var.go | 14 ++++++++++---- internal/templater/templater.go | 15 ++++++++------- task.go | 2 +- task_test.go | 2 ++ testdata/checksum/Taskfile.yml | 3 ++- variables.go | 8 ++++---- 11 files changed, 40 insertions(+), 26 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index c4882332..02a8db7e 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -356,6 +356,11 @@ Two special variables `{{.CHECKSUM}}` and `{{.TIMESTAMP}}` are available for interpolation within `status` commands, depending on the method assigned to fingerprint the sources. Only `source` globs are fingerprinted. +Note that the `{{.TIMESTAMP}}` variable is a "live" Go time struct, and can be +formatted using any of the methods that `Time` responds to. + +See [the Go Time documentation](https://golang.org/pkg/time/) for more information. + You can use `--force` or `-f` if you want to force a task to run even when up-to-date. diff --git a/internal/status/checksum.go b/internal/status/checksum.go index bdd2aff9..9d449d65 100644 --- a/internal/status/checksum.go +++ b/internal/status/checksum.go @@ -74,7 +74,7 @@ func (c *Checksum) checksum(files ...string) (string, error) { } // Value implements the Checker Interface -func (c *Checksum) Value() (string, error) { +func (c *Checksum) Value() (interface{}, error) { return c.checksum() } diff --git a/internal/status/none.go b/internal/status/none.go index c67dc437..cca3888b 100644 --- a/internal/status/none.go +++ b/internal/status/none.go @@ -9,7 +9,7 @@ func (None) IsUpToDate() (bool, error) { } // Value implements the Checker interface -func (None) Value() (string, error) { +func (None) Value() (interface{}, error) { return "", nil } diff --git a/internal/status/status.go b/internal/status/status.go index 45388b49..2648d27c 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -9,7 +9,7 @@ var ( // Checker is an interface that checks if the status is up-to-date type Checker interface { IsUpToDate() (bool, error) - Value() (string, error) + Value() (interface{}, error) OnError() error Kind() string } diff --git a/internal/status/timestamp.go b/internal/status/timestamp.go index a97549fc..97881f58 100644 --- a/internal/status/timestamp.go +++ b/internal/status/timestamp.go @@ -1,7 +1,6 @@ package status import ( - "fmt" "os" "time" ) @@ -47,22 +46,22 @@ func (t *Timestamp) Kind() string { } // Value implements the Checker Interface -func (t *Timestamp) Value() (string, error) { +func (t *Timestamp) Value() (interface{}, error) { sources, err := glob(t.Dir, t.Sources) if err != nil { - return "", err + return time.Now(), err } sourcesMaxTime, err := getMaxTime(sources...) if err != nil { - return "", err + return time.Now(), err } if sourcesMaxTime.IsZero() { - return "0", nil + return time.Unix(0, 0), nil } - return fmt.Sprintf("%d", sourcesMaxTime.Unix()), nil + return sourcesMaxTime, nil } func getMinTime(files ...string) (time.Time, error) { diff --git a/internal/taskfile/var.go b/internal/taskfile/var.go index f52e0f8b..ad0f5e90 100644 --- a/internal/taskfile/var.go +++ b/internal/taskfile/var.go @@ -13,17 +13,22 @@ var ( // Vars is a string[string] variables map. type Vars map[string]Var -// ToStringMap converts Vars to a string map containing only the static +// ToCacheMap converts Vars to a map containing only the static // variables -func (vs Vars) ToStringMap() (m map[string]string) { - m = make(map[string]string, len(vs)) +func (vs Vars) ToCacheMap() (m map[string](interface{})) { + m = make(map[string](interface{}), len(vs)) for k, v := range vs { if v.Sh != "" { // Dynamic variable is not yet resolved; trigger // to be used in templates. continue } - m[k] = v.Static + + if v.Live != nil { + m[k] = v.Live + } else { + m[k] = v.Static + } } return } @@ -31,6 +36,7 @@ func (vs Vars) ToStringMap() (m map[string]string) { // Var represents either a static or dynamic variable. type Var struct { Static string + Live interface{} Sh string } diff --git a/internal/templater/templater.go b/internal/templater/templater.go index d601c99a..d43117e0 100644 --- a/internal/templater/templater.go +++ b/internal/templater/templater.go @@ -14,12 +14,12 @@ import ( type Templater struct { Vars taskfile.Vars - strMap map[string]string - err error + cacheMap map[string](interface{}) + err error } -func (r *Templater) RefreshStringMap() { - r.strMap = r.Vars.ToStringMap() +func (r *Templater) RefreshCacheMap() { + r.cacheMap = r.Vars.ToCacheMap() } func (r *Templater) Replace(str string) string { @@ -33,12 +33,12 @@ func (r *Templater) Replace(str string) string { return "" } - if r.strMap == nil { - r.strMap = r.Vars.ToStringMap() + if r.cacheMap == nil { + r.cacheMap = r.Vars.ToCacheMap() } var b bytes.Buffer - if err = templ.Execute(&b, r.strMap); err != nil { + if err = templ.Execute(&b, r.cacheMap); err != nil { r.err = err return "" } @@ -66,6 +66,7 @@ func (r *Templater) ReplaceVars(vars taskfile.Vars) taskfile.Vars { for k, v := range vars { new[k] = taskfile.Var{ Static: r.Replace(v.Static), + Live: v.Live, Sh: r.Replace(v.Sh), } } diff --git a/task.go b/task.go index 40b1dc98..657abbcd 100644 --- a/task.go +++ b/task.go @@ -355,7 +355,7 @@ func getEnviron(t *taskfile.Task) []string { } environ := os.Environ() - for k, v := range t.Env.ToStringMap() { + for k, v := range t.Env.ToCacheMap() { environ = append(environ, fmt.Sprintf("%s=%s", k, v)) } return environ diff --git a/task_test.go b/task_test.go index 1e126a2b..6cb95058 100644 --- a/task_test.go +++ b/task_test.go @@ -423,8 +423,10 @@ func TestStatusChecksum(t *testing.T) { buff.Reset() inf, _ := os.Stat(filepath.Join(dir, "source.txt")) ts := fmt.Sprintf("%d", inf.ModTime().Unix()) + tf := fmt.Sprintf("%s", inf.ModTime()) assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "build-with-timestamp"})) assert.Contains(t, buff.String(), ts) + assert.Contains(t, buff.String(), tf) } func TestInit(t *testing.T) { diff --git a/testdata/checksum/Taskfile.yml b/testdata/checksum/Taskfile.yml index b9d5f6e9..029264fb 100644 --- a/testdata/checksum/Taskfile.yml +++ b/testdata/checksum/Taskfile.yml @@ -19,4 +19,5 @@ build-with-timestamp: sources: - ./source.txt status: - - echo "{{.TIMESTAMP}}" + - echo '{{.TIMESTAMP.Unix }}' + - echo '{{.TIMESTAMP}}' diff --git a/variables.go b/variables.go index 57e7ae78..8307fc4d 100644 --- a/variables.go +++ b/variables.go @@ -106,10 +106,10 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { return nil, err } - vars[strings.ToUpper(checker.Kind())] = taskfile.Var{Static: value} - // Adding new static variables, requires us to refresh the templaters - // cache of the the static values - r.RefreshStringMap() + vars[strings.ToUpper(checker.Kind())] = taskfile.Var{Live: value} + // Adding new variables, requires us to refresh the templaters + // cache of the the values manually + r.RefreshCacheMap() new.Status = r.ReplaceSlice(origTask.Status) } From 6b0935d6cf3d22b673c68bea56604604b5a9cfbe Mon Sep 17 00:00:00 2001 From: Stephen Prater Date: Sun, 25 Aug 2019 13:47:29 -0700 Subject: [PATCH 5/5] Fix tests --- internal/status/timestamp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/status/timestamp.go b/internal/status/timestamp.go index 6fa89c4a..3801c1ac 100644 --- a/internal/status/timestamp.go +++ b/internal/status/timestamp.go @@ -47,7 +47,7 @@ func (t *Timestamp) Kind() string { // Value implements the Checker Interface func (t *Timestamp) Value() (interface{}, error) { - sources, err := glob(t.Dir, t.Sources) + sources, err := globs(t.Dir, t.Sources) if err != nil { return time.Now(), err }