github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/core/build_target.go (about) 1 package core 2 3 import ( 4 "fmt" 5 "os" 6 "path" 7 "path/filepath" 8 "reflect" 9 "sort" 10 "strings" 11 "sync/atomic" 12 "time" 13 ) 14 15 // OutDir is the root output directory for everything. 16 const OutDir string = "plz-out" 17 18 // TmpDir is the root of the temporary directory for building targets & running tests. 19 const TmpDir string = "plz-out/tmp" 20 21 // GenDir is the output directory for non-binary targets. 22 const GenDir string = "plz-out/gen" 23 24 // BinDir is the output directory for binary targets. 25 const BinDir string = "plz-out/bin" 26 27 // DefaultBuildingDescription is the default description for targets when they're building. 28 const DefaultBuildingDescription = "Building..." 29 30 // Suffixes for temporary directories 31 const buildDirSuffix = "._build" 32 const testDirSuffix = "._test" 33 34 // A BuildTarget is a representation of a build target and all information about it; 35 // its name, dependencies, build commands, etc. 36 type BuildTarget struct { 37 // N.B. The tags on these fields are used by query print to help it print them. 38 39 // Identifier of this build target 40 Label BuildLabel `name:"name"` 41 // If this target is in a subrepo, this will be the one it's in. 42 Subrepo *Subrepo `print:"false"` 43 // Dependencies of this target. 44 // Maps the original declaration to whatever dependencies actually got attached, 45 // which may be more than one in some cases. Also contains info about exporting etc. 46 dependencies []depInfo `name:"deps"` 47 // List of build target patterns that can use this build target. 48 Visibility []BuildLabel 49 // Source files of this rule. Can refer to build rules themselves. 50 Sources []BuildInput `name:"srcs"` 51 // Named source files of this rule; as above but identified by name. 52 NamedSources map[string][]BuildInput `name:"srcs"` 53 // Data files of this rule. Similar to sources but used at runtime, typically by tests. 54 Data []BuildInput 55 // Output files of this rule. All are paths relative to this package. 56 outputs []string `name:"outs"` 57 // Named output subsets of this rule. All are paths relative to this package but can be 58 // captured separately; for example something producing C code might separate its outputs 59 // into sources and headers. 60 namedOutputs map[string][]string `name:"outs"` 61 // Optional output files of this rule. Same as outs but aren't required to be produced always. 62 // Can be glob patterns. 63 OptionalOutputs []string `name:"optional_outs"` 64 // Optional labels applied to this rule. Used for including/excluding rules. 65 Labels []string 66 // Shell command to run. 67 Command string `name:"cmd"` 68 // Per-configuration shell commands to run. 69 Commands map[string]string `name:"cmd"` 70 // Shell command to run for test targets. 71 TestCommand string `name:"test_cmd"` 72 // Per-configuration test commands to run. 73 TestCommands map[string]string `name:"test_cmd"` 74 // Represents the state of this build target (see below) 75 state int32 `print:"false"` 76 // True if this target is a binary (ie. runnable, will appear in plz-out/bin) 77 IsBinary bool `name:"binary"` 78 // True if this target is a test 79 IsTest bool `name:"test"` 80 // Indicates that the target can only be depended on by tests or other rules with this set. 81 // Used to restrict non-deployable code and also affects coverage detection. 82 TestOnly bool `name:"test_only"` 83 // True if we're going to containerise the test. 84 Containerise bool `name:"container"` 85 // True if the build action is sandboxed. 86 Sandbox bool 87 // True if the test action is sandboxed. 88 TestSandbox bool `name:"test_sandbox"` 89 // True if the target is a test and has no output file. 90 // Default is false, meaning all tests must produce test.results as output. 91 NoTestOutput bool `name:"no_test_output"` 92 // True if this target needs access to its transitive dependencies to build. 93 // This would be false for most 'normal' genrules but true for eg. compiler steps 94 // that need to build in everything. 95 NeedsTransitiveDependencies bool `name:"needs_transitive_deps"` 96 // True if this target blocks recursive exploring for transitive dependencies. 97 // This is typically false for _library rules which aren't complete, and true 98 // for _binary rules which normally are, and genrules where you don't care about 99 // the inputs, only whatever they were turned into. 100 OutputIsComplete bool `name:"output_is_complete"` 101 // If true, the rule is given an env var at build time that contains the hash of its 102 // transitive dependencies, which can be used to identify the output in a predictable way. 103 Stamp bool 104 // Marks the target as a filegroup. 105 IsFilegroup bool `print:"false"` 106 // Marks the target as a hash_filegroup. 107 IsHashFilegroup bool `print:"false"` 108 // Marks the target as a remote_file. 109 IsRemoteFile bool `print:"false"` 110 // Marks that the target was added in a post-build function. 111 AddedPostBuild bool `print:"false"` 112 // If true, the interactive progress display will try to infer the target's progress 113 // via some heuristics on its output. 114 ShowProgress bool `name:"progress"` 115 // If ShowProgress is true, this is used to store the current progress of the target. 116 Progress float32 `print:"false"` 117 // Containerisation settings that override the defaults. 118 ContainerSettings *TargetContainerSettings `name:"container"` 119 // Results of test, if it is one 120 Results TestResults `print:"false"` 121 // Description displayed while the command is building. 122 // Default is just "Building" but it can be customised. 123 BuildingDescription string `name:"building_description"` 124 // Acceptable hashes of the outputs of this rule. If the output doesn't match any of these 125 // it's an error at build time. Can be used to validate third-party deps. 126 Hashes []string 127 // Licences that this target is subject to. 128 Licences []string 129 // Any secrets that this rule requires. 130 // Secrets are similar to sources but are always absolute system paths and affect the hash 131 // differently; they are not used to determine the hash for retrieving a file from cache, but 132 // if changed locally will still force a rebuild. They're not copied into the source directory 133 // (or indeed anywhere by plz). 134 Secrets []string 135 // BUILD language functions to call before / after target is built. Allows deferred manipulation of the build graph. 136 PreBuildFunction PreBuildFunction `name:"pre_build"` 137 PostBuildFunction PostBuildFunction `name:"post_build"` 138 // Languages this rule requires. These are an arbitrary set and the only meaning is that they 139 // correspond to entries in Provides; if rules match up then it allows choosing a specific 140 // dependency (consider eg. code generated from protobufs; this mechanism allows us to expose 141 // one rule but only compile the appropriate code for each library that consumes it). 142 Requires []string 143 // Dependent rules this rule provides for each language. Matches up to Requires as described above. 144 Provides map[string]BuildLabel 145 // Stores the hash of this build rule before any post-build function is run. 146 RuleHash []byte `name:"exported_deps"` // bit of a hack to call this exported_deps... 147 // Tools that this rule will use, ie. other rules that it may use at build time which are not 148 // copied into its source directory. 149 Tools []BuildInput 150 // Named tools, similar to named sources. 151 namedTools map[string][]BuildInput `name:"tools"` 152 // Flakiness of test, ie. number of times we will rerun it before giving up. 0 is the default and 153 // is interpreted the same way as 1 would be (ie. one run only). 154 Flakiness int `name:"flaky"` 155 // Timeouts for build/test actions 156 BuildTimeout time.Duration `name:"timeout"` 157 TestTimeout time.Duration `name:"test_timeout"` 158 // Extra output files from the test. 159 // These are in addition to the usual test.results output file. 160 TestOutputs []string 161 } 162 163 // A PreBuildFunction is a type that allows hooking a pre-build callback. 164 type PreBuildFunction interface { 165 fmt.Stringer 166 // Call calls this pre-build function 167 Call(target *BuildTarget) error 168 } 169 170 // A PostBuildFunction is a type that allows hooking a post-build callback. 171 type PostBuildFunction interface { 172 fmt.Stringer 173 // Call calls this pre-build function with this target and its output. 174 Call(target *BuildTarget, output string) error 175 } 176 177 type depInfo struct { 178 declared BuildLabel // the originally declared dependency 179 deps []*BuildTarget // list of actual deps 180 resolved bool // has the graph resolved it 181 exported bool // is it an exported dependency 182 source bool // is it implicit because it's a source (not true if it's a dependency too) 183 data bool // is it a data item for a test 184 } 185 186 // A BuildTargetState tracks the current state of this target in regard to whether it's built 187 // or not. Targets only move forwards through this (i.e. the state of a target only ever increases). 188 type BuildTargetState int32 189 190 // The available states for a target. 191 const ( 192 Inactive BuildTargetState = iota // Target isn't used in current build 193 Semiactive // Target would be active if we needed a build 194 Active // Target is going to be used in current build 195 Pending // Target is ready to be built but not yet started. 196 Building // Target is currently being built 197 Stopped // We stopped building the target because we'd gone as far as needed. 198 Built // Target has been successfully built 199 Cached // Target has been retrieved from the cache 200 Unchanged // Target has been built but hasn't changed since last build 201 Reused // Outputs of previous build have been reused. 202 Failed // Target failed for some reason 203 ) 204 205 // String implements the fmt.Stringer interface. 206 func (s BuildTargetState) String() string { 207 if s == Inactive { 208 return "Inactive" 209 } else if s == Semiactive { 210 return "Semiactive" 211 } else if s == Active { 212 return "Active" 213 } else if s == Pending { 214 return "Pending" 215 } else if s == Building { 216 return "Building" 217 } else if s == Stopped { 218 return "Stopped" 219 } else if s == Built { 220 return "Built" 221 } else if s == Cached { 222 return "Cached" 223 } else if s == Unchanged { 224 return "Unchanged" 225 } else if s == Reused { 226 return "Reused" 227 } else if s == Failed { 228 return "Failed" 229 } 230 return "Unknown" 231 } 232 233 // TargetContainerSettings are known settings controlling containerisation for a particular target. 234 type TargetContainerSettings struct { 235 // Image to use for this test 236 DockerImage string `name:"docker_image"` 237 // Username / Uid to run as 238 DockerUser string `name:"docker_user"` 239 // Extra arguments to pass to 'docker run' 240 DockerRunArgs string `name:"docker_run_args"` 241 } 242 243 // ToMap returns this struct as a map. 244 func (settings *TargetContainerSettings) ToMap() map[string]string { 245 m := map[string]string{} 246 v := reflect.ValueOf(settings).Elem() 247 for i := 0; i < v.NumField(); i++ { 248 if s := v.Field(i).String(); s != "" { 249 m[v.Type().Field(i).Tag.Get("name")] = s 250 } 251 } 252 return m 253 } 254 255 // NewBuildTarget constructs & returns a new BuildTarget. 256 func NewBuildTarget(label BuildLabel) *BuildTarget { 257 return &BuildTarget{ 258 Label: label, 259 state: int32(Inactive), 260 BuildingDescription: DefaultBuildingDescription, 261 } 262 } 263 264 // TmpDir returns the temporary working directory for this target, eg. 265 // //mickey/donald:goofy -> plz-out/tmp/mickey/donald/goofy._build 266 // Note the extra subdirectory to keep rules separate from one another, and the .build suffix 267 // to attempt to keep rules from duplicating the names of sub-packages; obviously that is not 268 // 100% reliable but we don't have a better solution right now. 269 func (target *BuildTarget) TmpDir() string { 270 return path.Join(TmpDir, target.Label.PackageName, target.Label.Name+buildDirSuffix) 271 } 272 273 // OutDir returns the output directory for this target, eg. 274 // //mickey/donald:goofy -> plz-out/gen/mickey/donald (or plz-out/bin if it's a binary) 275 func (target *BuildTarget) OutDir() string { 276 if target.IsBinary { 277 return path.Join(BinDir, target.Label.PackageName) 278 } 279 return path.Join(GenDir, target.Label.PackageName) 280 } 281 282 // TestDir returns the test directory for this target, eg. 283 // //mickey/donald:goofy -> plz-out/tmp/mickey/donald/goofy._test 284 // This is different to TmpDir so we run tests in a clean environment 285 // and to facilitate containerising tests. 286 func (target *BuildTarget) TestDir() string { 287 return path.Join(TmpDir, target.Label.PackageName, target.Label.Name+testDirSuffix) 288 } 289 290 // AllSourcePaths returns all the source paths for this target 291 func (target *BuildTarget) AllSourcePaths(graph *BuildGraph) []string { 292 return target.allSourcePaths(graph, BuildInput.Paths) 293 } 294 295 // AllFullSourcePaths returns all the source paths for this target, with a leading 296 // plz-out/gen etc if appropriate. 297 func (target *BuildTarget) AllFullSourcePaths(graph *BuildGraph) []string { 298 return target.allSourcePaths(graph, BuildInput.FullPaths) 299 } 300 301 // AllLocalSourcePaths returns the local part of all the source paths for this target, 302 // i.e. without this target's package in it. 303 func (target *BuildTarget) AllLocalSourcePaths(graph *BuildGraph) []string { 304 return target.allSourcePaths(graph, BuildInput.LocalPaths) 305 } 306 307 type buildPathsFunc func(BuildInput, *BuildGraph) []string 308 309 func (target *BuildTarget) allSourcePaths(graph *BuildGraph, full buildPathsFunc) []string { 310 ret := make([]string, 0, len(target.Sources)) 311 for _, source := range target.AllSources() { 312 ret = append(ret, target.sourcePaths(graph, source, full)...) 313 } 314 return ret 315 } 316 317 // DeclaredDependencies returns all the targets this target declared any kind of dependency on (including sources and tools). 318 func (target *BuildTarget) DeclaredDependencies() []BuildLabel { 319 ret := make(BuildLabels, len(target.dependencies)) 320 for i, dep := range target.dependencies { 321 ret[i] = dep.declared 322 } 323 sort.Sort(ret) 324 return ret 325 } 326 327 // DeclaredDependenciesStrict returns the original declaration of this target's dependencies. 328 func (target *BuildTarget) DeclaredDependenciesStrict() []BuildLabel { 329 ret := make(BuildLabels, 0, len(target.dependencies)) 330 for _, dep := range target.dependencies { 331 if !dep.exported && !dep.source && !target.IsTool(dep.declared) { 332 ret = append(ret, dep.declared) 333 } 334 } 335 sort.Sort(ret) 336 return ret 337 } 338 339 // Dependencies returns the resolved dependencies of this target. 340 func (target *BuildTarget) Dependencies() []*BuildTarget { 341 ret := make(BuildTargets, 0, len(target.dependencies)) 342 for _, deps := range target.dependencies { 343 for _, dep := range deps.deps { 344 ret = append(ret, dep) 345 } 346 } 347 sort.Sort(ret) 348 return ret 349 } 350 351 // BuildDependencies returns the build-time dependencies of this target (i.e. not data). 352 func (target *BuildTarget) BuildDependencies() []*BuildTarget { 353 ret := make(BuildTargets, 0, len(target.dependencies)) 354 for _, deps := range target.dependencies { 355 if !deps.data { 356 for _, dep := range deps.deps { 357 ret = append(ret, dep) 358 } 359 } 360 } 361 sort.Sort(ret) 362 return ret 363 } 364 365 // ExportedDependencies returns any exported dependencies of this target. 366 func (target *BuildTarget) ExportedDependencies() []BuildLabel { 367 ret := make(BuildLabels, 0, len(target.dependencies)) 368 for _, info := range target.dependencies { 369 if info.exported { 370 ret = append(ret, info.declared) 371 } 372 } 373 return ret 374 } 375 376 // DependenciesFor returns the dependencies that relate to a given label. 377 func (target *BuildTarget) DependenciesFor(label BuildLabel) []*BuildTarget { 378 info := target.dependencyInfo(label) 379 if info != nil { 380 return info.deps 381 } 382 return nil 383 } 384 385 // DeclaredOutputs returns the outputs from this target's original declaration. 386 // Hence it's similar to Outputs() but without the resolving of other rule names. 387 func (target *BuildTarget) DeclaredOutputs() []string { 388 return target.outputs 389 } 390 391 // DeclaredNamedOutputs returns the named outputs from this target's original declaration. 392 func (target *BuildTarget) DeclaredNamedOutputs() map[string][]string { 393 return target.namedOutputs 394 } 395 396 // DeclaredOutputNames is a convenience function to return the names of the declared 397 // outputs in a consistent order. 398 func (target *BuildTarget) DeclaredOutputNames() []string { 399 ret := make([]string, 0, len(target.namedOutputs)) 400 for name := range target.namedOutputs { 401 ret = append(ret, name) 402 } 403 sort.Strings(ret) 404 return ret 405 } 406 407 // Outputs returns a slice of all the outputs of this rule. 408 func (target *BuildTarget) Outputs() []string { 409 var ret []string 410 if target.IsFilegroup && !target.IsHashFilegroup { 411 ret = make([]string, 0, len(target.Sources)) 412 // Filegroups just re-output their inputs. 413 for _, src := range target.Sources { 414 if namedLabel, ok := src.(NamedOutputLabel); ok { 415 // Bit of a hack, but this needs different treatment from either of the others. 416 for _, dep := range target.DependenciesFor(namedLabel.BuildLabel) { 417 ret = append(ret, dep.NamedOutputs(namedLabel.Output)...) 418 } 419 } else if label := src.nonOutputLabel(); label == nil { 420 ret = append(ret, src.LocalPaths(nil)[0]) 421 } else { 422 for _, dep := range target.DependenciesFor(*label) { 423 ret = append(ret, dep.Outputs()...) 424 } 425 } 426 } 427 } else { 428 // Must really copy the slice before sorting it ([:] is too shallow) 429 ret = make([]string, len(target.outputs)) 430 copy(ret, target.outputs) 431 } 432 if target.namedOutputs != nil { 433 for _, outputs := range target.namedOutputs { 434 ret = append(ret, outputs...) 435 } 436 } 437 sort.Strings(ret) 438 return ret 439 } 440 441 // NamedOutputs returns a slice of all the outputs of this rule with a given name. 442 // If the name is not declared by this rule it panics. 443 func (target *BuildTarget) NamedOutputs(name string) []string { 444 if target.namedOutputs == nil { 445 return nil 446 } 447 if outs, present := target.namedOutputs[name]; present { 448 return outs 449 } 450 return nil 451 } 452 453 // SourcePaths returns the source paths for a given set of sources. 454 func (target *BuildTarget) SourcePaths(graph *BuildGraph, sources []BuildInput) []string { 455 ret := make([]string, 0, len(sources)) 456 for _, source := range sources { 457 ret = append(ret, target.sourcePaths(graph, source, BuildInput.Paths)...) 458 } 459 return ret 460 } 461 462 // sourcePaths returns the source paths for a single source. 463 func (target *BuildTarget) sourcePaths(graph *BuildGraph, source BuildInput, f buildPathsFunc) []string { 464 if label := source.nonOutputLabel(); label != nil { 465 ret := []string{} 466 for _, providedLabel := range graph.TargetOrDie(*label).ProvideFor(target) { 467 ret = append(ret, f(providedLabel, graph)...) 468 } 469 return ret 470 } 471 return f(source, graph) 472 } 473 474 // allDepsBuilt returns true if all the dependencies of a target are built. 475 func (target *BuildTarget) allDepsBuilt() bool { 476 if !target.allDependenciesResolved() { 477 return false // Target still has some deps pending parse. 478 } 479 for _, deps := range target.dependencies { 480 for _, dep := range deps.deps { 481 if dep.State() < Built { 482 return false 483 } 484 } 485 } 486 return true 487 } 488 489 // allDependenciesResolved returns true once all the dependencies of a target have been 490 // parsed and resolved to real targets. 491 func (target *BuildTarget) allDependenciesResolved() bool { 492 for _, deps := range target.dependencies { 493 if !deps.resolved { 494 return false 495 } 496 } 497 return true 498 } 499 500 // isExperimental returns true if the given target is in the "experimental" tree 501 func isExperimental(state *BuildState, target *BuildTarget) bool { 502 for _, exp := range state.experimentalLabels { 503 if exp.Includes(target.Label) { 504 return true 505 } 506 } 507 return false 508 } 509 510 // CanSee returns true if target can see the given dependency, or false if not. 511 func (target *BuildTarget) CanSee(state *BuildState, dep *BuildTarget) bool { 512 // Targets are always visible to other targets in the same directory. 513 if target.Label.PackageName == dep.Label.PackageName { 514 return true 515 } else if isExperimental(state, dep) && !isExperimental(state, target) { 516 log.Error("Target %s cannot depend on experimental target %s", target.Label, dep.Label) 517 return false 518 } 519 parent := target.Label.Parent() 520 // Visibility constraints currently do not include subrepos to handle cross-compiling nicely. 521 // This is a little dodgy (you can see into non-cross-compiled subrepos by duplicating their 522 // package names) but not the biggest concern right now. 523 if target.Subrepo != nil { 524 parent = target.Subrepo.MakeRelative(parent) 525 } 526 for _, vis := range dep.Visibility { 527 if vis.Includes(parent) { 528 return true 529 } else if dep.Subrepo != nil && dep.Subrepo.MakeRelative(vis).Includes(parent) { 530 return true 531 } 532 } 533 if dep.Subrepo != nil && dep.Subrepo.MakeRelative(dep.Label).PackageName == parent.PackageName { 534 return true 535 } else if dep.Label.PackageName == parent.PackageName { 536 return true 537 } 538 if isExperimental(state, target) { 539 log.Warning("Visibility restrictions suppressed for %s since %s is in the experimental tree", dep.Label, target.Label) 540 return true 541 } 542 return false 543 } 544 545 // CheckDependencyVisibility checks that all declared dependencies of this target are visible to it. 546 // Returns an error if not, or nil if all's well. 547 func (target *BuildTarget) CheckDependencyVisibility(state *BuildState) error { 548 for _, d := range target.dependencies { 549 dep := state.Graph.TargetOrDie(d.declared) 550 if !target.CanSee(state, dep) { 551 return fmt.Errorf("Target %s isn't visible to %s", dep.Label, target.Label) 552 } else if dep.TestOnly && !(target.IsTest || target.TestOnly) { 553 if isExperimental(state, target) { 554 log.Warning("Test-only restrictions suppressed for %s since %s is in the experimental tree", dep.Label, target.Label) 555 } else { 556 return fmt.Errorf("Target %s can't depend on %s, it's marked test_only", target.Label, dep.Label) 557 } 558 } 559 } 560 return nil 561 } 562 563 // CheckDuplicateOutputs checks if any of the outputs of this target duplicate one another. 564 // Returns an error if so, or nil if all's well. 565 func (target *BuildTarget) CheckDuplicateOutputs() error { 566 outputs := map[string]struct{}{} 567 for _, output := range target.Outputs() { 568 if _, present := outputs[output]; present { 569 return fmt.Errorf("Target %s declares output file %s multiple times", target.Label, output) 570 } 571 outputs[output] = struct{}{} 572 } 573 return nil 574 } 575 576 // CheckSecrets checks that this target's secrets are available. 577 // We run this check before building because we don't attempt to copy them, but any rule 578 // requiring them will presumably fail if they aren't available. 579 // Returns an error if any aren't. 580 func (target *BuildTarget) CheckSecrets() error { 581 for _, secret := range target.Secrets { 582 if path := ExpandHomePath(secret); !PathExists(path) { 583 return fmt.Errorf("Path %s doesn't exist; it's required to build %s", secret, target.Label) 584 } 585 } 586 return nil 587 } 588 589 // HasDependency checks if a target already depends on this label. 590 func (target *BuildTarget) HasDependency(label BuildLabel) bool { 591 return target.dependencyInfo(label) != nil 592 } 593 594 // hasResolvedDependency returns true if a particular dependency has been resolved to real targets yet. 595 func (target *BuildTarget) hasResolvedDependency(label BuildLabel) bool { 596 info := target.dependencyInfo(label) 597 return info != nil && info.resolved 598 } 599 600 // resolveDependency resolves a particular dependency on a target. 601 func (target *BuildTarget) resolveDependency(label BuildLabel, dep *BuildTarget) { 602 info := target.dependencyInfo(label) 603 if info == nil { 604 target.dependencies = append(target.dependencies, depInfo{declared: label}) 605 info = &target.dependencies[len(target.dependencies)-1] 606 } 607 if dep != nil { 608 info.deps = append(info.deps, dep) 609 } 610 info.resolved = true 611 } 612 613 // dependencyInfo returns the information about a declared dependency, or nil if the target doesn't have it. 614 func (target *BuildTarget) dependencyInfo(label BuildLabel) *depInfo { 615 for i, info := range target.dependencies { 616 if info.declared == label { 617 return &target.dependencies[i] 618 } 619 } 620 return nil 621 } 622 623 // IsSourceOnlyDep returns true if the given dependency was only declared on the srcs of the target. 624 func (target *BuildTarget) IsSourceOnlyDep(label BuildLabel) bool { 625 info := target.dependencyInfo(label) 626 return info != nil && info.source 627 } 628 629 // State returns the target's current state. 630 func (target *BuildTarget) State() BuildTargetState { 631 return BuildTargetState(atomic.LoadInt32(&target.state)) 632 } 633 634 // SetState sets a target's current state. 635 func (target *BuildTarget) SetState(state BuildTargetState) { 636 atomic.StoreInt32(&target.state, int32(state)) 637 } 638 639 // SyncUpdateState oves the target's state from before to after via a lock. 640 // Returns true if successful, false if not (which implies something else changed the state first). 641 // The nature of our build graph ensures that most transitions are only attempted by 642 // one thread simultaneously, but this one can be attempted by several at once 643 // (eg. if a depends on b and c, which finish building simultaneously, they race to queue a). 644 func (target *BuildTarget) SyncUpdateState(before, after BuildTargetState) bool { 645 return atomic.CompareAndSwapInt32(&target.state, int32(before), int32(after)) 646 } 647 648 // AddLabel adds the given label to this target if it doesn't already have it. 649 func (target *BuildTarget) AddLabel(label string) { 650 if !target.HasLabel(label) { 651 target.Labels = append(target.Labels, label) 652 } 653 } 654 655 // HasLabel returns true if target has the given label. 656 func (target *BuildTarget) HasLabel(label string) bool { 657 for _, l := range target.Labels { 658 if l == label { 659 return true 660 } 661 } 662 return label == "test" && target.IsTest 663 } 664 665 // PrefixedLabels returns all labels of this target with the given prefix. 666 func (target *BuildTarget) PrefixedLabels(prefix string) []string { 667 ret := []string{} 668 for _, l := range target.Labels { 669 if strings.HasPrefix(l, prefix) { 670 ret = append(ret, strings.TrimPrefix(l, prefix)) 671 } 672 } 673 return ret 674 } 675 676 // HasAnyLabel returns true if target has any of these labels. 677 func (target *BuildTarget) HasAnyLabel(labels []string) bool { 678 for _, label := range labels { 679 if target.HasLabel(label) { 680 return true 681 } 682 } 683 return false 684 } 685 686 // HasAllLabels returns true if target has all of these labels. 687 func (target *BuildTarget) HasAllLabels(labels []string) bool { 688 for _, label := range labels { 689 if !target.HasLabel(label) { 690 return false 691 } 692 } 693 return true 694 } 695 696 // ShouldInclude handles the typical include/exclude logic for a target's labels; returns true if 697 // target has any include label and not an exclude one. 698 // Each include/exclude can have multiple comma-separated labels; in this case, all of the labels 699 // in a given group must match. 700 func (target *BuildTarget) ShouldInclude(includes, excludes []string) bool { 701 if target.HasLabel("manual") || target.HasLabel("manual:"+OsArch) { 702 return false 703 } 704 705 if len(includes) == 0 && len(excludes) == 0 { 706 return true 707 } 708 709 // Include by default if no includes are specified. 710 shouldInclude := len(includes) == 0 711 for _, include := range includes { 712 if target.HasAllLabels(strings.Split(include, ",")) { 713 shouldInclude = true 714 break 715 } 716 } 717 for _, exclude := range excludes { 718 if target.HasAllLabels(strings.Split(exclude, ",")) { 719 shouldInclude = false 720 break 721 } 722 } 723 return shouldInclude 724 } 725 726 // AddProvide adds a new provide entry to this target. 727 func (target *BuildTarget) AddProvide(language string, label BuildLabel) { 728 if target.Provides == nil { 729 target.Provides = map[string]BuildLabel{language: label} 730 } else { 731 target.Provides[language] = label 732 } 733 } 734 735 // ProvideFor returns the build label that we'd provide for the given target. 736 func (target *BuildTarget) ProvideFor(other *BuildTarget) []BuildLabel { 737 ret := []BuildLabel{} 738 if target.Provides != nil { 739 // Never do this if the other target has a data dependency on us. 740 for _, data := range other.Data { 741 if label := data.Label(); label != nil && *label == target.Label { 742 return []BuildLabel{target.Label} 743 } 744 } 745 for _, require := range other.Requires { 746 if label, present := target.Provides[require]; present { 747 ret = append(ret, label) 748 } 749 } 750 if len(ret) > 0 { 751 return ret 752 } 753 } 754 return []BuildLabel{target.Label} 755 } 756 757 // AddSource adds a source to the build target, deduplicating against existing entries. 758 func (target *BuildTarget) AddSource(source BuildInput) { 759 target.Sources = target.addSource(target.Sources, source) 760 } 761 762 func (target *BuildTarget) addSource(sources []BuildInput, source BuildInput) []BuildInput { 763 for _, src := range sources { 764 if source == src { 765 return sources 766 } 767 } 768 // Add a dependency if this is not just a file. 769 if label := source.Label(); label != nil { 770 target.AddMaybeExportedDependency(*label, false, true) 771 } 772 return append(sources, source) 773 } 774 775 // AddNamedSource adds a source to the target which is tagged with a particular name. 776 // For example, C++ rules add sources tagged as "sources" and "headers" to distinguish 777 // two conceptually different kinds of input. 778 func (target *BuildTarget) AddNamedSource(name string, source BuildInput) { 779 if target.NamedSources == nil { 780 target.NamedSources = map[string][]BuildInput{name: target.addSource(nil, source)} 781 } else { 782 target.NamedSources[name] = target.addSource(target.NamedSources[name], source) 783 } 784 } 785 786 // AddTool adds a new tool to the target. 787 func (target *BuildTarget) AddTool(tool BuildInput) { 788 target.Tools = append(target.Tools, tool) 789 if label := tool.Label(); label != nil { 790 target.AddDependency(*label) 791 } 792 } 793 794 // AddDatum adds a new item of data to the target. 795 func (target *BuildTarget) AddDatum(datum BuildInput) { 796 target.Data = append(target.Data, datum) 797 if label := datum.Label(); label != nil { 798 target.AddDependency(*label) 799 target.dependencyInfo(*label).data = true 800 } 801 } 802 803 // AddNamedTool adds a new tool to the target. 804 func (target *BuildTarget) AddNamedTool(name string, tool BuildInput) { 805 if target.namedTools == nil { 806 target.namedTools = map[string][]BuildInput{name: {tool}} 807 } else { 808 target.namedTools[name] = append(target.namedTools[name], tool) 809 } 810 if label := tool.Label(); label != nil { 811 target.AddDependency(*label) 812 } 813 } 814 815 // AddCommand adds a new config-specific command to this build target. 816 // Adding a general command is still done by simply setting the Command member. 817 func (target *BuildTarget) AddCommand(config, command string) { 818 if target.Command != "" { 819 panic(fmt.Sprintf("Adding named command %s to %s, but it already has a general command set", config, target.Label)) 820 } else if target.Commands == nil { 821 target.Commands = map[string]string{config: command} 822 } else { 823 target.Commands[config] = command 824 } 825 } 826 827 // AddTestCommand adds a new config-specific test command to this build target. 828 // Adding a general command is still done by simply setting the TestCommand member. 829 func (target *BuildTarget) AddTestCommand(config, command string) { 830 if target.TestCommand != "" { 831 panic(fmt.Sprintf("Adding named test command %s to %s, but it already has a general test command set", config, target.Label)) 832 } else if target.TestCommands == nil { 833 target.TestCommands = map[string]string{config: command} 834 } else { 835 target.TestCommands[config] = command 836 } 837 } 838 839 // GetCommand returns the command we should use to build this target for the current config. 840 func (target *BuildTarget) GetCommand(state *BuildState) string { 841 return target.getCommand(state, target.Commands, target.Command) 842 } 843 844 // GetCommandConfig returns the command we should use to build this target for the given config. 845 func (target *BuildTarget) GetCommandConfig(config string) string { 846 if config == "" { 847 return target.Command 848 } 849 return target.Commands[config] 850 } 851 852 // GetTestCommand returns the command we should use to test this target for the current config. 853 func (target *BuildTarget) GetTestCommand(state *BuildState) string { 854 return target.getCommand(state, target.TestCommands, target.TestCommand) 855 } 856 857 func (target *BuildTarget) getCommand(state *BuildState, commands map[string]string, singleCommand string) string { 858 if commands == nil { 859 return singleCommand 860 } else if command, present := commands[state.Config.Build.Config]; present { 861 return command // Has command for current config, good 862 } else if command, present := commands[state.Config.Build.FallbackConfig]; present { 863 return command // Has command for default config, fall back to that 864 } 865 // Oh dear, target doesn't have any matching config. Panicking is a bit heavy here, instead 866 // fall back to an arbitrary (but consistent) one. 867 highestCommand := "" 868 highestConfig := "" 869 for config, command := range commands { 870 if config > highestConfig { 871 highestConfig = config 872 highestCommand = command 873 } 874 } 875 log.Warning("%s doesn't have a command for %s (or %s), falling back to %s", 876 target.Label, state.Config.Build.Config, state.Config.Build.FallbackConfig, highestConfig) 877 return highestCommand 878 } 879 880 // AllSources returns all the sources of this rule. 881 func (target *BuildTarget) AllSources() []BuildInput { 882 ret := target.Sources[:] 883 if target.NamedSources != nil { 884 keys := make([]string, 0, len(target.NamedSources)) 885 for k := range target.NamedSources { 886 keys = append(keys, k) 887 } 888 sort.Strings(keys) 889 for _, k := range keys { 890 ret = append(ret, target.NamedSources[k]...) 891 } 892 } 893 return ret 894 } 895 896 // AllLocalSources returns all the "local" sources of this rule, i.e. all sources that are 897 // actually sources in the repo, not other rules or system srcs etc. 898 func (target *BuildTarget) AllLocalSources() []string { 899 ret := []string{} 900 for _, src := range target.AllSources() { 901 if file, ok := src.(FileLabel); ok { 902 ret = append(ret, file.Paths(nil)[0]) 903 } 904 } 905 return ret 906 } 907 908 // HasSource returns true if this target has the given file as a source (named or not). 909 func (target *BuildTarget) HasSource(source string) bool { 910 for _, src := range target.AllSources() { 911 if src.String() == source { // Comparison is a bit dodgy tbh 912 return true 913 } 914 } 915 return false 916 } 917 918 // AllTools returns all the tools for this rule in some canonical order. 919 func (target *BuildTarget) AllTools() []BuildInput { 920 if target.namedTools == nil { 921 return target.Tools // Leave them in input order, that's sufficiently consistent. 922 } 923 tools := make([]BuildInput, len(target.Tools), len(target.Tools)+len(target.namedTools)*2) 924 copy(tools, target.Tools) 925 for _, name := range target.ToolNames() { 926 tools = append(tools, target.namedTools[name]...) 927 } 928 return tools 929 } 930 931 // ToolNames returns an ordered list of tool names. 932 func (target *BuildTarget) ToolNames() []string { 933 ret := make([]string, 0, len(target.namedTools)) 934 for name := range target.namedTools { 935 ret = append(ret, name) 936 } 937 sort.Strings(ret) 938 return ret 939 } 940 941 // NamedTools returns the tools with the given name. 942 func (target *BuildTarget) NamedTools(name string) []BuildInput { 943 return target.namedTools[name] 944 } 945 946 // AllData returns all the data paths for this target. 947 func (target *BuildTarget) AllData(graph *BuildGraph) []string { 948 ret := make([]string, 0, len(target.Data)) 949 for _, datum := range target.Data { 950 ret = append(ret, datum.Paths(graph)...) 951 } 952 return ret 953 } 954 955 // AddDependency adds a dependency to this target. It deduplicates against any existing deps. 956 func (target *BuildTarget) AddDependency(dep BuildLabel) { 957 target.AddMaybeExportedDependency(dep, false, false) 958 } 959 960 // AddMaybeExportedDependency adds a dependency to this target which may be exported. It deduplicates against any existing deps. 961 func (target *BuildTarget) AddMaybeExportedDependency(dep BuildLabel, exported, source bool) { 962 if dep == target.Label { 963 log.Fatalf("Attempted to add %s as a dependency of itself.\n", dep) 964 } 965 info := target.dependencyInfo(dep) 966 if info == nil { 967 target.dependencies = append(target.dependencies, depInfo{declared: dep, exported: exported, source: source}) 968 } else { 969 info.exported = info.exported || exported 970 info.source = info.source && source 971 info.data = false // It's not *only* data any more. 972 } 973 } 974 975 // IsTool returns true if the given build label is a tool used by this target. 976 func (target *BuildTarget) IsTool(tool BuildLabel) bool { 977 for _, t := range target.Tools { 978 if t == tool { 979 return true 980 } 981 } 982 for _, tools := range target.namedTools { 983 for _, t := range tools { 984 if t == tool { 985 return true 986 } 987 } 988 } 989 return false 990 } 991 992 // toolPath returns a path to this target when used as a tool. 993 func (target *BuildTarget) toolPath() string { 994 outputs := target.Outputs() 995 ret := make([]string, len(outputs)) 996 for i, o := range outputs { 997 ret[i], _ = filepath.Abs(path.Join(target.OutDir(), o)) 998 } 999 return strings.Join(ret, " ") 1000 } 1001 1002 // AddOutput adds a new output to the target if it's not already there. 1003 func (target *BuildTarget) AddOutput(output string) { 1004 target.outputs = target.insert(target.outputs, output) 1005 } 1006 1007 // AddOptionalOutput adds a new optional output to the target if it's not already there. 1008 func (target *BuildTarget) AddOptionalOutput(output string) { 1009 target.OptionalOutputs = target.insert(target.OptionalOutputs, output) 1010 } 1011 1012 // AddTestOutput adds a new test output to the target if it's not already there. 1013 func (target *BuildTarget) AddTestOutput(output string) { 1014 target.TestOutputs = target.insert(target.TestOutputs, output) 1015 } 1016 1017 // AddNamedOutput adds a new output to the target under a named group. 1018 // No attempt to deduplicate against unnamed outputs is currently made. 1019 func (target *BuildTarget) AddNamedOutput(name, output string) { 1020 if target.namedOutputs == nil { 1021 target.namedOutputs = map[string][]string{name: {output}} 1022 return 1023 } 1024 target.namedOutputs[name] = target.insert(target.namedOutputs[name], output) 1025 } 1026 1027 // insert adds a string into a slice if it's not already there. Sorted order is maintained. 1028 func (target *BuildTarget) insert(sl []string, s string) []string { 1029 for i, x := range sl { 1030 if s == x { 1031 // Already present. 1032 return sl 1033 } else if x > s { 1034 // Insert in this location. Make an attempt to be efficient. 1035 sl = append(sl, "") 1036 copy(sl[i+1:], sl[i:]) 1037 sl[i] = s 1038 return sl 1039 } 1040 } 1041 return append(sl, s) 1042 } 1043 1044 // AddLicence adds a licence to the target if it's not already there. 1045 func (target *BuildTarget) AddLicence(licence string) { 1046 licence = strings.TrimSpace(licence) 1047 for _, l := range target.Licences { 1048 if l == licence { 1049 return 1050 } 1051 } 1052 target.Licences = append(target.Licences, licence) 1053 } 1054 1055 // AddHash adds a new acceptable hash to the target. 1056 func (target *BuildTarget) AddHash(hash string) { 1057 target.Hashes = append(target.Hashes, hash) 1058 } 1059 1060 // AddRequire adds a new requirement to the target. 1061 func (target *BuildTarget) AddRequire(require string) { 1062 target.Requires = append(target.Requires, require) 1063 // Requirements are also implicit labels 1064 target.AddLabel(require) 1065 } 1066 1067 // SetContainerSetting sets one of the fields on the container settings by name. 1068 func (target *BuildTarget) SetContainerSetting(name, value string) error { 1069 if target.ContainerSettings == nil { 1070 target.ContainerSettings = &TargetContainerSettings{} 1071 } 1072 t := reflect.TypeOf(*target.ContainerSettings) 1073 for i := 0; i < t.NumField(); i++ { 1074 if strings.ToLower(t.Field(i).Name) == name { 1075 v := reflect.ValueOf(target.ContainerSettings) 1076 v.Elem().Field(i).SetString(value) 1077 return nil 1078 } 1079 } 1080 return fmt.Errorf("Field %s isn't a valid container setting", name) 1081 } 1082 1083 // OutMode returns the mode to set outputs of a target to. 1084 func (target *BuildTarget) OutMode() os.FileMode { 1085 if target.IsBinary { 1086 return 0555 1087 } 1088 return 0444 1089 } 1090 1091 // PostBuildOutputFileName returns the post-build output file for this target. 1092 func (target *BuildTarget) PostBuildOutputFileName() string { 1093 return ".build_output_" + target.Label.Name 1094 } 1095 1096 // Parent finds the parent of a build target, or nil if the target is parentless. 1097 // Note that this is a fairly informal relationship; we identify it by labels with the convention of 1098 // a leading _ and trailing hashtag on child rules, rather than storing pointers between them in the graph. 1099 // The parent returned, if any, will be the ultimate ancestor of the target. 1100 func (target *BuildTarget) Parent(graph *BuildGraph) *BuildTarget { 1101 parent := target.Label.Parent() 1102 if parent == target.Label { 1103 return nil 1104 } 1105 return graph.Target(parent) 1106 } 1107 1108 // HasParent returns true if the target has a parent rule that's not itself. 1109 func (target *BuildTarget) HasParent() bool { 1110 return target.Label.HasParent() 1111 } 1112 1113 // BuildTargets makes a slice of build targets sortable by their labels. 1114 type BuildTargets []*BuildTarget 1115 1116 func (slice BuildTargets) Len() int { 1117 return len(slice) 1118 } 1119 func (slice BuildTargets) Less(i, j int) bool { 1120 return slice[i].Label.Less(slice[j].Label) 1121 } 1122 func (slice BuildTargets) Swap(i, j int) { 1123 slice[i], slice[j] = slice[j], slice[i] 1124 }