From 36614dccf815c187c8547c995475a8e289112cfc Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Sat, 25 Mar 2017 16:06:49 -0300 Subject: [PATCH] More sophisticated cyclic dependency detection --- cyclic.go | 28 ++++++++++++++++++++++++++++ cyclic_test.go | 36 ++++++++++++++++++++++++++++++++++++ task.go | 20 +++++--------------- 3 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 cyclic.go create mode 100644 cyclic_test.go diff --git a/cyclic.go b/cyclic.go new file mode 100644 index 00000000..afa52895 --- /dev/null +++ b/cyclic.go @@ -0,0 +1,28 @@ +package task + +func HasCyclicDep(m map[string]*Task) bool { + visits := make(map[string]struct{}, len(m)) + + var checkCyclicDep func(string, *Task) bool + checkCyclicDep = func(name string, t *Task) bool { + if _, ok := visits[name]; ok { + return false + } + visits[name] = struct{}{} + defer delete(visits, name) + + for _, d := range t.Deps { + if !checkCyclicDep(d, m[d]) { + return false + } + } + return true + } + + for k, v := range m { + if !checkCyclicDep(k, v) { + return true + } + } + return false +} diff --git a/cyclic_test.go b/cyclic_test.go new file mode 100644 index 00000000..08f8e087 --- /dev/null +++ b/cyclic_test.go @@ -0,0 +1,36 @@ +package task_test + +import ( + "testing" + + "github.com/go-task/task" +) + +func TestCyclicDepCheck(t *testing.T) { + isCyclic := map[string]*task.Task{ + "task-a": &task.Task{ + Deps: []string{"task-b"}, + }, + "task-b": &task.Task{ + Deps: []string{"task-a"}, + }, + } + + if !task.HasCyclicDep(isCyclic) { + t.Error("Task should be cyclic") + } + + isNotCyclic := map[string]*task.Task{ + "task-a": &task.Task{ + Deps: []string{"task-c"}, + }, + "task-b": &task.Task{ + Deps: []string{"task-c"}, + }, + "task-c": &task.Task{}, + } + + if task.HasCyclicDep(isNotCyclic) { + t.Error("Task should not be cyclic") + } +} diff --git a/task.go b/task.go index d21c47de..22d61ef7 100644 --- a/task.go +++ b/task.go @@ -21,9 +21,6 @@ var ( // Tasks constains the tasks parsed from Taskfile Tasks = make(map[string]*Task) - - runnedTasks = make(map[string]struct{}) - mu sync.Mutex ) // Task represents a task @@ -55,6 +52,10 @@ func Run() { log.Fatal(err) } + if HasCyclicDep(Tasks) { + log.Fatal("Cyclic dependency detected") + } + // check if given tasks exist for _, a := range args { if _, ok := Tasks[a]; !ok { @@ -74,18 +75,6 @@ func Run() { // RunTask runs a task by its name func RunTask(name string) error { - if strings.HasPrefix(name, "^") { - name = strings.TrimPrefix(name, "^") - } else { - mu.Lock() - if _, found := runnedTasks[name]; found { - mu.Unlock() - return &cyclicDepError{name} - } - runnedTasks[name] = struct{}{} - mu.Unlock() - } - t, ok := Tasks[name] if !ok { return &taskNotFoundError{name} @@ -180,6 +169,7 @@ func (t *Task) runCommand(i int) error { } if strings.HasPrefix(c, "^") { + c = strings.TrimPrefix(c, "^") if err = RunTask(c); err != nil { return err }