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 }