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  }