diff --git a/task_test.go b/task_test.go index 689cf621f5..e9a9e08582 100644 --- a/task_test.go +++ b/task_test.go @@ -1243,6 +1243,12 @@ func TestIncludesFlatten(t *testing.T) { {name: "included flatten with deps", taskfile: "Taskfile.yml", task: "with_deps", expectedOutput: "gen from included\nwith_deps from included\n"}, {name: "included flatten nested", taskfile: "Taskfile.yml", task: "from_nested", expectedOutput: "from nested\n"}, {name: "included flatten multiple same task", taskfile: "Taskfile.multiple.yml", task: "gen", expectedErr: true, expectedOutput: "task: Found multiple tasks (gen) included by \"included\"\""}, + {name: "included flatten overridden", taskfile: "Taskfile.override.yml", task: "override", expectedOutput: "overridden task\n"}, + {name: "included flatten override unique task", taskfile: "Taskfile.override.yml", task: "unique", expectedOutput: "unique task\n"}, + {name: "included flatten override", taskfile: "Taskfile.override.yml", task: "gen", expectedOutput: "gen from included\n"}, + {name: "included flatten override with parent", taskfile: "Taskfile.override.yml", task: "override_with_parent", expectedOutput: "task: [included:override] echo \"base task\"\nbase task\noverridden task\n"}, + {name: "included flatten override with deps", taskfile: "Taskfile.override.yml", task: "with_deps", expectedOutput: "gen from included\nwith_deps from included\n"}, + {name: "included flatten override nested", taskfile: "Taskfile.override.yml", task: "from_nested", expectedOutput: "from nested\n"}, } for _, test := range tests { diff --git a/taskfile/ast/include.go b/taskfile/ast/include.go index 659070de4a..afeee015a5 100644 --- a/taskfile/ast/include.go +++ b/taskfile/ast/include.go @@ -18,6 +18,7 @@ type Include struct { AdvancedImport bool Vars *Vars Flatten bool + Override bool } // Includes represents information about included tasksfiles @@ -83,6 +84,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error { Optional bool Internal bool Flatten bool + Override bool Aliases []string Vars *Vars } @@ -97,6 +99,7 @@ func (include *Include) UnmarshalYAML(node *yaml.Node) error { include.AdvancedImport = true include.Vars = includedTaskfile.Vars include.Flatten = includedTaskfile.Flatten + include.Override = includedTaskfile.Override return nil } @@ -118,5 +121,6 @@ func (include *Include) DeepCopy() *Include { AdvancedImport: include.AdvancedImport, Vars: include.Vars.DeepCopy(), Flatten: include.Flatten, + Override: include.Override, } } diff --git a/taskfile/ast/tasks.go b/taskfile/ast/tasks.go index a440a09617..87160de634 100644 --- a/taskfile/ast/tasks.go +++ b/taskfile/ast/tasks.go @@ -100,13 +100,18 @@ func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) e task.IncludedTaskfileVars = includedTaskfileVars.DeepCopy() } + // Check for task name conflicts, if t1 already has a task with the same name fail + // If marked as override, replace the task with t1's task if t1.Get(taskName) != nil { - return &errors.TaskNameFlattenConflictError{ - TaskName: taskName, - Include: include.Namespace, + if include.Override { + return nil + } else { + return &errors.TaskNameFlattenConflictError{ + TaskName: taskName, + Include: include.Namespace, + } } } - // Add the task to the merged taskfile t1.Set(taskName, task) return nil diff --git a/taskfile/reader.go b/taskfile/reader.go index 32783dea66..e2b810dc74 100644 --- a/taskfile/reader.go +++ b/taskfile/reader.go @@ -110,6 +110,7 @@ func (r *Reader) include(node Node) error { Optional: include.Optional, Internal: include.Internal, Flatten: include.Flatten, + Override: include.Override, Aliases: include.Aliases, AdvancedImport: include.AdvancedImport, Vars: include.Vars, diff --git a/testdata/includes_flatten/Taskfile.override.yml b/testdata/includes_flatten/Taskfile.override.yml new file mode 100644 index 0000000000..73f3f29eac --- /dev/null +++ b/testdata/includes_flatten/Taskfile.override.yml @@ -0,0 +1,26 @@ +version: '3' + +includes: + included: + taskfile: ./included + dir: ./included + flatten: true + override: true + +tasks: + default: + cmds: + - echo root_directory > root_directory.txt + + override: + cmds: + - echo "overridden task" + + override_with_parent: + cmds: + - task included:override + - echo "overridden task" + + unique: + cmds: + - echo "unique task" diff --git a/testdata/includes_flatten/included/Taskfile.yml b/testdata/includes_flatten/included/Taskfile.yml index c7af41dca1..33f4eb987d 100644 --- a/testdata/includes_flatten/included/Taskfile.yml +++ b/testdata/includes_flatten/included/Taskfile.yml @@ -15,9 +15,12 @@ tasks: - gen cmds: - echo "with_deps from included" - - + pwd: desc: Print working directory cmds: - pwd + + override: + cmds: + - echo "base task" diff --git a/website/docs/reference/schema.mdx b/website/docs/reference/schema.mdx index e67a033c23..9e7ea07f6d 100644 --- a/website/docs/reference/schema.mdx +++ b/website/docs/reference/schema.mdx @@ -31,6 +31,7 @@ toc_max_heading_level: 5 | `dir` | `string` | The parent Taskfile directory | The working directory of the included tasks when run. | | `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. | | `flatten` | `bool` | `false` | If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, an error will be thrown. | +| `override` | `bool` | `false` | If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, the included task is overridden. | | `internal` | `bool` | `false` | Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`. | | `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. | | `vars` | `map[string]Variable` | | A set of variables to apply to the included Taskfile. | diff --git a/website/docs/usage.mdx b/website/docs/usage.mdx index e05f726827..32b881df02 100644 --- a/website/docs/usage.mdx +++ b/website/docs/usage.mdx @@ -441,9 +441,63 @@ task: Found multiple tasks (greet) included by "lib" ``` +#### Flatten Override +When using flatten, it may be desirable to override a specific command that is included. When enabled, you can avoid multiple task conflicts by instead overriding the task in the default namespace. +If multiple tasks have the same name, the highest order task is executed: + + + + + ```yaml + version: '3' + includes: + lib: + taskfile: ./Included.yml + flatten: true + override: true + + tasks: + greet: + cmds: + - echo "Greet" + - task: foo + ``` + + + + + + ```yaml + version: '3' + + tasks: + greet: + cmds: + - echo "Foo" + ``` + + + + +If you run `task -a` it will print: +```text +task: Available tasks for this project: +* default: +* greet: +``` + +If you run `task greet` it will print: +```text +Greet +Foo +``` ### Vars of included Taskfiles diff --git a/website/static/schema.json b/website/static/schema.json index f87511f56a..8b76dd2771 100644 --- a/website/static/schema.json +++ b/website/static/schema.json @@ -623,6 +623,10 @@ "description": "If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, an error will be thrown.", "type": "boolean" }, + "override": { + "description": "If `true`, the tasks from the included Taskfile will be available in the including Taskfile without a namespace. If a task with the same name already exists in the including Taskfile, the included task is overridden.", + "type": "boolean" + }, "internal": { "description": "Stops any task in the included Taskfile from being callable on the command line. These commands will also be omitted from the output when used with `--list`.", "type": "boolean"