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  }