Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ tasks:
desc: Downloads cli dependencies
cmds:
- task: go-get
vars: {REPO: github.com/golang/lint/golint}
vars: {REPO: golang.org/x/lint/golint}
- task: go-get
vars: {REPO: github.com/golang/dep/cmd/dep}
- task: go-get
Expand Down Expand Up @@ -68,7 +68,7 @@ tasks:
ci:
cmds:
- task: go-get
vars: {REPO: github.com/golang/lint/golint}
vars: {REPO: golang.org/x/lint/golint}
- task: lint
- task: test

Expand Down
20 changes: 18 additions & 2 deletions docs/taskfile_versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,21 @@ tasks:
ignore_error: true
```

[output]: https://github.com/go-task/task#output-syntax
[ignore_errors]: https://github.com/go-task/task#ignore-errors
## Version 2.2

Version 2.2 comes with a global `includes` options to include other
Taskfiles:

```yaml
version: '2'
includes:
docs: ./documentation # will look for ./documentation/Taskfile.yml
docker: ./DockerTasks.yml
```

Please check the [documentation][includes]

[output]: usage#output-syntax
[ignore_errors]: usage#ignore-errors
[includes]: usage#including-other-taskfiles
27 changes: 26 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ tasks:
hallo: welt
```

## OS specific task
## Operating System specific tasks

If you add a `Taskfile_{{GOOS}}.yml` you can override or amend your Taskfile
based on the operating system.
Expand Down Expand Up @@ -86,6 +86,31 @@ It's also possible to have an OS specific `Taskvars.yml` file, like
`Taskvars_windows.yml`, `Taskfile_linux.yml`, or `Taskvars_darwin.yml`. See the
[variables section](#variables) below.

## Including other Taskfiles

If you want to share tasks between different projects (Taskfiles), you can use
the importing mechanism to include other Taskfiles using the `includes` keyword:

```yaml
version: '2'

includes:
docs: ./documentation # will look for ./documentation/Taskfile.yml
docker: ./DockerTasks.yml
```

The tasks described in the given Taskfiles will be available with the informed
namespace. So, you'd call `task docs:serve` to run the `serve` task from
`documentation/Taskfile.yml` or `task docker:build` to run the `build` task
from the `DockerTasks.yml` file.

> The included Taskfiles must be using the same schema version the main
> 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. =)

## Task directory

By default, tasks will be executed in the directory where the Taskfile is
Expand Down
15 changes: 13 additions & 2 deletions internal/taskfile/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package taskfile

import (
"fmt"
"strings"
)

// NamespaceSeparator contains the character that separates namescapes
const NamespaceSeparator = ":"

