mirror of
https://github.com/go-task/task.git
synced 2025-02-15 14:03:30 +02:00
Add support for multi-level includes and cyclic include detection
This commit is contained in:
parent
79f595d8d1
commit
02e7ff27c7
7
task.go
7
task.go
@ -106,7 +106,12 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
|||||||
// Setup setups Executor's internal state
|
// Setup setups Executor's internal state
|
||||||
func (e *Executor) Setup() error {
|
func (e *Executor) Setup() error {
|
||||||
var err error
|
var err error
|
||||||
e.Taskfile, err = read.Taskfile(e.Dir, e.Entrypoint)
|
e.Taskfile, err = read.Taskfile(&read.ReaderNode{
|
||||||
|
Dir: e.Dir,
|
||||||
|
Entrypoint: e.Entrypoint,
|
||||||
|
Parent: nil,
|
||||||
|
Optional: false,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
25
task_test.go
25
task_test.go
@ -753,6 +753,31 @@ func TestIncludes(t *testing.T) {
|
|||||||
tt.Run(t)
|
tt.Run(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncludesMultiLevel(t *testing.T) {
|
||||||
|
tt := fileContentTest{
|
||||||
|
Dir: "testdata/includes_multi_level",
|
||||||
|
Target: "default",
|
||||||
|
TrimSpace: true,
|
||||||
|
Files: map[string]string{},
|
||||||
|
}
|
||||||
|
tt.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncludeCycle(t *testing.T) {
|
||||||
|
const dir = "testdata/includes_cycle"
|
||||||
|
expectedError := "include cycle detected between testdata/includes_cycle/Taskfile.yml <--> testdata/includes_cycle/one/two/Taskfile.yml"
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.Executor{
|
||||||
|
Dir: dir,
|
||||||
|
Stdout: &buff,
|
||||||
|
Stderr: &buff,
|
||||||
|
Silent: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.EqualError(t, e.Setup(), expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIncorrectVersionIncludes(t *testing.T) {
|
func TestIncorrectVersionIncludes(t *testing.T) {
|
||||||
const dir = "testdata/incorrect_includes"
|
const dir = "testdata/incorrect_includes"
|
||||||
expectedError := "task: Import with additional parameters is only available starting on Taskfile version v3"
|
expectedError := "task: Import with additional parameters is only available starting on Taskfile version v3"
|
||||||
|
@ -15,29 +15,35 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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 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")
|
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"}
|
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
|
// Taskfile reads a Taskfile for a given directory
|
||||||
// Uses current dir when dir is left empty. Uses Taskfile.yml
|
// Uses current dir when dir is left empty. Uses Taskfile.yml
|
||||||
// or Taskfile.yaml when entrypoint is left empty
|
// or Taskfile.yaml when entrypoint is left empty
|
||||||
func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||||
if dir == "" {
|
if readerNode.Dir == "" {
|
||||||
d, err := os.Getwd()
|
d, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
readerNode.Entrypoint = filepath.Base(path)
|
||||||
|
|
||||||
t, err := readTaskfile(path)
|
t, err := readTaskfile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,10 +74,32 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !filepath.IsAbs(path) {
|
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 err != nil {
|
||||||
if includedTask.Optional {
|
if includedTask.Optional {
|
||||||
return nil
|
return nil
|
||||||
@ -79,14 +107,6 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
|||||||
return err
|
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 {
|
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
|
||||||
return ErrIncludedTaskfilesCantHaveDotenvs
|
return ErrIncludedTaskfilesCantHaveDotenvs
|
||||||
}
|
}
|
||||||
@ -94,12 +114,12 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
|||||||
if includedTask.AdvancedImport {
|
if includedTask.AdvancedImport {
|
||||||
for k, v := range includedTaskfile.Vars.Mapping {
|
for k, v := range includedTaskfile.Vars.Mapping {
|
||||||
o := v
|
o := v
|
||||||
o.Dir = filepath.Join(dir, includedTask.Dir)
|
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
|
||||||
includedTaskfile.Vars.Mapping[k] = o
|
includedTaskfile.Vars.Mapping[k] = o
|
||||||
}
|
}
|
||||||
for k, v := range includedTaskfile.Env.Mapping {
|
for k, v := range includedTaskfile.Env.Mapping {
|
||||||
o := v
|
o := v
|
||||||
o.Dir = filepath.Join(dir, includedTask.Dir)
|
o.Dir = filepath.Join(readerNode.Dir, includedTask.Dir)
|
||||||
includedTaskfile.Env.Mapping[k] = o
|
includedTaskfile.Env.Mapping[k] = o
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +140,7 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if v < 3.0 {
|
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 {
|
if _, err = os.Stat(path); err == nil {
|
||||||
osTaskfile, err := readTaskfile(path)
|
osTaskfile, err := readTaskfile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
12
testdata/includes_cycle/Taskfile.yml
vendored
Normal file
12
testdata/includes_cycle/Taskfile.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
'one': ./one/Taskfile.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo "called_dep" > called_dep.txt
|
||||||
|
level1:
|
||||||
|
cmds:
|
||||||
|
- echo "hello level 1"
|
9
testdata/includes_cycle/one/Taskfile.yml
vendored
Normal file
9
testdata/includes_cycle/one/Taskfile.yml
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
'two': ./two/Taskfile.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
level2:
|
||||||
|
cmds:
|
||||||
|
- echo "hello level 2"
|
9
testdata/includes_cycle/one/two/Taskfile.yml
vendored
Normal file
9
testdata/includes_cycle/one/two/Taskfile.yml
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
bad: "../../Taskfile.yml"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
level3:
|
||||||
|
cmds:
|
||||||
|
- echo "hello level 3"
|
12
testdata/includes_multi_level/Taskfile.yml
vendored
Normal file
12
testdata/includes_multi_level/Taskfile.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
'one': ./one/Taskfile.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- echo "called_dep" > called_dep.txt
|
||||||
|
level1:
|
||||||
|
cmds:
|
||||||
|
- echo "hello level 1"
|
9
testdata/includes_multi_level/one/Taskfile.yml
vendored
Normal file
9
testdata/includes_multi_level/one/Taskfile.yml
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
'two': ./two/Taskfile.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
level2:
|
||||||
|
cmds:
|
||||||
|
- echo "hello level 2"
|
6
testdata/includes_multi_level/one/two/Taskfile.yml
vendored
Normal file
6
testdata/includes_multi_level/one/two/Taskfile.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
level3:
|
||||||
|
cmds:
|
||||||
|
- echo "hello level 3"
|
Loading…
x
Reference in New Issue
Block a user