1
0
mirror of https://github.com/go-task/task.git synced 2025-04-21 12:17:07 +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 :::info
For the `checksum` (default) method to work, it is only necessary to For the `checksum` (default) or `timestamp` method to work, it is only necessary to
inform the source files, but if you want to use the `timestamp` method, you inform the source files.
also need to inform the generated files with `generates`. 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 { 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]") var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
// replaces invalid caracters on filenames with "-" // replaces invalid caracters on filenames with "-"
func (*Checksum) normalizeFilename(f string) string { func NormalizeFilename(f string) string {
return checksumFilenameRegexp.ReplaceAllString(f, "-") return checksumFilenameRegexp.ReplaceAllString(f, "-")
} }

View File

@ -16,6 +16,6 @@ func TestNormalizeFilename(t *testing.T) {
{"foo1bar2baz3", "foo1bar2baz3"}, {"foo1bar2baz3", "foo1bar2baz3"},
} }
for _, test := range tests { 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 ( import (
"os" "os"
"path/filepath"
"time" "time"
) )
// Timestamp checks if any source change compared with the generated files, // Timestamp checks if any source change compared with the generated files,
// using file modifications timestamps. // using file modifications timestamps.
type Timestamp struct { type Timestamp struct {
TempDir string
Task string
Dir string Dir string
Sources []string Sources []string
Generates []string Generates []string
Dry bool
} }
// IsUpToDate implements the Checker interface // IsUpToDate implements the Checker interface
func (t *Timestamp) IsUpToDate() (bool, error) { func (t *Timestamp) IsUpToDate() (bool, error) {
if len(t.Sources) == 0 || len(t.Generates) == 0 { if len(t.Sources) == 0 {
return false, nil return false, nil
} }
@ -28,17 +32,43 @@ func (t *Timestamp) IsUpToDate() (bool, error) {
return false, nil return false, nil
} }
sourcesMaxTime, err := getMaxTime(sources...) timestampFile := t.timestampFilePath()
if err != nil || sourcesMaxTime.IsZero() {
// 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 return false, nil
} }
generatesMinTime, err := getMinTime(generates...) // check if any of the source files is newer than the max time of the generates
if err != nil || generatesMinTime.IsZero() { shouldUpdate, err := anyFileNewerThan(sources, generateMaxTime)
if err != nil {
return false, 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 { func (t *Timestamp) Kind() string {
@ -64,18 +94,6 @@ func (t *Timestamp) Value() (interface{}, error) {
return sourcesMaxTime, nil 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) { func getMaxTime(files ...string) (time.Time, error) {
var t time.Time var t time.Time
for _, f := range files { for _, f := range files {
@ -88,11 +106,19 @@ func getMaxTime(files ...string) (time.Time, error) {
return t, nil return t, nil
} }
func minTime(a, b time.Time) time.Time { // if the modification time of any of the files is newer than the the given time, returns true
if !a.IsZero() && a.Before(b) { // This function is lazy, as it stops when it finds a file newer than the given time
return a func anyFileNewerThan(files []string, givenTime time.Time) (bool, error) {
for _, f := range files {
info, err := os.Stat(f)
if err != nil {
return false, err
} }
return b if info.ModTime().After(givenTime) {
return true, nil
}
}
return false, nil
} }
func maxTime(a, b time.Time) time.Time { func maxTime(a, b time.Time) time.Time {
@ -106,3 +132,7 @@ func maxTime(a, b time.Time) time.Time {
func (*Timestamp) OnError() error { func (*Timestamp) OnError() error {
return nil 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 { func (e *Executor) timestampChecker(t *taskfile.Task) status.Checker {
return &status.Timestamp{ return &status.Timestamp{
TempDir: e.TempDir,
Task: t.Name(),
Dir: t.Dir, Dir: t.Dir,
Sources: t.Sources, Sources: t.Sources,
Generates: t.Generates, Generates: t.Generates,
Dry: e.Dry,
} }
} }