Skip to content

Commit a567f7e

Browse files
authored
Merge pull request #134 from go-task/feature/include
Support including Taskfiles
2 parents b77fcd6 + 5720936 commit a567f7e

File tree

13 files changed

+142
-10
lines changed

13 files changed

+142
-10
lines changed

Taskfile.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ tasks:
2323
desc: Downloads cli dependencies
2424
cmds:
2525
- task: go-get
26-
vars: {REPO: github.com/golang/lint/golint}
26+
vars: {REPO: golang.org/x/lint/golint}
2727
- task: go-get
2828
vars: {REPO: github.com/golang/dep/cmd/dep}
2929
- task: go-get
@@ -68,7 +68,7 @@ tasks:
6868
ci:
6969
cmds:
7070
- task: go-get
71-
vars: {REPO: github.com/golang/lint/golint}
71+
vars: {REPO: golang.org/x/lint/golint}
7272
- task: lint
7373
- task: test
7474

docs/taskfile_versions.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,21 @@ tasks:
128128
ignore_error: true
129129
```
130130

131-
[output]: https://github.com/go-task/task#output-syntax
132-
[ignore_errors]: https://github.com/go-task/task#ignore-errors
131+
## Version 2.2
132+
133+
Version 2.2 comes with a global `includes` options to include other
134+
Taskfiles:
135+
136+
```yaml
137+
version: '2'
138+
139+
includes:
140+
docs: ./documentation # will look for ./documentation/Taskfile.yml
141+
docker: ./DockerTasks.yml
142+
```
143+
144+
Please check the [documentation][includes]
145+
146+
[output]: usage#output-syntax
147+
[ignore_errors]: usage#ignore-errors
148+
[includes]: usage#including-other-taskfiles

docs/usage.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ tasks:
4848
hallo: welt
4949
```
5050
51-
## OS specific task
51+
## Operating System specific tasks
5252
5353
If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
5454
based on the operating system.
@@ -86,6 +86,31 @@ It's also possible to have an OS specific `Taskvars.yml` file, like
8686
`Taskvars_windows.yml`, `Taskfile_linux.yml`, or `Taskvars_darwin.yml`. See the
8787
[variables section](#variables) below.
8888

89+
## Including other Taskfiles
90+
91+
If you want to share tasks between different projects (Taskfiles), you can use
92+
the importing mechanism to include other Taskfiles using the `includes` keyword:
93+
94+
```yaml
95+
version: '2'
96+
97+
includes:
98+
docs: ./documentation # will look for ./documentation/Taskfile.yml
99+
docker: ./DockerTasks.yml
100+
```
101+
102+
The tasks described in the given Taskfiles will be available with the informed
103+
namespace. So, you'd call `task docs:serve` to run the `serve` task from
104+
`documentation/Taskfile.yml` or `task docker:build` to run the `build` task
105+
from the `DockerTasks.yml` file.
106+
107+
> The included Taskfiles must be using the same schema version the main
108+
> Taskfile uses.
109+
110+
> Also, for now included Taskfiles can't include other Taskfiles.
111+
> This was a deliberate decision to keep use and implementation simple.
112+
> If you disagree, open an GitHub issue and explain your use case. =)
113+
89114
## Task directory
90115

91116
By default, tasks will be executed in the directory where the Taskfile is

internal/taskfile/merge.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package taskfile
22

33
import (
44
"fmt"
5+
"strings"
56
)
67

