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  }