github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/core/state.go (about) 1 package core 2 3 import ( 4 "fmt" 5 "sort" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 "github.com/Workiva/go-datastructures/queue" 11 12 "cli" 13 ) 14 15 // startTime is as close as we can conveniently get to process start time. 16 var startTime = time.Now() 17 18 // A TaskType identifies the kind of task returned from NextTask() 19 type TaskType int 20 21 // The values here are fiddled to make Compare work easily. 22 // Essentially we prioritise on the higher bits only and use the lower ones to make 23 // the values unique. 24 // Subinclude tasks order first, but we're happy for all build / parse / test tasks 25 // to be treated equivalently. 26 const ( 27 Kill TaskType = 0x0000 | 0 28 SubincludeBuild = 0x1000 | 1 29 SubincludeParse = 0x2000 | 2 30 Build = 0x4000 | 3 31 Parse = 0x4000 | 4 32 Test = 0x4000 | 5 33 Stop = 0x8000 | 6 34 priorityMask = ^0x00FF 35 ) 36 37 type pendingTask struct { 38 Label BuildLabel // Label of target to parse 39 Dependor BuildLabel // The target that depended on it (only for parse tasks) 40 Type TaskType 41 } 42 43 func (t pendingTask) Compare(that queue.Item) int { 44 return int((t.Type & priorityMask) - (that.(pendingTask).Type & priorityMask)) 45 } 46 47 // A Parser is the interface to reading and interacting with BUILD files. 48 type Parser interface { 49 // ParseFile parses a single BUILD file into the given package. 50 ParseFile(state *BuildState, pkg *Package, filename string) error 51 // RunPreBuildFunction runs a pre-build function for a target. 52 RunPreBuildFunction(threadID int, state *BuildState, target *BuildTarget) error 53 // RunPostBuildFunction runs a post-build function for a target. 54 RunPostBuildFunction(threadID int, state *BuildState, target *BuildTarget, output string) error 55 // UndeferAnyParses undefers any pending parses that are waiting for this target to build. 56 UndeferAnyParses(state *BuildState, target *BuildTarget) 57 } 58 59 // A BuildState tracks the current state of the build & related data. 60 // As well as tracking the build graph and config, it also tracks the set of current 61 // tasks and maintains a queue of them, along with various related counters which are 62 // used to determine when we're finished. 63 // Tasks are internally tracked by priority, which is determined by their type. 64 type BuildState struct { 65 Graph *BuildGraph 66 // Stream of pending tasks 67 pendingTasks *queue.PriorityQueue 68 // Stream of results from the build 69 Results chan *BuildResult 70 // Stream of results pushed to remote clients. 71 // Will be nil until server is initialised. 72 RemoteResults chan *BuildResult 73 // Last results for each thread. These are used to catch up remote clients quickly. 74 LastResults []*BuildResult 75 // Timestamp that the build is considered to start at. 76 StartTime time.Time 77 // Various system statistics. Mostly used during remote communication. 78 Stats *SystemStats 79 // Configuration options 80 Config *Configuration 81 // Parser implementation. Other things can call this to perform various external parse tasks. 82 Parser Parser 83 // Worker pool for the parser 84 ParsePool Pool 85 // Hashes of variouts bits of the configuration, used for incrementality. 86 Hashes struct { 87 // Hash of the general config, not including specialised bits. 88 Config []byte 89 // Hash of the config relating to containerisation for tests. 90 Containerisation []byte 91 } 92 // Level of verbosity during the build 93 Verbosity int 94 // Cache to store / retrieve old build results. 95 Cache Cache 96 // Targets that we were originally requested to build 97 OriginalTargets []BuildLabel 98 // Arguments to tests. 99 TestArgs []string 100 // Labels of targets that we will include / exclude 101 Include, Exclude []string 102 // Actual targets to exclude from discovery 103 ExcludeTargets []BuildLabel 104 // True if we require rule hashes to be correctly verified (usually the case). 105 VerifyHashes bool 106 // Aggregated coverage for this run 107 Coverage TestCoverage 108 // True if tests should calculate coverage metrics 109 NeedCoverage bool 110 // True if we intend to build targets. False if we're just parsing 111 // (although some may be built if they're needed for parse). 112 NeedBuild bool 113 // True if we're running tests. False if we're only building or parsing. 114 NeedTests bool 115 // True if we want to calculate target hashes (ie. 'plz hash'). 116 NeedHashesOnly bool 117 // True if we only want to prepare build directories (ie. 'plz build --prepare') 118 PrepareOnly bool 119 // True if we're going to run a shell after builds are prepared. 120 PrepareShell bool 121 // Number of times to run each test target. 0 == once each, plus flakes if necessary. 122 NumTestRuns int 123 // True to clean working directories after successful builds. 124 CleanWorkdirs bool 125 // True if we're forcing a rebuild of the original targets. 126 ForceRebuild bool 127 // True to always show test output, even on success. 128 ShowTestOutput bool 129 // True to print all output of all tasks to stderr. 130 ShowAllOutput bool 131 // True to attach a debugger on test failure. 132 DebugTests bool 133 // Number of running workers 134 numWorkers int 135 // Experimental directories 136 experimentalLabels []BuildLabel 137 // Various items for tracking progress. 138 progress *stateProgress 139 } 140 141 // A stateProgress records various points of progress for a State. 142 // This is split out from above so we can share it between multiple instances. 143 type stateProgress struct { 144 // Used to count the number of currently active/pending targets 145 numActive int64 146 numPending int64 147 numDone int64 148 mutex sync.Mutex 149 // Used to track subinclude() calls that block until targets are built. 150 pendingTargets map[BuildLabel]chan struct{} 151 pendingTargetMutex sync.Mutex 152 // Used to track general package parsing requests. 153 pendingPackages map[string]chan struct{} 154 pendingPackageMutex sync.Mutex 155 // The set of known states 156 allStates []*BuildState 157 } 158 159 // SystemStats stores information about the system. 160 type SystemStats struct { 161 Memory struct { 162 Total, Used uint64 163 UsedPercent float64 164 } 165 // This is somewhat abbreviated to the "interesting" values and is aggregated 166 // across all CPUs for convenience of display. 167 // We're a bit casual about the exact definition of Used (it's the sum of some 168 // fields that seem relevant) and IOWait is not fully reliable. 169 CPU struct { 170 Used, IOWait float64 171 Count int 172 } 173 } 174 175 // AddActiveTarget increments the counter for a newly active build target. 176 func (state *BuildState) AddActiveTarget() { 177 atomic.AddInt64(&state.progress.numActive, 1) 178 } 179 180 // AddPendingParse adds a task for a pending parse of a build label. 181 func (state *BuildState) AddPendingParse(label, dependor BuildLabel, forSubinclude bool) { 182 atomic.AddInt64(&state.progress.numActive, 1) 183 atomic.AddInt64(&state.progress.numPending, 1) 184 if forSubinclude { 185 state.pendingTasks.Put(pendingTask{Label: label, Dependor: dependor, Type: SubincludeParse}) 186 } else { 187 state.pendingTasks.Put(pendingTask{Label: label, Dependor: dependor, Type: Parse}) 188 } 189 } 190 191 // AddPendingBuild adds a task for a pending build of a target. 192 func (state *BuildState) AddPendingBuild(label BuildLabel, forSubinclude bool) { 193 if forSubinclude { 194 state.addPending(label, SubincludeBuild) 195 } else { 196 state.addPending(label, Build) 197 } 198 } 199 200 // AddPendingTest adds a task for a pending test of a target. 201 func (state *BuildState) AddPendingTest(label BuildLabel) { 202 if state.NeedTests { 203 state.addPending(label, Test) 204 } 205 } 206 207 // NextTask receives the next task that should be processed according to the priority queues. 208 func (state *BuildState) NextTask() (BuildLabel, BuildLabel, TaskType) { 209 t, err := state.pendingTasks.Get(1) 210 if err != nil { 211 log.Fatalf("error receiving next task: %s", err) 212 } 213 task := t[0].(pendingTask) 214 return task.Label, task.Dependor, task.Type 215 } 216 217 func (state *BuildState) addPending(label BuildLabel, t TaskType) { 218 atomic.AddInt64(&state.progress.numPending, 1) 219 state.pendingTasks.Put(pendingTask{Label: label, Type: t}) 220 } 221 222 // TaskDone indicates that a single task is finished. Should be called after one is finished with 223 // a task returned from NextTask(), or from a call to ExtraTask(). 224 func (state *BuildState) TaskDone() { 225 atomic.AddInt64(&state.progress.numDone, 1) 226 if atomic.AddInt64(&state.progress.numPending, -1) <= 0 { 227 state.Stop(state.numWorkers) 228 } 229 } 230 231 // Stop adds n stop tasks to the list of pending tasks, which stops n workers after all their other tasks are done. 232 func (state *BuildState) Stop(n int) { 233 for i := 0; i < n; i++ { 234 state.pendingTasks.Put(pendingTask{Type: Stop}) 235 } 236 } 237 238 // Kill adds n kill tasks to the list of pending tasks, which stops n workers before they do anything else. 239 func (state *BuildState) Kill(n int) { 240 for i := 0; i < n; i++ { 241 state.pendingTasks.Put(pendingTask{Type: Kill}) 242 } 243 } 244 245 // KillAll kills all the workers. 246 func (state *BuildState) KillAll() { 247 state.Kill(state.numWorkers) 248 } 249 250 // IsOriginalTarget returns true if a target is an original target, ie. one specified on the command line. 251 func (state *BuildState) IsOriginalTarget(label BuildLabel) bool { 252 return state.isOriginalTarget(label, false) 253 } 254 255 // isOriginalTarget implementsIsOriginalTarget, optionally allowing disabling matching :all labels. 256 func (state *BuildState) isOriginalTarget(label BuildLabel, exact bool) bool { 257 for _, original := range state.OriginalTargets { 258 if original == label || (!exact && original.IsAllTargets() && original.PackageName == label.PackageName) { 259 return true 260 } 261 } 262 return false 263 } 264 265 // SetIncludeAndExclude sets the include / exclude labels. 266 // Handles build labels on Exclude so should be preferred over setting them directly. 267 func (state *BuildState) SetIncludeAndExclude(include, exclude []string) { 268 state.Include = include 269 for _, e := range exclude { 270 if LooksLikeABuildLabel(e) { 271 if label, err := parseMaybeRelativeBuildLabel(e, ""); err != nil { 272 log.Fatalf("%s", err) 273 } else { 274 state.ExcludeTargets = append(state.ExcludeTargets, label) 275 } 276 } else { 277 state.Exclude = append(state.Exclude, e) 278 } 279 } 280 } 281 282 // ShouldInclude returns true if the given target is included by the include/exclude flags. 283 func (state *BuildState) ShouldInclude(target *BuildTarget) bool { 284 for _, e := range state.ExcludeTargets { 285 if e.Includes(target.Label) { 286 return false 287 } 288 } 289 return target.ShouldInclude(state.Include, state.Exclude) 290 } 291 292 // AddOriginalTarget adds one of the original targets and enqueues it for parsing / building. 293 func (state *BuildState) AddOriginalTarget(label BuildLabel, addToList bool) { 294 // Check it's not excluded first. 295 for _, e := range state.ExcludeTargets { 296 if e.Includes(label) { 297 return 298 } 299 } 300 if addToList { 301 // The sets of original targets are duplicated between states for all architectures, 302 // we must add it to all of them to ensure everything sees the same set. 303 for _, s := range state.progress.allStates { 304 s.OriginalTargets = append(s.OriginalTargets, label) 305 } 306 } 307 state.AddPendingParse(label, OriginalTarget, false) 308 } 309 310 // LogBuildResult logs the result of a target either building or parsing. 311 func (state *BuildState) LogBuildResult(tid int, label BuildLabel, status BuildResultStatus, description string) { 312 if status == PackageParsed { 313 // We may have parse tasks waiting for this package to exist, check for them. 314 state.progress.pendingPackageMutex.Lock() 315 if ch, present := state.progress.pendingPackages[label.PackageName]; present { 316 close(ch) // This signals to anyone waiting that it's done. 317 } 318 state.progress.pendingPackageMutex.Unlock() 319 return // We don't notify anything else on these. 320 } 321 state.logResult(&BuildResult{ 322 ThreadID: tid, 323 Time: time.Now(), 324 Label: label, 325 Status: status, 326 Err: nil, 327 Description: description, 328 }) 329 if status == TargetBuilt || status == TargetCached { 330 // We may have parse tasks waiting for this guy to build, check for them. 331 state.progress.pendingTargetMutex.Lock() 332 if ch, present := state.progress.pendingTargets[label]; present { 333 close(ch) // This signals to anyone waiting that it's done. 334 } 335 state.progress.pendingTargetMutex.Unlock() 336 } 337 } 338 339 // LogTestResult logs the result of a target once its tests have completed. 340 func (state *BuildState) LogTestResult(tid int, label BuildLabel, status BuildResultStatus, results *TestResults, coverage *TestCoverage, err error, format string, args ...interface{}) { 341 state.logResult(&BuildResult{ 342 ThreadID: tid, 343 Time: time.Now(), 344 Label: label, 345 Status: status, 346 Err: err, 347 Description: fmt.Sprintf(format, args...), 348 Tests: *results, 349 }) 350 state.progress.mutex.Lock() 351 defer state.progress.mutex.Unlock() 352 state.Coverage.Aggregate(coverage) 353 } 354 355 // LogBuildError logs a failure for a target to parse, build or test. 356 func (state *BuildState) LogBuildError(tid int, label BuildLabel, status BuildResultStatus, err error, format string, args ...interface{}) { 357 state.logResult(&BuildResult{ 358 ThreadID: tid, 359 Time: time.Now(), 360 Label: label, 361 Status: status, 362 Err: err, 363 Description: fmt.Sprintf(format, args...), 364 }) 365 } 366 367 func (state *BuildState) logResult(result *BuildResult) { 368 state.Results <- result 369 if state.RemoteResults != nil { 370 state.RemoteResults <- result 371 state.LastResults[result.ThreadID] = result 372 } 373 } 374 375 // NumActive returns the number of currently active tasks (i.e. those that are 376 // scheduled to be built at some point, or have been built already). 377 func (state *BuildState) NumActive() int { 378 return int(atomic.LoadInt64(&state.progress.numActive)) 379 } 380 381 // NumDone returns the number of tasks that have been completed so far. 382 func (state *BuildState) NumDone() int { 383 return int(atomic.LoadInt64(&state.progress.numDone)) 384 } 385 386 // SetTaskNumbers allows a caller to set the number of active and done tasks. 387 // This may drastically confuse matters if used incorrectly. 388 func (state *BuildState) SetTaskNumbers(active, done int64) { 389 atomic.StoreInt64(&state.progress.numActive, active) 390 atomic.StoreInt64(&state.progress.numDone, done) 391 } 392 393 // ExpandOriginalTargets expands any pseudo-targets (ie. :all, ... has already been resolved to a bunch :all targets) 394 // from the set of original targets. 395 func (state *BuildState) ExpandOriginalTargets() BuildLabels { 396 ret := BuildLabels{} 397 for _, label := range state.OriginalTargets { 398 if label.IsAllTargets() || label.IsAllSubpackages() { 399 ret = append(ret, state.expandOriginalPseudoTarget(label)...) 400 } else { 401 ret = append(ret, label) 402 } 403 } 404 return ret 405 } 406 407 // expandOriginalPseudoTarget expands one original pseudo-target (i.e. :all or /...) and sorts it 408 func (state *BuildState) expandOriginalPseudoTarget(label BuildLabel) BuildLabels { 409 ret := BuildLabels{} 410 addPackage := func(pkg *Package) { 411 for _, target := range pkg.AllTargets() { 412 if target.ShouldInclude(state.Include, state.Exclude) && (!state.NeedTests || target.IsTest) { 413 ret = append(ret, target.Label) 414 } 415 } 416 } 417 if label.IsAllTargets() { 418 addPackage(state.Graph.PackageOrDie(label.PackageName)) 419 } else { 420 for name, pkg := range state.Graph.PackageMap() { 421 if label.Includes(BuildLabel{PackageName: name}) { 422 addPackage(pkg) 423 } 424 } 425 } 426 sort.Sort(ret) 427 return ret 428 } 429 430 // ExpandVisibleOriginalTargets expands any pseudo-targets (ie. :all, ... has already been resolved to a bunch :all targets) 431 // from the set of original targets. Hidden targets are not included. 432 func (state *BuildState) ExpandVisibleOriginalTargets() BuildLabels { 433 ret := BuildLabels{} 434 for _, target := range state.ExpandOriginalTargets() { 435 if !target.HasParent() || state.isOriginalTarget(target, true) { 436 ret = append(ret, target) 437 } 438 } 439 return ret 440 } 441 442 // WaitForPackage either returns the given package which is already parsed and available, 443 // or returns nil if nothing's parsed it already, in which case everything else calling this 444 // will wait for the caller to parse it themselves. 445 func (state *BuildState) WaitForPackage(packageName string) *Package { 446 if p := state.Graph.Package(packageName); p != nil { 447 return p 448 } 449 state.progress.pendingPackageMutex.Lock() 450 if ch, present := state.progress.pendingPackages[packageName]; present { 451 state.progress.pendingPackageMutex.Unlock() 452 state.ParsePool.AddWorker() 453 <-ch 454 state.ParsePool.StopWorker() 455 return state.Graph.Package(packageName) 456 } 457 // Nothing's registered this so we do it ourselves. 458 state.progress.pendingPackages[packageName] = make(chan struct{}) 459 state.progress.pendingPackageMutex.Unlock() 460 return state.Graph.Package(packageName) // Important to check again; it's possible to race against this whole lot. 461 } 462 463 // WaitForBuiltTarget blocks until the given label is available as a build target and has been successfully built. 464 func (state *BuildState) WaitForBuiltTarget(l BuildLabel, dependingPackage string) *BuildTarget { 465 if t := state.Graph.Target(l); t != nil { 466 if state := t.State(); state >= Built && state != Failed { 467 return t 468 } 469 } 470 // okay, we need to register and wait for this guy. 471 state.progress.pendingTargetMutex.Lock() 472 if ch, present := state.progress.pendingTargets[l]; present { 473 // Something's already registered for this, get on the train 474 state.progress.pendingTargetMutex.Unlock() 475 log.Debug("Pausing parse of //%s to wait for %s", dependingPackage, l) 476 state.ParsePool.AddWorker() 477 <-ch 478 state.ParsePool.StopWorker() 479 log.Debug("Resuming parse of //%s now %s is ready", dependingPackage, l) 480 return state.Graph.Target(l) 481 } 482 // Nothing's registered this, set it up. 483 state.progress.pendingTargets[l] = make(chan struct{}) 484 state.progress.pendingTargetMutex.Unlock() 485 state.AddPendingParse(l, BuildLabel{PackageName: dependingPackage, Name: "all"}, true) 486 // Do this all over; the re-checking that happens here is actually fairly important to resolve 487 // a potential race condition if the target was built between us checking earlier and registering 488 // the channel just now. 489 return state.WaitForBuiltTarget(l, dependingPackage) 490 } 491 492 // ForTarget returns the state associated with a given target. 493 // This differs if the target is in a subrepo for a different architecture. 494 func (state *BuildState) ForTarget(target *BuildTarget) *BuildState { 495 if target.Subrepo != nil && target.Subrepo.State != nil { 496 return target.Subrepo.State 497 } 498 return state 499 } 500 501 // ForArch creates a copy of this BuildState for a different architecture. 502 func (state *BuildState) ForArch(arch cli.Arch) *BuildState { 503 state.progress.mutex.Lock() 504 defer state.progress.mutex.Unlock() 505 // Check if we've got this one already. 506 // N.B. This implicitly handles the case of the host architecture 507 for _, s := range state.progress.allStates { 508 if s.Config.Build.Arch == arch { 509 return s 510 } 511 } 512 // Duplicate & alter configuration 513 c := &Configuration{} 514 *c = *state.Config 515 c.Build.Arch = arch 516 c.buildEnvStored = &storedBuildEnv{} 517 // Load the architecture-specific config file. 518 // This is slightly wrong in that other things (e.g. user-specified command line overrides) should 519 // in fact take priority over this, but that's a lot more fiddly to get right. 520 if err := readConfigFile(c, ".plzconfig_"+arch.String()); err != nil { 521 log.Fatalf("Failed to read config file for %s: %s", arch, err) 522 } 523 s := &BuildState{} 524 *s = *state 525 s.Config = c 526 state.progress.allStates = append(state.progress.allStates, s) 527 return s 528 } 529 530 // NewBuildState constructs and returns a new BuildState. 531 // Everyone should use this rather than attempting to construct it themselves; 532 // callers can't initialise all the required private fields. 533 func NewBuildState(numThreads int, cache Cache, verbosity int, config *Configuration) *BuildState { 534 state := &BuildState{ 535 Graph: NewGraph(), 536 pendingTasks: queue.NewPriorityQueue(10000, true), // big hint, why not 537 Results: make(chan *BuildResult, numThreads*100), 538 LastResults: make([]*BuildResult, numThreads), 539 StartTime: startTime, 540 Config: config, 541 Verbosity: verbosity, 542 Cache: cache, 543 ParsePool: NewPool(numThreads), 544 VerifyHashes: true, 545 NeedBuild: true, 546 Coverage: TestCoverage{Files: map[string][]LineCoverage{}}, 547 numWorkers: numThreads, 548 Stats: &SystemStats{}, 549 progress: &stateProgress{ 550 numActive: 1, // One for the initial target adding on the main thread. 551 numPending: 1, 552 pendingTargets: map[BuildLabel]chan struct{}{}, 553 pendingPackages: map[string]chan struct{}{}, 554 }, 555 } 556 state.progress.allStates = []*BuildState{state} 557 state.Hashes.Config = config.Hash() 558 state.Hashes.Containerisation = config.ContainerisationHash() 559 config.Please.NumThreads = numThreads 560 for _, exp := range config.Parse.ExperimentalDir { 561 state.experimentalLabels = append(state.experimentalLabels, BuildLabel{PackageName: exp, Name: "..."}) 562 } 563 return state 564 } 565 566 // NewDefaultBuildState creates a BuildState for the default configuration. 567 // This is useful for tests etc that don't need to customise anything about it. 568 func NewDefaultBuildState() *BuildState { 569 return NewBuildState(1, nil, 4, DefaultConfiguration()) 570 } 571 572 // A BuildResult represents a single event in the build process, i.e. a target starting or finishing 573 // building, or reaching some milestone within those steps. 574 type BuildResult struct { 575 // Thread id (or goroutine id, really) that generated this result. 576 ThreadID int 577 // Timestamp of this event 578 Time time.Time 579 // Target which has just changed 580 Label BuildLabel 581 // Its current status 582 Status BuildResultStatus 583 // Error, only populated for failure statuses 584 Err error 585 // Description of what's going on right now. 586 Description string 587 // Test results 588 Tests TestResults 589 } 590 591 // A BuildResultStatus represents the status of a target when we log a build result. 592 type BuildResultStatus int 593 594 // The collection of expected build result statuses. 595 const ( 596 PackageParsing BuildResultStatus = iota 597 PackageParsed 598 ParseFailed 599 TargetBuilding 600 TargetBuildStopped 601 TargetBuilt 602 TargetCached 603 TargetBuildFailed 604 TargetTesting 605 TargetTested 606 TargetTestFailed 607 ) 608 609 // Category returns the broad area that this event represents in the tasks we perform for a target. 610 func (s BuildResultStatus) Category() string { 611 switch s { 612 case PackageParsing, PackageParsed, ParseFailed: 613 return "Parse" 614 case TargetBuilding, TargetBuildStopped, TargetBuilt, TargetBuildFailed: 615 return "Build" 616 case TargetTesting, TargetTested, TargetTestFailed: 617 return "Test" 618 default: 619 return "Other" 620 } 621 }