github.com/tiagovtristao/plz@v13.4.0+incompatible/src/parse/parse_step.go (about) 1 // Package parse implements parsing of the BUILD files via an embedded Python interpreter. 2 // 3 // The actual work here is done by an embedded PyPy instance. Various rules are built in to 4 // the binary itself using go-bindata to embed the .py files; these are always available to 5 // all programs which is rather nice, but it does mean that must be run before 'go run' etc 6 // will work as expected. 7 package parse 8 9 import ( 10 "fmt" 11 "path" 12 "strings" 13 14 "gopkg.in/op/go-logging.v1" 15 16 "github.com/thought-machine/please/src/cli" 17 "github.com/thought-machine/please/src/core" 18 "github.com/thought-machine/please/src/fs" 19 "github.com/thought-machine/please/src/worker" 20 ) 21 22 var log = logging.MustGetLogger("parse") 23 24 // Parse parses the package corresponding to a single build label. The label can be :all to add all targets in a package. 25 // It is not an error if the package has already been parsed. 26 // 27 // By default, after the package is parsed, any targets that are now needed for the build and ready 28 // to be built are queued, and any new packages are queued for parsing. When a specific label is requested 29 // this is straightforward, but when parsing for pseudo-targets like :all and ..., various flags affect it: 30 // 'include' and 'exclude' refer to the labels of targets to be added. If 'include' is non-empty then only 31 // targets with at least one matching label are added. Any targets with a label in 'exclude' are not added. 32 // 'forSubinclude' is set when the parse is required for a subinclude target so should proceed 33 // even when we're not otherwise building targets. 34 func Parse(tid int, state *core.BuildState, label, dependor core.BuildLabel, include, exclude []string, forSubinclude bool) { 35 if err := parse(tid, state, label, dependor, include, exclude, forSubinclude); err != nil { 36 state.LogBuildError(tid, label, core.ParseFailed, err, "Failed to parse package") 37 } 38 } 39 40 func parse(tid int, state *core.BuildState, label, dependor core.BuildLabel, include, exclude []string, forSubinclude bool) error { 41 // See if something else has parsed this package first. 42 pkg := state.WaitForPackage(label) 43 if pkg != nil { 44 // Does exist, all we need to do is toggle on this target 45 return activateTarget(state, pkg, label, dependor, forSubinclude, include, exclude) 46 } 47 // If we get here then it falls to us to parse this package. 48 state.LogBuildResult(tid, label, core.PackageParsing, "Parsing...") 49 50 subrepo, err := checkSubrepo(tid, state, label, dependor) 51 if err != nil { 52 return err 53 } else if subrepo != nil && subrepo.Target != nil { 54 // We have got the definition of the subrepo but it depends on something, make sure that has been built. 55 state.WaitForBuiltTarget(subrepo.Target.Label, label) 56 } 57 pkg, err = parsePackage(state, label, dependor, subrepo) 58 if err != nil { 59 return err 60 } 61 state.LogBuildResult(tid, label, core.PackageParsed, "Parsed package") 62 return activateTarget(state, pkg, label, dependor, forSubinclude, include, exclude) 63 } 64 65 // checkSubrepo checks whether this guy exists within a subrepo. If so we will need to make sure that's available first. 66 func checkSubrepo(tid int, state *core.BuildState, label, dependor core.BuildLabel) (*core.Subrepo, error) { 67 if label.Subrepo == "" { 68 return nil, nil 69 } else if subrepo := state.Graph.Subrepo(label.Subrepo); subrepo != nil { 70 return subrepo, nil 71 } 72 // We don't have the definition of it at all. Need to parse that first. 73 sl := label.SubrepoLabel() 74 if err := parseSubrepoPackage(tid, state, sl.PackageName, "", label); err != nil { 75 return nil, err 76 } else if err := parseSubrepoPackage(tid, state, sl.PackageName, dependor.Subrepo, label); err != nil { 77 return nil, err 78 } 79 if subrepo := state.Graph.Subrepo(label.Subrepo); subrepo != nil { 80 return subrepo, nil 81 } else if subrepo := checkArchSubrepo(state, label.Subrepo); subrepo != nil { 82 return subrepo, nil 83 } 84 return nil, fmt.Errorf("Subrepo %s is not defined", label.Subrepo) 85 } 86 87 // parseSubrepoPackage parses a package to make sure subrepos are available. 88 func parseSubrepoPackage(tid int, state *core.BuildState, pkg, subrepo string, dependor core.BuildLabel) error { 89 if state.Graph.Package(pkg, subrepo) == nil { 90 // Don't have it already, must parse. 91 label := core.BuildLabel{Subrepo: subrepo, PackageName: pkg, Name: "all"} 92 return parse(tid, state, label, dependor, nil, nil, true) 93 } 94 return nil 95 } 96 97 // checkArchSubrepo checks if a target refers to a cross-compiling subrepo. 98 // Those don't have to be explicitly defined - maybe we should insist on that, but it's nicer not to have to. 99 func checkArchSubrepo(state *core.BuildState, name string) *core.Subrepo { 100 var arch cli.Arch 101 if err := arch.UnmarshalFlag(name); err == nil { 102 return state.Graph.MaybeAddSubrepo(core.SubrepoForArch(state, arch)) 103 } 104 return nil 105 } 106 107 // activateTarget marks a target as active (ie. to be built) and adds its dependencies as pending parses. 108 func activateTarget(state *core.BuildState, pkg *core.Package, label, dependor core.BuildLabel, forSubinclude bool, include, exclude []string) error { 109 if !label.IsAllTargets() && state.Graph.Target(label) == nil { 110 if label.Subrepo == "" && label.PackageName == "" && label.Name == dependor.Subrepo { 111 if subrepo := checkArchSubrepo(state, label.Name); subrepo != nil { 112 return nil 113 } 114 } 115 msg := fmt.Sprintf("Parsed build file %s but it doesn't contain target %s", pkg.Filename, label.Name) 116 if dependor != core.OriginalTarget { 117 msg += fmt.Sprintf(" (depended on by %s)", dependor) 118 } 119 return fmt.Errorf(msg + suggestTargets(pkg, label, dependor)) 120 } 121 if state.ParsePackageOnly && !forSubinclude { 122 return nil // Some kinds of query don't need a full recursive parse. 123 } else if label.IsAllTargets() { 124 for _, target := range pkg.AllTargets() { 125 // Don't activate targets that were added in a post-build function; that causes a race condition 126 // between the post-build functions running and other things trying to activate them too early. 127 if state.ShouldInclude(target) && !target.AddedPostBuild { 128 // Must always do this for coverage because we need to calculate sources of 129 // non-test targets later on. 130 if !state.NeedTests || target.IsTest || state.NeedCoverage { 131 addDep(state, target.Label, dependor, false, dependor.IsAllTargets()) 132 } 133 } 134 } 135 } else { 136 for _, l := range state.Graph.DependentTargets(dependor, label) { 137 // We use :all to indicate a dependency needed for parse. 138 addDep(state, l, dependor, false, forSubinclude || dependor.IsAllTargets()) 139 } 140 } 141 return nil 142 } 143 144 // parsePackage performs the initial parse of a package. 145 func parsePackage(state *core.BuildState, label, dependor core.BuildLabel, subrepo *core.Subrepo) (*core.Package, error) { 146 packageName := label.PackageName 147 pkg := core.NewPackage(packageName) 148 pkg.Subrepo = subrepo 149 if subrepo != nil { 150 pkg.SubrepoName = subrepo.Name 151 } 152 filename, dir := buildFileName(state, label.PackageName, subrepo) 153 if filename == "" { 154 if success, err := providePackage(state, pkg); err != nil { 155 return nil, err 156 } else if !success && packageName == "" && dependor.Subrepo == "pleasings" && subrepo == nil && state.Config.Parse.BuiltinPleasings { 157 // Deliberate fallthrough, for the case where someone depended on the default 158 // @pleasings subrepo, and there is no BUILD file at their root. 159 } else if !success { 160 exists := core.PathExists(dir) 161 // Handle quite a few cases to provide more obvious error messages. 162 if dependor != core.OriginalTarget && exists { 163 return nil, fmt.Errorf("%s depends on %s, but there's no BUILD file in %s/", dependor, label, dir) 164 } else if dependor != core.OriginalTarget { 165 return nil, fmt.Errorf("%s depends on %s, but the directory %s doesn't exist", dependor, label, dir) 166 } else if exists { 167 return nil, fmt.Errorf("Can't build %s; there's no BUILD file in %s/", label, dir) 168 } 169 return nil, fmt.Errorf("Can't build %s; the directory %s doesn't exist", label, dir) 170 } 171 } else { 172 pkg.Filename = filename 173 if err := state.Parser.ParseFile(state, pkg, pkg.Filename); err != nil { 174 return nil, err 175 } 176 } 177 // If the config setting is on, we "magically" register a default repo called @pleasings. 178 if packageName == "" && subrepo == nil && state.Config.Parse.BuiltinPleasings && pkg.Target("pleasings") == nil { 179 if _, err := state.Parser.(*aspParser).asp.ParseReader(pkg, strings.NewReader(pleasings)); err != nil { 180 log.Fatalf("Failed to load pleasings: %s", err) // This shouldn't happen, of course. 181 } 182 } 183 addPackage(state, pkg) 184 return pkg, nil 185 } 186 187 // addPackage adds the given package to the graph, with appropriate dependencies and whatnot. 188 func addPackage(state *core.BuildState, pkg *core.Package) { 189 allTargets := pkg.AllTargets() 190 for _, target := range allTargets { 191 state.Graph.AddTarget(target) 192 if target.IsFilegroup { 193 // At least register these guys as outputs. 194 // It's difficult to handle non-file sources because we don't know if they're 195 // parsed yet - recall filegroups are a special case for this since they don't 196 // explicitly declare their outputs but can re-output other rules' outputs. 197 for _, src := range target.AllLocalSources() { 198 pkg.MustRegisterOutput(src, target) 199 } 200 } else { 201 for _, out := range target.DeclaredOutputs() { 202 pkg.MustRegisterOutput(out, target) 203 } 204 for _, out := range target.TestOutputs { 205 if !fs.IsGlob(out) { 206 pkg.MustRegisterOutput(out, target) 207 } 208 } 209 } 210 } 211 // Do this in a separate loop so we get intra-package dependencies right now. 212 for _, target := range allTargets { 213 for _, dep := range target.DeclaredDependencies() { 214 state.Graph.AddDependency(target.Label, dep) 215 } 216 } 217 // Verify some details of the output files in the background. Don't need to wait for this 218 // since it only issues warnings sometimes. 219 go pkg.VerifyOutputs() 220 state.Graph.AddPackage(pkg) // Calling this means nobody else will add entries to pendingTargets for this package. 221 } 222 223 // buildFileName returns the name of the BUILD file for a package, or the empty string if one 224 // doesn't exist. It also returns the directory that it looked in. 225 func buildFileName(state *core.BuildState, pkgName string, subrepo *core.Subrepo) (string, string) { 226 config := state.Config 227 if subrepo != nil { 228 pkgName = subrepo.Dir(pkgName) 229 config = subrepo.State.Config 230 } 231 // Bazel defines targets in its "external" package from its WORKSPACE file. 232 // We will fake this by treating that as an actual package file... 233 // TODO(peterebden): They may be moving away from their "external" nomenclature? 234 if state.Config.Bazel.Compatibility && pkgName == "external" || pkgName == "workspace" { 235 return "WORKSPACE", "" 236 } 237 for _, buildFileName := range config.Parse.BuildFileName { 238 if filename := path.Join(core.RepoRoot, pkgName, buildFileName); fs.FileExists(filename) { 239 return filename, pkgName 240 } 241 } 242 return "", pkgName 243 } 244 245 // Adds a single target to the build queue. 246 func addDep(state *core.BuildState, label, dependor core.BuildLabel, rescan, forceBuild bool) { 247 // Stop at any package that's not loaded yet 248 if state.Graph.PackageByLabel(label) == nil { 249 if forceBuild { 250 log.Debug("Adding forced pending parse of %s", label) 251 } 252 state.AddPendingParse(label, dependor, forceBuild) 253 return 254 } 255 target := state.Graph.Target(label) 256 if target == nil { 257 log.Fatalf("Target %s (referenced by %s) doesn't exist\n", label, dependor) 258 } 259 if target.State() >= core.Active && !rescan && !forceBuild { 260 return // Target is already tagged to be built and likely on the queue. 261 } 262 // Only do this bit if we actually need to build the target 263 if !target.SyncUpdateState(core.Inactive, core.Semiactive) && !rescan && !forceBuild { 264 return 265 } 266 if state.NeedBuild || forceBuild { 267 if target.SyncUpdateState(core.Semiactive, core.Active) { 268 state.AddActiveTarget() 269 if target.IsTest && state.NeedTests { 270 state.AddActiveTarget() // Tests count twice if we're gonna run them. 271 } 272 } 273 } 274 // If this target has no deps, add it to the queue now, otherwise handle its deps. 275 // Only add if we need to build targets (not if we're just parsing) but we might need it to parse... 276 if target.State() == core.Active && state.Graph.AllDepsBuilt(target) { 277 if target.SyncUpdateState(core.Active, core.Pending) { 278 state.AddPendingBuild(label, dependor.IsAllTargets()) 279 } 280 if !rescan { 281 return 282 } 283 } 284 for _, dep := range target.DeclaredDependencies() { 285 // Check the require/provide stuff; we may need to add a different target. 286 if len(target.Requires) > 0 { 287 if depTarget := state.Graph.Target(dep); depTarget != nil && len(depTarget.Provides) > 0 { 288 for _, provided := range depTarget.ProvideFor(target) { 289 addDep(state, provided, label, false, forceBuild) 290 } 291 continue 292 } 293 } 294 addDep(state, dep, label, false, forceBuild) 295 } 296 } 297 298 func rescanDeps(state *core.BuildState, changed map[*core.BuildTarget]struct{}) { 299 // Run over all the changed targets in this package and ensure that any newly added dependencies enter the build queue. 300 for target := range changed { 301 if !state.Graph.AllDependenciesResolved(target) { 302 for _, dep := range target.DeclaredDependencies() { 303 state.Graph.AddDependency(target.Label, dep) 304 } 305 } 306 if s := target.State(); s < core.Built && s > core.Inactive { 307 addDep(state, target.Label, core.OriginalTarget, true, false) 308 } 309 } 310 } 311 312 // This is the builtin subrepo for pleasings. 313 // TODO(peterebden): Should really provide a github_archive builtin that knows how to construct 314 // the URL and strip_prefix etc. 315 const pleasings = ` 316 http_archive( 317 name = "pleasings", 318 strip_prefix = "pleasings-master", 319 urls = ["https://github.com/thought-machine/pleasings/archive/master.zip"], 320 ) 321 ` 322 323 // providePackage looks through all the configured BUILD file providers to see if any of them 324 // can handle the given package. It returns true if any of them did. 325 // N.B. More than one is allowed to handle a single directory. 326 func providePackage(state *core.BuildState, pkg *core.Package) (bool, error) { 327 if len(state.Config.Provider) == 0 { 328 return false, nil 329 } 330 success := false 331 label := pkg.Label() 332 for name, p := range state.Config.Provider { 333 if !shouldProvide(p.Path, label) { 334 continue 335 } 336 t := state.WaitForBuiltTarget(p.Target, label) 337 outs := t.Outputs() 338 if !t.IsBinary && len(outs) != 1 { 339 log.Error("Cannot use %s as build provider %s, it must be a binary with exactly 1 output.", p.Target, name) 340 continue 341 } 342 dir := pkg.SourceRoot() 343 resp, err := worker.ProvideParse(state, path.Join(t.OutDir(), outs[0]), dir) 344 if err != nil { 345 return false, fmt.Errorf("Failed to start build provider %s: %s", name, err) 346 } else if resp != "" { 347 log.Debug("Received BUILD file from %s provider for %s: %s", name, dir, resp) 348 if err := state.Parser.ParseReader(state, pkg, strings.NewReader(resp)); err != nil { 349 return false, err 350 } 351 success = true 352 } 353 } 354 return success, nil 355 } 356 357 // shouldProvide returns true if a provider's set of configured paths overlaps a package. 358 func shouldProvide(paths []core.BuildLabel, label core.BuildLabel) bool { 359 for _, p := range paths { 360 if p.Includes(label) { 361 return true 362 } 363 } 364 return false 365 }