1
0
mirror of https://github.com/go-task/task.git synced 2025-02-13 13:59:32 +02:00

fix: avoid reruns when the timestamp method is used (#977)

This commit is contained in:
Amin Yahyaabadi 2023-01-14 12:17:36 -08:00 committed by GitHub
parent fce7575b03
commit 347fcf9f67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 28 deletions

View File

@ -667,9 +667,9 @@ The method `none` skips any validation and always run the task.
:::info
For the `checksum` (default) method to work, it is only necessary to
inform the source files, but if you want to use the `timestamp` method, you
also need to inform the generated files with `generates`.
For the `checksum` (default) or `timestamp` method to work, it is only necessary to
inform the source files.
When the `timestamp` method is used, the last time of the running the task is considered as a generate.
:::

View File

@ -109,12 +109,12 @@ func (*Checksum) Kind() string {
}
func (c *Checksum) checksumFilePath() string {
return filepath.Join(c.TempDir, "checksum", c.normalizeFilename(c.Task))
return filepath.Join(c.TempDir, "checksum", NormalizeFilename(c.Task))
}
var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
// replaces invalid caracters on filenames with "-"
func (*Checksum) normalizeFilename(f string) string {
func NormalizeFilename(f string) string {
return checksumFilenameRegexp.ReplaceAllString(f, "-")
}

View File

@ -16,6 +16,6 @@ func TestNormalizeFilename(t *testing.T) {
{"foo1bar2baz3", "foo1bar2baz3"},
}
for _, test := range tests {
assert.Equal(t, test.Out, (&Checksum{}).normalizeFilename(test.In))
assert.Equal(t, test.Out, NormalizeFilename(test.In))
}
}

View File

@ -2,20 +2,24 @@ package status
import (
"os"
"path/filepath"
"time"
)
// Timestamp 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
}
// IsUpToDate implements the Checker interface
func (t *Timestamp) IsUpToDate() (bool, error) {
if len(t.Sources) == 0 || len(t.Generates) == 0 {
if len(t.Sources) == 0 {
return false, nil
}
@ -28,17 +32,43 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
return false, nil
}
sourcesMaxTime, err := getMaxTime(sources...)
if err != nil || sourcesMaxTime.IsZero() {
timestampFile := t.timestampFilePath()
// if the file exists, add the file path to the generates
// if the generate file is old, the task will be executed
_, err = os.Stat(timestampFile)
if err == nil {
generates = append(generates, timestampFile)
} else {
// create the timestamp file for the next execution when the file does not exist
if !t.Dry {
_ = os.MkdirAll(filepath.Dir(timestampFile), 0o755)
_, _ = os.Create(timestampFile)
}
}
taskTime := time.Now()
// compare the time of the generates and sources. If the generates are old, the task will be executed
// get the max time of the generates
generateMaxTime, err := getMaxTime(generates...)
if err != nil || generateMaxTime.IsZero() {
return false, nil
}
generatesMinTime, err := getMinTime(generates...)
if err != nil || generatesMinTime.IsZero() {
// check if any of the source files is newer than the max time of the generates
shouldUpdate, err := anyFileNewerThan(sources, generateMaxTime)
if err != nil {
return false, nil
}
return !generatesMinTime.Before(sourcesMaxTime), nil
// modify the metadata of the file to the the current time
if !t.Dry {
_ = os.Chtimes(timestampFile, taskTime, taskTime)
}
return !shouldUpdate, nil
}
func (t *Timestamp) Kind() string {
@ -64,18 +94,6 @@ func (t *Timestamp) Value() (interface{}, error) {
return sourcesMaxTime, nil
}
func getMinTime(files ...string) (time.Time, error) {
var t time.Time
for _, f := range files {
info, err := os.Stat(f)
if err != nil {
return time.Time{}, err
}
t = minTime(t, info.ModTime())
}
return t, nil
}
func getMaxTime(files ...string) (time.Time, error) {
var t time.Time
for _, f := range files {
@ -88,11 +106,19 @@ func getMaxTime(files ...string) (time.Time, error) {
return t, nil
}
func minTime(a, b time.Time) time.Time {
if !a.IsZero() && a.Before(b) {
return a
// if the modification time of any of the files is newer than the the given time, returns true
// This function is lazy, as it stops when it finds a file newer than the given time
func anyFileNewerThan(files []string, givenTime time.Time) (bool, error) {
for _, f := range files {
info, err := os.Stat(f)
if err != nil {
return false, err
}
if info.ModTime().After(givenTime) {
return true, nil
}
}
return b
return false, nil
}
func maxTime(a, b time.Time) time.Time {
@ -106,3 +132,7 @@ func maxTime(a, b time.Time) time.Time {
func (*Timestamp) OnError() error {
return nil
}
func (t *Timestamp) timestampFilePath() string {
return filepath.Join(t.TempDir, "timestamp", NormalizeFilename(t.Task))
}

View File

@ -87,9 +87,12 @@ func (e *Executor) getStatusChecker(t *taskfile.Task) (status.Checker, error) {
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,
}
}