github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/bob/playbook/build.go (about) 1 package playbook 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/benchkram/bob/bobtask/hash" 10 "github.com/benchkram/bob/pkg/boblog" 11 "github.com/benchkram/bob/pkg/usererror" 12 ) 13 14 // Build the playbook starting at root. 15 func (p *Playbook) Build(ctx context.Context) (err error) { 16 17 if p.start.IsZero() { 18 p.start = time.Now() 19 } 20 21 // Setup worker pool and queue. 22 workers := p.maxParallel 23 boblog.Log.Info(fmt.Sprintf("Using %d workers", workers)) 24 25 p.pickTaskColors() 26 27 wm := p.startWorkers(ctx, workers) 28 29 // listen for idle workers 30 go func() { 31 // A buffer for workers which have 32 // no workload assigned. 33 workerBuffer := []int{} 34 35 for workerID := range wm.idleChan { 36 37 task, err := p.Next() 38 if err != nil { 39 40 if errors.Is(err, ErrDone) { 41 wm.stopWorkers() 42 // exit 43 break 44 } 45 46 wm.addError(fmt.Errorf("worker-availability-queue: unexpected error comming from Next(): %w", err)) 47 wm.stopWorkers() 48 break 49 } 50 51 // Push workload to the worker or store the worker for later. 52 if task != nil { 53 // Send workload to worker 54 wm.workloadQueues[workerID] <- task 55 56 // There might be more workload left. 57 // Reqeuing a worker from the buffer. 58 if len(workerBuffer) > 0 { 59 wID := workerBuffer[len(workerBuffer)-1] 60 workerBuffer = workerBuffer[:len(workerBuffer)-1] 61 62 // requeue a buffered worker 63 wm.idleChan <- wID 64 } 65 } else { 66 67 // No task yet ready to be worked on but the playbook is not done yet. 68 // Therfore the worker is stored in a buffer and is requeued on 69 // the next change to the playbook. 70 workerBuffer = append(workerBuffer, workerID) 71 } 72 } 73 74 // to assure even idling workers will be shutdown. 75 wm.closeWorkloadQueues() 76 }() 77 78 wm.workerWG.Wait() 79 80 // iterate through tasks and logs 81 // skipped input files. 82 var skippedInputs int 83 for _, t := range wm.processed { 84 skippedInputs = logSkippedInputs( 85 skippedInputs, 86 t.ColoredName(), 87 t.LogSkippedInput(), 88 ) 89 } 90 91 p.summary(wm.processed) 92 93 if len(wm.errors) > 0 { 94 // Pass only the very first processing error. 95 return wm.errors[0] 96 } 97 98 // sync any newly generated artifacts with the remote store 99 if p.enablePush { 100 for taskName, artifact := range p.inputHashes(true) { 101 err = p.pushArtifact(ctx, artifact, taskName) 102 if err != nil { 103 return usererror.Wrap(err) 104 } 105 } 106 } 107 108 return nil 109 } 110 111 // inputHashes returns and array of input hashes of the playbook, 112 // optionally filters tasks without targets. 113 func (p *Playbook) inputHashes(filterTarget bool) map[string]hash.In { 114 artifactIds := make(map[string]hash.In) 115 116 for _, t := range p.TasksOptimized { 117 if filterTarget && !t.TargetExists() { 118 continue 119 } 120 121 h, err := t.HashIn() 122 if err != nil { 123 continue 124 } 125 126 artifactIds[t.Name()] = h 127 } 128 return artifactIds 129 }