1
0
mirror of https://github.com/go-task/task.git synced 2025-02-09 13:47:06 +02:00

feat: implement gentle force experiment draft (#1216)

* feat: implement gentle force experiment draft

* docs: changelog
This commit is contained in:
Pete Davison 2023-06-18 02:32:18 +01:00 committed by GitHub
parent d8a12fe56d
commit 5fdaa9aa36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 139 additions and 35 deletions

View File

@ -6,8 +6,10 @@
- e.g. `taskfile.yml`, `taskfile.yaml`, `taskfile.dist.yml` &
`taskfile.dist.yaml`
- Bug fixed were made to the
[npm installation method](https://taskfile.dev/installation/#npm).
(#1190, by @sounisi5011).
[npm installation method](https://taskfile.dev/installation/#npm). (#1190, by
@sounisi5011).
- Added the [gentle force experiment](https://taskfile.dev/experiments) as a
draft (#1200, #1216 by @pd93).
## v3.26.0 - 2023-06-10

View File

@ -13,7 +13,7 @@ func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) {
for _, arg := range args {
if !strings.Contains(arg, "=") {
calls = append(calls, taskfile.Call{Task: arg})
calls = append(calls, taskfile.Call{Task: arg, Direct: true})
continue
}
@ -22,7 +22,7 @@ func ParseV3(args ...string) ([]taskfile.Call, *taskfile.Vars) {
}
if len(calls) == 0 {
calls = append(calls, taskfile.Call{Task: "default"})
calls = append(calls, taskfile.Call{Task: "default", Direct: true})
}
return calls, globals
@ -35,7 +35,7 @@ func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) {
for _, arg := range args {
if !strings.Contains(arg, "=") {
calls = append(calls, taskfile.Call{Task: arg})
calls = append(calls, taskfile.Call{Task: arg, Direct: true})
continue
}
@ -52,7 +52,7 @@ func ParseV2(args ...string) ([]taskfile.Call, *taskfile.Vars) {
}
if len(calls) == 0 {
calls = append(calls, taskfile.Call{Task: "default"})
calls = append(calls, taskfile.Call{Task: "default", Direct: true})
}
return calls, globals

View File

@ -20,17 +20,17 @@ func TestArgsV3(t *testing.T) {
{
Args: []string{"task-a", "task-b", "task-c"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
{Task: "task-a", Direct: true},
{Task: "task-b", Direct: true},
{Task: "task-c", Direct: true},
},
},
{
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
{Task: "task-a", Direct: true},
{Task: "task-b", Direct: true},
{Task: "task-c", Direct: true},
},
ExpectedGlobals: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(
@ -46,7 +46,7 @@ func TestArgsV3(t *testing.T) {
{
Args: []string{"task-a", "CONTENT=with some spaces"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-a", Direct: true},
},
ExpectedGlobals: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(
@ -60,8 +60,8 @@ func TestArgsV3(t *testing.T) {
{
Args: []string{"FOO=bar", "task-a", "task-b"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-a", Direct: true},
{Task: "task-b", Direct: true},
},
ExpectedGlobals: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(
@ -75,19 +75,19 @@ func TestArgsV3(t *testing.T) {
{
Args: nil,
ExpectedCalls: []taskfile.Call{
{Task: "default"},
{Task: "default", Direct: true},
},
},
{
Args: []string{},
ExpectedCalls: []taskfile.Call{
{Task: "default"},
{Task: "default", Direct: true},
},
},
{
Args: []string{"FOO=bar", "BAR=baz"},
ExpectedCalls: []taskfile.Call{
{Task: "default"},
{Task: "default", Direct: true},
},
ExpectedGlobals: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(
@ -122,16 +122,17 @@ func TestArgsV2(t *testing.T) {
{
Args: []string{"task-a", "task-b", "task-c"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-c"},
{Task: "task-a", Direct: true},
{Task: "task-b", Direct: true},
{Task: "task-c", Direct: true},
},
},
{
Args: []string{"task-a", "FOO=bar", "task-b", "task-c", "BAR=baz", "BAZ=foo"},
ExpectedCalls: []taskfile.Call{
{
Task: "task-a",
Task: "task-a",
Direct: true,
Vars: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(
map[string]taskfile.Var{
@ -141,9 +142,10 @@ func TestArgsV2(t *testing.T) {
),
},
},
{Task: "task-b"},
{Task: "task-b", Direct: true},
{
Task: "task-c",
Task: "task-c",
Direct: true,
Vars: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(
map[string]taskfile.Var{
@ -160,7 +162,8 @@ func TestArgsV2(t *testing.T) {
Args: []string{"task-a", "CONTENT=with some spaces"},
ExpectedCalls: []taskfile.Call{
{
Task: "task-a",
Task: "task-a",
Direct: true,
Vars: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(
map[string]taskfile.Var{
@ -175,8 +178,8 @@ func TestArgsV2(t *testing.T) {
{
Args: []string{"FOO=bar", "task-a", "task-b"},
ExpectedCalls: []taskfile.Call{
{Task: "task-a"},
{Task: "task-b"},
{Task: "task-a", Direct: true},
{Task: "task-b", Direct: true},
},
ExpectedGlobals: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(
@ -190,19 +193,19 @@ func TestArgsV2(t *testing.T) {
{
Args: nil,
ExpectedCalls: []taskfile.Call{
{Task: "default"},
{Task: "default", Direct: true},
},
},
{
Args: []string{},
ExpectedCalls: []taskfile.Call{
{Task: "default"},
{Task: "default", Direct: true},
},
},
{
Args: []string{"FOO=bar", "BAR=baz"},
ExpectedCalls: []taskfile.Call{
{Task: "default"},
{Task: "default", Direct: true},
},
ExpectedGlobals: &taskfile.Vars{
OrderedMap: orderedmap.FromMapWithOrder(

View File

@ -15,6 +15,7 @@ import (
"github.com/go-task/task/v3"
"github.com/go-task/task/v3/args"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/sort"
ver "github.com/go-task/task/v3/internal/version"
@ -53,6 +54,7 @@ var flags struct {
taskSort string
status bool
force bool
forceAll bool
watch bool
verbose bool
silent bool
@ -78,7 +80,6 @@ func main() {
Verbose: flags.verbose,
Color: flags.color,
}
if err, ok := err.(*errors.TaskRunError); ok && flags.exitCode {
l.Errf(logger.Red, "%v\n", err)
os.Exit(err.TaskExitCode())
@ -110,7 +111,6 @@ func run() error {
pflag.BoolVarP(&flags.listJson, "json", "j", false, "Formats task list as JSON.")
pflag.StringVar(&flags.taskSort, "sort", "", "Changes the order of the tasks when listed. [default|alphanumeric|none].")
pflag.BoolVar(&flags.status, "status", false, "Exits with non-zero exit code if any of the given tasks is not up-to-date.")
pflag.BoolVarP(&flags.force, "force", "f", false, "Forces execution even when the task is up-to-date.")
pflag.BoolVarP(&flags.watch, "watch", "w", false, "Enables watch of the given task.")
pflag.BoolVarP(&flags.verbose, "verbose", "v", false, "Enables verbose mode.")
pflag.BoolVarP(&flags.silent, "silent", "s", false, "Disables echoing.")
@ -129,6 +129,15 @@ func run() error {
pflag.IntVarP(&flags.concurrency, "concurrency", "C", 0, "Limit number tasks to run concurrently.")
pflag.DurationVarP(&flags.interval, "interval", "I", 0, "Interval to watch for changes.")
pflag.BoolVarP(&flags.global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}.")
// Gentle force experiment will override the force flag and add a new force-all flag
if experiments.GentleForce {
pflag.BoolVarP(&flags.force, "force", "f", false, "Forces execution of the directly called task.")
pflag.BoolVar(&flags.forceAll, "force-all", false, "Forces execution of the called task and all its dependant tasks.")
} else {
pflag.BoolVarP(&flags.forceAll, "force", "f", false, "Forces execution even when the task is up-to-date.")
}
pflag.Parse()
if flags.version {
@ -194,6 +203,7 @@ func run() error {
e := task.Executor{
Force: flags.force,
ForceAll: flags.forceAll,
Watch: flags.watch,
Verbose: flags.verbose,
Silent: flags.silent,

View File

@ -70,6 +70,27 @@ version 3 as soon as possible.
A list of changes between version 2 and version 3 are available in the [Task v3
Release Notes][version-3-release-notes].
### ![experiment] Gentle Force ([#1200](https://github.com/go-task/task/issues/1200))
- Environment variable: `TASK_X_FORCE=1`
- Breaks: `--force` flag
The `--force` flag currently forces _all_ tasks to run regardless of the status
checks. This can be useful, but we have found that most of the time users only
expect the direct task they are calling to be forced and _not_ all of its
dependant tasks.
This experiment changes the `--force` flag to only force the directly called
task. All dependant tasks will have their statuses checked as normal and will
only run if Task considers them to be out of date. A new `--force-all` flag will
also be added to maintain the current behavior for users that need this
functionality.
If you want to migrate, but continue to force all dependant tasks to run, you
should replace all uses of the `--force` flag with `--force-all`. Alternatively,
if you want to adopt the new behavior, you can continue to use the `--force`
flag as you do now!
<!-- prettier-ignore-start -->
[breaking-change-proposal]: https://github.com/go-task/task/discussions/1191
[deprecate-version-2-schema]: https://github.com/go-task/task/issues/1197

View File

@ -11,13 +11,13 @@ import (
const envPrefix = "TASK_X_"
var TestExperiment bool
var GentleForce bool
func init() {
if err := readDotEnv(); err != nil {
panic(err)
}
TestExperiment = parseEnv("TestExperiment")
GentleForce = parseEnv("GENTLE_FORCE")
}
func parseEnv(xName string) bool {

View File

@ -50,6 +50,7 @@ type Executor struct {
TempDir string
Entrypoint string
Force bool
ForceAll bool
Watch bool
Verbose bool
Silent bool
@ -179,7 +180,8 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
return err
}
if !e.Force {
skipFingerprinting := e.ForceAll || (call.Direct && e.Force)
if !skipFingerprinting {
if err := ctx.Err(); err != nil {
return err
}

View File

@ -2120,3 +2120,49 @@ func TestSilence(t *testing.T) {
buff.Reset()
}
func TestForce(t *testing.T) {
tests := []struct {
name string
env map[string]string
force bool
forceAll bool
}{
{
name: "force",
force: true,
},
{
name: "force-all",
forceAll: true,
},
{
name: "force with gentle force experiment",
force: true,
env: map[string]string{
"TASK_X_GENTLE_FORCE": "1",
},
},
{
name: "force-all with gentle force experiment",
forceAll: true,
env: map[string]string{
"TASK_X_GENTLE_FORCE": "1",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buff bytes.Buffer
e := task.Executor{
Dir: "testdata/force",
Stdout: &buff,
Stderr: &buff,
Force: tt.force,
ForceAll: tt.forceAll,
}
require.NoError(t, e.Setup())
require.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "task-with-dep", Direct: true}))
})
}
}

View File

@ -5,4 +5,5 @@ type Call struct {
Task string
Vars *Vars
Silent bool
Direct bool // Was the task called directly or via another task?
}

19
testdata/force/Taskfile.yml vendored Normal file
View File

@ -0,0 +1,19 @@
version: "3"
tasks:
task-with-dep:
status: [ test true ]
deps: [ indirect ]
cmds:
- echo "direct"
task-with-subtask:
status: [ test true ]
cmds:
- task: indirect
- echo "direct"
indirect:
status: [ test true ]
cmds:
- echo "indirect"