Skip to content

Commit

Permalink
feat: root remote taskfiles
Browse files Browse the repository at this point in the history
  • Loading branch information
pd93 committed Feb 13, 2024
1 parent bebf982 commit 796f435
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 131 deletions.
9 changes: 0 additions & 9 deletions cmd/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -203,14 +202,6 @@ func run() error {
flags.dir = home
}

if flags.dir != "" && flags.entrypoint != "" {
return errors.New("task: You can't set both --dir and --taskfile")
}
if flags.entrypoint != "" {
flags.dir = filepath.Dir(flags.entrypoint)
flags.entrypoint = filepath.Base(flags.entrypoint)
}

if flags.output.Name != "group" {
if flags.output.Group.Begin != "" {
return errors.New("task: You can't set --output-group-begin without --output=group")
Expand Down
2 changes: 1 addition & 1 deletion setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (e *Executor) setCurrentDir() error {
}

func (e *Executor) readTaskfile() error {
node, err := taskfile.NewRootNode(e.Dir, e.Entrypoint, e.Insecure)
node, err := taskfile.NewRootNode(e.Logger, e.Entrypoint, e.Dir, e.Insecure)
if err != nil {
return err
}
Expand Down
16 changes: 6 additions & 10 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (fct fileContentTest) Run(t *testing.T) {

for name, expectContent := range fct.Files {
t.Run(fct.name(name), func(t *testing.T) {
path := filepathext.SmartJoin(fct.Dir, name)
path := filepathext.SmartJoin(e.Dir, name)
b, err := os.ReadFile(path)
require.NoError(t, err, "Error reading file")
s := string(b)
Expand Down Expand Up @@ -1102,8 +1102,8 @@ func TestIncludesOptionalExplicitFalse(t *testing.T) {

func TestIncludesFromCustomTaskfile(t *testing.T) {
tt := fileContentTest{
Entrypoint: "testdata/includes_yaml/Custom.ext",
Dir: "testdata/includes_yaml",
Entrypoint: "Custom.ext",
Target: "default",
TrimSpace: true,
Files: map[string]string{
Expand Down Expand Up @@ -1465,16 +1465,12 @@ func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
}

func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
const dir = "testdata/dotenv/error_included_envs"
const entry = "Taskfile.yml"

var buff bytes.Buffer
e := task.Executor{
Dir: dir,
Entrypoint: entry,
Summary: true,
Stdout: &buff,
Stderr: &buff,
Dir: "testdata/dotenv/error_included_envs",
Summary: true,
Stdout: &buff,
Stderr: &buff,
}

err := e.Setup()
Expand Down
39 changes: 0 additions & 39 deletions taskfile/ast/include.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ package ast

import (
"fmt"
"path/filepath"
"strings"

"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
omap "github.com/go-task/task/v3/internal/omap"

"gopkg.in/yaml.v3"
Expand All @@ -22,7 +18,6 @@ type Include struct {
Aliases []string
AdvancedImport bool
Vars *Vars
BaseDir string // The directory from which the including taskfile was loaded; used to resolve relative paths
}

// Includes represents information about included tasksfiles
Expand Down Expand Up @@ -120,39 +115,5 @@ func (include *Include) DeepCopy() *Include {
Internal: include.Internal,
AdvancedImport: include.AdvancedImport,
Vars: include.Vars.DeepCopy(),
BaseDir: include.BaseDir,
}
}

// FullTaskfilePath returns the fully qualified path to the included taskfile
func (include *Include) FullTaskfilePath() (string, error) {
return include.resolvePath(include.Taskfile)
}

// FullDirPath returns the fully qualified path to the included taskfile's working directory
func (include *Include) FullDirPath() (string, error) {
return include.resolvePath(include.Dir)
}

func (include *Include) resolvePath(path string) (string, error) {
// If the file is remote, we don't need to resolve the path
if strings.Contains(include.Taskfile, "://") {
return path, nil
}

path, err := execext.Expand(path)
if err != nil {
return "", err
}

if filepathext.IsAbs(path) {
return path, nil
}

result, err := filepath.Abs(filepathext.SmartJoin(include.BaseDir, path))
if err != nil {
return "", fmt.Errorf("task: error resolving path %s relative to %s: %w", path, include.BaseDir, err)
}

return result, nil
}
32 changes: 14 additions & 18 deletions taskfile/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,54 @@ package taskfile
import (
"context"
"os"
"path/filepath"
"strings"

"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/taskfile/ast"
)

type Node interface {
Read(ctx context.Context) ([]byte, error)
Parent() Node
Location() string
Dir() string
Optional() bool
Remote() bool
BaseDir() string
ResolveIncludeEntrypoint(include ast.Include) (string, error)
ResolveIncludeDir(include ast.Include) (string, error)
}

func NewRootNode(
dir string,
l *logger.Logger,
entrypoint string,
dir string,
insecure bool,
) (Node, error) {
// Check if there is something to read on STDIN
stat, _ := os.Stdin.Stat()
if (stat.Mode()&os.ModeCharDevice) == 0 && stat.Size() > 0 {
return NewStdinNode(dir)
}
// If no entrypoint is specified, search for a taskfile
if entrypoint == "" {
root, err := ExistsWalk(dir)
if err != nil {
return nil, err
}
return NewNode(root, insecure)
}
// Use the specified entrypoint
uri := filepath.Join(dir, entrypoint)
return NewNode(uri, insecure)
return NewNode(l, entrypoint, dir, insecure)
}

func NewNode(
uri string,
l *logger.Logger,
entrypoint string,
dir string,
insecure bool,
opts ...NodeOption,
) (Node, error) {
var node Node
var err error
switch getScheme(uri) {
switch getScheme(entrypoint) {
case "http", "https":
node, err = NewHTTPNode(uri, insecure, opts...)
node, err = NewHTTPNode(l, entrypoint, dir, insecure, opts...)
default:
// If no other scheme matches, we assume it's a file
node, err = NewFileNode(uri, opts...)
node, err = NewFileNode(l, entrypoint, dir, opts...)
}
if node.Remote() && !experiments.RemoteTaskfiles.Enabled {
return nil, errors.New("task: Remote taskfiles are not enabled. You can read more about this experiment and how to enable it at https://taskfile.dev/experiments/remote-taskfiles")
Expand Down
6 changes: 6 additions & 0 deletions taskfile/node_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ type (
BaseNode struct {
parent Node
optional bool
dir string
}
)

func NewBaseNode(opts ...NodeOption) *BaseNode {
node := &BaseNode{
parent: nil,
optional: false,
dir: "",
}

// Apply options
Expand Down Expand Up @@ -45,3 +47,7 @@ func WithOptional(optional bool) NodeOption {
func (node *BaseNode) Optional() bool {
return node.optional
}

func (node *BaseNode) Dir() string {
return node.dir
}
88 changes: 73 additions & 15 deletions taskfile/node_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,36 @@ import (
"io"
"os"
"path/filepath"
"strings"

"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/taskfile/ast"
)

// A FileNode is a node that reads a taskfile from the local filesystem.
type FileNode struct {
*BaseNode
Dir string
Entrypoint string
}

func NewFileNode(uri string, opts ...NodeOption) (*FileNode, error) {
func NewFileNode(l *logger.Logger, entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
var err error
base := NewBaseNode(opts...)
if uri == "" {
d, err := os.Getwd()
if err != nil {
return nil, err
}
uri = d
}
path, err := Exists(uri)
entrypoint, dir, err = resolveFileNodeEntrypointAndDir(l, entrypoint, dir)
if err != nil {
return nil, err
}
base.dir = dir
return &FileNode{
BaseNode: base,
Dir: filepath.Dir(path),
Entrypoint: filepath.Base(path),
Entrypoint: entrypoint,
}, nil
}

func (node *FileNode) Location() string {
return filepathext.SmartJoin(node.Dir, node.Entrypoint)
return node.Entrypoint
}

func (node *FileNode) Remote() bool {
Expand All @@ -53,6 +50,67 @@ func (node *FileNode) Read(ctx context.Context) ([]byte, error) {
return io.ReadAll(f)
}

func (node *FileNode) BaseDir() string {
return node.Dir
// resolveFileNodeEntrypointAndDir resolves checks the values of entrypoint and dir and
// populates them with default values if necessary.
func resolveFileNodeEntrypointAndDir(l *logger.Logger, entrypoint, dir string) (string, string, error) {
var err error
if entrypoint != "" {
entrypoint, err = Exists(l, entrypoint)
if err != nil {
return "", "", err
}
if dir == "" {
dir = filepath.Dir(entrypoint)
}
return entrypoint, dir, nil
}
if dir == "" {
dir, err = os.Getwd()
if err != nil {
return "", "", err
}
}
entrypoint, err = ExistsWalk(l, dir)
if err != nil {
return "", "", err
}
dir = filepath.Dir(entrypoint)
return entrypoint, dir, nil
}

func (node *FileNode) ResolveIncludeEntrypoint(include ast.Include) (string, error) {
// If the file is remote, we don't need to resolve the path
if strings.Contains(include.Taskfile, "://") {
return include.Taskfile, nil
}

path, err := execext.Expand(include.Taskfile)
if err != nil {
return "", err
}

if filepathext.IsAbs(path) {
return path, nil
}

// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
// This means that files are included relative to one another
entrypointDir := filepath.Dir(node.Entrypoint)
return filepathext.SmartJoin(entrypointDir, path), nil
}

func (node *FileNode) ResolveIncludeDir(include ast.Include) (string, error) {
path, err := execext.Expand(include.Dir)
if err != nil {
return "", err
}

if filepathext.IsAbs(path) {
return path, nil
}

// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
// This means that files are included relative to one another
entrypointDir := filepath.Dir(node.Entrypoint)
return filepathext.SmartJoin(entrypointDir, path), nil
}
Loading

0 comments on commit 796f435

Please sign in to comment.