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  }