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

     1  package playbook
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/benchkram/bob/bobtask"
     8  	"github.com/benchkram/bob/bobtask/target"
     9  	"github.com/benchkram/bob/pkg/boblog"
    10  	"github.com/benchkram/errz"
    11  )
    12  
    13  // RebuildInfo contains information about a task rebuild: if it's required and the cause for it
    14  type RebuildInfo struct {
    15  	// IsRequired tells if the task requires rebuild again
    16  	IsRequired bool
    17  	// Cause tells why the rebuild is required
    18  	Cause RebuildCause
    19  	// VerifyResult is the result of target filesystem verification
    20  	VerifyResult target.VerifyResult
    21  }
    22  
    23  // TaskNeedsRebuild check if a tasks need a rebuild by looking at its hash value
    24  // and its child tasks.
    25  func (p *Playbook) TaskNeedsRebuild(taskID int) (rebuildInfo RebuildInfo, err error) {
    26  	task := p.TasksOptimized[taskID]
    27  	coloredName := task.ColoredName()
    28  
    29  	// Rebuild strategy set to `always`
    30  	if task.Rebuild() == bobtask.RebuildAlways {
    31  		boblog.Log.V(3).Info(fmt.Sprintf("%-*s\tNEEDS REBUILD\t(rebuild set to always)", p.namePad, coloredName))
    32  
    33  		// For a forced rebuild all task targets are marked as invalid
    34  		invalidFiles := make(map[string][]target.Reason)
    35  		if task.TargetExists() {
    36  			t, err := task.Target()
    37  			errz.Fatal(err)
    38  			invalidFiles = t.AsInvalidFiles(target.ReasonForcedByNoCache)
    39  		}
    40  
    41  		return RebuildInfo{
    42  			IsRequired: true,
    43  			Cause:      TaskForcedRebuild,
    44  			VerifyResult: target.VerifyResult{
    45  				TargetIsValid: len(invalidFiles) == 0,
    46  				InvalidFiles:  invalidFiles,
    47  			},
    48  		}, nil
    49  	}
    50  
    51  	// Did a child task change?
    52  	if p.didChildTaskChange(task.Name()) {
    53  		// Andrei Boar added this check.
    54  		// I'm unsure about the reason for it.
    55  		verifyResult := target.NewVerifyResult()
    56  		if task.TargetExists() {
    57  			tt, err := task.Target()
    58  			errz.Fatal(err)
    59  			verifyResult = tt.VerifyShallow()
    60  			if !verifyResult.TargetIsValid {
    61  				return RebuildInfo{IsRequired: true, Cause: TargetInvalid, VerifyResult: verifyResult}, nil
    62  			}
    63  		}
    64  
    65  		boblog.Log.V(3).Info(fmt.Sprintf("%-*s\tNEEDS REBUILD\t(dependecy changed)", p.namePad, coloredName))
    66  		return RebuildInfo{IsRequired: true, Cause: DependencyChanged}, nil
    67  	}
    68  
    69  	// Did the current task change?
    70  	// Indicating a cache miss in buildinfostore.
    71  	rebuildRequired, err := task.DidTaskChange()
    72  	errz.Fatal(err)
    73  	if rebuildRequired {
    74  		boblog.Log.V(3).Info(fmt.Sprintf("%-*s\tNEEDS REBUILD\t(input changed)", p.namePad, coloredName))
    75  
    76  		invalidFiles := make(map[string][]target.Reason)
    77  		if task.TargetExists() {
    78  			t, err := task.Target()
    79  			errz.Fatal(err)
    80  			invalidFiles = t.AsInvalidFiles(target.ReasonMissing)
    81  		}
    82  
    83  		return RebuildInfo{IsRequired: true, Cause: InputNotFoundInBuildInfo, VerifyResult: target.VerifyResult{
    84  			TargetIsValid: len(invalidFiles) == 0,
    85  			InvalidFiles:  invalidFiles,
    86  		}}, nil
    87  	}
    88  
    89  	// Check rebuild due to invalidated targets
    90  	target, err := task.Target()
    91  	if err != nil {
    92  		return RebuildInfo{IsRequired: true, Cause: ""}, nil
    93  	}
    94  	if target != nil {
    95  		verifyResult := target.VerifyShallow()
    96  		if !verifyResult.TargetIsValid {
    97  			boblog.Log.V(3).Info(fmt.Sprintf("%-*s\tNEEDS REBUILD\t(invalid targets)", p.namePad, coloredName))
    98  			return RebuildInfo{IsRequired: true, Cause: TargetInvalid, VerifyResult: verifyResult}, nil
    99  		}
   100  
   101  		// Check if target exists in local store
   102  		hashIn, err := task.HashIn()
   103  		errz.Fatal(err)
   104  		if !task.ArtifactExists(hashIn) {
   105  			boblog.Log.V(3).Info(fmt.Sprintf("%-*s\tNEEDS REBUILD\t(target does not exist in localstore)", p.namePad, coloredName))
   106  			return RebuildInfo{IsRequired: true, Cause: TargetNotInLocalStore, VerifyResult: verifyResult}, nil
   107  		}
   108  	}
   109  
   110  	return RebuildInfo{IsRequired: false}, err
   111  }
   112  
   113  // didChildTaskChange iterates through all child tasks to verify if any of them changed.
   114  func (p *Playbook) didChildTaskChange(taskName string) bool {
   115  	var Done = fmt.Errorf("done")
   116  	err := p.Tasks.walk(taskName, func(tn string, t *Status, err error) error {
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		// Ignore the task itself
   122  		if taskName == tn {
   123  			return nil
   124  		}
   125  
   126  		// Check if child task changed
   127  		if t.State() != StateNoRebuildRequired {
   128  			return Done
   129  		}
   130  
   131  		return nil
   132  	})
   133  
   134  	return errors.Is(err, Done)
   135  }