...

Source file src/github.com/bazelbuild/bazel-gazelle/cmd/gazelle/fix-update.go

Documentation: github.com/bazelbuild/bazel-gazelle/cmd/gazelle

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

View as plain text