github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/bob/playbook/next.go (about) 1 package playbook 2 3 import ( 4 "fmt" 5 ) 6 7 func (p *Playbook) Next() (_ *Status, err error) { 8 if p.done { 9 return nil, ErrDone 10 } 11 12 // translate dependen tasks name to id's and store them in the task. 13 p.oncePrepareOptimizedAccess.Do(func() { 14 _ = p.Tasks.walk(p.root, func(taskname string, task *Status, _ error) error { 15 for _, dependentTaskName := range task.DependsOn { 16 t := p.Tasks[dependentTaskName] 17 task.DependsOnIDs = append(task.DependsOnIDs, t.TaskID) 18 } 19 return nil 20 }) 21 }) 22 23 // Walk the task chain and determine the next build task. Send it to the task channel. 24 // Returns `taskQueued` when a task has been send to the taskChannel. 25 // Returns `taskFailed` when a task has failed. 26 // Once it returns `nil` the playbook is done with it's work. 27 var taskQueued = fmt.Errorf("task queued") 28 var taskFailed = fmt.Errorf("task failed") 29 30 type result struct { 31 t *Status 32 state string // queued, playbook-done, failed 33 } 34 c := make(chan result, 1) 35 36 // Starting the walk function in a goroutine to be able 37 // to return a ready to be processed task immeadiately 38 // from Next(). 39 go func(output chan result) { 40 didAllTaskComplete := true 41 _ = p.TasksOptimized.walkBottomFirst(p.rootID, func(taskID int, task *Status, err error) error { 42 if err != nil { 43 return err 44 } 45 46 switch task.State() { 47 case StatePending: 48 didAllTaskComplete = false 49 // Check if all dependent tasks are completed 50 for _, dependentTaskID := range task.Task.DependsOnIDs { 51 t := p.TasksOptimized[dependentTaskID] 52 53 state := t.State() 54 if state != StateCompleted && state != StateNoRebuildRequired { 55 // A dependent task is not completed. 56 // So this task is not yet ready to run. 57 return nil 58 } 59 } 60 case StateFailed: 61 output <- result{t: task, state: "failed"} 62 return taskFailed 63 case StateCanceled: 64 output <- result{t: task, state: "canceled"} 65 return nil 66 case StateNoRebuildRequired: 67 return nil 68 case StateCompleted: 69 return nil 70 case StateRunning: 71 didAllTaskComplete = false 72 return nil 73 case StateQueued: 74 didAllTaskComplete = false 75 return nil 76 default: 77 } 78 79 // TODO: for async assure to handle send to a closed channel. 80 _ = p.setTaskState(task.TaskID, StateQueued, nil) 81 output <- result{t: task, state: "queued"} 82 return taskQueued 83 }) 84 85 if didAllTaskComplete { 86 output <- result{t: nil, state: "playbook-done"} 87 } 88 close(output) 89 }(c) 90 91 for r := range c { 92 switch r.state { 93 case "queued": 94 return r.t, nil 95 case "failed": 96 fallthrough 97 case "canceled": 98 fallthrough 99 case "playbook-done": 100 p.done = true 101 return nil, ErrDone 102 } 103 } 104 105 return nil, nil 106 107 }