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 }