Add support for multi-level includes and cyclic include detection

This commit is contained in:
tylermmorton
2022-01-14 22:38:37 -05:00
parent 79f595d8d1
commit 02e7ff27c7
9 changed files with 127 additions and 20 deletions

View File

@@ -15,29 +15,35 @@ import (
)
var (
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
// ErrIncludedTaskfilesCantHaveDotenvs is returned when a included Taskfile contains dotenvs
ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles can't have dotenv declarations. Please, move the dotenv declaration to the main Taskfile")
defaultTaskfiles = []string{"Taskfile.yml", "Taskfile.yaml"}
)
type ReaderNode struct {
Dir string
Entrypoint string
Optional bool
Parent *ReaderNode
}
// Taskfile reads a Taskfile for a given directory
// Uses current dir when dir is left empty. Uses Taskfile.yml
// or Taskfile.yaml when entrypoint is left empty
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
if dir == "" {
func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
if readerNode.Dir == "" {
d, err := os.Getwd()
if err != nil {
return nil, err
}
dir = d
readerNode.Dir = d
}
path, err := exists(filepath.Join(dir, entrypoint))
path, err := exists(filepath.Join(readerNode.Dir, readerNode.Entrypoint))
if err != nil {
return nil, err
}
readerNode.Entrypoint = filepath.Base(path)
t, err := readTaskfile(path)
if err != nil {
@@ -68,10 +74,32 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
return err
}
if !filepath.IsAbs(path) {
path = filepath.Join(dir, path)
path = filepath.Join(readerNode.Dir, path)
}
path, err = exists(path)
// check for cyclic include references by walking up
// node tree of parents and comparing paths
var curNode = readerNode
for curNode.Parent != nil {
curNode = curNode.Parent
curPath := filepath.Join(curNode.Dir, curNode.Entrypoint)
if curPath == path {
return fmt.Errorf("include cycle detected between %s <--> %s",
curPath,
filepath.Join(readerNode.Dir, readerNode.Entrypoint),
)
}
}
// if we made it here then there is no cyclic include
readOpts := &ReaderNode{
Dir: filepath.Dir(path),
Entrypoint: filepath.Base(path),
Parent: readerNode,
Optional: includedTask.Optional,
}
includedTaskfile, err := Taskfile(readOpts)
if err != nil {
if includedTask.Optional {
return nil
@@ -79,14 +107,6 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
return err
}
includedTaskfile, err := readTaskfile(path)
if err != nil {
return err
}
if includedTaskfile.Includes.Len() > 0 {
return ErrIncludedTaskfilesCantHaveIncludes
}
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
return ErrIncludedTaskfilesCantHaveDotenvs
}
@@ -94,12 +114,12 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
if includedTask.AdvancedImport {
for k, v := range includedTaskfile.Vars.Mapping {
o := v
o.Dir = filepath.Join(dir, includedTask.Dir)
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
includedTaskfile.Vars.Mapping[k] = o
}
for k, v := range includedTaskfile.Env.Mapping {
o := v
o.Dir = filepath.Join(dir, includedTask.Dir)
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
includedTaskfile.Env.Mapping[k] = o
}
@@ -120,7 +140,7 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
}
if v < 3.0 {
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
path = filepath.Join(readerNode.Dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
if err != nil {