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 }