mirror of
https://github.com/go-task/task.git
synced 2025-04-17 12:06:30 +02:00
Merge pull request #656 from tylermmorton/master
Add support for multi-level includes
This commit is contained in:
commit
41cd7acc87
@ -164,10 +164,6 @@ includes:
|
|||||||
> The included Taskfiles must be using the same schema version the main
|
> The included Taskfiles must be using the same schema version the main
|
||||||
> Taskfile uses.
|
> Taskfile uses.
|
||||||
|
|
||||||
> Also, for now included Taskfiles can't include other Taskfiles.
|
|
||||||
> This was a deliberate decision to keep use and implementation simple.
|
|
||||||
> If you disagree, open an GitHub issue and explain your use case. =)
|
|
||||||
|
|
||||||
### Optional includes
|
### Optional includes
|
||||||
|
|
||||||
Includes marked as optional will allow Task to continue execution as normal if
|
Includes marked as optional will allow Task to continue execution as normal if
|
||||||
|
7
task.go
7
task.go
@ -107,7 +107,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
|
||||||
}
|
}
|
||||||
|
29
task_test.go
29
task_test.go
@ -753,6 +753,35 @@ 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{
|
||||||
|
"called_one.txt": "one",
|
||||||
|
"called_two.txt": "two",
|
||||||
|
"called_three.txt": "three",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tt.Run(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncludeCycle(t *testing.T) {
|
||||||
|
const dir = "testdata/includes_cycle"
|
||||||
|
expectedError := "task: 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,8 +15,6 @@ 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")
|
||||||
|
|
||||||
@ -28,21 +26,29 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
@ -74,9 +80,8 @@ 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)
|
path, err = exists(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if includedTask.Optional {
|
if includedTask.Optional {
|
||||||
@ -85,12 +90,23 @@ func Taskfile(dir string, entrypoint string) (*taskfile.Taskfile, error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
includedTaskfile, err := readTaskfile(path)
|
includeReaderNode := &ReaderNode{
|
||||||
if err != nil {
|
Dir: filepath.Dir(path),
|
||||||
|
Entrypoint: filepath.Base(path),
|
||||||
|
Parent: readerNode,
|
||||||
|
Optional: includedTask.Optional,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkCircularIncludes(includeReaderNode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if includedTaskfile.Includes.Len() > 0 {
|
|
||||||
return ErrIncludedTaskfilesCantHaveIncludes
|
includedTaskfile, err := Taskfile(includeReaderNode)
|
||||||
|
if err != nil {
|
||||||
|
if includedTask.Optional {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
|
if v >= 3.0 && len(includedTaskfile.Dotenv) > 0 {
|
||||||
@ -100,12 +116,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +144,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 {
|
||||||
@ -178,3 +194,25 @@ func exists(path string) (string, error) {
|
|||||||
|
|
||||||
return "", fmt.Errorf(`task: No Taskfile found in "%s". Use "task --init" to create a new one`, path)
|
return "", fmt.Errorf(`task: No Taskfile found in "%s". Use "task --init" to create a new one`, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkCircularIncludes(node *ReaderNode) error {
|
||||||
|
if node == nil {
|
||||||
|
return errors.New("task: failed to check for include cycle: node was nil")
|
||||||
|
}
|
||||||
|
if node.Parent == nil {
|
||||||
|
return errors.New("task: failed to check for include cycle: node.Parent was nil")
|
||||||
|
}
|
||||||
|
var curNode = node
|
||||||
|
var basePath = filepath.Join(node.Dir, node.Entrypoint)
|
||||||
|
for curNode.Parent != nil {
|
||||||
|
curNode = curNode.Parent
|
||||||
|
curPath := filepath.Join(curNode.Dir, curNode.Entrypoint)
|
||||||
|
if curPath == basePath {
|
||||||
|
return fmt.Errorf("task: include cycle detected between %s <--> %s",
|
||||||
|
curPath,
|
||||||
|
filepath.Join(node.Parent.Dir, node.Parent.Entrypoint),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 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"
|
11
testdata/includes_multi_level/Taskfile.yml
vendored
Normal file
11
testdata/includes_multi_level/Taskfile.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
'one': ./one/
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
cmds:
|
||||||
|
- task: one:default
|
||||||
|
- task: one:two:default
|
||||||
|
- task: one:two:three:default
|
1
testdata/includes_multi_level/called_one.txt
vendored
Normal file
1
testdata/includes_multi_level/called_one.txt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
one
|
1
testdata/includes_multi_level/called_three.txt
vendored
Normal file
1
testdata/includes_multi_level/called_three.txt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
three
|
1
testdata/includes_multi_level/called_two.txt
vendored
Normal file
1
testdata/includes_multi_level/called_two.txt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
two
|
8
testdata/includes_multi_level/one/Taskfile.yml
vendored
Normal file
8
testdata/includes_multi_level/one/Taskfile.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
'two': ./two/
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default: echo one > called_one.txt
|
||||||
|
|
7
testdata/includes_multi_level/one/two/Taskfile.yml
vendored
Normal file
7
testdata/includes_multi_level/one/two/Taskfile.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
'three': ./three/Taskfile.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default: echo two > called_two.txt
|
5
testdata/includes_multi_level/one/two/three/Taskfile.yml
vendored
Normal file
5
testdata/includes_multi_level/one/two/three/Taskfile.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default: echo three > called_three.txt
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user