github.com/tiagovtristao/plz@v13.4.0+incompatible/src/core/build_label.go (about)

     1  package core
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"strings"
     8  
     9  	"github.com/jessevdk/go-flags"
    10  	"gopkg.in/op/go-logging.v1"
    11  )
    12  
    13  var log = logging.MustGetLogger("core")
    14  
    15  // A BuildLabel is a representation of an identifier of a build target, e.g. //spam/eggs:ham
    16  // corresponds to BuildLabel{PackageName: spam/eggs name: ham}
    17  // BuildLabels are always absolute, so relative identifiers
    18  // like :ham are always parsed into an absolute form.
    19  // There is also implicit expansion of the final element of a target (ala Blaze)
    20  // so //spam/eggs is equivalent to //spam/eggs:eggs
    21  //
    22  // It can also be in a subrepo, in which case the syntax is @subrepo//spam/eggs:ham.
    23  type BuildLabel struct {
    24  	PackageName string
    25  	Name        string
    26  	Subrepo     string
    27  }
    28  
    29  // WholeGraph represents parsing the entire graph (i.e. //...).
    30  // We use this specially in one or two places.
    31  var WholeGraph = []BuildLabel{{PackageName: "", Name: "..."}}
    32  
    33  // BuildLabelStdin is used to indicate that we're going to consume build labels from stdin.
    34  var BuildLabelStdin = BuildLabel{PackageName: "", Name: "_STDIN"}
    35  
    36  // OriginalTarget is used to indicate one of the originally requested targets on the command line.
    37  var OriginalTarget = BuildLabel{PackageName: "", Name: "_ORIGINAL"}
    38  
    39  // String returns a string representation of this build label.
    40  func (label BuildLabel) String() string {
    41  	s := "//" + label.PackageName
    42  	if label.Subrepo != "" {
    43  		s = "@" + label.Subrepo + s
    44  	}
    45  	if label.IsAllSubpackages() {
    46  		if label.PackageName == "" {
    47  			return s + "..."
    48  		}
    49  		return s + "/..."
    50  	}
    51  	return s + ":" + label.Name
    52  }
    53  
    54  // NewBuildLabel constructs a new build label from the given components. Panics on failure.
    55  func NewBuildLabel(pkgName, name string) BuildLabel {
    56  	label, err := TryNewBuildLabel(pkgName, name)
    57  	if err != nil {
    58  		panic(err)
    59  	}
    60  	return label
    61  }
    62  
    63  // TryNewBuildLabel constructs a new build label from the given components.
    64  func TryNewBuildLabel(pkgName, name string) (BuildLabel, error) {
    65  	if err := validateNames(pkgName, name); err != nil {
    66  		return BuildLabel{}, err
    67  	}
    68  	return BuildLabel{PackageName: pkgName, Name: name}, nil
    69  }
    70  
    71  // validateNames returns an error if the package name of target name isn't accepted.
    72  func validateNames(pkgName, name string) error {
    73  	if !validatePackageName(pkgName) {
    74  		return fmt.Errorf("Invalid package name: %s", pkgName)
    75  	} else if !validateTargetName(name) {
    76  		return fmt.Errorf("Invalid target name: %s", name)
    77  	} else if err := validateSuffixes(pkgName, name); err != nil {
    78  		return err
    79  	}
    80  	return nil
    81  }
    82  
    83  // validateSuffixes checks that there are no invalid suffixes on the target name.
    84  func validateSuffixes(pkgName, name string) error {
    85  	if strings.HasSuffix(name, buildDirSuffix) ||
    86  		strings.HasSuffix(name, testDirSuffix) ||
    87  		strings.HasSuffix(pkgName, buildDirSuffix) ||
    88  		strings.HasSuffix(pkgName, testDirSuffix) {
    89  
    90  		return fmt.Errorf("._build and ._test are reserved suffixes")
    91  	}
    92  	return nil
    93  }
    94  
    95  // validatePackageName checks whether this string is a valid package name and returns true if so.
    96  func validatePackageName(name string) bool {
    97  	return name == "" || (name[0] != '/' && name[len(name)-1] != '/' && !strings.ContainsAny(name, `|$*?[]{}:()&\`) && !strings.Contains(name, "//"))
    98  }
    99  
   100  // validateTargetName checks whether this string is a valid target name and returns true if so.
   101  func validateTargetName(name string) bool {
   102  	return name != "" && !strings.ContainsAny(name, `|$*?[]{}:()&/\`) && (name[0] != '.' || name == "...") &&
   103  		!strings.HasSuffix(name, buildDirSuffix) && !strings.HasSuffix(name, testDirSuffix)
   104  }
   105  
   106  // ParseBuildLabel parses a single build label from a string. Panics on failure.
   107  func ParseBuildLabel(target, currentPath string) BuildLabel {
   108  	label, err := TryParseBuildLabel(target, currentPath)
   109  	if err != nil {
   110  		panic(err)
   111  	}
   112  	return label
   113  }
   114  
   115  // TryParseBuildLabel attempts to parse a single build label from a string. Returns an error if unsuccessful.
   116  func TryParseBuildLabel(target, currentPath string) (BuildLabel, error) {
   117  	if pkg, name, subrepo := parseBuildLabelParts(target, currentPath, nil); name != "" {
   118  		return BuildLabel{PackageName: pkg, Name: name, Subrepo: subrepo}, nil
   119  	}
   120  	return BuildLabel{}, fmt.Errorf("Invalid build label: %s", target)
   121  }
   122  
   123  // ParseBuildLabelContext parses a build label in the context of a package.
   124  // It panics on error.
   125  func ParseBuildLabelContext(target string, pkg *Package) BuildLabel {
   126  	if p, name, subrepo := parseBuildLabelParts(target, pkg.Name, pkg.Subrepo); name != "" {
   127  		if subrepo == "" && pkg.Subrepo != nil && target[0] != '@' {
   128  			subrepo = pkg.Subrepo.Name
   129  		}
   130  		return BuildLabel{PackageName: p, Name: name, Subrepo: subrepo}
   131  	}
   132  	// It's gonna fail, let this guy panic for us.
   133  	return ParseBuildLabel(target, pkg.Name)
   134  }
   135  
   136  // parseBuildLabelParts parses a build label into the package & name parts.
   137  // If valid, the name string will always be populated; the package string might not be if it's a local form.
   138  func parseBuildLabelParts(target, currentPath string, subrepo *Subrepo) (string, string, string) {
   139  	if len(target) < 2 { // Always must start with // or : and must have at least one char following.
   140  		return "", "", ""
   141  	} else if target[0] == ':' {
   142  		if !validateTargetName(target[1:]) {
   143  			return "", "", ""
   144  		}
   145  		return currentPath, target[1:], ""
   146  	} else if target[0] == '@' {
   147  		// @subrepo//pkg:target or @subrepo:target syntax
   148  		idx := strings.Index(target, "//")
   149  		if idx == -1 {
   150  			// if subrepo and target are the same name, then @subrepo syntax will also suffice
   151  			if idx = strings.IndexRune(target, ':'); idx == -1 {
   152  				return "", target[1:], target[1:]
   153  			}
   154  		}
   155  		pkg, name, _ := parseBuildLabelParts(target[idx:], currentPath, subrepo)
   156  		return pkg, name, target[1:idx]
   157  	} else if target[0] != '/' || target[1] != '/' {
   158  		return "", "", ""
   159  	} else if idx := strings.IndexRune(target, ':'); idx != -1 {
   160  		pkg := target[2:idx]
   161  		name := target[idx+1:]
   162  		// Check ... explicitly to prevent :... which isn't allowed.
   163  		if !validatePackageName(pkg) || !validateTargetName(name) || name == "..." {
   164  			return "", "", ""
   165  		}
   166  		return pkg, name, ""
   167  	} else if !validatePackageName(target[2:]) {
   168  		return "", "", ""
   169  	}
   170  	// Must be the abbreviated form (//pkg) or subtargets (//pkg/...), there's no : in it.
   171  	if strings.HasSuffix(target, "/...") {
   172  		return strings.TrimRight(target[2:len(target)-3], "/"), "...", ""
   173  	} else if idx := strings.LastIndexByte(target, '/'); idx != -1 {
   174  		return target[2:], target[idx+1:], ""
   175  	}
   176  	return target[2:], target[2:], ""
   177  }
   178  
   179  // As above, but allows parsing of relative labels (eg. src/parse/rules:python_rules)
   180  // which is convenient at the shell prompt
   181  func parseMaybeRelativeBuildLabel(target, subdir string) (BuildLabel, error) {
   182  	// Try the ones that don't need locating the repo root first.
   183  	startsWithColon := strings.HasPrefix(target, ":")
   184  	if !startsWithColon {
   185  		if label, err := TryParseBuildLabel(target, ""); err == nil || strings.HasPrefix(target, "//") {
   186  			return label, err
   187  		}
   188  	}
   189  	// Now we need to locate the repo root and initial package.
   190  	// Deliberately leave this till after the above to facilitate the --repo_root flag.
   191  	if subdir == "" {
   192  		MustFindRepoRoot()
   193  		subdir = initialPackage
   194  	}
   195  	if startsWithColon {
   196  		return TryParseBuildLabel(target, subdir)
   197  	}
   198  	// Presumably it's just underneath this directory (note that if it was absolute we returned above)
   199  	return TryParseBuildLabel("//"+path.Join(subdir, target), "")
   200  }
   201  
   202  // ParseBuildLabels parses a bunch of build labels from strings. It dies on failure.
   203  // Relative labels are allowed since this is generally used at initialisation.
   204  func ParseBuildLabels(targets []string) []BuildLabel {
   205  	ret := make([]BuildLabel, len(targets))
   206  	for i, target := range targets {
   207  		if label, err := parseMaybeRelativeBuildLabel(target, ""); err != nil {
   208  			log.Fatalf("%s", err)
   209  		} else {
   210  			ret[i] = label
   211  		}
   212  	}
   213  	return ret
   214  }
   215  
   216  // IsAllSubpackages returns true if the label ends in ..., ie. it includes all subpackages.
   217  func (label BuildLabel) IsAllSubpackages() bool {
   218  	return label.Name == "..."
   219  }
   220  
   221  // IsAllTargets returns true if the label is the pseudo-label referring to all targets in this package.
   222  func (label BuildLabel) IsAllTargets() bool {
   223  	return label.Name == "all"
   224  }
   225  
   226  // Includes returns true if label includes the other label (//pkg:target1 is covered by //pkg:all etc).
   227  func (label BuildLabel) Includes(that BuildLabel) bool {
   228  	if (label.PackageName == "" && label.IsAllSubpackages()) ||
   229  		that.PackageName == label.PackageName ||
   230  		strings.HasPrefix(that.PackageName, label.PackageName+"/") {
   231  		// We're in the same package or a subpackage of this visibility spec
   232  		if label.IsAllSubpackages() {
   233  			return true
   234  		} else if label.PackageName == that.PackageName {
   235  			if label.Name == that.Name || label.IsAllTargets() {
   236  				return true
   237  			}
   238  		}
   239  	}
   240  	return false
   241  }
   242  
   243  // Less returns true if this build label would sort less than another one.
   244  func (label BuildLabel) Less(other BuildLabel) bool {
   245  	if label.PackageName == other.PackageName {
   246  		return label.Name < other.Name
   247  	}
   248  	return label.PackageName < other.PackageName
   249  }
   250  
   251  // Paths is an implementation of BuildInput interface; we use build labels directly as inputs.
   252  func (label BuildLabel) Paths(graph *BuildGraph) []string {
   253  	return addPathPrefix(graph.TargetOrDie(label).Outputs(), label.PackageName)
   254  }
   255  
   256  // FullPaths is an implementation of BuildInput interface.
   257  func (label BuildLabel) FullPaths(graph *BuildGraph) []string {
   258  	target := graph.TargetOrDie(label)
   259  	return addPathPrefix(target.Outputs(), target.OutDir())
   260  }
   261  
   262  // addPathPrefix adds a prefix to all the entries in a slice.
   263  func addPathPrefix(paths []string, prefix string) []string {
   264  	ret := make([]string, len(paths))
   265  	for i, output := range paths {
   266  		ret[i] = path.Join(prefix, output)
   267  	}
   268  	return ret
   269  }
   270  
   271  // LocalPaths is an implementation of BuildInput interface.
   272  func (label BuildLabel) LocalPaths(graph *BuildGraph) []string {
   273  	return graph.TargetOrDie(label).Outputs()
   274  }
   275  
   276  // Label is an implementation of BuildInput interface. It always returns this label.
   277  func (label BuildLabel) Label() *BuildLabel {
   278  	return &label
   279  }
   280  
   281  func (label BuildLabel) nonOutputLabel() *BuildLabel {
   282  	return &label
   283  }
   284  
   285  // UnmarshalFlag unmarshals a build label from a command line flag. Implementation of flags.Unmarshaler interface.
   286  func (label *BuildLabel) UnmarshalFlag(value string) error {
   287  	// This is only allowable here, not in any other usage of build labels.
   288  	if value == "-" {
   289  		*label = BuildLabelStdin
   290  		return nil
   291  	} else if l, err := parseMaybeRelativeBuildLabel(value, ""); err != nil {
   292  		// This has to be fatal because of the way we're using the flags package;
   293  		// we lose incoming flags if we return errors.
   294  		// But don't die in completion mode.
   295  		if os.Getenv("PLZ_COMPLETE") == "" {
   296  			log.Fatalf("%s", err)
   297  		}
   298  	} else {
   299  		*label = l
   300  	}
   301  	return nil
   302  }
   303  
   304  // UnmarshalText implements the encoding.TextUnmarshaler interface.
   305  // This is used by gcfg to unmarshal the config files.
   306  func (label *BuildLabel) UnmarshalText(text []byte) error {
   307  	l, err := TryParseBuildLabel(string(text), "")
   308  	*label = l
   309  	return err
   310  }
   311  
   312  // Parent returns what would be the parent of a build label, or the label itself if it's parentless.
   313  // Note that there is not a concrete guarantee that the returned label exists in the build graph,
   314  // and that the label returned is the ultimate ancestor (ie. not necessarily immediate parent).
   315  func (label BuildLabel) Parent() BuildLabel {
   316  	index := strings.IndexRune(label.Name, '#')
   317  	if index == -1 || !strings.HasPrefix(label.Name, "_") {
   318  		return label
   319  	}
   320  	label.Name = strings.TrimLeft(label.Name[:index], "_")
   321  	return label
   322  }
   323  
   324  // HasParent returns true if the build label has a parent that's not itself.
   325  func (label BuildLabel) HasParent() bool {
   326  	return label.Parent() != label
   327  }
   328  
   329  // IsEmpty returns true if this is an empty build label, i.e. nothing's populated it yet.
   330  func (label BuildLabel) IsEmpty() bool {
   331  	return label.PackageName == "" && label.Name == ""
   332  }
   333  
   334  // PackageDir returns a path to the directory this target is in.
   335  // This is equivalent to PackageName in all cases except when at the repo root, when this
   336  // will return . instead. This is often easier to use in build rules.
   337  func (label BuildLabel) PackageDir() string {
   338  	if label.PackageName == "" {
   339  		return "."
   340  	}
   341  	return label.PackageName
   342  }
   343  
   344  // SubrepoLabel returns a build label corresponding to the subrepo part of this build label.
   345  func (label BuildLabel) SubrepoLabel() BuildLabel {
   346  	if idx := strings.LastIndexByte(label.Subrepo, '/'); idx != -1 {
   347  		return BuildLabel{PackageName: label.Subrepo[:idx], Name: label.Subrepo[idx+1:]}
   348  	}
   349  	// This is legit, the subrepo is defined at the root.
   350  	return BuildLabel{Name: label.Subrepo}
   351  }
   352  
   353  // Complete implements the flags.Completer interface, which is used for shell completion.
   354  // Unfortunately it's rather awkward to handle here; we need to do a proper parse in order
   355  // to find out what the possible build labels are, and we're not ready for that yet.
   356  // Returning to main is also awkward since the flags haven't parsed properly; all in all
   357  // it seems an easier (albeit inelegant) solution to start things over by re-execing ourselves.
   358  func (label BuildLabel) Complete(match string) []flags.Completion {
   359  	if match == "" {
   360  		os.Exit(0)
   361  	}
   362  	os.Setenv("PLZ_COMPLETE", match)
   363  	os.Unsetenv("GO_FLAGS_COMPLETION")
   364  	exec, _ := os.Executable()
   365  	out, _, err := ExecWithTimeout(nil, "", os.Environ(), 0, 0, false, false, append([]string{exec}, os.Args[1:]...), "")
   366  	if err != nil {
   367  		return nil
   368  	}
   369  	ret := []flags.Completion{}
   370  	for _, line := range strings.Split(string(out), "\n") {
   371  		if line != "" {
   372  			ret = append(ret, flags.Completion{Item: line})
   373  		}
   374  	}
   375  	return ret
   376  }
   377  
   378  // A packageKey is a cut-down version of BuildLabel that only contains the package part.
   379  // It's used to key maps and so forth that don't care about the target name.
   380  type packageKey struct {
   381  	Name, Subrepo string
   382  }
   383  
   384  // String implements the traditional fmt.Stringer interface.
   385  func (key packageKey) String() string {
   386  	if key.Subrepo != "" {
   387  		return "@" + key.Subrepo + "//" + key.Name
   388  	}
   389  	return key.Name
   390  }
   391  
   392  // LooksLikeABuildLabel returns true if the string appears to be a build label, false if not.
   393  // Useful for cases like rule sources where sources can be a filename or a label.
   394  func LooksLikeABuildLabel(str string) bool {
   395  	return strings.HasPrefix(str, "//") || strings.HasPrefix(str, ":") || (strings.HasPrefix(str, "@") && (strings.ContainsRune(str, ':') || strings.Contains(str, "//")))
   396  }
   397  
   398  // BuildLabels makes slices of build labels sortable.
   399  type BuildLabels []BuildLabel
   400  
   401  func (slice BuildLabels) Len() int {
   402  	return len(slice)
   403  }
   404  func (slice BuildLabels) Less(i, j int) bool {
   405  	return slice[i].Less(slice[j])
   406  }
   407  func (slice BuildLabels) Swap(i, j int) {
   408  	slice[i], slice[j] = slice[j], slice[i]
   409  }