8+
// NamespaceSeparator contains the character that separates namescapes
9+
const NamespaceSeparator = ":"
10+
711
// Merge merges the second Taskfile into the first
8-
func Merge(t1, t2 *Taskfile) error {
12+
func Merge(t1, t2 *Taskfile, namespaces ...string) error {
913
if t1.Version != t2.Version {
1014
return fmt.Errorf(`Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
1115
}
@@ -16,12 +20,19 @@ func Merge(t1, t2 *Taskfile) error {
1620
if t2.Output != "" {
1721
t1.Output = t2.Output
1822
}
23+
for k, v := range t2.Includes {
24+
t1.Includes[k] = v
25+
}
1926
for k, v := range t2.Vars {
2027
t1.Vars[k] = v
2128
}
2229
for k, v := range t2.Tasks {
23-
t1.Tasks[k] = v
30+
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = v
2431
}
2532

2633
return nil
2734
}
35+
36+
func taskNameWithNamespace(taskName string, namespaces ...string) string {
37+
return strings.Join(append(namespaces, taskName), NamespaceSeparator)
38+
}

internal/taskfile/read/taskfile.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package read
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
67
"path/filepath"
@@ -11,6 +12,9 @@ import (
1112
"gopkg.in/yaml.v2"
1213
)
1314

15+
// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
16+
var ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")
17+
1418
// Taskfile reads a Taskfile for a given directory
1519
func Taskfile(dir string) (*taskfile.Taskfile, error) {
1620
path := filepath.Join(dir, "Taskfile.yml")
@@ -22,6 +26,27 @@ func Taskfile(dir string) (*taskfile.Taskfile, error) {
2226
return nil, err
2327
}
2428

29+
for namespace, path := range t.Includes {
30+
path = filepath.Join(dir, path)
31+
info, err := os.Stat(path)
32+
if err != nil {
33+
return nil, err
34+
}
35+
if info.IsDir() {
36+
path = filepath.Join(path, "Taskfile.yml")
37+
}
38+
includedTaskfile, err := readTaskfile(path)
39+
if err != nil {
40+
return nil, err
41+
}
42+
if len(includedTaskfile.Includes) > 0 {
43+
return nil, ErrIncludedTaskfilesCantHaveIncludes
44+
}
45+
if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
46+
return nil, err
47+
}
48+
}
49+
2550
path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
2651
if _, err = os.Stat(path); err == nil {
2752
osTaskfile, err := readTaskfile(path)

internal/taskfile/taskfile.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type Taskfile struct {
55
Version string
66
Expansions int
77
Output string
8+
Includes map[string]string
89
Vars Vars
910
Tasks Tasks
1011
}
@@ -20,6 +21,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
2021
Version string
2122
Expansions int
2223
Output string
24+
Includes map[string]string
2325
Vars Vars
2426
Tasks Tasks
2527
}
@@ -29,6 +31,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
2931
tf.Version = taskfile.Version
3032
tf.Expansions = taskfile.Expansions
3133
tf.Output = taskfile.Output
34+
tf.Includes = taskfile.Includes
3235
tf.Vars = taskfile.Vars
3336
tf.Tasks = taskfile.Tasks
3437
if tf.Expansions <= 0 {

internal/taskfile/version/version.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var (
99
v2 = mustVersion("2")
1010
v21 = mustVersion("2.1")
1111
v22 = mustVersion("2.2")
12+
v23 = mustVersion("2.3")
1213
)
1314

1415
// IsV1 returns if is a given Taskfile version is version 1
@@ -31,6 +32,11 @@ func IsV22(v *semver.Constraints) bool {
3132
return v.Check(v22)
3233
}
3334

35+
// IsV23 returns if is a given Taskfile version is at least version 2.3
36+
func IsV23(v *semver.Constraints) bool {
37+
return v.Check(v23)
38+
}
39+
3440
func mustVersion(s string) *semver.Version {
3541
v, err := semver.NewVersion(s)
3642
if err != nil {

task.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,21 +116,24 @@ func (e *Executor) Setup() error {
116116
Vars: e.taskvars,
117117
Logger: e.Logger,
118118
}
119-
case version.IsV2(v), version.IsV21(v):
119+
case version.IsV2(v), version.IsV21(v), version.IsV22(v):
120120
e.Compiler = &compilerv2.CompilerV2{
121121
Dir: e.Dir,
122122
Taskvars: e.taskvars,
123123
TaskfileVars: e.Taskfile.Vars,
124124
Expansions: e.Taskfile.Expansions,
125125
Logger: e.Logger,
126126
}
127-
case version.IsV22(v):
128-
return fmt.Errorf(`task: Taskfile versions greater than v2.1 not implemented in the version of Task`)
127+
case version.IsV23(v):
128+
return fmt.Errorf(`task: Taskfile versions greater than v2.3 not implemented in the version of Task`)
129129
}
130130

131131
if !version.IsV21(v) && e.Taskfile.Output != "" {
132132
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
133133
}
134+
if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
135+
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
136+
}
134137
switch e.Taskfile.Output {
135138
case "", "interleaved":
136139
e.Output = output.Interleaved{}

task_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,3 +470,17 @@ func TestDry(t *testing.T) {
470470
t.Errorf("File should not exist %s", file)
471471
}
472472
}
473+
474+
func TestIncludes(t *testing.T) {
475+
tt := fileContentTest{
476+
Dir: "testdata/includes",
477+
Target: "default",
478+
TrimSpace: true,
479+
Files: map[string]string{
480+
"main.txt": "main",
481+
"included_directory.txt": "included_directory",
482+
"included_taskfile.txt": "included_taskfile",
483+
},
484+
}
485+
tt.Run(t)
486+
}

testdata/includes/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.txt

0 commit comments

Comments
 (0)