mirror of
https://github.com/go-task/task.git
synced 2026-02-24 20:20:30 +01:00
Store the TaskfileGraph in the Executor so it can be used for lazy variable resolution when SCOPED_INCLUDES experiment is enabled. The graph is now preserved after reading, before merging into the final Taskfile. This allows traversing the DAG at runtime to resolve variables from the correct scope.
621 lines
15 KiB
Go
621 lines
15 KiB
Go
package task
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/puzpuzpuz/xsync/v4"
|
|
"github.com/sajari/fuzzy"
|
|
|
|
"github.com/go-task/task/v3/internal/logger"
|
|
"github.com/go-task/task/v3/internal/output"
|
|
"github.com/go-task/task/v3/internal/sort"
|
|
"github.com/go-task/task/v3/taskfile/ast"
|
|
)
|
|
|
|
type (
|
|
// An ExecutorOption is any type that can apply a configuration to an
|
|
// [Executor].
|
|
ExecutorOption interface {
|
|
ApplyToExecutor(*Executor)
|
|
}
|
|
// An Executor is used for processing Taskfile(s) and executing the task(s)
|
|
// within them.
|
|
Executor struct {
|
|
// Flags
|
|
Dir string
|
|
Entrypoint string
|
|
TempDir TempDir
|
|
Force bool
|
|
ForceAll bool
|
|
Insecure bool
|
|
Download bool
|
|
Offline bool
|
|
TrustedHosts []string
|
|
Timeout time.Duration
|
|
CacheExpiryDuration time.Duration
|
|
RemoteCacheDir string
|
|
CACert string
|
|
Cert string
|
|
CertKey string
|
|
Watch bool
|
|
Verbose bool
|
|
Silent bool
|
|
DisableFuzzy bool
|
|
AssumeYes bool
|
|
AssumeTerm bool // Used for testing
|
|
Interactive bool
|
|
Dry bool
|
|
Summary bool
|
|
Parallel bool
|
|
Color bool
|
|
Concurrency int
|
|
Interval time.Duration
|
|
Failfast bool
|
|
|
|
// I/O
|
|
Stdin io.Reader
|
|
Stdout io.Writer
|
|
Stderr io.Writer
|
|
|
|
// Internal
|
|
Taskfile *ast.Taskfile
|
|
Graph *ast.TaskfileGraph
|
|
Logger *logger.Logger
|
|
Compiler *Compiler
|
|
Output output.Output
|
|
OutputStyle ast.Output
|
|
TaskSorter sort.Sorter
|
|
UserWorkingDir string
|
|
EnableVersionCheck bool
|
|
|
|
fuzzyModel *fuzzy.Model
|
|
fuzzyModelOnce sync.Once
|
|
|
|
promptedVars *ast.Vars // vars collected via interactive prompts
|
|
concurrencySemaphore chan struct{}
|
|
taskCallCount map[string]*int32
|
|
mkdirMutexMap map[string]*sync.Mutex
|
|
executionHashes map[string]context.Context
|
|
executionHashesMutex sync.Mutex
|
|
watchedDirs *xsync.Map[string, bool]
|
|
}
|
|
TempDir struct {
|
|
Remote string
|
|
Fingerprint string
|
|
}
|
|
)
|
|
|
|
// NewExecutor creates a new [Executor] and applies the given functional options
|
|
// to it.
|
|
func NewExecutor(opts ...ExecutorOption) *Executor {
|
|
e := &Executor{
|
|
Timeout: time.Second * 10,
|
|
Stdin: os.Stdin,
|
|
Stdout: os.Stdout,
|
|
Stderr: os.Stderr,
|
|
Logger: nil,
|
|
Compiler: nil,
|
|
Output: nil,
|
|
OutputStyle: ast.Output{},
|
|
TaskSorter: sort.AlphaNumericWithRootTasksFirst,
|
|
UserWorkingDir: "",
|
|
fuzzyModel: nil,
|
|
concurrencySemaphore: nil,
|
|
taskCallCount: map[string]*int32{},
|
|
mkdirMutexMap: map[string]*sync.Mutex{},
|
|
executionHashes: map[string]context.Context{},
|
|
executionHashesMutex: sync.Mutex{},
|
|
}
|
|
e.Options(opts...)
|
|
return e
|
|
}
|
|
|
|
// Options loops through the given [ExecutorOption] functions and applies them
|
|
// to the [Executor].
|
|
func (e *Executor) Options(opts ...ExecutorOption) {
|
|
for _, opt := range opts {
|
|
opt.ApplyToExecutor(e)
|
|
}
|
|
}
|
|
|
|
// WithDir sets the working directory of the [Executor]. By default, the
|
|
// directory is set to the user's current working directory.
|
|
func WithDir(dir string) ExecutorOption {
|
|
return &dirOption{dir}
|
|
}
|
|
|
|
type dirOption struct {
|
|
dir string
|
|
}
|
|
|
|
func (o *dirOption) ApplyToExecutor(e *Executor) {
|
|
e.Dir = o.dir
|
|
}
|
|
|
|
// WithEntrypoint sets the entrypoint (main Taskfile) of the [Executor]. By
|
|
// default, Task will search for one of the default Taskfiles in the given
|
|
// directory.
|
|
func WithEntrypoint(entrypoint string) ExecutorOption {
|
|
return &entrypointOption{entrypoint}
|
|
}
|
|
|
|
type entrypointOption struct {
|
|
entrypoint string
|
|
}
|
|
|
|
func (o *entrypointOption) ApplyToExecutor(e *Executor) {
|
|
e.Entrypoint = o.entrypoint
|
|
}
|
|
|
|
// WithTempDir sets the temporary directory that will be used by [Executor] for
|
|
// storing temporary files like checksums and cached remote files. By default,
|
|
// the temporary directory is set to the user's temporary directory.
|
|
func WithTempDir(tempDir TempDir) ExecutorOption {
|
|
return &tempDirOption{tempDir}
|
|
}
|
|
|
|
type tempDirOption struct {
|
|
tempDir TempDir
|
|
}
|
|
|
|
func (o *tempDirOption) ApplyToExecutor(e *Executor) {
|
|
e.TempDir = o.tempDir
|
|
}
|
|
|
|
// WithForce ensures that the [Executor] always runs a task, even when
|
|
// fingerprinting or prompts would normally stop it.
|
|
func WithForce(force bool) ExecutorOption {
|
|
return &forceOption{force}
|
|
}
|
|
|
|
type forceOption struct {
|
|
force bool
|
|
}
|
|
|
|
func (o *forceOption) ApplyToExecutor(e *Executor) {
|
|
e.Force = o.force
|
|
}
|
|
|
|
// WithForceAll ensures that the [Executor] always runs all tasks (including
|
|
// subtasks), even when fingerprinting or prompts would normally stop them.
|
|
func WithForceAll(forceAll bool) ExecutorOption {
|
|
return &forceAllOption{forceAll}
|
|
}
|
|
|
|
type forceAllOption struct {
|
|
forceAll bool
|
|
}
|
|
|
|
func (o *forceAllOption) ApplyToExecutor(e *Executor) {
|
|
e.ForceAll = o.forceAll
|
|
}
|
|
|
|
// WithInsecure allows the [Executor] to make insecure connections when reading
|
|
// remote taskfiles. By default, insecure connections are rejected.
|
|
func WithInsecure(insecure bool) ExecutorOption {
|
|
return &insecureOption{insecure}
|
|
}
|
|
|
|
type insecureOption struct {
|
|
insecure bool
|
|
}
|
|
|
|
func (o *insecureOption) ApplyToExecutor(e *Executor) {
|
|
e.Insecure = o.insecure
|
|
}
|
|
|
|
// WithDownload forces the [Executor] to download a fresh copy of the taskfile
|
|
// from the remote source.
|
|
func WithDownload(download bool) ExecutorOption {
|
|
return &downloadOption{download}
|
|
}
|
|
|
|
type downloadOption struct {
|
|
download bool
|
|
}
|
|
|
|
func (o *downloadOption) ApplyToExecutor(e *Executor) {
|
|
e.Download = o.download
|
|
}
|
|
|
|
// WithOffline stops the [Executor] from being able to make network connections.
|
|
// It will still be able to read local files and cached copies of remote files.
|
|
func WithOffline(offline bool) ExecutorOption {
|
|
return &offlineOption{offline}
|
|
}
|
|
|
|
type offlineOption struct {
|
|
offline bool
|
|
}
|
|
|
|
func (o *offlineOption) ApplyToExecutor(e *Executor) {
|
|
e.Offline = o.offline
|
|
}
|
|
|
|
// WithTrustedHosts configures the [Executor] with a list of trusted hosts for remote
|
|
// Taskfiles. Hosts in this list will not prompt for user confirmation.
|
|
func WithTrustedHosts(trustedHosts []string) ExecutorOption {
|
|
return &trustedHostsOption{trustedHosts}
|
|
}
|
|
|
|
type trustedHostsOption struct {
|
|
trustedHosts []string
|
|
}
|
|
|
|
func (o *trustedHostsOption) ApplyToExecutor(e *Executor) {
|
|
e.TrustedHosts = o.trustedHosts
|
|
}
|
|
|
|
// WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By
|
|
// default, the timeout is set to 10 seconds.
|
|
func WithTimeout(timeout time.Duration) ExecutorOption {
|
|
return &timeoutOption{timeout}
|
|
}
|
|
|
|
type timeoutOption struct {
|
|
timeout time.Duration
|
|
}
|
|
|
|
func (o *timeoutOption) ApplyToExecutor(e *Executor) {
|
|
e.Timeout = o.timeout
|
|
}
|
|
|
|
// WithCacheExpiryDuration sets the duration after which the cache is considered
|
|
// expired. By default, the cache is 0 (disabled).
|
|
func WithCacheExpiryDuration(duration time.Duration) ExecutorOption {
|
|
return &cacheExpiryDurationOption{duration: duration}
|
|
}
|
|
|
|
type cacheExpiryDurationOption struct {
|
|
duration time.Duration
|
|
}
|
|
|
|
func (o *cacheExpiryDurationOption) ApplyToExecutor(r *Executor) {
|
|
r.CacheExpiryDuration = o.duration
|
|
}
|
|
|
|
// WithRemoteCacheDir sets the directory where remote taskfiles are cached.
|
|
func WithRemoteCacheDir(dir string) ExecutorOption {
|
|
return &remoteCacheDirOption{dir: dir}
|
|
}
|
|
|
|
type remoteCacheDirOption struct {
|
|
dir string
|
|
}
|
|
|
|
func (o *remoteCacheDirOption) ApplyToExecutor(e *Executor) {
|
|
e.RemoteCacheDir = o.dir
|
|
}
|
|
|
|
// WithCACert sets the path to a custom CA certificate for TLS connections.
|
|
func WithCACert(caCert string) ExecutorOption {
|
|
return &caCertOption{caCert: caCert}
|
|
}
|
|
|
|
type caCertOption struct {
|
|
caCert string
|
|
}
|
|
|
|
func (o *caCertOption) ApplyToExecutor(e *Executor) {
|
|
e.CACert = o.caCert
|
|
}
|
|
|
|
// WithCert sets the path to a client certificate for TLS connections.
|
|
func WithCert(cert string) ExecutorOption {
|
|
return &certOption{cert: cert}
|
|
}
|
|
|
|
type certOption struct {
|
|
cert string
|
|
}
|
|
|
|
func (o *certOption) ApplyToExecutor(e *Executor) {
|
|
e.Cert = o.cert
|
|
}
|
|
|
|
// WithCertKey sets the path to a client certificate key for TLS connections.
|
|
func WithCertKey(certKey string) ExecutorOption {
|
|
return &certKeyOption{certKey: certKey}
|
|
}
|
|
|
|
type certKeyOption struct {
|
|
certKey string
|
|
}
|
|
|
|
func (o *certKeyOption) ApplyToExecutor(e *Executor) {
|
|
e.CertKey = o.certKey
|
|
}
|
|
|
|
// WithWatch tells the [Executor] to keep running in the background and watch
|
|
// for changes to the fingerprint of the tasks that are run. When changes are
|
|
// detected, a new task run is triggered.
|
|
func WithWatch(watch bool) ExecutorOption {
|
|
return &watchOption{watch}
|
|
}
|
|
|
|
type watchOption struct {
|
|
watch bool
|
|
}
|
|
|
|
func (o *watchOption) ApplyToExecutor(e *Executor) {
|
|
e.Watch = o.watch
|
|
}
|
|
|
|
// WithVerbose tells the [Executor] to output more information about the tasks
|
|
// that are run.
|
|
func WithVerbose(verbose bool) ExecutorOption {
|
|
return &verboseOption{verbose}
|
|
}
|
|
|
|
type verboseOption struct {
|
|
verbose bool
|
|
}
|
|
|
|
func (o *verboseOption) ApplyToExecutor(e *Executor) {
|
|
e.Verbose = o.verbose
|
|
}
|
|
|
|
// WithSilent tells the [Executor] to suppress all output except for the output
|
|
// of the tasks that are run.
|
|
func WithSilent(silent bool) ExecutorOption {
|
|
return &silentOption{silent}
|
|
}
|
|
|
|
type silentOption struct {
|
|
silent bool
|
|
}
|
|
|
|
func (o *silentOption) ApplyToExecutor(e *Executor) {
|
|
e.Silent = o.silent
|
|
}
|
|
|
|
// WithDisableFuzzy tells the [Executor] to disable fuzzy matching for task names.
|
|
func WithDisableFuzzy(disableFuzzy bool) ExecutorOption {
|
|
return &disableFuzzyOption{disableFuzzy}
|
|
}
|
|
|
|
type disableFuzzyOption struct {
|
|
disableFuzzy bool
|
|
}
|
|
|
|
func (o *disableFuzzyOption) ApplyToExecutor(e *Executor) {
|
|
e.DisableFuzzy = o.disableFuzzy
|
|
}
|
|
|
|
// WithAssumeYes tells the [Executor] to assume "yes" for all prompts.
|
|
func WithAssumeYes(assumeYes bool) ExecutorOption {
|
|
return &assumeYesOption{assumeYes}
|
|
}
|
|
|
|
type assumeYesOption struct {
|
|
assumeYes bool
|
|
}
|
|
|
|
func (o *assumeYesOption) ApplyToExecutor(e *Executor) {
|
|
e.AssumeYes = o.assumeYes
|
|
}
|
|
|
|
// WithAssumeTerm is used for testing purposes to simulate a terminal.
|
|
func WithAssumeTerm(assumeTerm bool) ExecutorOption {
|
|
return &assumeTermOption{assumeTerm}
|
|
}
|
|
|
|
type assumeTermOption struct {
|
|
assumeTerm bool
|
|
}
|
|
|
|
func (o *assumeTermOption) ApplyToExecutor(e *Executor) {
|
|
e.AssumeTerm = o.assumeTerm
|
|
}
|
|
|
|
// WithInteractive tells the [Executor] to prompt for missing required variables.
|
|
func WithInteractive(interactive bool) ExecutorOption {
|
|
return &interactiveOption{interactive}
|
|
}
|
|
|
|
type interactiveOption struct {
|
|
interactive bool
|
|
}
|
|
|
|
func (o *interactiveOption) ApplyToExecutor(e *Executor) {
|
|
e.Interactive = o.interactive
|
|
}
|
|
|
|
// WithDry tells the [Executor] to output the commands that would be run without
|
|
// actually running them.
|
|
func WithDry(dry bool) ExecutorOption {
|
|
return &dryOption{dry}
|
|
}
|
|
|
|
type dryOption struct {
|
|
dry bool
|
|
}
|
|
|
|
func (o *dryOption) ApplyToExecutor(e *Executor) {
|
|
e.Dry = o.dry
|
|
}
|
|
|
|
// WithSummary tells the [Executor] to output a summary of the given tasks
|
|
// instead of running them.
|
|
func WithSummary(summary bool) ExecutorOption {
|
|
return &summaryOption{summary}
|
|
}
|
|
|
|
type summaryOption struct {
|
|
summary bool
|
|
}
|
|
|
|
func (o *summaryOption) ApplyToExecutor(e *Executor) {
|
|
e.Summary = o.summary
|
|
}
|
|
|
|
// WithParallel tells the [Executor] to run tasks given in the same call in
|
|
// parallel.
|
|
func WithParallel(parallel bool) ExecutorOption {
|
|
return ¶llelOption{parallel}
|
|
}
|
|
|
|
type parallelOption struct {
|
|
parallel bool
|
|
}
|
|
|
|
func (o *parallelOption) ApplyToExecutor(e *Executor) {
|
|
e.Parallel = o.parallel
|
|
}
|
|
|
|
// WithColor tells the [Executor] whether or not to output using colorized
|
|
// strings.
|
|
func WithColor(color bool) ExecutorOption {
|
|
return &colorOption{color}
|
|
}
|
|
|
|
type colorOption struct {
|
|
color bool
|
|
}
|
|
|
|
func (o *colorOption) ApplyToExecutor(e *Executor) {
|
|
e.Color = o.color
|
|
}
|
|
|
|
// WithConcurrency sets the maximum number of tasks that the [Executor] can run
|
|
// in parallel.
|
|
func WithConcurrency(concurrency int) ExecutorOption {
|
|
return &concurrencyOption{concurrency}
|
|
}
|
|
|
|
type concurrencyOption struct {
|
|
concurrency int
|
|
}
|
|
|
|
func (o *concurrencyOption) ApplyToExecutor(e *Executor) {
|
|
e.Concurrency = o.concurrency
|
|
}
|
|
|
|
// WithInterval sets the interval at which the [Executor] will wait for
|
|
// duplicated events before running a task.
|
|
func WithInterval(interval time.Duration) ExecutorOption {
|
|
return &intervalOption{interval}
|
|
}
|
|
|
|
type intervalOption struct {
|
|
interval time.Duration
|
|
}
|
|
|
|
func (o *intervalOption) ApplyToExecutor(e *Executor) {
|
|
e.Interval = o.interval
|
|
}
|
|
|
|
// WithOutputStyle sets the output style of the [Executor]. By default, the
|
|
// output style is set to the style defined in the Taskfile.
|
|
func WithOutputStyle(outputStyle ast.Output) ExecutorOption {
|
|
return &outputStyleOption{outputStyle}
|
|
}
|
|
|
|
type outputStyleOption struct {
|
|
outputStyle ast.Output
|
|
}
|
|
|
|
func (o *outputStyleOption) ApplyToExecutor(e *Executor) {
|
|
e.OutputStyle = o.outputStyle
|
|
}
|
|
|
|
// WithTaskSorter sets the sorter that the [Executor] will use to sort tasks. By
|
|
// default, the sorter is set to sort tasks alphabetically, but with tasks with
|
|
// no namespace (in the root Taskfile) first.
|
|
func WithTaskSorter(sorter sort.Sorter) ExecutorOption {
|
|
return &taskSorterOption{sorter}
|
|
}
|
|
|
|
type taskSorterOption struct {
|
|
sorter sort.Sorter
|
|
}
|
|
|
|
func (o *taskSorterOption) ApplyToExecutor(e *Executor) {
|
|
e.TaskSorter = o.sorter
|
|
}
|
|
|
|
// WithStdin sets the [Executor]'s standard input [io.Reader].
|
|
func WithStdin(stdin io.Reader) ExecutorOption {
|
|
return &stdinOption{stdin}
|
|
}
|
|
|
|
type stdinOption struct {
|
|
stdin io.Reader
|
|
}
|
|
|
|
func (o *stdinOption) ApplyToExecutor(e *Executor) {
|
|
e.Stdin = o.stdin
|
|
}
|
|
|
|
// WithStdout sets the [Executor]'s standard output [io.Writer].
|
|
func WithStdout(stdout io.Writer) ExecutorOption {
|
|
return &stdoutOption{stdout}
|
|
}
|
|
|
|
type stdoutOption struct {
|
|
stdout io.Writer
|
|
}
|
|
|
|
func (o *stdoutOption) ApplyToExecutor(e *Executor) {
|
|
e.Stdout = o.stdout
|
|
}
|
|
|
|
// WithStderr sets the [Executor]'s standard error [io.Writer].
|
|
func WithStderr(stderr io.Writer) ExecutorOption {
|
|
return &stderrOption{stderr}
|
|
}
|
|
|
|
type stderrOption struct {
|
|
stderr io.Writer
|
|
}
|
|
|
|
func (o *stderrOption) ApplyToExecutor(e *Executor) {
|
|
e.Stderr = o.stderr
|
|
}
|
|
|
|
// WithIO sets the [Executor]'s standard input, output, and error to the same
|
|
// [io.ReadWriter].
|
|
func WithIO(rw io.ReadWriter) ExecutorOption {
|
|
return &ioOption{rw}
|
|
}
|
|
|
|
type ioOption struct {
|
|
rw io.ReadWriter
|
|
}
|
|
|
|
func (o *ioOption) ApplyToExecutor(e *Executor) {
|
|
e.Stdin = o.rw
|
|
e.Stdout = o.rw
|
|
e.Stderr = o.rw
|
|
}
|
|
|
|
// WithVersionCheck tells the [Executor] whether or not to check the version of
|
|
func WithVersionCheck(enableVersionCheck bool) ExecutorOption {
|
|
return &versionCheckOption{enableVersionCheck}
|
|
}
|
|
|
|
type versionCheckOption struct {
|
|
enableVersionCheck bool
|
|
}
|
|
|
|
func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
|
|
e.EnableVersionCheck = o.enableVersionCheck
|
|
}
|
|
|
|
// WithFailfast tells the [Executor] whether or not to check the version of
|
|
func WithFailfast(failfast bool) ExecutorOption {
|
|
return &failfastOption{failfast}
|
|
}
|
|
|
|
type failfastOption struct {
|
|
failfast bool
|
|
}
|
|
|
|
func (o *failfastOption) ApplyToExecutor(e *Executor) {
|
|
e.Failfast = o.failfast
|
|
}
|