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