github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/cmd/gazelle/fix-update.go (about) 1 /* Copyright 2017 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 "errors" 20 "flag" 21 "fmt" 22 "io/ioutil" 23 "log" 24 "os" 25 "path/filepath" 26 "strings" 27 28 "github.com/bazelbuild/bazel-gazelle/internal/config" 29 "github.com/bazelbuild/bazel-gazelle/internal/label" 30 "github.com/bazelbuild/bazel-gazelle/internal/merger" 31 "github.com/bazelbuild/bazel-gazelle/internal/packages" 32 "github.com/bazelbuild/bazel-gazelle/internal/repos" 33 "github.com/bazelbuild/bazel-gazelle/internal/resolve" 34 "github.com/bazelbuild/bazel-gazelle/internal/rules" 35 "github.com/bazelbuild/bazel-gazelle/internal/wspace" 36 bf "github.com/bazelbuild/buildtools/build" 37 ) 38 39 // updateConfig holds configuration information needed to run the fix and 40 // update commands. This includes everything in config.Config, but it also 41 // includes some additional fields that aren't relevant to other packages. 42 type updateConfig struct { 43 c *config.Config 44 emit emitFunc 45 outDir, outSuffix string 46 repos []repos.Repo 47 } 48 49 type emitFunc func(*config.Config, *bf.File, string) error 50 51 var modeFromName = map[string]emitFunc{ 52 "print": printFile, 53 "fix": fixFile, 54 "diff": diffFile, 55 } 56 57 // visitRecord stores information about about a directory visited with 58 // packages.Walk. 59 type visitRecord struct { 60 // pkgRel is the slash-separated path to the visited directory, relative to 61 // the repository root. "" for the repository root itself. 62 pkgRel string 63 64 // rules is a list of generated Go rules. 65 rules []bf.Expr 66 67 // empty is a list of empty Go rules that may be deleted. 68 empty []bf.Expr 69 70 // file is the build file being processed. 71 file *bf.File 72 } 73 74 type byPkgRel []visitRecord 75 76 func (vs byPkgRel) Len() int { return len(vs) } 77 func (vs byPkgRel) Less(i, j int) bool { return vs[i].pkgRel < vs[j].pkgRel } 78 func (vs byPkgRel) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } 79 80 func runFixUpdate(cmd command, args []string) error { 81 uc, err := newFixUpdateConfiguration(cmd, args) 82 if err != nil { 83 return err 84 } 85 if cmd == fixCmd { 86 // Only check the version when "fix" is run. Generated build files 87 // frequently work with older version of rules_go, and we don't want to 88 // nag too much since there's no way to disable this warning. 89 checkRulesGoVersion(uc.c.RepoRoot) 90 } 91 92 l := label.NewLabeler(uc.c) 93 ruleIndex := resolve.NewRuleIndex() 94 95 var visits []visitRecord 96 97 // Visit all directories in the repository. 98 packages.Walk(uc.c, uc.c.RepoRoot, func(dir, rel string, c *config.Config, pkg *packages.Package, file *bf.File, isUpdateDir bool) { 99 // If this file is ignored or if Gazelle was not asked to update this 100 // directory, just index the build file and move on. 101 if !isUpdateDir { 102 if file != nil { 103 ruleIndex.AddRulesFromFile(c, file) 104 } 105 return 106 } 107 108 // Fix any problems in the file. 109 if file != nil { 110 file = merger.FixFileMinor(c, file) 111 fixedFile := merger.FixFile(c, file) 112 if cmd == fixCmd { 113 file = fixedFile 114 } else if fixedFile != file { 115 log.Printf("%s: warning: file contains rules whose structure is out of date. Consider running 'gazelle fix'.", file.Path) 116 } 117 } 118 119 // If the file exists, but no Go code is present, create an empty package. 120 // This lets us delete existing rules. 121 if pkg == nil && file != nil { 122 pkg = packages.EmptyPackage(c, dir, rel) 123 } 124 125 // Generate new rules and merge them into the existing file (if present). 126 if pkg != nil { 127 g := rules.NewGenerator(c, l, file) 128 rules, empty, err := g.GenerateRules(pkg) 129 if err != nil { 130 log.Print(err) 131 return 132 } 133 if file == nil { 134 file = &bf.File{ 135 Path: filepath.Join(c.RepoRoot, filepath.FromSlash(rel), c.DefaultBuildFileName()), 136 Stmt: rules, 137 } 138 } else { 139 file, rules = merger.MergeFile(rules, empty, file, merger.PreResolveAttrs) 140 } 141 visits = append(visits, visitRecord{ 142 pkgRel: rel, 143 rules: rules, 144 empty: empty, 145 file: file, 146 }) 147 } 148 149 // Add library rules to the dependency resolution table. 150 if file != nil { 151 ruleIndex.AddRulesFromFile(c, file) 152 } 153 }) 154 155 // Finish building the index for dependency resolution. 156 ruleIndex.Finish() 157 158 // Resolve dependencies. 159 rc := repos.NewRemoteCache(uc.repos) 160 resolver := resolve.NewResolver(uc.c, l, ruleIndex, rc) 161 for i := range visits { 162 for j := range visits[i].rules { 163 visits[i].rules[j] = resolver.ResolveRule(visits[i].rules[j], visits[i].pkgRel) 164 } 165 visits[i].file, _ = merger.MergeFile(visits[i].rules, visits[i].empty, visits[i].file, merger.PostResolveAttrs) 166 } 167 168 // Emit merged files. 169 for _, v := range visits { 170 rules.SortLabels(v.file) 171 v.file = merger.FixLoads(v.file) 172 bf.Rewrite(v.file, nil) // have buildifier 'format' our rules. 173 174 path := v.file.Path 175 if uc.outDir != "" { 176 stem := filepath.Base(v.file.Path) + uc.outSuffix 177 path = filepath.Join(uc.outDir, v.pkgRel, stem) 178 } 179 if err := uc.emit(uc.c, v.file, path); err != nil { 180 log.Print(err) 181 } 182 } 183 return nil 184 } 185 186 func newFixUpdateConfiguration(cmd command, args []string) (*updateConfig, error) { 187 uc := &updateConfig{c: &config.Config{}} 188 var err error 189 190 fs := flag.NewFlagSet("gazelle", flag.ContinueOnError) 191 // Flag will call this on any parse error. Don't print usage unless 192 // -h or -help were passed explicitly. 193 fs.Usage = func() {} 194 195 knownImports := multiFlag{} 196 buildFileName := fs.String("build_file_name", "BUILD.bazel,BUILD", "comma-separated list of valid build file names.\nThe first element of the list is the name of output build files to generate.") 197 buildTags := fs.String("build_tags", "", "comma-separated list of build tags. If not specified, Gazelle will not\n\tfilter sources with build constraints.") 198 external := fs.String("external", "external", "external: resolve external packages with go_repository\n\tvendored: resolve external packages as packages in vendor/") 199 var goPrefix explicitFlag 200 fs.Var(&goPrefix, "go_prefix", "prefix of import paths in the current workspace") 201 repoRoot := fs.String("repo_root", "", "path to a directory which corresponds to go_prefix, otherwise gazelle searches for it.") 202 fs.Var(&knownImports, "known_import", "import path for which external resolution is skipped (can specify multiple times)") 203 mode := fs.String("mode", "fix", "print: prints all of the updated BUILD files\n\tfix: rewrites all of the BUILD files in place\n\tdiff: computes the rewrite but then just does a diff") 204 outDir := fs.String("experimental_out_dir", "", "write build files to an alternate directory tree") 205 outSuffix := fs.String("experimental_out_suffix", "", "extra suffix appended to build file names. Only used if -experimental_out_dir is also set.") 206 var proto explicitFlag 207 fs.Var(&proto, "proto", "default: generates new proto rules\n\tdisable: does not touch proto rules\n\tlegacy (deprecated): generates old proto rules") 208 if err := fs.Parse(args); err != nil { 209 if err == flag.ErrHelp { 210 fixUpdateUsage(fs) 211 os.Exit(0) 212 } 213 // flag already prints the error; don't print it again. 214 log.Fatal("Try -help for more information.") 215 } 216 217 uc.c.Dirs = fs.Args() 218 if len(uc.c.Dirs) == 0 { 219 uc.c.Dirs = []string{"."} 220 } 221 for i := range uc.c.Dirs { 222 uc.c.Dirs[i], err = filepath.Abs(uc.c.Dirs[i]) 223 if err != nil { 224 return nil, err 225 } 226 } 227 228 if *repoRoot != "" { 229 uc.c.RepoRoot = *repoRoot 230 } else if len(uc.c.Dirs) == 1 { 231 uc.c.RepoRoot, err = wspace.Find(uc.c.Dirs[0]) 232 if err != nil { 233 return nil, fmt.Errorf("-repo_root not specified, and WORKSPACE cannot be found: %v", err) 234 } 235 } else { 236 uc.c.RepoRoot, err = wspace.Find(".") 237 if err != nil { 238 return nil, fmt.Errorf("-repo_root not specified, and WORKSPACE cannot be found: %v", err) 239 } 240 } 241 242 for _, dir := range uc.c.Dirs { 243 if !isDescendingDir(dir, uc.c.RepoRoot) { 244 return nil, fmt.Errorf("dir %q is not a subdirectory of repo root %q", dir, uc.c.RepoRoot) 245 } 246 } 247 248 uc.c.ValidBuildFileNames = strings.Split(*buildFileName, ",") 249 if len(uc.c.ValidBuildFileNames) == 0 { 250 return nil, fmt.Errorf("no valid build file names specified") 251 } 252 253 uc.c.SetBuildTags(*buildTags) 254 uc.c.PreprocessTags() 255 256 if goPrefix.set { 257 uc.c.GoPrefix = goPrefix.value 258 } else { 259 uc.c.GoPrefix, err = loadGoPrefix(uc.c) 260 if err != nil { 261 return nil, err 262 } 263 } 264 if err := config.CheckPrefix(uc.c.GoPrefix); err != nil { 265 return nil, err 266 } 267 268 uc.c.ShouldFix = cmd == fixCmd 269 270 uc.c.DepMode, err = config.DependencyModeFromString(*external) 271 if err != nil { 272 return nil, err 273 } 274 275 if proto.set { 276 uc.c.ProtoMode, err = config.ProtoModeFromString(proto.value) 277 if err != nil { 278 return nil, err 279 } 280 uc.c.ProtoModeExplicit = true 281 } 282 283 emit, ok := modeFromName[*mode] 284 if !ok { 285 return nil, fmt.Errorf("unrecognized emit mode: %q", *mode) 286 } 287 uc.emit = emit 288 289 uc.outDir = *outDir 290 uc.outSuffix = *outSuffix 291 292 workspacePath := filepath.Join(uc.c.RepoRoot, "WORKSPACE") 293 workspaceContent, err := ioutil.ReadFile(workspacePath) 294 if os.IsNotExist(err) { 295 workspaceContent = nil 296 } else if err != nil { 297 return nil, err 298 } 299 workspace, err := bf.Parse(workspacePath, workspaceContent) 300 if err != nil { 301 return nil, err 302 } 303 uc.repos = repos.ListRepositories(workspace) 304 repoPrefixes := make(map[string]bool) 305 for _, r := range uc.repos { 306 repoPrefixes[r.GoPrefix] = true 307 } 308 for _, imp := range knownImports { 309 if repoPrefixes[imp] { 310 continue 311 } 312 repo := repos.Repo{ 313 Name: label.ImportPathToBazelRepoName(imp), 314 GoPrefix: imp, 315 } 316 uc.repos = append(uc.repos, repo) 317 } 318 319 return uc, nil 320 } 321 322 func fixUpdateUsage(fs *flag.FlagSet) { 323 fmt.Fprintln(os.Stderr, `usage: gazelle [fix|update] [flags...] [package-dirs...] 324 325 The update command creates new build files and update existing BUILD files 326 when needed. 327 328 The fix command also creates and updates build files, and in addition, it may 329 make potentially breaking updates to usage of rules. For example, it may 330 delete obsolete rules or rename existing rules. 331 332 There are several output modes which can be selected with the -mode flag. The 333 output mode determines what Gazelle does with updated BUILD files. 334 335 fix (default) - write updated BUILD files back to disk. 336 print - print updated BUILD files to stdout. 337 diff - diff updated BUILD files against existing files in unified format. 338 339 Gazelle accepts a list of paths to Go package directories to process (defaults 340 to the working directory if none are given). It recursively traverses 341 subdirectories. All directories must be under the directory specified by 342 -repo_root; if -repo_root is not given, this is the directory containing the 343 WORKSPACE file. 344 345 FLAGS: 346 `) 347 fs.PrintDefaults() 348 } 349 350 func loadBuildFile(c *config.Config, dir string) (*bf.File, error) { 351 var buildPath string 352 for _, base := range c.ValidBuildFileNames { 353 p := filepath.Join(dir, base) 354 fi, err := os.Stat(p) 355 if err == nil { 356 if fi.Mode().IsRegular() { 357 buildPath = p 358 break 359 } 360 continue 361 } 362 if !os.IsNotExist(err) { 363 return nil, err 364 } 365 } 366 if buildPath == "" { 367 return nil, os.ErrNotExist 368 } 369 370 data, err := ioutil.ReadFile(buildPath) 371 if err != nil { 372 return nil, err 373 } 374 return bf.Parse(buildPath, data) 375 } 376 377 func loadGoPrefix(c *config.Config) (string, error) { 378 f, err := loadBuildFile(c, c.RepoRoot) 379 if err != nil { 380 return "", errors.New("-go_prefix not set") 381 } 382 for _, d := range config.ParseDirectives(f) { 383 if d.Key == "prefix" { 384 return d.Value, nil 385 } 386 } 387 for _, s := range f.Stmt { 388 c, ok := s.(*bf.CallExpr) 389 if !ok { 390 continue 391 } 392 l, ok := c.X.(*bf.LiteralExpr) 393 if !ok { 394 continue 395 } 396 if l.Token != "go_prefix" { 397 continue 398 } 399 if len(c.List) != 1 { 400 return "", fmt.Errorf("-go_prefix not set, and %s has go_prefix(%v) with too many args", f.Path, c.List) 401 } 402 v, ok := c.List[0].(*bf.StringExpr) 403 if !ok { 404 return "", fmt.Errorf("-go_prefix not set, and %s has go_prefix(%v) which is not a string", f.Path, bf.FormatString(c.List[0])) 405 } 406 return v.Value, nil 407 } 408 return "", fmt.Errorf("-go_prefix not set, and no # gazelle:prefix directive found in %s", f.Path) 409 } 410 411 func isDescendingDir(dir, root string) bool { 412 rel, err := filepath.Rel(root, dir) 413 if err != nil { 414 return false 415 } 416 if rel == "." { 417 return true 418 } 419 return !strings.HasPrefix(rel, "..") 420 }