blob: a16860df1a71f0c149335c8d9bfab443de97a47f [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanskicbd02be2021-09-24 13:39:12 +02004package toolbase
5
6import (
7 "fmt"
8 "regexp"
9 "strings"
10)
11
12// BazelLabel is a label, as defined by Bazel's documentation:
13//
14// https://docs.bazel.build/versions/main/skylark/lib/Label.html
15type BazelLabel struct {
16 WorkspaceName string
17 PackagePath []string
18 Name string
19}
20
21func (b BazelLabel) Package() string {
22 return strings.Join(b.PackagePath, "/")
23}
24
25func (b BazelLabel) String() string {
26 return fmt.Sprintf("@%s//%s:%s", b.WorkspaceName, b.Package(), b.Name)
27}
28
29var (
30 // reLabel splits a Bazel label into a workspace name (if set) and a
31 // workspace root relative package/name.
32 reLabel = regexp.MustCompile(`^(@[^:/]+)?//(.+)$`)
33 // rePathPart matches valid label path parts.
34 rePathPart = regexp.MustCompile(`^[^:/]+$`)
35)
36
37// ParseBazelLabel converts parses a string representation of a Bazel Label. If
38// the given representation is invalid or for some other reason unparseable,
39// nil is returned.
40func ParseBazelLabel(s string) *BazelLabel {
41 res := BazelLabel{
Tim Windelschmidtafeb4c42024-07-17 21:37:26 +020042 WorkspaceName: "@",
Serge Bazanskicbd02be2021-09-24 13:39:12 +020043 }
44
45 // Split label into workspace name (if set) and a workspace root relative
46 // package/name.
47 m := reLabel.FindStringSubmatch(s)
48 if m == nil {
49 return nil
50 }
51 packageRel := m[2]
52 if m[1] != "" {
53 res.WorkspaceName = m[1][1:]
54 }
55
56 // Split path by ':', which is the target name delimiter. If it appears
57 // exactly once, interpret everything to its right as the target name.
58 targetSplit := strings.Split(packageRel, ":")
59 switch len(targetSplit) {
60 case 1:
61 case 2:
62 packageRel = targetSplit[0]
63 res.Name = targetSplit[1]
64 if !rePathPart.MatchString(res.Name) {
65 return nil
66 }
67 default:
68 return nil
69 }
70
71 // Split the package path by /, and if the name was not explicitly given,
72 // use the last element of the package path.
73 if packageRel == "" {
74 res.PackagePath = nil
75 } else {
76 res.PackagePath = strings.Split(packageRel, "/")
77 }
78 if res.Name == "" {
79 res.Name = res.PackagePath[len(res.PackagePath)-1]
80 }
81
82 // Ensure all parts of the package path are valid.
83 for _, p := range res.PackagePath {
84 if !rePathPart.MatchString(p) {
85 return nil
86 }
87 }
88
89 return &res
90}