1
0
mirror of https://github.com/go-task/task.git synced 2025-05-31 23:19:42 +02:00
task/internal/status/timestamp.go
2023-01-14 17:18:26 -03:00

147 lines
3.2 KiB
Go

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 {
return false, nil
}
sources, err := globs(t.Dir, t.Sources)
if err != nil {
return false, nil
}
generates, err := globs(t.Dir, t.Generates)
if err != nil {
return false, nil
}
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 {
if err := os.MkdirAll(filepath.Dir(timestampFile), 0o755); err != nil {
return false, err
}
f, err := os.Create(timestampFile)
if err != nil {
return false, err
}
f.Close()
}
}
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
}
// 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
}
// Modify the metadata of the file to the the current time.
if !t.Dry {
if err := os.Chtimes(timestampFile, taskTime, taskTime); err != nil {
return false, err
}
}
return !shouldUpdate, nil
}
func (t *Timestamp) Kind() string {
return "timestamp"
}
// Value implements the Checker Interface
func (t *Timestamp) Value() (interface{}, error) {
sources, err := globs(t.Dir, t.Sources)
if err != nil {
return time.Now(), err
}
sourcesMaxTime, err := getMaxTime(sources...)
if err != nil {
return time.Now(), err
}
if sourcesMaxTime.IsZero() {
return time.Unix(0, 0), nil
}
return sourcesMaxTime, nil
}
func getMaxTime(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 = maxTime(t, info.ModTime())
}
return t, nil
}
func maxTime(a, b time.Time) time.Time {
if a.After(b) {
return a
}
return b
}
// 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 false, nil
}
// OnError implements the Checker interface
func (*Timestamp) OnError() error {
return nil
}
func (t *Timestamp) timestampFilePath() string {
return filepath.Join(t.TempDir, "timestamp", normalizeFilename(t.Task))
}