diff --git a/cmd/task/task.go b/cmd/task/task.go index d5f2b7ba..b6388b81 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -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" ) @@ -110,16 +111,69 @@ func run() error { return nil } - e := task.NewExecutor( - flags.WithFlags(), - task.WithVersionCheck(true), + 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, + flags.Timeout, ) - if err := e.Setup(); err != nil { + if err != nil { return err } + tempDir, err := task.NewTempDir(node.Dir()) + if err != nil { + return err + } + + reader := taskfile.NewReader( + flags.WithFlags(), + 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") + }), + ) + + 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 + } + + tf, err := graph.Merge() + if err != nil { + return err + } + + executor := task.NewExecutor(tf, + flags.WithFlags(), + task.WithDir(node.Dir()), + task.WithTempDir(tempDir), + ) + if err := executor.Setup(); 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) } @@ -131,9 +185,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 } @@ -165,17 +219,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...) } diff --git a/executor.go b/executor.go index 8f9233ef..b9560469 100644 --- a/executor.go +++ b/executor.go @@ -26,27 +26,22 @@ 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 + Entrypoint 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 @@ -72,17 +67,13 @@ 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(tf *ast.Taskfile, opts ...ExecutorOption) *Executor { e := &Executor{ - Timeout: time.Second * 10, + Taskfile: tf, Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, @@ -143,12 +134,12 @@ func (o *entrypointOption) ApplyToExecutor(e *Executor) { // 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) { @@ -183,76 +174,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. diff --git a/internal/flags/flags.go b/internal/flags/flags.go index dab9fdf8..38279e71 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -15,6 +15,7 @@ import ( "github.com/go-task/task/v3/experiments" "github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/sort" + "github.com/go-task/task/v3/taskfile" "github.com/go-task/task/v3/taskfile/ast" ) @@ -201,7 +202,7 @@ func Validate() error { // WithFlags is a special internal functional option that is used to pass flags // from the CLI into any constructor that accepts functional options. -func WithFlags() task.ExecutorOption { +func WithFlags() *flagsOption { return &flagsOption{} } @@ -231,11 +232,6 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) { task.WithEntrypoint(Entrypoint), task.WithForce(Force), task.WithForceAll(ForceAll), - task.WithInsecure(Insecure), - task.WithDownload(Download), - task.WithOffline(Offline), - task.WithTimeout(Timeout), - task.WithCacheExpiryDuration(CacheExpiryDuration), task.WithWatch(Watch), task.WithVerbose(Verbose), task.WithSilent(Silent), @@ -251,3 +247,12 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) { task.WithVersionCheck(true), ) } + +func (o *flagsOption) ApplyToReader(r *taskfile.Reader) { + r.Options( + taskfile.WithInsecure(Insecure), + taskfile.WithDownload(Download), + taskfile.WithOffline(Offline), + taskfile.WithCacheExpiryDuration(CacheExpiryDuration), + ) +} diff --git a/setup.go b/setup.go index b4926f2d..e2bdfc0e 100644 --- a/setup.go +++ b/setup.go @@ -4,18 +4,13 @@ import ( "context" "fmt" "os" - "path/filepath" "slices" - "strings" "sync" "github.com/Masterminds/semver/v3" "github.com/sajari/fuzzy" "github.com/go-task/task/v3/errors" - "github.com/go-task/task/v3/internal/env" - "github.com/go-task/task/v3/internal/execext" - "github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/output" "github.com/go-task/task/v3/internal/version" @@ -25,16 +20,6 @@ import ( func (e *Executor) Setup() error { e.setupLogger() - node, err := e.getRootNode() - if err != nil { - return err - } - if err := e.setupTempDir(); err != nil { - return err - } - if err := e.readTaskfile(node); err != nil { - return err - } e.setupFuzzyModel() e.setupStdFiles() if err := e.setupOutput(); err != nil { @@ -54,46 +39,6 @@ func (e *Executor) Setup() error { return nil } -func (e *Executor) getRootNode() (taskfile.Node, error) { - node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout) - if err != nil { - return nil, err - } - e.Dir = node.Dir() - return node, err -} - -func (e *Executor) readTaskfile(node taskfile.Node) error { - ctx, cf := context.WithTimeout(context.Background(), e.Timeout) - defer cf() - debugFunc := func(s string) { - e.Logger.VerboseOutf(logger.Magenta, s) - } - promptFunc := func(s string) error { - return e.Logger.Prompt(logger.Yellow, s, "n", "y", "yes") - } - reader := taskfile.NewReader( - taskfile.WithInsecure(e.Insecure), - taskfile.WithDownload(e.Download), - taskfile.WithOffline(e.Offline), - taskfile.WithTempDir(e.TempDir.Remote), - taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration), - taskfile.WithDebugFunc(debugFunc), - taskfile.WithPromptFunc(promptFunc), - ) - graph, err := reader.Read(ctx, node) - if err != nil { - if errors.Is(err, context.DeadlineExceeded) { - return &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: e.Timeout} - } - return err - } - if e.Taskfile, err = graph.Merge(); err != nil { - return err - } - return nil -} - func (e *Executor) setupFuzzyModel() { if e.Taskfile == nil { return @@ -115,52 +60,6 @@ func (e *Executor) setupFuzzyModel() { e.fuzzyModel = model } -func (e *Executor) setupTempDir() error { - if e.TempDir != (TempDir{}) { - return nil - } - - tempDir := env.GetTaskEnv("TEMP_DIR") - if tempDir == "" { - e.TempDir = TempDir{ - Remote: filepathext.SmartJoin(e.Dir, ".task"), - Fingerprint: filepathext.SmartJoin(e.Dir, ".task"), - } - } else if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") { - tempDir, err := execext.ExpandLiteral(tempDir) - if err != nil { - return err - } - projectDir, _ := filepath.Abs(e.Dir) - projectName := filepath.Base(projectDir) - e.TempDir = TempDir{ - Remote: tempDir, - Fingerprint: filepathext.SmartJoin(tempDir, projectName), - } - - } else { - e.TempDir = TempDir{ - Remote: filepathext.SmartJoin(e.Dir, tempDir), - Fingerprint: filepathext.SmartJoin(e.Dir, tempDir), - } - } - - remoteDir := env.GetTaskEnv("REMOTE_DIR") - if remoteDir != "" { - if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") { - remoteTempDir, err := execext.ExpandLiteral(remoteDir) - if err != nil { - return err - } - e.TempDir.Remote = remoteTempDir - } else { - e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task") - } - } - - return nil -} - func (e *Executor) setupStdFiles() { if e.Stdin == nil { e.Stdin = os.Stdin diff --git a/temp_dir.go b/temp_dir.go new file mode 100644 index 00000000..98ed8ecf --- /dev/null +++ b/temp_dir.go @@ -0,0 +1,78 @@ +package task + +import ( + "path/filepath" + "strings" + + "github.com/go-task/task/v3/internal/env" + "github.com/go-task/task/v3/internal/execext" + "github.com/go-task/task/v3/internal/filepathext" +) + +type TempDir struct { + Remote string + Fingerprint string +} + +func NewTempDir(dir string) (*TempDir, error) { + tempDir, err := setupTempDirFingerprint(dir) + if err != nil { + return nil, err + } + + err = setupTempDirRemote(dir, tempDir) + if err != nil { + return nil, err + } + + return tempDir, nil +} + +func setupTempDirFingerprint(dir string) (*TempDir, error) { + tempDir := env.GetTaskEnv("TEMP_DIR") + + if tempDir == "" { + return &TempDir{ + Remote: filepathext.SmartJoin(dir, ".task"), + Fingerprint: filepathext.SmartJoin(dir, ".task"), + }, nil + } + + if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") { + tempDir, err := execext.ExpandLiteral(tempDir) + if err != nil { + return nil, err + } + projectDir, _ := filepath.Abs(dir) + projectName := filepath.Base(projectDir) + return &TempDir{ + Remote: tempDir, + Fingerprint: filepathext.SmartJoin(tempDir, projectName), + }, nil + } + + return &TempDir{ + Remote: filepathext.SmartJoin(dir, tempDir), + Fingerprint: filepathext.SmartJoin(dir, tempDir), + }, nil +} + +func setupTempDirRemote(dir string, tempDir *TempDir) error { + remoteDir := env.GetTaskEnv("REMOTE_DIR") + + if remoteDir == "" { + return nil + } + + if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") { + remoteTempDir, err := execext.ExpandLiteral(remoteDir) + if err != nil { + return err + } + tempDir.Remote = remoteTempDir + return nil + } + + tempDir.Remote = filepathext.SmartJoin(dir, ".task") + return nil +}