mirror of
https://github.com/go-task/task.git
synced 2026-08-29 10:08:27 +02:00
149 lines
3.6 KiB
Go
149 lines
3.6 KiB
Go
|
|
package fingerprint
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
|
||
|
|
"github.com/go-task/task/v3/internal/logger"
|
||
|
|
"github.com/go-task/task/v3/taskfile/ast"
|
||
|
|
)
|
||
|
|
|
||
|
|
type (
|
||
|
|
FingerprinterOption func(*Fingerprinter)
|
||
|
|
|
||
|
|
// A Fingerprinter answers whether a task is up-to-date. It owns the
|
||
|
|
// resolution of the fingerprinting method and the checkers behind it.
|
||
|
|
Fingerprinter struct {
|
||
|
|
defaultMethod string
|
||
|
|
tempDir string
|
||
|
|
dry bool
|
||
|
|
logger *logger.Logger
|
||
|
|
statusChecker StatusCheckable
|
||
|
|
sourcesChecker SourcesCheckable
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
func WithStatusChecker(checker StatusCheckable) FingerprinterOption {
|
||
|
|
return func(f *Fingerprinter) {
|
||
|
|
f.statusChecker = checker
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func WithSourcesChecker(checker SourcesCheckable) FingerprinterOption {
|
||
|
|
return func(f *Fingerprinter) {
|
||
|
|
f.sourcesChecker = checker
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewFingerprinter uses defaultMethod for tasks that don't declare one.
|
||
|
|
func NewFingerprinter(
|
||
|
|
defaultMethod string,
|
||
|
|
tempDir string,
|
||
|
|
dry bool,
|
||
|
|
logger *logger.Logger,
|
||
|
|
opts ...FingerprinterOption,
|
||
|
|
) *Fingerprinter {
|
||
|
|
f := &Fingerprinter{
|
||
|
|
defaultMethod: defaultMethod,
|
||
|
|
tempDir: tempDir,
|
||
|
|
dry: dry,
|
||
|
|
logger: logger,
|
||
|
|
}
|
||
|
|
for _, opt := range opts {
|
||
|
|
opt(f)
|
||
|
|
}
|
||
|
|
return f
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *Fingerprinter) resolveMethod(t *ast.Task) string {
|
||
|
|
if t.Method != "" {
|
||
|
|
return t.Method
|
||
|
|
}
|
||
|
|
return f.defaultMethod
|
||
|
|
}
|
||
|
|
|
||
|
|
// Kind names the fingerprint variable ("checksum", "timestamp" or "none") the
|
||
|
|
// resolved method injects. An invalid method is reported as "checksum" here and
|
||
|
|
// rejected by the entry points that build a checker.
|
||
|
|
func (f *Fingerprinter) Kind(t *ast.Task) string {
|
||
|
|
if f.sourcesChecker != nil {
|
||
|
|
return f.sourcesChecker.Kind()
|
||
|
|
}
|
||
|
|
switch method := f.resolveMethod(t); method {
|
||
|
|
case "timestamp", "none":
|
||
|
|
return method
|
||
|
|
default:
|
||
|
|
return "checksum"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// SourceValue returns the value of the fingerprint variable for the given task.
|
||
|
|
// It is potentially expensive, so only call it when the task references it.
|
||
|
|
func (f *Fingerprinter) SourceValue(t *ast.Task) (any, error) {
|
||
|
|
sourcesChecker, err := f.resolveSourcesChecker(t)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return sourcesChecker.Value(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
// UpToDate considers both the status commands and the sources of a task; one
|
||
|
|
// that declares neither never is.
|
||
|
|
func (f *Fingerprinter) UpToDate(ctx context.Context, t *ast.Task) (bool, error) {
|
||
|
|
var statusUpToDate bool
|
||
|
|
var sourcesUpToDate bool
|
||
|
|
|
||
|
|
statusChecker := f.statusChecker
|
||
|
|
if statusChecker == nil {
|
||
|
|
statusChecker = NewStatusChecker(f.logger)
|
||
|
|
}
|
||
|
|
sourcesChecker, err := f.resolveSourcesChecker(t)
|
||
|
|
if err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
|
||
|
|
statusIsSet := len(t.Status) != 0
|
||
|
|
sourcesIsSet := len(t.Sources) != 0
|
||
|
|
|
||
|
|
if statusIsSet {
|
||
|
|
statusUpToDate, err = statusChecker.IsUpToDate(ctx, t)
|
||
|
|
if err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if sourcesIsSet {
|
||
|
|
sourcesUpToDate, err = sourcesChecker.IsUpToDate(t)
|
||
|
|
if err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if statusIsSet && sourcesIsSet {
|
||
|
|
return statusUpToDate && sourcesUpToDate, nil
|
||
|
|
}
|
||
|
|
if statusIsSet {
|
||
|
|
return statusUpToDate, nil
|
||
|
|
}
|
||
|
|
if sourcesIsSet {
|
||
|
|
return sourcesUpToDate, nil
|
||
|
|
}
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// OnError lets the resolved sources checker clean up after a failed run.
|
||
|
|
func (f *Fingerprinter) OnError(t *ast.Task) error {
|
||
|
|
sourcesChecker, err := f.resolveSourcesChecker(t)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return sourcesChecker.OnError(t)
|
||
|
|
}
|
||
|
|
|
||
|
|
// resolveSourcesChecker is the single place where a task is mapped to a checker.
|
||
|
|
func (f *Fingerprinter) resolveSourcesChecker(t *ast.Task) (SourcesCheckable, error) {
|
||
|
|
if f.sourcesChecker != nil {
|
||
|
|
return f.sourcesChecker, nil
|
||
|
|
}
|
||
|
|
return NewSourcesChecker(f.resolveMethod(t), f.tempDir, f.dry)
|
||
|
|
}
|