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  }