// Merge merges the second Taskfile into the first
func Merge(t1, t2 *Taskfile) error {
func Merge(t1, t2 *Taskfile, namespaces ...string) error {
if t1.Version != t2.Version {
return fmt.Errorf(`Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
}
Expand All @@ -16,12 +20,19 @@ func Merge(t1, t2 *Taskfile) error {
if t2.Output != "" {
t1.Output = t2.Output
}
for k, v := range t2.Includes {
t1.Includes[k] = v
}
for k, v := range t2.Vars {
t1.Vars[k] = v
}
for k, v := range t2.Tasks {
t1.Tasks[k] = v
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = v
}

return nil
}

func taskNameWithNamespace(taskName string, namespaces ...string) string {
return strings.Join(append(namespaces, taskName), NamespaceSeparator)
}
25 changes: 25 additions & 0 deletions internal/taskfile/read/taskfile.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package read

import (
"errors"
"fmt"
"os"
"path/filepath"
Expand All @@ -11,6 +12,9 @@ import (
"gopkg.in/yaml.v2"
)

// ErrIncludedTaskfilesCantHaveIncludes is returned when a included Taskfile contains includes
var ErrIncludedTaskfilesCantHaveIncludes = errors.New("task: Included Taskfiles can't have includes. Please, move the include to the main Taskfile")

// Taskfile reads a Taskfile for a given directory
func Taskfile(dir string) (*taskfile.Taskfile, error) {
path := filepath.Join(dir, "Taskfile.yml")
Expand All @@ -22,6 +26,27 @@ func Taskfile(dir string) (*taskfile.Taskfile, error) {
return nil, err
}

for namespace, path := range t.Includes {
path = filepath.Join(dir, path)
info, err := os.Stat(path)
if err != nil {
return nil, err
}
if info.IsDir() {
path = filepath.Join(path, "Taskfile.yml")
}
includedTaskfile, err := readTaskfile(path)
if err != nil {
return nil, err
}
if len(includedTaskfile.Includes) > 0 {
return nil, ErrIncludedTaskfilesCantHaveIncludes
}
if err = taskfile.Merge(t, includedTaskfile, namespace); err != nil {
return nil, err
}
}

path = filepath.Join(dir, fmt.Sprintf("Taskfile_%s.yml", runtime.GOOS))
if _, err = os.Stat(path); err == nil {
osTaskfile, err := readTaskfile(path)
Expand Down
3 changes: 3 additions & 0 deletions internal/taskfile/taskfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type Taskfile struct {
Version string
Expansions int
Output string
Includes map[string]string
Vars Vars
Tasks Tasks
}
Expand All @@ -20,6 +21,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
Version string
Expansions int
Output string
Includes map[string]string
Vars Vars
Tasks Tasks
}
Expand All @@ -29,6 +31,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
tf.Version = taskfile.Version
tf.Expansions = taskfile.Expansions
tf.Output = taskfile.Output
tf.Includes = taskfile.Includes
tf.Vars = taskfile.Vars
tf.Tasks = taskfile.Tasks
if tf.Expansions <= 0 {
Expand Down
6 changes: 6 additions & 0 deletions internal/taskfile/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var (
v2 = mustVersion("2")
v21 = mustVersion("2.1")
v22 = mustVersion("2.2")
v23 = mustVersion("2.3")
)

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

// IsV23 returns if is a given Taskfile version is at least version 2.3
func IsV23(v *semver.Constraints) bool {
return v.Check(v23)
}

func mustVersion(s string) *semver.Version {
v, err := semver.NewVersion(s)
if err != nil {
Expand Down
9 changes: 6 additions & 3 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,24 @@ func (e *Executor) Setup() error {
Vars: e.taskvars,
Logger: e.Logger,
}
case version.IsV2(v), version.IsV21(v):
case version.IsV2(v), version.IsV21(v), version.IsV22(v):
e.Compiler = &compilerv2.CompilerV2{
Dir: e.Dir,
Taskvars: e.taskvars,
TaskfileVars: e.Taskfile.Vars,
Expansions: e.Taskfile.Expansions,
Logger: e.Logger,
}
case version.IsV22(v):
return fmt.Errorf(`task: Taskfile versions greater than v2.1 not implemented in the version of Task`)
case version.IsV23(v):
return fmt.Errorf(`task: Taskfile versions greater than v2.3 not implemented in the version of Task`)
}

if !version.IsV21(v) && e.Taskfile.Output != "" {
return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
}
if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
}
switch e.Taskfile.Output {
case "", "interleaved":
e.Output = output.Interleaved{}
Expand Down
14 changes: 14 additions & 0 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,17 @@ func TestDry(t *testing.T) {
t.Errorf("File should not exist %s", file)
}
}

func TestIncludes(t *testing.T) {
tt := fileContentTest{
Dir: "testdata/includes",
Target: "default",
TrimSpace: true,
Files: map[string]string{
"main.txt": "main",
"included_directory.txt": "included_directory",
"included_taskfile.txt": "included_taskfile",
},
}
tt.Run(t)
}
1 change: 1 addition & 0 deletions testdata/includes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.txt
16 changes: 16 additions & 0 deletions testdata/includes/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '2'

includes:
included: ./included
included_taskfile: ./Taskfile2.yml

tasks:
default:
cmds:
- task: gen
- task: included:gen
- task: included_taskfile:gen

gen:
cmds:
- echo main > main.txt
6 changes: 6 additions & 0 deletions testdata/includes/Taskfile2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: '2'

tasks:
gen:
cmds:
- echo included_taskfile > included_taskfile.txt
6 changes: 6 additions & 0 deletions testdata/includes/included/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: '2'

tasks:
gen:
cmds:
- echo included_directory > included_directory.txt