github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/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 "bytes" 20 "context" 21 "errors" 22 "flag" 23 "fmt" 24 "log" 25 "os" 26 "path/filepath" 27 "sort" 28 "strings" 29 "syscall" 30 31 "github.com/bazelbuild/bazel-gazelle/config" 32 gzflag "github.com/bazelbuild/bazel-gazelle/flag" 33 "github.com/bazelbuild/bazel-gazelle/internal/wspace" 34 "github.com/bazelbuild/bazel-gazelle/label" 35 "github.com/bazelbuild/bazel-gazelle/language" 36 "github.com/bazelbuild/bazel-gazelle/merger" 37 "github.com/bazelbuild/bazel-gazelle/repo" 38 "github.com/bazelbuild/bazel-gazelle/resolve" 39 "github.com/bazelbuild/bazel-gazelle/rule" 40 "github.com/bazelbuild/bazel-gazelle/walk" 41 "github.com/bazelbuild/buildtools/build" 42 ) 43 44 // updateConfig holds configuration information needed to run the fix and 45 // update commands. This includes everything in config.Config, but it also 46 // includes some additional fields that aren't relevant to other packages. 47 type updateConfig struct { 48 dirs []string 49 emit emitFunc 50 repos []repo.Repo 51 workspaceFiles []*rule.File 52 walkMode walk.Mode 53 patchPath string 54 patchBuffer bytes.Buffer 55 print0 bool 56 profile profiler 57 } 58 59 type emitFunc func(c *config.Config, f *rule.File) error 60 61 var modeFromName = map[string]emitFunc{ 62 "print": printFile, 63 "fix": fixFile, 64 "diff": diffFile, 65 } 66 67 const updateName = "_update" 68 69 func getUpdateConfig(c *config.Config) *updateConfig { 70 return c.Exts[updateName].(*updateConfig) 71 } 72 73 type updateConfigurer struct { 74 mode string 75 recursive bool 76 knownImports []string 77 repoConfigPath string 78 cpuProfile string 79 memProfile string 80 } 81 82 func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) { 83 uc := &updateConfig{} 84 c.Exts[updateName] = uc 85 86 c.ShouldFix = cmd == "fix" 87 88 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") 89 fs.BoolVar(&ucr.recursive, "r", true, "when true, gazelle will update subdirectories recursively") 90 fs.StringVar(&uc.patchPath, "patch", "", "when set with -mode=diff, gazelle will write to a file instead of stdout") 91 fs.BoolVar(&uc.print0, "print0", false, "when set with -mode=fix, gazelle will print the names of rewritten files separated with \\0 (NULL)") 92 fs.StringVar(&ucr.cpuProfile, "cpuprofile", "", "write cpu profile to `file`") 93 fs.StringVar(&ucr.memProfile, "memprofile", "", "write memory profile to `file`") 94 fs.Var(&gzflag.MultiFlag{Values: &ucr.knownImports}, "known_import", "import path for which external resolution is skipped (can specify multiple times)") 95 fs.StringVar(&ucr.repoConfigPath, "repo_config", "", "file where Gazelle should load repository configuration. Defaults to WORKSPACE.") 96 } 97 98 func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { 99 uc := getUpdateConfig(c) 100 101 var ok bool 102 uc.emit, ok = modeFromName[ucr.mode] 103 if !ok { 104 return fmt.Errorf("unrecognized emit mode: %q", ucr.mode) 105 } 106 if uc.patchPath != "" && ucr.mode != "diff" { 107 return fmt.Errorf("-patch set but -mode is %s, not diff", ucr.mode) 108 } 109 if uc.patchPath != "" && !filepath.IsAbs(uc.patchPath) { 110 uc.patchPath = filepath.Join(c.WorkDir, uc.patchPath) 111 } 112 p, err := newProfiler(ucr.cpuProfile, ucr.memProfile) 113 if err != nil { 114 return err 115 } 116 uc.profile = p 117 118 dirs := fs.Args() 119 if len(dirs) == 0 { 120 dirs = []string{"."} 121 } 122 uc.dirs = make([]string, len(dirs)) 123 for i, arg := range dirs { 124 dir := arg 125 if !filepath.IsAbs(dir) { 126 dir = filepath.Join(c.WorkDir, dir) 127 } 128 dir, err := filepath.EvalSymlinks(dir) 129 if err != nil { 130 return fmt.Errorf("%s: failed to resolve symlinks: %v", arg, err) 131 } 132 if !isDescendingDir(dir, c.RepoRoot) { 133 return fmt.Errorf("%s: not a subdirectory of repo root %s", arg, c.RepoRoot) 134 } 135 uc.dirs[i] = dir 136 } 137 138 if ucr.recursive && c.IndexLibraries { 139 uc.walkMode = walk.VisitAllUpdateSubdirsMode 140 } else if c.IndexLibraries { 141 uc.walkMode = walk.VisitAllUpdateDirsMode 142 } else if ucr.recursive { 143 uc.walkMode = walk.UpdateSubdirsMode 144 } else { 145 uc.walkMode = walk.UpdateDirsMode 146 } 147 148 // Load the repo configuration file (WORKSPACE by default) to find out 149 // names and prefixes of other go_repositories. This affects external 150 // dependency resolution for Go. 151 // TODO(jayconrod): Go-specific code should be moved to language/go. 152 if ucr.repoConfigPath == "" { 153 ucr.repoConfigPath = wspace.FindWORKSPACEFile(c.RepoRoot) 154 } 155 repoConfigFile, err := rule.LoadWorkspaceFile(ucr.repoConfigPath, "") 156 if err != nil && !os.IsNotExist(err) && !isDirErr(err) { 157 return err 158 } else if err == nil { 159 c.Repos, _, err = repo.ListRepositories(repoConfigFile) 160 if err != nil { 161 return err 162 } 163 } 164 for _, imp := range ucr.knownImports { 165 uc.repos = append(uc.repos, repo.Repo{ 166 Name: label.ImportPathToBazelRepoName(imp), 167 GoPrefix: imp, 168 }) 169 } 170 171 for _, r := range c.Repos { 172 if r.Kind() == "go_repository" { 173 var name string 174 if apparentName := c.ModuleToApparentName(r.AttrString("module_name")); apparentName != "" { 175 name = apparentName 176 } else { 177 name = r.Name() 178 } 179 uc.repos = append(uc.repos, repo.Repo{ 180 Name: name, 181 GoPrefix: r.AttrString("importpath"), 182 }) 183 } 184 } 185 186 // If the repo configuration file is not WORKSPACE, also load WORKSPACE 187 // and any declared macro files so we can apply fixes. 188 workspacePath := wspace.FindWORKSPACEFile(c.RepoRoot) 189 var workspace *rule.File 190 if ucr.repoConfigPath == workspacePath { 191 workspace = repoConfigFile 192 } else { 193 workspace, err = rule.LoadWorkspaceFile(workspacePath, "") 194 if err != nil && !os.IsNotExist(err) && !isDirErr(err) { 195 return err 196 } 197 } 198 if workspace != nil { 199 c.RepoName = findWorkspaceName(workspace) 200 _, repoFileMap, err := repo.ListRepositories(workspace) 201 if err != nil { 202 return err 203 } 204 seen := make(map[*rule.File]bool) 205 for _, f := range repoFileMap { 206 if !seen[f] { 207 uc.workspaceFiles = append(uc.workspaceFiles, f) 208 seen[f] = true 209 } 210 } 211 sort.Slice(uc.workspaceFiles, func(i, j int) bool { 212 return uc.workspaceFiles[i].Path < uc.workspaceFiles[j].Path 213 }) 214 } 215 216 return nil 217 } 218 219 func (ucr *updateConfigurer) KnownDirectives() []string { return nil } 220 221 func (ucr *updateConfigurer) Configure(c *config.Config, rel string, f *rule.File) {} 222 223 // visitRecord stores information about about a directory visited with 224 // packages.Walk. 225 type visitRecord struct { 226 // pkgRel is the slash-separated path to the visited directory, relative to 227 // the repository root. "" for the repository root itself. 228 pkgRel string 229 230 // c is the configuration for the directory with directives applied. 231 c *config.Config 232 233 // rules is a list of generated Go rules. 234 rules []*rule.Rule 235 236 // imports contains opaque import information for each rule in rules. 237 imports []interface{} 238 239 // empty is a list of empty Go rules that may be deleted. 240 empty []*rule.Rule 241 242 // file is the build file being processed. 243 file *rule.File 244 245 // mappedKinds are mapped kinds used during this visit. 246 mappedKinds []config.MappedKind 247 mappedKindInfo map[string]rule.KindInfo 248 } 249 250 var genericLoads = []rule.LoadInfo{ 251 { 252 Name: "@bazel_gazelle//:def.bzl", 253 Symbols: []string{"gazelle"}, 254 }, 255 } 256 257 func runFixUpdate(wd string, cmd command, args []string) (err error) { 258 cexts := make([]config.Configurer, 0, len(languages)+4) 259 cexts = append(cexts, 260 &config.CommonConfigurer{}, 261 &updateConfigurer{}, 262 &walk.Configurer{}, 263 &resolve.Configurer{}) 264 265 for _, lang := range languages { 266 cexts = append(cexts, lang) 267 } 268 269 c, err := newFixUpdateConfiguration(wd, cmd, args, cexts) 270 if err != nil { 271 return err 272 } 273 274 mrslv := newMetaResolver() 275 kinds := make(map[string]rule.KindInfo) 276 loads := genericLoads 277 exts := make([]interface{}, 0, len(languages)) 278 for _, lang := range languages { 279 for kind, info := range lang.Kinds() { 280 mrslv.AddBuiltin(kind, lang) 281 kinds[kind] = info 282 } 283 if moduleAwareLang, ok := lang.(language.ModuleAwareLanguage); ok { 284 loads = append(loads, moduleAwareLang.ApparentLoads(c.ModuleToApparentName)...) 285 } else { 286 loads = append(loads, lang.Loads()...) 287 } 288 exts = append(exts, lang) 289 } 290 ruleIndex := resolve.NewRuleIndex(mrslv.Resolver, exts...) 291 292 if err := fixRepoFiles(c, loads); err != nil { 293 return err 294 } 295 296 ctx, cancel := context.WithCancel(context.Background()) 297 defer cancel() 298 for _, lang := range languages { 299 if life, ok := lang.(language.LifecycleManager); ok { 300 life.Before(ctx) 301 } 302 } 303 304 // Visit all directories in the repository. 305 var visits []visitRecord 306 uc := getUpdateConfig(c) 307 defer func() { 308 if err := uc.profile.stop(); err != nil { 309 log.Printf("stopping profiler: %v", err) 310 } 311 }() 312 313 var errorsFromWalk []error 314 walk.Walk(c, cexts, uc.dirs, uc.walkMode, func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string) { 315 // If this file is ignored or if Gazelle was not asked to update this 316 // directory, just index the build file and move on. 317 if !update { 318 if c.IndexLibraries && f != nil { 319 for _, repl := range c.KindMap { 320 mrslv.MappedKind(rel, repl) 321 } 322 for _, r := range f.Rules { 323 ruleIndex.AddRule(c, r, f) 324 } 325 } 326 return 327 } 328 329 // Fix any problems in the file. 330 if f != nil { 331 for _, l := range filterLanguages(c, languages) { 332 l.Fix(c, f) 333 } 334 } 335 336 // Generate rules. 337 var empty, gen []*rule.Rule 338 var imports []interface{} 339 for _, l := range filterLanguages(c, languages) { 340 res := l.GenerateRules(language.GenerateArgs{ 341 Config: c, 342 Dir: dir, 343 Rel: rel, 344 File: f, 345 Subdirs: subdirs, 346 RegularFiles: regularFiles, 347 GenFiles: genFiles, 348 OtherEmpty: empty, 349 OtherGen: gen, 350 }) 351 if len(res.Gen) != len(res.Imports) { 352 log.Panicf("%s: language %s generated %d rules but returned %d imports", rel, l.Name(), len(res.Gen), len(res.Imports)) 353 } 354 empty = append(empty, res.Empty...) 355 gen = append(gen, res.Gen...) 356 imports = append(imports, res.Imports...) 357 } 358 if f == nil && len(gen) == 0 { 359 return 360 } 361 362 // Apply and record relevant kind mappings. 363 var ( 364 mappedKinds []config.MappedKind 365 mappedKindInfo = make(map[string]rule.KindInfo) 366 ) 367 // We apply map_kind to all rules, including pre-existing ones. 368 var allRules []*rule.Rule 369 allRules = append(allRules, gen...) 370 if f != nil { 371 allRules = append(allRules, f.Rules...) 372 } 373 374 maybeRecordReplacement := func(ruleKind string) (*string, error) { 375 repl, err := lookupMapKindReplacement(c.KindMap, ruleKind) 376 if err != nil { 377 return nil, err 378 } 379 if repl != nil { 380 mappedKindInfo[repl.KindName] = kinds[ruleKind] 381 mappedKinds = append(mappedKinds, *repl) 382 mrslv.MappedKind(rel, *repl) 383 return &repl.KindName, nil 384 } 385 return nil, nil 386 } 387 388 for _, r := range allRules { 389 if replacementName, err := maybeRecordReplacement(r.Kind()); err != nil { 390 errorsFromWalk = append(errorsFromWalk, fmt.Errorf("looking up mapped kind: %w", err)) 391 } else if replacementName != nil { 392 r.SetKind(*replacementName) 393 } 394 395 for i, arg := range r.Args() { 396 // Only check the first arg - this supports the maybe(java_library, ...) pattern, 397 // but avoids potential false positives from other uses of symbols. 398 if i != 0 { 399 break 400 } 401 if ident, ok := arg.(*build.Ident); ok { 402 // Don't allow re-mapping symbols that aren't known loads of a plugin. 403 if _, knownKind := kinds[ident.Name]; !knownKind { 404 continue 405 } 406 if replacementName, err := maybeRecordReplacement(ident.Name); err != nil { 407 errorsFromWalk = append(errorsFromWalk, fmt.Errorf("looking up mapped kind: %w", err)) 408 } else if replacementName != nil { 409 if err := r.UpdateArg(i, &build.Ident{Name: *replacementName}); err != nil { 410 log.Panicf("%s: %v", rel, err) 411 } 412 } 413 } 414 } 415 } 416 for _, r := range empty { 417 if repl, ok := c.KindMap[r.Kind()]; ok { 418 mappedKindInfo[repl.KindName] = kinds[r.Kind()] 419 mappedKinds = append(mappedKinds, repl) 420 mrslv.MappedKind(rel, repl) 421 r.SetKind(repl.KindName) 422 } 423 } 424 425 // Insert or merge rules into the build file. 426 if f == nil { 427 f = rule.EmptyFile(filepath.Join(dir, c.DefaultBuildFileName()), rel) 428 for _, r := range gen { 429 r.Insert(f) 430 } 431 } else { 432 merger.MergeFile(f, empty, gen, merger.PreResolve, 433 unionKindInfoMaps(kinds, mappedKindInfo)) 434 } 435 visits = append(visits, visitRecord{ 436 pkgRel: rel, 437 c: c, 438 rules: gen, 439 imports: imports, 440 empty: empty, 441 file: f, 442 mappedKinds: mappedKinds, 443 mappedKindInfo: mappedKindInfo, 444 }) 445 446 // Add library rules to the dependency resolution table. 447 if c.IndexLibraries { 448 for _, r := range f.Rules { 449 ruleIndex.AddRule(c, r, f) 450 } 451 } 452 }) 453 454 for _, lang := range languages { 455 if finishable, ok := lang.(language.FinishableLanguage); ok { 456 finishable.DoneGeneratingRules() 457 } 458 } 459 460 if len(errorsFromWalk) == 1 { 461 return errorsFromWalk[0] 462 } 463 464 if len(errorsFromWalk) > 1 { 465 var additionalErrors []string 466 for _, error := range errorsFromWalk[1:] { 467 additionalErrors = append(additionalErrors, error.Error()) 468 } 469 470 return fmt.Errorf("encountered multiple errors: %w, %v", errorsFromWalk[0], strings.Join(additionalErrors, ", ")) 471 } 472 473 // Finish building the index for dependency resolution. 474 ruleIndex.Finish() 475 476 // Resolve dependencies. 477 rc, cleanupRc := repo.NewRemoteCache(uc.repos) 478 defer func() { 479 if cerr := cleanupRc(); err == nil && cerr != nil { 480 err = cerr 481 } 482 }() 483 if err := maybePopulateRemoteCacheFromGoMod(c, rc); err != nil { 484 log.Print(err) 485 } 486 for _, v := range visits { 487 for i, r := range v.rules { 488 from := label.New(c.RepoName, v.pkgRel, r.Name()) 489 if rslv := mrslv.Resolver(r, v.pkgRel); rslv != nil { 490 rslv.Resolve(v.c, ruleIndex, rc, r, v.imports[i], from) 491 } 492 } 493 merger.MergeFile(v.file, v.empty, v.rules, merger.PostResolve, 494 unionKindInfoMaps(kinds, v.mappedKindInfo)) 495 } 496 for _, lang := range languages { 497 if life, ok := lang.(language.LifecycleManager); ok { 498 life.AfterResolvingDeps(ctx) 499 } 500 } 501 502 // Emit merged files. 503 var exit error 504 for _, v := range visits { 505 merger.FixLoads(v.file, applyKindMappings(v.mappedKinds, loads)) 506 if err := uc.emit(v.c, v.file); err != nil { 507 if err == errExit { 508 exit = err 509 } else { 510 log.Print(err) 511 } 512 } 513 } 514 if uc.patchPath != "" { 515 if err := os.WriteFile(uc.patchPath, uc.patchBuffer.Bytes(), 0o666); err != nil { 516 return err 517 } 518 } 519 520 return exit 521 } 522 523 // lookupMapKindReplacement finds a mapped replacement for rule kind `kind`, resolving transitively. 524 // i.e. if go_library is mapped to custom_go_library, and custom_go_library is mapped to other_go_library, 525 // looking up go_library will return other_go_library. 526 // It returns an error on a loop, and may return nil if no remapping should be performed. 527 func lookupMapKindReplacement(kindMap map[string]config.MappedKind, kind string) (*config.MappedKind, error) { 528 var mapped *config.MappedKind 529 seenKinds := make(map[string]struct{}) 530 seenKindPath := []string{kind} 531 for { 532 replacement, ok := kindMap[kind] 533 if !ok { 534 break 535 } 536 537 seenKindPath = append(seenKindPath, replacement.KindName) 538 if _, alreadySeen := seenKinds[replacement.KindName]; alreadySeen { 539 return nil, fmt.Errorf("found loop of map_kind replacements: %s", strings.Join(seenKindPath, " -> ")) 540 } 541 542 seenKinds[replacement.KindName] = struct{}{} 543 mapped = &replacement 544 if kind == replacement.KindName { 545 break 546 } 547 548 kind = replacement.KindName 549 } 550 551 return mapped, nil 552 } 553 554 func newFixUpdateConfiguration(wd string, cmd command, args []string, cexts []config.Configurer) (*config.Config, error) { 555 c := config.New() 556 c.WorkDir = wd 557 558 fs := flag.NewFlagSet("gazelle", flag.ContinueOnError) 559 // Flag will call this on any parse error. Don't print usage unless 560 // -h or -help were passed explicitly. 561 fs.Usage = func() {} 562 563 for _, cext := range cexts { 564 cext.RegisterFlags(fs, cmd.String(), c) 565 } 566 567 if err := fs.Parse(args); err != nil { 568 if err == flag.ErrHelp { 569 fixUpdateUsage(fs) 570 return nil, err 571 } 572 // flag already prints the error; don't print it again. 573 log.Fatal("Try -help for more information.") 574 } 575 576 for _, cext := range cexts { 577 if err := cext.CheckFlags(fs, c); err != nil { 578 return nil, err 579 } 580 } 581 582 return c, nil 583 } 584 585 func fixUpdateUsage(fs *flag.FlagSet) { 586 fmt.Fprint(os.Stderr, `usage: gazelle [fix|update] [flags...] [package-dirs...] 587 588 The update command creates new build files and update existing BUILD files 589 when needed. 590 591 The fix command also creates and updates build files, and in addition, it may 592 make potentially breaking updates to usage of rules. For example, it may 593 delete obsolete rules or rename existing rules. 594 595 There are several output modes which can be selected with the -mode flag. The 596 output mode determines what Gazelle does with updated BUILD files. 597 598 fix (default) - write updated BUILD files back to disk. 599 print - print updated BUILD files to stdout. 600 diff - diff updated BUILD files against existing files in unified format. 601 602 Gazelle accepts a list of paths to Go package directories to process (defaults 603 to the working directory if none are given). It recursively traverses 604 subdirectories. All directories must be under the directory specified by 605 -repo_root; if -repo_root is not given, this is the directory containing the 606 WORKSPACE file. 607 608 FLAGS: 609 610 `) 611 fs.PrintDefaults() 612 } 613 614 func fixRepoFiles(c *config.Config, loads []rule.LoadInfo) error { 615 uc := getUpdateConfig(c) 616 if !c.ShouldFix { 617 return nil 618 } 619 shouldFix := false 620 for _, d := range uc.dirs { 621 if d == c.RepoRoot { 622 shouldFix = true 623 } 624 } 625 if !shouldFix { 626 return nil 627 } 628 629 for _, f := range uc.workspaceFiles { 630 merger.FixLoads(f, loads) 631 workspaceFile := wspace.FindWORKSPACEFile(c.RepoRoot) 632 633 if f.Path == workspaceFile { 634 removeLegacyGoRepository(f) 635 if err := merger.CheckGazelleLoaded(f); err != nil { 636 return err 637 } 638 } 639 if err := uc.emit(c, f); err != nil { 640 return err 641 } 642 } 643 return nil 644 } 645 646 // removeLegacyGoRepository removes loads of go_repository from 647 // @io_bazel_rules_go. FixLoads should be called after this; it will load from 648 // @bazel_gazelle. 649 func removeLegacyGoRepository(f *rule.File) { 650 for _, l := range f.Loads { 651 if l.Name() == "@io_bazel_rules_go//go:def.bzl" { 652 l.Remove("go_repository") 653 if l.IsEmpty() { 654 l.Delete() 655 } 656 } 657 } 658 } 659 660 func findWorkspaceName(f *rule.File) string { 661 var name string 662 for _, r := range f.Rules { 663 if r.Kind() == "workspace" { 664 name = r.Name() 665 break 666 } 667 } 668 // HACK(bazelbuild/rules_go#2355, bazelbuild/rules_go#2387): 669 // We can't patch the WORKSPACE file with the correct name because Bazel 670 // writes it first; our patches won't apply. 671 if name == "com_google_googleapis" { 672 return "go_googleapis" 673 } 674 return name 675 } 676 677 func isDescendingDir(dir, root string) bool { 678 rel, err := filepath.Rel(root, dir) 679 if err != nil { 680 return false 681 } 682 if rel == "." { 683 return true 684 } 685 return !strings.HasPrefix(rel, "..") 686 } 687 688 func findOutputPath(c *config.Config, f *rule.File) string { 689 if c.ReadBuildFilesDir == "" && c.WriteBuildFilesDir == "" { 690 return f.Path 691 } 692 baseDir := c.WriteBuildFilesDir 693 if c.WriteBuildFilesDir == "" { 694 baseDir = c.RepoRoot 695 } 696 outputDir := filepath.Join(baseDir, filepath.FromSlash(f.Pkg)) 697 defaultOutputPath := filepath.Join(outputDir, c.DefaultBuildFileName()) 698 ents, err := os.ReadDir(outputDir) 699 if err != nil { 700 // Ignore error. Directory probably doesn't exist. 701 return defaultOutputPath 702 } 703 outputPath := rule.MatchBuildFile(outputDir, c.ValidBuildFileNames, ents) 704 if outputPath == "" { 705 return defaultOutputPath 706 } 707 return outputPath 708 } 709 710 // maybePopulateRemoteCacheFromGoMod reads go.mod and adds a root to rc for each 711 // module requirement. This lets the Go extension avoid a network lookup for 712 // unknown imports with -external=external, and it lets dependency resolution 713 // succeed with -external=static when it might not otherwise. 714 // 715 // This function does not override roots added from WORKSPACE (or some other 716 // configuration file), but it's useful when there is no such file. In most 717 // cases, a user of Gazelle with indirect Go dependencies does not need to add 718 // '# gazelle:repository' or '# gazelle:repository_macro' directives to their 719 // WORKSPACE file. This need was frustrating for developers in non-Go 720 // repositories with go_repository dependencies declared in macros. It wasn't 721 // obvious that Gazelle was missing these. 722 // 723 // This function is regrettably Go specific and does not belong here, but it 724 // can't be moved to //language/go until //repo is broken up and moved there. 725 func maybePopulateRemoteCacheFromGoMod(c *config.Config, rc *repo.RemoteCache) error { 726 haveGo := false 727 for name := range c.Exts { 728 if name == "go" { 729 haveGo = true 730 break 731 } 732 } 733 if !haveGo { 734 return nil 735 } 736 737 goModPath := filepath.Join(c.RepoRoot, "go.mod") 738 if _, err := os.Stat(goModPath); err != nil { 739 return nil 740 } 741 742 return rc.PopulateFromGoMod(goModPath) 743 } 744 745 func unionKindInfoMaps(a, b map[string]rule.KindInfo) map[string]rule.KindInfo { 746 if len(a) == 0 { 747 return b 748 } 749 if len(b) == 0 { 750 return a 751 } 752 result := make(map[string]rule.KindInfo, len(a)+len(b)) 753 for _, m := range []map[string]rule.KindInfo{a, b} { 754 for k, v := range m { 755 result[k] = v 756 } 757 } 758 return result 759 } 760 761 // applyKindMappings returns a copy of LoadInfo that includes c.KindMap. 762 func applyKindMappings(mappedKinds []config.MappedKind, loads []rule.LoadInfo) []rule.LoadInfo { 763 if len(mappedKinds) == 0 { 764 return loads 765 } 766 767 // Add new RuleInfos or replace existing ones with merged ones. 768 mappedLoads := make([]rule.LoadInfo, len(loads)) 769 copy(mappedLoads, loads) 770 for _, mappedKind := range mappedKinds { 771 mappedLoads = appendOrMergeKindMapping(mappedLoads, mappedKind) 772 } 773 return mappedLoads 774 } 775 776 // appendOrMergeKindMapping adds LoadInfo for the given replacement. 777 func appendOrMergeKindMapping(mappedLoads []rule.LoadInfo, mappedKind config.MappedKind) []rule.LoadInfo { 778 // If mappedKind.KindLoad already exists in the list, create a merged copy. 779 for i, load := range mappedLoads { 780 if load.Name == mappedKind.KindLoad { 781 mappedLoads[i].Symbols = append(load.Symbols, mappedKind.KindName) 782 return mappedLoads 783 } 784 } 785 786 // Add a new LoadInfo. 787 return append(mappedLoads, rule.LoadInfo{ 788 Name: mappedKind.KindLoad, 789 Symbols: []string{mappedKind.KindName}, 790 }) 791 } 792 793 func isDirErr(err error) bool { 794 if err == nil { 795 return false 796 } 797 var pe *os.PathError 798 if errors.As(err, &pe) { 799 return pe.Err == syscall.EISDIR 800 } 801 return false 802 }