github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/bob/playbook/build_internal.go (about)

     1  package playbook
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  
     9  	"github.com/benchkram/bob/bobtask"
    10  	"github.com/benchkram/bob/bobtask/processed"
    11  	"github.com/benchkram/bob/pkg/boblog"
    12  	"github.com/benchkram/errz"
    13  )
    14  
    15  // build a single task and update the playbook state after completion.
    16  func (p *Playbook) build(ctx context.Context, task *bobtask.Task) (pt *processed.Task, err error) {
    17  	defer errz.Recover(&err)
    18  
    19  	// if `pt` is `nil` errz.Fatal()
    20  	// returns a nil task which could lead
    21  	// to memory leaks down the line.
    22  	pt = &processed.Task{Task: task}
    23  
    24  	// A task is flagged successful before
    25  	var taskSuccessFul bool
    26  	var taskErr error
    27  	defer func() {
    28  		if !taskSuccessFul {
    29  			errr := p.TaskFailed(task.TaskID, taskErr)
    30  			if errr != nil {
    31  				boblog.Log.Error(errr, "Setting the task state to failed, failed.")
    32  			}
    33  		}
    34  	}()
    35  
    36  	coloredName := task.ColoredName()
    37  
    38  	done := make(chan struct{})
    39  	defer close(done)
    40  
    41  	go func() {
    42  		select {
    43  		case <-done:
    44  		case <-ctx.Done():
    45  			if errors.Is(ctx.Err(), context.Canceled) {
    46  				boblog.Log.V(1).Info(fmt.Sprintf("%-*s\t%s", p.namePad, coloredName, StateCanceled))
    47  				_ = p.TaskCanceled(task.TaskID)
    48  			}
    49  		}
    50  	}()
    51  
    52  	rebuild, err := p.TaskNeedsRebuild(task.TaskID)
    53  	errz.Fatal(err)
    54  	boblog.Log.V(2).Info(fmt.Sprintf("TaskNeedsRebuild [rebuildRequired: %t] [cause:%s]", rebuild.IsRequired, rebuild.Cause))
    55  
    56  	// Task might need a rebuild due to an input change.
    57  	// Could still be possible to load the targets from the artifact store.
    58  	// If a task needs a rebuild due to a dependency change => rebuild.
    59  	if rebuild.IsRequired {
    60  		switch rebuild.Cause {
    61  		case InputNotFoundInBuildInfo:
    62  			hashIn, err := task.HashIn()
    63  			errz.Fatal(err)
    64  
    65  			// pull artifact if it exists on the remote. if exists locally will use that one
    66  			err = p.pullArtifact(ctx, hashIn, task, false)
    67  			errz.Fatal(err)
    68  
    69  			success, err := task.ArtifactExtract(hashIn, rebuild.VerifyResult.InvalidFiles)
    70  			if err != nil {
    71  				// if local artifact is corrupted due to incomplete previous download, try a fresh download
    72  				if errors.Is(err, io.ErrUnexpectedEOF) {
    73  					err = p.pullArtifact(ctx, hashIn, task, true)
    74  					errz.Fatal(err)
    75  					success, err = task.ArtifactExtract(hashIn, rebuild.VerifyResult.InvalidFiles)
    76  				}
    77  			}
    78  
    79  			errz.Fatal(err)
    80  			if success {
    81  				rebuild.IsRequired = false
    82  
    83  				// In case an artifact was synced from the remote store no buildinfo exists...
    84  				// To avoid subsequent artifact extraction the Buildinfo is created after
    85  				// extracting the artifact.
    86  				buildInfo, err := p.computeBuildinfo(task.Name())
    87  				errz.Fatal(err)
    88  				err = p.storeBuildInfo(task.Name(), buildInfo)
    89  				errz.Fatal(err)
    90  			}
    91  		case TargetInvalid:
    92  			boblog.Log.V(2).Info(fmt.Sprintf("%-*s\t%s, extracting artifact", p.namePad, coloredName, rebuild.Cause))
    93  			hashIn, err := task.HashIn()
    94  			errz.Fatal(err)
    95  			success, err := task.ArtifactExtract(hashIn, rebuild.VerifyResult.InvalidFiles)
    96  			errz.Fatal(err)
    97  			if success {
    98  				rebuild.IsRequired = false
    99  			}
   100  		case TargetNotInLocalStore:
   101  		case TaskForcedRebuild:
   102  		case DependencyChanged:
   103  		default:
   104  		}
   105  	}
   106  
   107  	if !rebuild.IsRequired {
   108  		status := StateNoRebuildRequired
   109  		boblog.Log.V(2).Info(fmt.Sprintf("%-*s\t%s", p.namePad, coloredName, status.Short()))
   110  		taskSuccessFul = true
   111  		return pt, p.TaskNoRebuildRequired(task.TaskID)
   112  	}
   113  
   114  	err = task.CleanTargetsWithReason(rebuild.VerifyResult.InvalidFiles)
   115  	errz.Fatal(err)
   116  
   117  	err = task.Run(ctx, p.namePad)
   118  	if err != nil {
   119  		taskSuccessFul = false
   120  		taskErr = err
   121  	}
   122  	errz.Fatal(err)
   123  
   124  	// FIXME: Is this placed correctly?
   125  	// Could also be done after the task completion is
   126  	// done (artifact validation & packaging).
   127  	//
   128  	// What does it do? It prevents the task from beeing
   129  	// flagged as failed in a defered function call.
   130  	taskSuccessFul = true
   131  
   132  	err = p.TaskCompleted(task.TaskID)
   133  	if errors.Is(err, ErrFailed) {
   134  		return pt, err
   135  	}
   136  	errz.Log(err)
   137  	errz.Fatal(err)
   138  
   139  	taskStatus, err := p.TaskStatus(task.Name())
   140  	errz.Fatal(err)
   141  
   142  	state := taskStatus.State()
   143  	boblog.Log.V(1).Info(fmt.Sprintf("%-*s\t%s", p.namePad, coloredName, "..."+state.Short()))
   144  
   145  	return pt, nil
   146  }