diff --git a/internal/fingerprint/checker.go b/internal/fingerprint/checker.go index d630c18b86..f1f9dc74e4 100644 --- a/internal/fingerprint/checker.go +++ b/internal/fingerprint/checker.go @@ -14,6 +14,7 @@ type StatusCheckable interface { // SourcesCheckable defines any type that can check if the sources of a task are up-to-date. type SourcesCheckable interface { IsUpToDate(t *ast.Task) (bool, error) + Update(t *ast.Task) error Value(t *ast.Task) (any, error) OnError(t *ast.Task) error Kind() string diff --git a/internal/fingerprint/sources_checksum.go b/internal/fingerprint/sources_checksum.go index 91cb6e384a..29222c787e 100644 --- a/internal/fingerprint/sources_checksum.go +++ b/internal/fingerprint/sources_checksum.go @@ -43,13 +43,6 @@ func (checker *ChecksumChecker) IsUpToDate(t *ast.Task) (bool, error) { return false, nil } - if !checker.dry && oldHash != newHash { - _ = os.MkdirAll(filepathext.SmartJoin(checker.tempDir, "checksum"), 0o755) - if err = os.WriteFile(checksumFile, []byte(newHash+"\n"), 0o644); err != nil { - return false, err - } - } - if len(t.Generates) > 0 { // For each specified 'generates' field, check whether the files actually exist for _, g := range t.Generates { @@ -72,6 +65,22 @@ func (checker *ChecksumChecker) IsUpToDate(t *ast.Task) (bool, error) { return oldHash == newHash, nil } +func (checker *ChecksumChecker) Update(t *ast.Task) error { + if !checker.dry { + if len(t.Sources) == 0 { + return nil + } + checksumFile := checker.checksumFilePath(t) + newHash, err := checker.checksum(t) + if err != nil { + return nil + } + _ = os.MkdirAll(filepathext.SmartJoin(checker.tempDir, "checksum"), 0o755) + return os.WriteFile(checksumFile, []byte(newHash+"\n"), 0o644) + } + return nil +} + func (checker *ChecksumChecker) Value(t *ast.Task) (any, error) { return checker.checksum(t) } diff --git a/internal/fingerprint/sources_none.go b/internal/fingerprint/sources_none.go index d13adc1c51..15b9edb0b1 100644 --- a/internal/fingerprint/sources_none.go +++ b/internal/fingerprint/sources_none.go @@ -10,6 +10,10 @@ func (NoneChecker) IsUpToDate(t *ast.Task) (bool, error) { return false, nil } +func (NoneChecker) Update(t *ast.Task) error { + return nil +} + func (NoneChecker) Value(t *ast.Task) (any, error) { return "", nil } diff --git a/internal/fingerprint/sources_timestamp.go b/internal/fingerprint/sources_timestamp.go index b1a6f299d5..e91f8e2c2a 100644 --- a/internal/fingerprint/sources_timestamp.go +++ b/internal/fingerprint/sources_timestamp.go @@ -44,22 +44,7 @@ func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) { _, 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 !checker.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. @@ -74,14 +59,39 @@ func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) { return false, nil } - // Modify the metadata of the file to the the current time. + return !shouldUpdate, nil +} + +func (checker *TimestampChecker) Update(t *ast.Task) error { if !checker.dry { - if err := os.Chtimes(timestampFile, taskTime, taskTime); err != nil { - return false, err + generates, err := Globs(t.Dir, t.Generates) + if err != nil { + return nil + } + if len(generates) == 0 { + return nil } - } - return !shouldUpdate, nil + timestampFile := checker.timestampFilePath(t) + _, err = os.Stat(timestampFile) + if err == nil { + // Modify the metadata of the file to the the current time. + taskTime := time.Now() + return os.Chtimes(timestampFile, taskTime, taskTime) + } + + // Compare the time of the generates and sources. If the generates are old, the task will be executed. + err = os.MkdirAll(filepath.Dir(timestampFile), 0o755) + if err != nil { + return err + } + f, err := os.Create(timestampFile) + if err != nil { + return err + } + return f.Close() + } + return nil } func (checker *TimestampChecker) Kind() string { diff --git a/internal/fingerprint/task.go b/internal/fingerprint/task.go index 2b48e114c9..c3866a88f2 100644 --- a/internal/fingerprint/task.go +++ b/internal/fingerprint/task.go @@ -55,13 +55,11 @@ func WithSourcesChecker(checker SourcesCheckable) CheckerOption { } } -func IsTaskUpToDate( +func getConfig( ctx context.Context, t *ast.Task, opts ...CheckerOption, -) (bool, error) { - var statusUpToDate bool - var sourcesUpToDate bool +) (*CheckerConfig, error) { var err error // Default config @@ -88,13 +86,29 @@ func IsTaskUpToDate( if config.sourcesChecker == nil { config.sourcesChecker, err = NewSourcesChecker(config.method, config.tempDir, config.dry) if err != nil { - return false, err + return nil, err } } + return config, nil +} + +func IsTaskUpToDate( + ctx context.Context, + t *ast.Task, + opts ...CheckerOption, +) (bool, error) { + var statusUpToDate bool + var sourcesUpToDate bool + var err error statusIsSet := len(t.Status) != 0 sourcesIsSet := len(t.Sources) != 0 + config, err := getConfig(ctx, t, opts...) + if err != nil { + return false, err + } + // If status is set, check if it is up-to-date if statusIsSet { statusUpToDate, err = config.statusChecker.IsUpToDate(ctx, t) @@ -130,3 +144,15 @@ func IsTaskUpToDate( // i.e. it is never considered "up-to-date" return false, nil } + +func UpdateTask( + ctx context.Context, + t *ast.Task, + opts ...CheckerOption, +) error { + config, err := getConfig(ctx, t, opts...) + if err != nil { + return err + } + return config.sourcesChecker.Update(t) +} diff --git a/internal/mocks/sources_checkable.go b/internal/mocks/sources_checkable.go index e234364f1b..285bd26b83 100644 --- a/internal/mocks/sources_checkable.go +++ b/internal/mocks/sources_checkable.go @@ -45,6 +45,10 @@ func (_m *SourcesCheckable) IsUpToDate(t *ast.Task) (bool, error) { return r0, r1 } +func (_m *SourcesCheckable) Update(t *ast.Task) error { + return nil +} + // SourcesCheckable_IsUpToDate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsUpToDate' type SourcesCheckable_IsUpToDate_Call struct { *mock.Call diff --git a/task.go b/task.go index 9200a471ed..99996f3d4a 100644 --- a/task.go +++ b/task.go @@ -200,6 +200,7 @@ func (e *Executor) RunTask(ctx context.Context, call ast.Call) error { } skipFingerprinting := e.ForceAll || (!call.Indirect && e.Force) + var method string if !skipFingerprinting { if err := ctx.Err(); err != nil { return err @@ -215,7 +216,7 @@ func (e *Executor) RunTask(ctx context.Context, call ast.Call) error { } // Get the fingerprinting method to use - method := e.Taskfile.Method + method = e.Taskfile.Method if t.Method != "" { method = t.Method } @@ -265,6 +266,17 @@ func (e *Executor) RunTask(ctx context.Context, call ast.Call) error { return &errors.TaskRunError{TaskName: t.Task, Err: err} } } + if !skipFingerprinting { + err := fingerprint.UpdateTask(ctx, t, + fingerprint.WithMethod(method), + fingerprint.WithTempDir(e.TempDir), + fingerprint.WithDry(e.Dry), + fingerprint.WithLogger(e.Logger), + ) + if err != nil { + return err + } + } e.Logger.VerboseErrf(logger.Magenta, "task: %q finished\n", call.Task) return nil }) diff --git a/task_test.go b/task_test.go index a8114965f1..60c7467a68 100644 --- a/task_test.go +++ b/task_test.go @@ -294,10 +294,6 @@ func TestStatus(t *testing.T) { assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String())) buff.Reset() - // sources: not up-to-date - require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-bar"})) - assert.Equal(t, "task: [gen-bar] touch bar.txt", strings.TrimSpace(buff.String())) - buff.Reset() // all: up-to-date require.NoError(t, e.Run(context.Background(), ast.Call{Task: "gen-bar"})) assert.Equal(t, `task: Task "gen-bar" is up to date`, strings.TrimSpace(buff.String()))