Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 59 additions & 11 deletions cmd/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/go-task/task/v3/internal/flags"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/version"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast"
)

Expand Down Expand Up @@ -110,16 +111,63 @@ func run() error {
return nil
}

e := task.NewExecutor(
if err := experiments.Validate(); err != nil {
log.Warnf("%s\n", err.Error())
}

// Create a new root node for the given entrypoint
node, err := taskfile.NewRootNode(
flags.Entrypoint,
flags.Dir,
flags.Insecure,
)
if err != nil {
return err
}

tempDir, err := task.NewTempDir(node.Dir())
if err != nil {
return err
}

reader := taskfile.NewReader(
flags.WithFlags(),
task.WithVersionCheck(true),
taskfile.WithTempDir(tempDir.Remote),
taskfile.WithDebugFunc(func(s string) {
log.VerboseOutf(logger.Magenta, s)
}),
taskfile.WithPromptFunc(func(s string) error {
return log.Prompt(logger.Yellow, s, "n", "y", "yes")
}),
)
if err := e.Setup(); err != nil {

ctx, cf := context.WithTimeout(context.Background(), flags.Timeout)
defer cf()
graph, err := reader.Read(ctx, node)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: flags.Timeout}
}
return err
}

executor, err := task.NewExecutor(graph,
flags.WithFlags(),
task.WithDir(node.Dir()),
task.WithTempDir(tempDir),
)
if err != nil {
return err
}

// If the download flag is specified, we should stop execution as soon as
// taskfile is downloaded
if flags.Download {
return nil
}

if flags.ClearCache {
cachePath := filepath.Join(e.TempDir.Remote, "remote")
cachePath := filepath.Join(executor.TempDir.Remote, "remote")
return os.RemoveAll(cachePath)
}

Expand All @@ -131,9 +179,9 @@ func run() error {
)
if listOptions.ShouldListTasks() {
if flags.Silent {
return e.ListTaskNames(flags.ListAll)
return executor.ListTaskNames(flags.ListAll)
}
foundTasks, err := e.ListTasks(listOptions)
foundTasks, err := executor.ListTasks(listOptions)
if err != nil {
return err
}
Expand Down Expand Up @@ -165,17 +213,17 @@ func run() error {
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
e.Taskfile.Vars.Merge(globals, nil)
executor.Taskfile.Vars.Merge(globals, nil)

if !flags.Watch {
e.InterceptInterruptSignals()
executor.InterceptInterruptSignals()
}

ctx := context.Background()
ctx = context.Background()

if flags.Status {
return e.Status(ctx, calls...)
return executor.Status(ctx, calls...)
}

return e.Run(ctx, calls...)
return executor.Run(ctx, calls...)
}
150 changes: 34 additions & 116 deletions executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"io"
"os"
"path/filepath"
"sync"
"time"

Expand All @@ -26,27 +27,21 @@ type (
// within them.
Executor struct {
// Flags
Dir string
Entrypoint string
TempDir TempDir
Force bool
ForceAll bool
Insecure bool
Download bool
Offline bool
Timeout time.Duration
CacheExpiryDuration time.Duration
Watch bool
Verbose bool
Silent bool
AssumeYes bool
AssumeTerm bool // Used for testing
Dry bool
Summary bool
Parallel bool
Color bool
Concurrency int
Interval time.Duration
Dir string
TempDir *TempDir
Force bool
ForceAll bool
Watch bool
Verbose bool
Silent bool
AssumeYes bool
AssumeTerm bool // Used for testing
Dry bool
Summary bool
Parallel bool
Color bool
Concurrency int
Interval time.Duration

// I/O
Stdin io.Reader
Expand All @@ -72,17 +67,17 @@ type (
executionHashesMutex sync.Mutex
watchedDirs *xsync.MapOf[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 {
func NewExecutor(graph *ast.TaskfileGraph, opts ...ExecutorOption) (*Executor, error) {
tf, err := graph.Merge()
if err != nil {
return nil, err
}
e := &Executor{
Timeout: time.Second * 10,
Taskfile: tf,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Expand All @@ -100,7 +95,10 @@ func NewExecutor(opts ...ExecutorOption) *Executor {
executionHashesMutex: sync.Mutex{},
}
e.Options(opts...)
return e
if err := e.setup(); err != nil {
return nil, err
}
return e, nil
}

// Options loops through the given [ExecutorOption] functions and applies them
Expand All @@ -122,33 +120,23 @@ type dirOption struct {
}

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
absDir, err := filepath.Abs(o.dir)
if err != nil {
e.Dir = o.dir
return
}
e.Dir = absDir
}

// 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 {
func WithTempDir(tempDir *TempDir) ExecutorOption {
return &tempDirOption{tempDir}
}

type tempDirOption struct {
tempDir TempDir
tempDir *TempDir
}

func (o *tempDirOption) ApplyToExecutor(e *Executor) {
Expand Down Expand Up @@ -183,76 +171,6 @@ 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
}

// 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 considered expired after 24 hours.
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
}

// 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.
Expand Down
Loading
Loading