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  }