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:
parent
fce7575b03
commit
347fcf9f67
@ -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.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
@ -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, "-")
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user