github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/cmd/move_labels/move_labels.go (about) 1 /* Copyright 2018 The Bazel Authors. All rights reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package main 17 18 import ( 19 "bytes" 20 "errors" 21 "flag" 22 "fmt" 23 "log" 24 "os" 25 "path" 26 "path/filepath" 27 "regexp" 28 "strings" 29 30 "github.com/bazelbuild/bazel-gazelle/internal/wspace" 31 "github.com/bazelbuild/bazel-gazelle/label" 32 "github.com/bazelbuild/bazel-gazelle/pathtools" 33 "github.com/bazelbuild/buildtools/build" 34 ) 35 36 const usageMessage = `usage: move_labels [-repo_root=root] [-from=dir] -to=dir 37 38 move_labels updates Bazel labels in a tree containing build files after the 39 tree has been moved to a new location. This is useful for vendoring 40 repositories that already have Bazel build files. 41 42 ` 43 44 func main() { 45 log.SetPrefix("move_labels: ") 46 log.SetFlags(0) 47 if err := run(os.Args[1:]); err != nil { 48 log.Fatal(err) 49 } 50 } 51 52 func run(args []string) error { 53 c, err := newConfiguration(args) 54 if err != nil { 55 return err 56 } 57 58 files, err := moveLabelsInDir(c) 59 if err != nil { 60 return err 61 } 62 63 var errs errorList 64 for _, file := range files { 65 content := build.Format(file) 66 if err := os.WriteFile(file.Path, content, 0o666); err != nil { 67 errs = append(errs, err) 68 } 69 } 70 if len(errs) > 0 { 71 return errs 72 } 73 return nil 74 } 75 76 func moveLabelsInDir(c *configuration) ([]*build.File, error) { 77 toRel, err := filepath.Rel(c.repoRoot, c.to) 78 if err != nil { 79 return nil, err 80 } 81 toRel = filepath.ToSlash(toRel) 82 83 var files []*build.File 84 var errors errorList 85 err = filepath.Walk(c.to, func(path string, info os.FileInfo, err error) error { 86 if err != nil { 87 return err 88 } 89 if name := info.Name(); name != "BUILD" && name != "BUILD.bazel" { 90 return nil 91 } 92 content, err := os.ReadFile(path) 93 if err != nil { 94 errors = append(errors, err) 95 return nil 96 } 97 file, err := build.Parse(path, content) 98 if err != nil { 99 errors = append(errors, err) 100 return nil 101 } 102 moveLabelsInFile(file, c.from, toRel) 103 files = append(files, file) 104 return nil 105 }) 106 if err != nil { 107 return nil, err 108 } 109 if len(errors) > 0 { 110 return nil, errors 111 } 112 return files, nil 113 } 114 115 func moveLabelsInFile(file *build.File, from, to string) { 116 build.Edit(file, func(x build.Expr, _ []build.Expr) build.Expr { 117 str, ok := x.(*build.StringExpr) 118 if !ok { 119 return nil 120 } 121 label := str.Value 122 var moved string 123 if strings.Contains(label, "$(location") { 124 moved = moveLocations(from, to, label) 125 } else { 126 moved = moveLabel(from, to, label) 127 } 128 if moved == label { 129 return nil 130 } 131 return &build.StringExpr{Value: moved} 132 }) 133 } 134 135 func moveLabel(from, to, str string) string { 136 l, err := label.Parse(str) 137 if err != nil { 138 return str 139 } 140 if l.Relative || l.Repo != "" || 141 l.Pkg == "visibility" || l.Pkg == "conditions" || 142 pathtools.HasPrefix(l.Pkg, to) || !pathtools.HasPrefix(l.Pkg, from) { 143 return str 144 } 145 l.Pkg = path.Join(to, pathtools.TrimPrefix(l.Pkg, from)) 146 return l.String() 147 } 148 149 var locationsRegexp = regexp.MustCompile(`\$\(locations?\s*([^)]*)\)`) 150 151 // moveLocations fixes labels within $(location) and $(locations) expansions. 152 func moveLocations(from, to, str string) string { 153 matches := locationsRegexp.FindAllStringSubmatchIndex(str, -1) 154 buf := new(bytes.Buffer) 155 pos := 0 156 for _, match := range matches { 157 buf.WriteString(str[pos:match[2]]) 158 label := str[match[2]:match[3]] 159 moved := moveLabel(from, to, label) 160 buf.WriteString(moved) 161 buf.WriteString(str[match[3]:match[1]]) 162 pos = match[1] 163 } 164 buf.WriteString(str[pos:]) 165 return buf.String() 166 } 167 168 type configuration struct { 169 // repoRoot is the repository root directory, formatted as an absolute 170 // file system path. 171 repoRoot string 172 173 // from is the original location of the build files within their repository, 174 // formatted as a slash-separated relative path from the original 175 // repository root. 176 from string 177 178 // to is the new location of the build files, formatted as an absolute 179 // file system path. 180 to string 181 } 182 183 func newConfiguration(args []string) (*configuration, error) { 184 var err error 185 c := &configuration{} 186 fs := flag.NewFlagSet("move_labels", flag.ContinueOnError) 187 fs.Usage = func() {} 188 fs.StringVar(&c.repoRoot, "repo_root", "", "repository root directory; inferred to be parent directory containing WORKSPACE file") 189 fs.StringVar(&c.from, "from", "", "original location of build files, formatted as a slash-separated relative path from the original repository root") 190 fs.StringVar(&c.to, "to", "", "new location of build files, formatted as a file system path") 191 if err := fs.Parse(args); err != nil { 192 if err == flag.ErrHelp { 193 fmt.Fprint(os.Stderr, usageMessage) 194 fs.PrintDefaults() 195 os.Exit(0) 196 } 197 // flag already prints an error; don't print again. 198 return nil, errors.New("Try -help for more information") 199 } 200 201 if c.repoRoot == "" { 202 c.repoRoot, err = findRepoRoot() 203 if err != nil { 204 return nil, err 205 } 206 } 207 c.repoRoot, err = filepath.Abs(c.repoRoot) 208 if err != nil { 209 return nil, err 210 } 211 212 if c.to == "" { 213 return nil, errors.New("-to must be specified. Try -help for more information.") 214 } 215 c.to, err = filepath.Abs(c.to) 216 if err != nil { 217 return nil, err 218 } 219 220 if len(fs.Args()) != 0 { 221 return nil, errors.New("No positional arguments expected. Try -help for more information.") 222 } 223 224 return c, nil 225 } 226 227 func findRepoRoot() (string, error) { 228 dir, err := os.Getwd() 229 if err != nil { 230 return "", err 231 } 232 root, err := wspace.FindRepoRoot(dir) 233 if err != nil { 234 return "", fmt.Errorf("could not find WORKSPACE file. -repo_root must be set explicitly") 235 } 236 return root, nil 237 } 238 239 type errorList []error 240 241 func (e errorList) Error() string { 242 buf := new(bytes.Buffer) 243 for _, err := range e { 244 fmt.Fprintln(buf, err.Error()) 245 } 246 return buf.String() 247 }