Feature/overrides
Overview
This PR introduces a new overrides keyword that allows Taskfiles to override tasks from other Taskfiles, providing a more flexible way to customize and extend task definitions. I have taken what was done in the includes directive and mapped it to overrides.
Key Features
- Task replacement: Unlike includes which errors on duplicate task names, overrides replaces existing tasks with the overridden version
- Automatic flattening: Overridden tasks are automatically available in the local namespace without prefixes
- Nested overrides: Support for multi-level override chains with proper precedence
- Full feature parity: Supports all include options (optional, internal, vars, dir, excludes, aliases)
Implementation Details
New Files
- taskfile/ast/override.go - Core override functionality and AST node definition
- testdata/overrides/ - Comprehensive test cases for override functionality
- testdata/overrides_cycle/ - Cycle detection tests
- testdata/overrides_flatten/ - Flattening behavior tests
- testdata/overrides_interpolation/ - Variable interpolation tests
- testdata/overrides_nested/ - Nested override tests
- testdata/overrides_optional/ - Optional override tests
- testdata/overrides_with_includes/ - Combined includes/overrides tests
- testdata/overrides_with_vars/ - Variable override tests
Modified Files
- taskfile/ast/taskfile.go - Added Overrides field to Taskfile struct
- taskfile/reader.go - Override processing logic with cycle detection
- task_test.go - Comprehensive test coverage for all override scenarios
- website/docs/usage.mdx - Documentation for the new feature
- Various schema and parsing files for YAML/JSON support
Usage Example
version: '3'
overrides:
lib:
taskfile: ./overrides.yml
tasks:
greet:
cmds:
- echo "Original"
If ./overrides.yml contains a greet task, it will replace the original version.
Testing
- Added extensive test coverage including:
- Basic override functionality
- Cycle detection and prevention
- Nested overrides with proper precedence
- Variable interpolation in overrides
- Integration with existing includes
- Optional and internal override modes
- Task exclusion from overrides
Documentation
- Added comprehensive documentation in website/docs/usage.mdx covering:
- Basic usage and syntax
- Key differences from includes
- All configuration options
- Integration patterns with existing features
- Multiple examples with tabbed code blocks
Backwards Compatibility
- Fully backwards compatible - existing Taskfiles continue to work unchanged
- The overrides keyword is optional and only processed when present
- Works alongside existing includes functionality
Could you use the existing ´imports´ schema with flatten, and then adjust the behaviour which currently results in the error "If multiple tasks have the same name, an error will be thrown:"?
version: '3'
includes:
lib:
taskfile: ./Included.yml
override: true # flatten is implicit with this option, perhaps.
Could you use the existing ´imports´ schema with
flatten, and then adjust the behaviour which currently results in the error "If multiple tasks have the same name, an error will be thrown:"?version: '3' includes: lib: taskfile: ./Included.yml override: true # flatten is implicit with this option, perhaps.
That was my original idea but @andreynering suggested creating a separate key. I may have misinterpreted the request, which is fine, we can adjust.
@andreynering - should we continue with this? Thoughts?
Hi @leaanthony. I'm sorry, I know I'm late on many reviews. I'll add this to my TODO list.
In the meantime, can you rebase?
Also, can you write a short summary on why you need this? I know we discussed at the time, but that was a while ago.
No worries! Will do the rebase when I get home.
Why it's needed:
When using Task for a build system, there will be users who wish to customise it for their own scenarios. Currently they would update their Taskfiles to include these or have to copy and paste and use different prefixes.
Ideally, they could keep all their custom changes in their own Taskfiles which gets overlayed onto the default ones so they can seamlessly pick up new updates.
This pattern is often used for env var systems where local changes are stored in a different file.
@andreynering - rebased and ready for review. Happy to discuss :+1:
Hello!
Thanks for your PR!
I have a couple of questions. This may have already been discussed with @andreynering, but why did you choose to create another struct instead of reusing includes?
When two taskfiles in overrides define the same task, the resulting behavior becomes inconsistent.
For example:
Taskfile.yml
version: '3'
overrides:
lib:
taskfile: ./overrides.yml
lib2:
taskfile: ./overrides2.yml
tasks:
greet:
cmds:
- echo "Original"
overrides.yml
version: '3'
tasks:
greet:
cmds:
- echo "Override1"
overrides2.yml
version: '3'
tasks:
greet:
cmds:
- echo "Override2"
This results in:
This is also why I chose to develop exclude instead of overrides (combined with flatten). This behavior is expected when including multiple taskfiles concurrently in different goroutines, but it can be misleading for users.
Thanks for taking the time to reply. I don't recall exactly, but it would have been a discussion on discord. I honestly do not mind how this is implemented, and I'm happy to take advice. The desired outcome is that you can have a set of "base" taskfiles that you do not need to touch, then have "local" versions that can override the tasks allowing local modification. Please feel free to guide me on this as Task has changed a bit since the original PR was created :+1: