| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame^] | 1 | // Copyright The Monogon Project Authors. |
| 2 | // SPDX-License-Identifier: Apache-2.0 |
| 3 | |
| Serge Bazanski | cbd02be | 2021-09-24 13:39:12 +0200 | [diff] [blame] | 4 | package toolbase |
| 5 | |
| 6 | import ( |
| 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 |
| 15 | type BazelLabel struct { |
| 16 | WorkspaceName string |
| 17 | PackagePath []string |
| 18 | Name string |
| 19 | } |
| 20 | |
| 21 | func (b BazelLabel) Package() string { |
| 22 | return strings.Join(b.PackagePath, "/") |
| 23 | } |
| 24 | |
| 25 | func (b BazelLabel) String() string { |
| 26 | return fmt.Sprintf("@%s//%s:%s", b.WorkspaceName, b.Package(), b.Name) |
| 27 | } |
| 28 | |
| 29 | var ( |
| 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. |
| 40 | func ParseBazelLabel(s string) *BazelLabel { |
| 41 | res := BazelLabel{ |
| Tim Windelschmidt | afeb4c4 | 2024-07-17 21:37:26 +0200 | [diff] [blame] | 42 | WorkspaceName: "@", |
| Serge Bazanski | cbd02be | 2021-09-24 13:39:12 +0200 | [diff] [blame] | 43 | } |
| 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 | } |