github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/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 "io/ioutil" 24 "log" 25 "os" 26 "path" 27 "path/filepath" 28 "regexp" 29 "strings" 30 31 "github.com/bazelbuild/bazel-gazelle/internal/label" 32 "github.com/bazelbuild/bazel-gazelle/internal/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 := ioutil.WriteFile(file.Path, content, 0666); 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 := ioutil.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 func isBuiltinLabel(label string) bool { 169 return strings.HasPrefix(label, "//visibility:") || strings.HasPrefix(label, "//conditions:") 170 } 171 172 type configuration struct { 173 // repoRoot is the repository root directory, formatted as an absolute 174 // file system path. 175 repoRoot string 176 177 // from is the original location of the build files within their repository, 178 // formatted as a slash-separated relative path from the original 179 // repository root. 180 from string 181 182 // to is the new location of the build files, formatted as an absolute 183 // file system path. 184 to string 185 } 186 187 func newConfiguration(args []string) (*configuration, error) { 188 var err error 189 c := &configuration{} 190 fs := flag.NewFlagSet("move_labels", flag.ContinueOnError) 191 fs.Usage = func() {} 192 fs.StringVar(&c.repoRoot, "repo_root", "", "repository root directory; inferred to be parent directory containing WORKSPACE file") 193 fs.StringVar(&c.from, "from", "", "original location of build files, formatted as a slash-separated relative path from the original repository root") 194 fs.StringVar(&c.to, "to", "", "new location of build files, formatted as a file system path") 195 if err := fs.Parse(args); err != nil { 196 if err == flag.ErrHelp { 197 fmt.Fprint(os.Stderr, usageMessage) 198 fs.PrintDefaults() 199 os.Exit(0) 200 } 201 // flag already prints an error; don't print again. 202 return nil, errors.New("Try -help for more information") 203 } 204 205 if c.repoRoot == "" { 206 c.repoRoot, err = findRepoRoot() 207 if err != nil { 208 return nil, err 209 } 210 } 211 c.repoRoot, err = filepath.Abs(c.repoRoot) 212 if err != nil { 213 return nil, err 214 } 215 216 if c.to == "" { 217 return nil, errors.New("-to must be specified. Try -help for more information.") 218 } 219 c.to, err = filepath.Abs(c.to) 220 if err != nil { 221 return nil, err 222 } 223 224 if len(fs.Args()) != 0 { 225 return nil, errors.New("No positional arguments expected. Try -help for more information.") 226 } 227 228 return c, nil 229 } 230 231 func findRepoRoot() (string, error) { 232 dir, err := os.Getwd() 233 if err != nil { 234 return "", err 235 } 236 for { 237 workspacePath := filepath.Join(dir, "WORKSPACE") 238 _, err := os.Stat(workspacePath) 239 if err == nil { 240 return dir, nil 241 } 242 if strings.HasSuffix(dir, string(os.PathSeparator)) { 243 // root directory 244 break 245 } 246 dir = filepath.Dir(dir) 247 } 248 return "", fmt.Errorf("could not find WORKSPACE file. -repo_root must be set explicitly.") 249 } 250 251 type errorList []error 252 253 func (e errorList) Error() string { 254 buf := new(bytes.Buffer) 255 for _, err := range e { 256 fmt.Fprintln(buf, err.Error()) 257 } 258 return buf.String() 259 }