github.com/wolfd/bazel-gazelle@v0.14.0/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 "flag" 20 "fmt" 21 "io/ioutil" 22 "log" 23 "os" 24 "path/filepath" 25 "strings" 26 27 "github.com/bazelbuild/bazel-gazelle/internal/config" 28 gzflag "github.com/bazelbuild/bazel-gazelle/internal/flag" 29 "github.com/bazelbuild/bazel-gazelle/internal/label" 30 "github.com/bazelbuild/bazel-gazelle/internal/merger" 31 "github.com/bazelbuild/bazel-gazelle/internal/repos" 32 "github.com/bazelbuild/bazel-gazelle/internal/resolve" 33 "github.com/bazelbuild/bazel-gazelle/internal/rule" 34 "github.com/bazelbuild/bazel-gazelle/internal/walk" 35 ) 36 37 // updateConfig holds configuration information needed to run the fix and 38 // update commands. This includes everything in config.Config, but it also 39 // includes some additional fields that aren't relevant to other packages. 40 type updateConfig struct { 41 emit emitFunc 42 repos []repos.Repo 43 } 44 45 type emitFunc func(path string, data []byte) error 46 47 var modeFromName = map[string]emitFunc{ 48 "print": printFile, 49 "fix": fixFile, 50 "diff": diffFile, 51 } 52 53 const updateName = "_update" 54 55 func getUpdateConfig(c *config.Config) *updateConfig { 56 return c.Exts[updateName].(*updateConfig) 57 } 58 59 type updateConfigurer struct { 60 mode string 61 } 62 63 func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) { 64 uc := &updateConfig{} 65 c.Exts[updateName] = uc 66 67 c.ShouldFix = cmd == "fix" 68 69 fs.StringVar(&ucr.mode, "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") 70 } 71 72 func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { 73 uc := getUpdateConfig(c) 74 var ok bool 75 uc.emit, ok = modeFromName[ucr.mode] 76 if !ok { 77 return fmt.Errorf("unrecognized emit mode: %q", ucr.mode) 78 } 79 80 c.Dirs = fs.Args() 81 if len(c.Dirs) == 0 { 82 c.Dirs = []string{"."} 83 } 84 for i := range c.Dirs { 85 dir, err := filepath.Abs(c.Dirs[i]) 86 if err != nil { 87 return fmt.Errorf("%s: failed to find absolute path: %v", c.Dirs[i], err) 88 } 89 dir, err = filepath.EvalSymlinks(dir) 90 if err != nil { 91 return fmt.Errorf("%s: failed to resolve symlinks: %v", c.Dirs[i], err) 92 } 93 if !isDescendingDir(dir, c.RepoRoot) { 94 return fmt.Errorf("dir %q is not a subdirectory of repo root %q", dir, c.RepoRoot) 95 } 96 c.Dirs[i] = dir 97 } 98 99 return nil 100 } 101 102 func (ucr *updateConfigurer) KnownDirectives() []string { return nil } 103 104 func (ucr *updateConfigurer) Configure(c *config.Config, rel string, f *rule.File) {} 105 106 // visitRecord stores information about about a directory visited with 107 // packages.Walk. 108 type visitRecord struct { 109 // pkgRel is the slash-separated path to the visited directory, relative to 110 // the repository root. "" for the repository root itself. 111 pkgRel string 112 113 // rules is a list of generated Go rules. 114 rules []*rule.Rule 115 116 // empty is a list of empty Go rules that may be deleted. 117 empty []*rule.Rule 118 119 // file is the build file being processed. 120 file *rule.File 121 } 122 123 type byPkgRel []visitRecord 124 125 func (vs byPkgRel) Len() int { return len(vs) } 126 func (vs byPkgRel) Less(i, j int) bool { return vs[i].pkgRel < vs[j].pkgRel } 127 func (vs byPkgRel) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } 128 129 var genericLoads = []rule.LoadInfo{ 130 { 131 Name: "@bazel_gazelle//:def.bzl", 132 Symbols: []string{"gazelle"}, 133 }, 134 } 135 136 func runFixUpdate(cmd command, args []string) error { 137 cexts := make([]config.Configurer, 0, len(languages)+2) 138 cexts = append(cexts, &config.CommonConfigurer{}, &updateConfigurer{}) 139 kindToResolver := make(map[string]resolve.Resolver) 140 kinds := make(map[string]rule.KindInfo) 141 loads := genericLoads 142 for _, lang := range languages { 143 cexts = append(cexts, lang) 144 for kind, info := range lang.Kinds() { 145 kindToResolver[kind] = lang 146 kinds[kind] = info 147 } 148 loads = append(loads, lang.Loads()...) 149 } 150 ruleIndex := resolve.NewRuleIndex(kindToResolver) 151 152 c, err := newFixUpdateConfiguration(cmd, args, cexts, loads) 153 if err != nil { 154 return err 155 } 156 157 if cmd == fixCmd { 158 // Only check the version when "fix" is run. Generated build files 159 // frequently work with older version of rules_go, and we don't want to 160 // nag too much since there's no way to disable this warning. 161 checkRulesGoVersion(c.RepoRoot) 162 } 163 164 // Visit all directories in the repository. 165 var visits []visitRecord 166 walk.Walk(c, cexts, func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string) { 167 // If this file is ignored or if Gazelle was not asked to update this 168 // directory, just index the build file and move on. 169 if !update { 170 if f != nil { 171 for _, r := range f.Rules { 172 ruleIndex.AddRule(c, r, f) 173 } 174 } 175 return 176 } 177 178 // Fix any problems in the file. 179 if f != nil { 180 for _, l := range languages { 181 l.Fix(c, f) 182 } 183 } 184 185 // Generate rules. 186 var empty, gen []*rule.Rule 187 for _, l := range languages { 188 lempty, lgen := l.GenerateRules(c, dir, rel, f, subdirs, regularFiles, genFiles, empty, gen) 189 empty = append(empty, lempty...) 190 gen = append(gen, lgen...) 191 } 192 if f == nil && len(gen) == 0 { 193 return 194 } 195 196 // Insert or merge rules into the build file. 197 if f == nil { 198 f = rule.EmptyFile(filepath.Join(dir, c.DefaultBuildFileName()), rel) 199 for _, r := range gen { 200 r.Insert(f) 201 } 202 } else { 203 merger.MergeFile(f, empty, gen, merger.PreResolve, kinds) 204 } 205 visits = append(visits, visitRecord{ 206 pkgRel: rel, 207 rules: gen, 208 empty: empty, 209 file: f, 210 }) 211 212 // Add library rules to the dependency resolution table. 213 for _, r := range f.Rules { 214 ruleIndex.AddRule(c, r, f) 215 } 216 }) 217 218 uc := getUpdateConfig(c) 219 220 // Finish building the index for dependency resolution. 221 ruleIndex.Finish() 222 223 // Resolve dependencies. 224 rc := repos.NewRemoteCache(uc.repos) 225 for _, v := range visits { 226 for _, r := range v.rules { 227 from := label.New(c.RepoName, v.pkgRel, r.Name()) 228 kindToResolver[r.Kind()].Resolve(c, ruleIndex, rc, r, from) 229 } 230 merger.MergeFile(v.file, v.empty, v.rules, merger.PostResolve, kinds) 231 } 232 233 // Emit merged files. 234 for _, v := range visits { 235 merger.FixLoads(v.file, loads) 236 content := v.file.Format() 237 outputPath := findOutputPath(c, v.file) 238 if err := uc.emit(outputPath, content); err != nil { 239 log.Print(err) 240 } 241 } 242 return nil 243 } 244 245 func newFixUpdateConfiguration(cmd command, args []string, cexts []config.Configurer, loads []rule.LoadInfo) (*config.Config, error) { 246 c := config.New() 247 248 fs := flag.NewFlagSet("gazelle", flag.ContinueOnError) 249 // Flag will call this on any parse error. Don't print usage unless 250 // -h or -help were passed explicitly. 251 fs.Usage = func() {} 252 253 var knownImports []string 254 fs.Var(&gzflag.MultiFlag{Values: &knownImports}, "known_import", "import path for which external resolution is skipped (can specify multiple times)") 255 256 for _, cext := range cexts { 257 cext.RegisterFlags(fs, cmd.String(), c) 258 } 259 260 if err := fs.Parse(args); err != nil { 261 if err == flag.ErrHelp { 262 fixUpdateUsage(fs) 263 return nil, err 264 } 265 // flag already prints the error; don't print it again. 266 log.Fatal("Try -help for more information.") 267 } 268 269 for _, cext := range cexts { 270 if err := cext.CheckFlags(fs, c); err != nil { 271 return nil, err 272 } 273 } 274 275 uc := getUpdateConfig(c) 276 workspacePath := filepath.Join(c.RepoRoot, "WORKSPACE") 277 if workspace, err := rule.LoadFile(workspacePath, ""); err != nil { 278 if !os.IsNotExist(err) { 279 return nil, err 280 } 281 } else { 282 if err := fixWorkspace(c, workspace, loads); err != nil { 283 return nil, err 284 } 285 c.RepoName = findWorkspaceName(workspace) 286 uc.repos = repos.ListRepositories(workspace) 287 } 288 repoPrefixes := make(map[string]bool) 289 for _, r := range uc.repos { 290 repoPrefixes[r.GoPrefix] = true 291 } 292 for _, imp := range knownImports { 293 if repoPrefixes[imp] { 294 continue 295 } 296 repo := repos.Repo{ 297 Name: label.ImportPathToBazelRepoName(imp), 298 GoPrefix: imp, 299 } 300 uc.repos = append(uc.repos, repo) 301 } 302 303 return c, nil 304 } 305 306 func fixUpdateUsage(fs *flag.FlagSet) { 307 fmt.Fprint(os.Stderr, `usage: gazelle [fix|update] [flags...] [package-dirs...] 308 309 The update command creates new build files and update existing BUILD files 310 when needed. 311 312 The fix command also creates and updates build files, and in addition, it may 313 make potentially breaking updates to usage of rules. For example, it may 314 delete obsolete rules or rename existing rules. 315 316 There are several output modes which can be selected with the -mode flag. The 317 output mode determines what Gazelle does with updated BUILD files. 318 319 fix (default) - write updated BUILD files back to disk. 320 print - print updated BUILD files to stdout. 321 diff - diff updated BUILD files against existing files in unified format. 322 323 Gazelle accepts a list of paths to Go package directories to process (defaults 324 to the working directory if none are given). It recursively traverses 325 subdirectories. All directories must be under the directory specified by 326 -repo_root; if -repo_root is not given, this is the directory containing the 327 WORKSPACE file. 328 329 FLAGS: 330 331 `) 332 fs.PrintDefaults() 333 } 334 335 func fixWorkspace(c *config.Config, workspace *rule.File, loads []rule.LoadInfo) error { 336 uc := getUpdateConfig(c) 337 if !c.ShouldFix { 338 return nil 339 } 340 shouldFix := false 341 for _, d := range c.Dirs { 342 if d == c.RepoRoot { 343 shouldFix = true 344 } 345 } 346 if !shouldFix { 347 return nil 348 } 349 350 merger.FixWorkspace(workspace) 351 merger.FixLoads(workspace, loads) 352 if err := merger.CheckGazelleLoaded(workspace); err != nil { 353 return err 354 } 355 return uc.emit(workspace.Path, workspace.Format()) 356 } 357 358 func findWorkspaceName(f *rule.File) string { 359 for _, r := range f.Rules { 360 if r.Kind() == "workspace" { 361 return r.Name() 362 } 363 } 364 return "" 365 } 366 367 func isDescendingDir(dir, root string) bool { 368 rel, err := filepath.Rel(root, dir) 369 if err != nil { 370 return false 371 } 372 if rel == "." { 373 return true 374 } 375 return !strings.HasPrefix(rel, "..") 376 } 377 378 func findOutputPath(c *config.Config, f *rule.File) string { 379 if c.ReadBuildFilesDir == "" && c.WriteBuildFilesDir == "" { 380 return f.Path 381 } 382 baseDir := c.WriteBuildFilesDir 383 if c.WriteBuildFilesDir == "" { 384 baseDir = c.RepoRoot 385 } 386 outputDir := filepath.Join(baseDir, filepath.FromSlash(f.Pkg)) 387 defaultOutputPath := filepath.Join(outputDir, c.DefaultBuildFileName()) 388 files, err := ioutil.ReadDir(outputDir) 389 if err != nil { 390 // Ignore error. Directory probably doesn't exist. 391 return defaultOutputPath 392 } 393 outputPath := rule.MatchBuildFileName(outputDir, c.ValidBuildFileNames, files) 394 if outputPath == "" { 395 return defaultOutputPath 396 } 397 return outputPath 398 }