Skip to content

Commit a1140aa

Browse files
committed
Merge branch 'cyclic-dep'
2 parents fb4b0a1 + 2dd3564 commit a1140aa

File tree

6 files changed

+44
-96
lines changed

6 files changed

+44
-96
lines changed

cyclic.go

Lines changed: 0 additions & 34 deletions
This file was deleted.

cyclic_test.go

Lines changed: 0 additions & 56 deletions
This file was deleted.

errors.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import (
66
)
77

88
var (
9-
// ErrCyclicDepDetected is returned when a cyclic dependency was found in the Taskfile
10-
ErrCyclicDepDetected = errors.New("task: cyclic dependency detected")
119
// ErrTaskfileAlreadyExists is returned on creating a Taskfile if one already exists
1210
ErrTaskfileAlreadyExists = errors.New("task: A Taskfile already exists")
1311
)
@@ -61,3 +59,18 @@ type dynamicVarError struct {
6159
func (err *dynamicVarError) Error() string {
6260
return fmt.Sprintf(`task: Command "%s" in taskvars file failed: %s`, err.cmd, err.cause)
6361
}
62+
63+
// MaximumTaskCallExceededError is returned when a task is called too
64+
// many times. In this case you probably have a cyclic dependendy or
65+
// infinite loop
66+
type MaximumTaskCallExceededError struct {
67+
task string
68+
}
69+
70+
func (e *MaximumTaskCallExceededError) Error() string {
71+
return fmt.Sprintf(
72+
`task: maximum task call exceeded (%d) for task "%s": probably an cyclic dep or infinite loop`,
73+
MaximumTaskCall,
74+
e.task,
75+
)
76+
}

task.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"path/filepath"
1010
"strings"
1111
"sync"
12+
"sync/atomic"
1213

1314
"github.com/go-task/task/execext"
1415

@@ -18,6 +19,9 @@ import (
1819
const (
1920
// TaskFilePath is the default Taskfile
2021
TaskFilePath = "Taskfile"
22+
// MaximumTaskCall is the max number of times a task can be called.
23+
// This exists to prevent infinite loops on cyclic dependencies
24+
MaximumTaskCall = 100
2125
)
2226

2327
// Executor executes a Taskfile
@@ -57,14 +61,12 @@ type Task struct {
5761
Vars Vars
5862
Set string
5963
Env Vars
64+
65+
callCount int32
6066
}
6167

6268
// Run runs Task
6369
func (e *Executor) Run(args ...string) error {
64-
if err := e.CheckCyclicDep(); err != nil {
65-
return err
66-
}
67-
6870
if e.Stdin == nil {
6971
e.Stdin = os.Stdin
7072
}
@@ -110,6 +112,10 @@ func (e *Executor) RunTask(ctx context.Context, name string, vars Vars) error {
110112
return &taskNotFoundError{name}
111113
}
112114

115+
if atomic.AddInt32(&t.callCount, 1) >= MaximumTaskCall {
116+
return &MaximumTaskCallExceededError{task: name}
117+
}
118+
113119
if err := e.runDeps(ctx, name, vars); err != nil {
114120
return err
115121
}

task_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,15 @@ func TestParams(t *testing.T) {
199199
assert.Equal(t, f.content, string(content))
200200
}
201201
}
202+
203+
func TestCyclicDep(t *testing.T) {
204+
const dir = "testdata/cyclic"
205+
206+
e := task.Executor{
207+
Dir: dir,
208+
Stdout: ioutil.Discard,
209+
Stderr: ioutil.Discard,
210+
}
211+
assert.NoError(t, e.ReadTaskfile())
212+
assert.IsType(t, &task.MaximumTaskCallExceededError{}, e.Run("task-1"))
213+
}

testdata/cyclic/Taskfile.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
task-1:
2+
deps:
3+
- task: task-2
4+
5+
task-2:
6+
deps:
7+
- task: task-1

0 commit comments

Comments
 (0)