github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/language/go/config.go (about)

     1  /* Copyright 2018 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 golang
    17  
    18  import (
    19  	"errors"
    20  	"flag"
    21  	"fmt"
    22  	"go/build"
    23  	"log"
    24  	"os"
    25  	"path"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strconv"
    29  	"strings"
    30  
    31  	"github.com/bazelbuild/bazel-gazelle/config"
    32  	gzflag "github.com/bazelbuild/bazel-gazelle/flag"
    33  	"github.com/bazelbuild/bazel-gazelle/internal/module"
    34  	"github.com/bazelbuild/bazel-gazelle/internal/version"
    35  	"github.com/bazelbuild/bazel-gazelle/language/proto"
    36  	"github.com/bazelbuild/bazel-gazelle/repo"
    37  	"github.com/bazelbuild/bazel-gazelle/rule"
    38  	bzl "github.com/bazelbuild/buildtools/build"
    39  	"golang.org/x/mod/modfile"
    40  )
    41  
    42  var minimumRulesGoVersion = version.Version{0, 29, 0}
    43  
    44  // goConfig contains configuration values related to Go rules.
    45  type goConfig struct {
    46  	// The name under which the rules_go repository can be referenced from the
    47  	// repository in which Gazelle is running.
    48  	rulesGoRepoName string
    49  
    50  	// rulesGoVersion is the version of io_bazel_rules_go being used. Determined
    51  	// by reading go/def.bzl. May be unset if the version can't be read.
    52  	rulesGoVersion version.Version
    53  
    54  	// genericTags is a set of tags that Gazelle considers to be true. Set with
    55  	// -build_tags or # gazelle:build_tags. Some tags, like gc, are always on.
    56  	genericTags map[string]bool
    57  
    58  	// prefix is a prefix of an import path, used to generate importpath
    59  	// attributes. Set with -go_prefix or # gazelle:prefix.
    60  	prefix string
    61  
    62  	// prefixRel is the package name of the directory where the prefix was set
    63  	// ("" for the root directory).
    64  	prefixRel string
    65  
    66  	// prefixSet indicates whether the prefix was set explicitly. It is an error
    67  	// to infer an importpath for a rule without setting the prefix.
    68  	prefixSet bool
    69  
    70  	// importMapPrefix is a prefix of a package path, used to generate importmap
    71  	// attributes. Set with # gazelle:importmap_prefix.
    72  	importMapPrefix string
    73  
    74  	// importMapPrefixRel is the package name of the directory where importMapPrefix
    75  	// was set ("" for the root directory).
    76  	importMapPrefixRel string
    77  
    78  	// depMode determines how imports that are not standard, indexed, or local
    79  	// (under the current prefix) should be resolved.
    80  	depMode dependencyMode
    81  
    82  	// goGenerateProto indicates whether to generate go_proto_library
    83  	goGenerateProto bool
    84  
    85  	// goNamingConvention controls the name of generated targets
    86  	goNamingConvention namingConvention
    87  
    88  	// goNamingConventionExternal controls the default naming convention for
    89  	// imports in external repositories with unknown naming conventions.
    90  	goNamingConventionExternal namingConvention
    91  
    92  	// goProtoCompilers is the protocol buffers compiler(s) to use for go code.
    93  	goProtoCompilers []string
    94  
    95  	// goProtoCompilersSet indicates whether goProtoCompiler was set explicitly.
    96  	goProtoCompilersSet bool
    97  
    98  	// goGrpcCompilers is the gRPC compiler(s) to use for go code.
    99  	goGrpcCompilers []string
   100  
   101  	// goGrpcCompilersSet indicates whether goGrpcCompiler was set explicitly.
   102  	goGrpcCompilersSet bool
   103  
   104  	// goRepositoryMode is true if Gazelle was invoked by a go_repository rule.
   105  	// In this mode, we won't go out to the network to resolve external deps.
   106  	goRepositoryMode bool
   107  
   108  	// By default, internal packages are only visible to its siblings.
   109  	// goVisibility adds a list of packages the internal packages should be
   110  	// visible to
   111  	goVisibility []string
   112  
   113  	// moduleMode is true if the current directory is intended to be built
   114  	// as part of a module. Minimal module compatibility won't be supported
   115  	// if this is true in the root directory. External dependencies may be
   116  	// resolved differently (also depending on goRepositoryMode).
   117  	moduleMode bool
   118  
   119  	// map between external repo names and their `build_naming_convention`
   120  	// attribute.
   121  	repoNamingConvention map[string]namingConvention
   122  
   123  	// submodules is a list of modules which have the current module's path
   124  	// as a prefix of their own path. This affects visibility attributes
   125  	// in internal packages.
   126  	submodules []moduleRepo
   127  
   128  	// testMode determines how go_test targets are generated.
   129  	testMode testMode
   130  
   131  	// buildDirectives, buildExternalAttr, buildExtraArgsAttr,
   132  	// buildFileGenerationAttr, buildFileNamesAttr, buildFileProtoModeAttr and
   133  	// buildTagsAttr are attributes for go_repository rules, set on the command
   134  	// line.
   135  	buildDirectivesAttr, buildExternalAttr, buildExtraArgsAttr, buildFileGenerationAttr, buildFileNamesAttr, buildFileProtoModeAttr, buildTagsAttr string
   136  }
   137  
   138  // testMode determines how go_test rules are generated.
   139  type testMode int
   140  
   141  const (
   142  	// defaultTestMode generates a go_test for the primary package in a directory.
   143  	defaultTestMode = iota
   144  
   145  	// fileTestMode generates a go_test for each Go test file.
   146  	fileTestMode
   147  )
   148  
   149  var (
   150  	defaultGoProtoCompilers = []string{"@io_bazel_rules_go//proto:go_proto"}
   151  	defaultGoGrpcCompilers  = []string{"@io_bazel_rules_go//proto:go_grpc"}
   152  )
   153  
   154  func (m testMode) String() string {
   155  	switch m {
   156  	case defaultTestMode:
   157  		return "default"
   158  	case fileTestMode:
   159  		return "file"
   160  	default:
   161  		return "unknown"
   162  	}
   163  }
   164  
   165  func testModeFromString(s string) (testMode, error) {
   166  	switch s {
   167  	case "default":
   168  		return defaultTestMode, nil
   169  	case "file":
   170  		return fileTestMode, nil
   171  	default:
   172  		return 0, fmt.Errorf("unrecognized go_test mode: %q", s)
   173  	}
   174  }
   175  
   176  func newGoConfig() *goConfig {
   177  	gc := &goConfig{
   178  		goProtoCompilers: defaultGoProtoCompilers,
   179  		goGrpcCompilers:  defaultGoGrpcCompilers,
   180  		goGenerateProto:  true,
   181  	}
   182  	gc.preprocessTags()
   183  	return gc
   184  }
   185  
   186  func getGoConfig(c *config.Config) *goConfig {
   187  	return c.Exts[goName].(*goConfig)
   188  }
   189  
   190  func (gc *goConfig) clone() *goConfig {
   191  	gcCopy := *gc
   192  	gcCopy.genericTags = make(map[string]bool)
   193  	for k, v := range gc.genericTags {
   194  		gcCopy.genericTags[k] = v
   195  	}
   196  	gcCopy.goProtoCompilers = gc.goProtoCompilers[:len(gc.goProtoCompilers):len(gc.goProtoCompilers)]
   197  	gcCopy.goGrpcCompilers = gc.goGrpcCompilers[:len(gc.goGrpcCompilers):len(gc.goGrpcCompilers)]
   198  	gcCopy.submodules = gc.submodules[:len(gc.submodules):len(gc.submodules)]
   199  	return &gcCopy
   200  }
   201  
   202  // preprocessTags adds some tags which are on by default before they are
   203  // used to match files.
   204  func (gc *goConfig) preprocessTags() {
   205  	if gc.genericTags == nil {
   206  		gc.genericTags = make(map[string]bool)
   207  	}
   208  	gc.genericTags["gc"] = true
   209  }
   210  
   211  // setBuildTags sets genericTags by parsing as a comma separated list. An
   212  // error will be returned for tags that wouldn't be recognized by "go build".
   213  // preprocessTags should be called before this.
   214  func (gc *goConfig) setBuildTags(tags string) error {
   215  	if tags == "" {
   216  		return nil
   217  	}
   218  	for _, t := range strings.Split(tags, ",") {
   219  		if strings.HasPrefix(t, "!") {
   220  			return fmt.Errorf("build tags can't be negated: %s", t)
   221  		}
   222  		gc.genericTags[t] = true
   223  	}
   224  	return nil
   225  }
   226  
   227  func getProtoMode(c *config.Config) proto.Mode {
   228  	if gc := getGoConfig(c); !gc.goGenerateProto {
   229  		return proto.DisableMode
   230  	} else if pc := proto.GetProtoConfig(c); pc != nil {
   231  		return pc.Mode
   232  	} else {
   233  		return proto.DisableGlobalMode
   234  	}
   235  }
   236  
   237  // dependencyMode determines how imports of packages outside of the prefix
   238  // are resolved.
   239  type dependencyMode int
   240  
   241  const (
   242  	// externalMode indicates imports should be resolved to external dependencies
   243  	// (declared in WORKSPACE). Calls out to the network if an import can't be resolved
   244  	// locally.
   245  	externalMode dependencyMode = iota
   246  
   247  	// staticMode indicates imports should be resolved only to dependencies known by
   248  	// Gazelle (declared in WORKSPACE). Unknown imports are ignored.
   249  	staticMode
   250  
   251  	// vendorMode indicates imports should be resolved to libraries in the
   252  	// vendor directory.
   253  	vendorMode
   254  )
   255  
   256  func (m dependencyMode) String() string {
   257  	switch m {
   258  	case externalMode:
   259  		return "external"
   260  	case staticMode:
   261  		return "static"
   262  	case vendorMode:
   263  		return "vendor"
   264  	}
   265  	return ""
   266  }
   267  
   268  type externalFlag struct {
   269  	depMode *dependencyMode
   270  }
   271  
   272  func (f *externalFlag) Set(value string) error {
   273  	switch value {
   274  	case "external":
   275  		*f.depMode = externalMode
   276  	case "static":
   277  		*f.depMode = staticMode
   278  	case "vendored":
   279  		*f.depMode = vendorMode
   280  	default:
   281  		return fmt.Errorf("unrecognized dependency mode: %q", value)
   282  	}
   283  	return nil
   284  }
   285  
   286  func (f *externalFlag) String() string {
   287  	if f == nil || f.depMode == nil {
   288  		return "external"
   289  	}
   290  	return f.depMode.String()
   291  }
   292  
   293  type tagsFlag func(string) error
   294  
   295  func (f tagsFlag) Set(value string) error {
   296  	return f(value)
   297  }
   298  
   299  func (f tagsFlag) String() string {
   300  	return ""
   301  }
   302  
   303  type namingConventionFlag struct {
   304  	nc *namingConvention
   305  }
   306  
   307  func (f namingConventionFlag) Set(value string) error {
   308  	if nc, err := namingConventionFromString(value); err != nil {
   309  		return err
   310  	} else {
   311  		*f.nc = nc
   312  		return nil
   313  	}
   314  }
   315  
   316  func (f *namingConventionFlag) String() string {
   317  	if f == nil || f.nc == nil {
   318  		return "naming_convention"
   319  	}
   320  	return f.nc.String()
   321  }
   322  
   323  // namingConvention determines how go targets are named.
   324  type namingConvention int
   325  
   326  const (
   327  	// Try to detect the naming convention in use.
   328  	unknownNamingConvention namingConvention = iota
   329  
   330  	// 'go_default_library' and 'go_default_test'
   331  	goDefaultLibraryNamingConvention
   332  
   333  	// For an import path that ends with foo, the go_library rules target is
   334  	// named 'foo', the go_test is named 'foo_test'.
   335  	// For a main package, the go_binary takes the 'foo' name, the library
   336  	// is named 'foo_lib', and the go_test is named 'foo_test'.
   337  	importNamingConvention
   338  
   339  	// Same as importNamingConvention, but generate alias rules for libraries that have
   340  	// the legacy 'go_default_library' name.
   341  	importAliasNamingConvention
   342  )
   343  
   344  func (nc namingConvention) String() string {
   345  	switch nc {
   346  	case goDefaultLibraryNamingConvention:
   347  		return "go_default_library"
   348  	case importNamingConvention:
   349  		return "import"
   350  	case importAliasNamingConvention:
   351  		return "import_alias"
   352  	}
   353  	return ""
   354  }
   355  
   356  func namingConventionFromString(s string) (namingConvention, error) {
   357  	switch s {
   358  	case "":
   359  		return unknownNamingConvention, nil
   360  	case "go_default_library":
   361  		return goDefaultLibraryNamingConvention, nil
   362  	case "import":
   363  		return importNamingConvention, nil
   364  	case "import_alias":
   365  		return importAliasNamingConvention, nil
   366  	default:
   367  		return unknownNamingConvention, fmt.Errorf("unknown naming convention %q", s)
   368  	}
   369  }
   370  
   371  type moduleRepo struct {
   372  	repoName, modulePath string
   373  }
   374  
   375  var (
   376  	validBuildExternalAttr       = []string{"external", "vendored"}
   377  	validBuildFileGenerationAttr = []string{"auto", "on", "off"}
   378  	validBuildFileProtoModeAttr  = []string{"default", "legacy", "disable", "disable_global", "package"}
   379  )
   380  
   381  func (*goLang) KnownDirectives() []string {
   382  	return []string{
   383  		"build_tags",
   384  		"go_generate_proto",
   385  		"go_grpc_compilers",
   386  		"go_naming_convention",
   387  		"go_naming_convention_external",
   388  		"go_proto_compilers",
   389  		"go_test",
   390  		"go_visibility",
   391  		"importmap_prefix",
   392  		"prefix",
   393  	}
   394  }
   395  
   396  func (*goLang) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
   397  	gc := newGoConfig()
   398  	switch cmd {
   399  	case "fix", "update":
   400  		fs.Var(
   401  			tagsFlag(gc.setBuildTags),
   402  			"build_tags",
   403  			"comma-separated list of build tags. If not specified, Gazelle will not\n\tfilter sources with build constraints.")
   404  		fs.Var(
   405  			&gzflag.ExplicitFlag{Value: &gc.prefix, IsSet: &gc.prefixSet},
   406  			"go_prefix",
   407  			"prefix of import paths in the current workspace")
   408  		fs.Var(
   409  			&externalFlag{&gc.depMode},
   410  			"external",
   411  			"external: resolve external packages with go_repository\n\tvendored: resolve external packages as packages in vendor/")
   412  		fs.Var(
   413  			&gzflag.MultiFlag{Values: &gc.goProtoCompilers, IsSet: &gc.goProtoCompilersSet},
   414  			"go_proto_compiler",
   415  			"go_proto_library compiler to use (may be repeated)")
   416  		fs.Var(
   417  			&gzflag.MultiFlag{Values: &gc.goGrpcCompilers, IsSet: &gc.goGrpcCompilersSet},
   418  			"go_grpc_compiler",
   419  			"go_proto_library compiler to use for gRPC (may be repeated)")
   420  		fs.BoolVar(
   421  			&gc.goRepositoryMode,
   422  			"go_repository_mode",
   423  			false,
   424  			"set when gazelle is invoked by go_repository")
   425  		fs.BoolVar(
   426  			&gc.moduleMode,
   427  			"go_repository_module_mode",
   428  			false,
   429  			"set when gazelle is invoked by go_repository in module mode")
   430  		fs.Var(
   431  			&namingConventionFlag{&gc.goNamingConvention},
   432  			"go_naming_convention",
   433  			"controls generated library names. One of (go_default_library, import, import_alias)")
   434  		fs.Var(
   435  			&namingConventionFlag{&gc.goNamingConventionExternal},
   436  			"go_naming_convention_external",
   437  			"controls naming convention used when resolving libraries in external repositories with unknown conventions")
   438  
   439  	case "update-repos":
   440  		fs.StringVar(&gc.buildDirectivesAttr,
   441  			"build_directives",
   442  			"",
   443  			"Sets the build_directives attribute for the generated go_repository rule(s).")
   444  		fs.Var(&gzflag.AllowedStringFlag{Value: &gc.buildExternalAttr, Allowed: validBuildExternalAttr},
   445  			"build_external",
   446  			"Sets the build_external attribute for the generated go_repository rule(s).")
   447  		fs.StringVar(&gc.buildExtraArgsAttr,
   448  			"build_extra_args",
   449  			"",
   450  			"Sets the build_extra_args attribute for the generated go_repository rule(s).")
   451  		fs.Var(&gzflag.AllowedStringFlag{Value: &gc.buildFileGenerationAttr, Allowed: validBuildFileGenerationAttr},
   452  			"build_file_generation",
   453  			"Sets the build_file_generation attribute for the generated go_repository rule(s).")
   454  		fs.StringVar(&gc.buildFileNamesAttr,
   455  			"build_file_names",
   456  			"",
   457  			"Sets the build_file_name attribute for the generated go_repository rule(s).")
   458  		fs.Var(&gzflag.AllowedStringFlag{Value: &gc.buildFileProtoModeAttr, Allowed: validBuildFileProtoModeAttr},
   459  			"build_file_proto_mode",
   460  			"Sets the build_file_proto_mode attribute for the generated go_repository rule(s).")
   461  		fs.StringVar(&gc.buildTagsAttr,
   462  			"build_tags",
   463  			"",
   464  			"Sets the build_tags attribute for the generated go_repository rule(s).")
   465  	}
   466  	c.Exts[goName] = gc
   467  }
   468  
   469  func (*goLang) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
   470  	// The base of the -go_prefix flag may be used to generate proto_library
   471  	// rule names when there are no .proto sources (empty rules to be deleted)
   472  	// or when the package name can't be determined.
   473  	// TODO(jayconrod): deprecate and remove this behavior.
   474  	gc := getGoConfig(c)
   475  	if pc := proto.GetProtoConfig(c); pc != nil {
   476  		pc.GoPrefix = gc.prefix
   477  	}
   478  
   479  	// List modules that may refer to internal packages in this module.
   480  	for _, r := range c.Repos {
   481  		if r.Kind() != "go_repository" {
   482  			continue
   483  		}
   484  		modulePath := r.AttrString("importpath")
   485  		if !strings.HasPrefix(modulePath, gc.prefix+"/") {
   486  			continue
   487  		}
   488  		m := moduleRepo{
   489  			repoName:   r.Name(),
   490  			modulePath: modulePath,
   491  		}
   492  		gc.submodules = append(gc.submodules, m)
   493  	}
   494  
   495  	return nil
   496  }
   497  
   498  func (*goLang) Configure(c *config.Config, rel string, f *rule.File) {
   499  	var gc *goConfig
   500  	if raw, ok := c.Exts[goName]; !ok {
   501  		gc = newGoConfig()
   502  	} else {
   503  		gc = raw.(*goConfig).clone()
   504  	}
   505  	c.Exts[goName] = gc
   506  
   507  	if rel == "" {
   508  		moduleToApparentName, err := module.ExtractModuleToApparentNameMapping(c.RepoRoot)
   509  		if err != nil {
   510  			log.Print(err)
   511  		} else {
   512  			gc.rulesGoRepoName = moduleToApparentName("rules_go")
   513  		}
   514  		if gc.rulesGoRepoName == "" {
   515  			// The legacy name used in WORKSPACE.
   516  			gc.rulesGoRepoName = "io_bazel_rules_go"
   517  		}
   518  
   519  		const message = `Gazelle may not be compatible with this version of rules_go.
   520  Update io_bazel_rules_go to a newer version in your WORKSPACE file.`
   521  		gc.rulesGoVersion, err = findRulesGoVersion(c)
   522  		if c.ShouldFix {
   523  			// Only check the version when "fix" is run. Generated build files
   524  			// frequently work with older version of rules_go, and we don't want to
   525  			// nag too much since there's no way to disable this warning.
   526  			// Also, don't print a warning if the rules_go repo hasn't been fetched,
   527  			// since that's a common issue when Gazelle is run as a separate binary.
   528  			if err != nil && err != errRulesGoRepoNotFound && c.ShouldFix {
   529  				log.Printf("%v\n%s", err, message)
   530  			} else if err == nil && gc.rulesGoVersion.Compare(minimumRulesGoVersion) < 0 {
   531  				log.Printf("Found RULES_GO_VERSION %s. Minimum compatible version is %s.\n%s", gc.rulesGoVersion, minimumRulesGoVersion, message)
   532  			}
   533  		}
   534  		repoNamingConvention := map[string]namingConvention{}
   535  		for _, repo := range c.Repos {
   536  			if repo.Kind() == "go_repository" {
   537  				if attr := repo.AttrString("build_naming_convention"); attr == "" {
   538  					// No naming convention specified.
   539  					// go_repsitory uses importAliasNamingConvention by default, so we
   540  					// could use whichever name.
   541  					// resolveExternal should take that as a signal to follow the current
   542  					// naming convention to avoid churn.
   543  					repoNamingConvention[repo.Name()] = importAliasNamingConvention
   544  				} else if nc, err := namingConventionFromString(attr); err != nil {
   545  					log.Printf("in go_repository named %q: %v", repo.Name(), err)
   546  				} else {
   547  					repoNamingConvention[repo.Name()] = nc
   548  				}
   549  			}
   550  		}
   551  		gc.repoNamingConvention = repoNamingConvention
   552  	}
   553  
   554  	if !gc.moduleMode {
   555  		st, err := os.Stat(filepath.Join(c.RepoRoot, filepath.FromSlash(rel), "go.mod"))
   556  		if err == nil && !st.IsDir() {
   557  			gc.moduleMode = true
   558  		}
   559  	}
   560  
   561  	if path.Base(rel) == "vendor" {
   562  		gc.importMapPrefix = InferImportPath(c, rel)
   563  		gc.importMapPrefixRel = rel
   564  		gc.prefix = ""
   565  		gc.prefixRel = rel
   566  	}
   567  
   568  	if f != nil {
   569  		setPrefix := func(prefix string) {
   570  			if err := checkPrefix(prefix); err != nil {
   571  				log.Print(err)
   572  				return
   573  			}
   574  			gc.prefix = prefix
   575  			gc.prefixSet = true
   576  			gc.prefixRel = rel
   577  		}
   578  		for _, d := range f.Directives {
   579  			switch d.Key {
   580  			case "build_tags":
   581  				if err := gc.setBuildTags(d.Value); err != nil {
   582  					log.Print(err)
   583  					continue
   584  				}
   585  				gc.preprocessTags()
   586  				if err := gc.setBuildTags(d.Value); err != nil {
   587  					log.Print(err)
   588  				}
   589  
   590  			case "go_generate_proto":
   591  				if goGenerateProto, err := strconv.ParseBool(d.Value); err == nil {
   592  					gc.goGenerateProto = goGenerateProto
   593  				} else {
   594  					log.Printf("parsing go_generate_proto: %v", err)
   595  				}
   596  
   597  			case "go_naming_convention":
   598  				if nc, err := namingConventionFromString(d.Value); err == nil {
   599  					gc.goNamingConvention = nc
   600  				} else {
   601  					log.Print(err)
   602  				}
   603  
   604  			case "go_naming_convention_external":
   605  				if nc, err := namingConventionFromString(d.Value); err == nil {
   606  					gc.goNamingConventionExternal = nc
   607  				} else {
   608  					log.Print(err)
   609  				}
   610  
   611  			case "go_grpc_compilers":
   612  				// Special syntax (empty value) to reset directive.
   613  				if d.Value == "" {
   614  					gc.goGrpcCompilersSet = false
   615  					gc.goGrpcCompilers = defaultGoGrpcCompilers
   616  				} else {
   617  					gc.goGrpcCompilersSet = true
   618  					gc.goGrpcCompilers = splitValue(d.Value)
   619  				}
   620  
   621  			case "go_proto_compilers":
   622  				// Special syntax (empty value) to reset directive.
   623  				if d.Value == "" {
   624  					gc.goProtoCompilersSet = false
   625  					gc.goProtoCompilers = defaultGoProtoCompilers
   626  				} else {
   627  					gc.goProtoCompilersSet = true
   628  					gc.goProtoCompilers = splitValue(d.Value)
   629  				}
   630  
   631  			case "go_test":
   632  				mode, err := testModeFromString(d.Value)
   633  				if err != nil {
   634  					log.Print(err)
   635  					continue
   636  				}
   637  				gc.testMode = mode
   638  
   639  			case "go_visibility":
   640  				gc.goVisibility = append(gc.goVisibility, strings.TrimSpace(d.Value))
   641  
   642  			case "importmap_prefix":
   643  				gc.importMapPrefix = d.Value
   644  				gc.importMapPrefixRel = rel
   645  
   646  			case "prefix":
   647  				setPrefix(d.Value)
   648  			}
   649  		}
   650  
   651  		if !gc.prefixSet {
   652  			for _, r := range f.Rules {
   653  				switch r.Kind() {
   654  				case "go_prefix":
   655  					args := r.Args()
   656  					if len(args) != 1 {
   657  						continue
   658  					}
   659  					s, ok := args[0].(*bzl.StringExpr)
   660  					if !ok {
   661  						continue
   662  					}
   663  					setPrefix(s.Value)
   664  
   665  				case "gazelle":
   666  					if prefix := r.AttrString("prefix"); prefix != "" {
   667  						setPrefix(prefix)
   668  					}
   669  				}
   670  			}
   671  		}
   672  		if !gc.prefixSet {
   673  			// Parse the module directive out of the go.mod file, if present.
   674  			goModPath := filepath.Join(c.RepoRoot, filepath.FromSlash(rel), "go.mod")
   675  			goMod, err := os.ReadFile(goModPath)
   676  			// Reading the go.mod file is best-effort and may fail for various reasons, such as
   677  			// the file not existing or being a directory. Do not report errors.
   678  			if err == nil {
   679  				goModFile, err := modfile.ParseLax(goModPath, goMod, nil)
   680  				// If the go.mod file exists but is malformed, report the error.
   681  				if err != nil {
   682  					log.Printf("parsing %s: %s", goModPath, err)
   683  				} else {
   684  					setPrefix(goModFile.Module.Mod.Path)
   685  				}
   686  			}
   687  		}
   688  	}
   689  
   690  	if gc.goNamingConvention == unknownNamingConvention {
   691  		gc.goNamingConvention = detectNamingConvention(c, f)
   692  	}
   693  }
   694  
   695  // checkPrefix checks that a string may be used as a prefix. We forbid local
   696  // (relative) imports and those beginning with "/". We allow the empty string,
   697  // but generated rules must not have an empty importpath.
   698  func checkPrefix(prefix string) error {
   699  	if strings.HasPrefix(prefix, "/") || build.IsLocalImport(prefix) {
   700  		return fmt.Errorf("invalid prefix: %q", prefix)
   701  	}
   702  	return nil
   703  }
   704  
   705  // splitDirective splits a comma-separated directive value into its component
   706  // parts, trimming each of any whitespace characters.
   707  func splitValue(value string) []string {
   708  	parts := strings.Split(value, ",")
   709  	values := make([]string, 0, len(parts))
   710  	for _, part := range parts {
   711  		values = append(values, strings.TrimSpace(part))
   712  	}
   713  	return values
   714  }
   715  
   716  // findRulesGoVersion attempts to infer the version of io_bazel_rules_go.
   717  // It can read the external directory (if bazel has fetched it), or it can
   718  // read WORKSPACE. Neither method is completely reliable.
   719  func findRulesGoVersion(c *config.Config) (version.Version, error) {
   720  	const message = `Gazelle may not be compatible with this version of rules_go.
   721  Update io_bazel_rules_go to a newer version in your WORKSPACE file.`
   722  
   723  	var vstr string
   724  	if rulesGoPath, err := repo.FindExternalRepo(c.RepoRoot, config.RulesGoRepoName); err == nil {
   725  		// Bazel has already fetched io_bazel_rules_go. We can read its version
   726  		// from //go:def.bzl.
   727  		defBzlPath := filepath.Join(rulesGoPath, "go", "def.bzl")
   728  		defBzlContent, err := os.ReadFile(defBzlPath)
   729  		if err != nil {
   730  			return nil, err
   731  		}
   732  		versionRe := regexp.MustCompile(`(?m)^RULES_GO_VERSION = ['"]([0-9.]*)['"]`)
   733  		match := versionRe.FindSubmatch(defBzlContent)
   734  		if match == nil {
   735  			return nil, fmt.Errorf("RULES_GO_VERSION not found in @%s//go:def.bzl.\n%s", config.RulesGoRepoName, message)
   736  		}
   737  		vstr = string(match[1])
   738  	} else {
   739  		// Bazel has not fetched io_bazel_rules_go. Maybe we can find it in the
   740  		// WORKSPACE file.
   741  		re := regexp.MustCompile(`github\.com/bazelbuild/rules_go/releases/download/v([0-9.]+)/`)
   742  	RepoLoop:
   743  		for _, r := range c.Repos {
   744  			if r.Kind() == "http_archive" && r.Name() == "io_bazel_rules_go" {
   745  				for _, u := range r.AttrStrings("urls") {
   746  					if m := re.FindStringSubmatch(u); m != nil {
   747  						vstr = m[1]
   748  						break RepoLoop
   749  					}
   750  				}
   751  			}
   752  		}
   753  	}
   754  
   755  	if vstr == "" {
   756  		// Couldn't find a version. We return a specific value since this is not
   757  		// usually a useful error to report.
   758  		return nil, errRulesGoRepoNotFound
   759  	}
   760  
   761  	return version.ParseVersion(vstr)
   762  }
   763  
   764  var errRulesGoRepoNotFound = errors.New(config.RulesGoRepoName + " external repository not found")
   765  
   766  // detectNamingConvention attempts to detect the naming convention in use by
   767  // reading build files in subdirectories of the repository root directory.
   768  //
   769  // If detectNamingConvention can't detect the naming convention (for example,
   770  // because no build files are found or multiple naming conventions are found),
   771  // importNamingConvention is returned.
   772  func detectNamingConvention(c *config.Config, rootFile *rule.File) namingConvention {
   773  	if !c.IndexLibraries {
   774  		// Indexing is disabled, which usually means speed is important and I/O
   775  		// should be minimized. Let's not open extra files or directories.
   776  		return importNamingConvention
   777  	}
   778  
   779  	detectInFile := func(f *rule.File) namingConvention {
   780  		for _, r := range f.Rules {
   781  			// NOTE: map_kind is not supported. c.KindMap will not be accurate in
   782  			// subdirectories.
   783  			kind := r.Kind()
   784  			name := r.Name()
   785  			if kind != "alias" && name == defaultLibName {
   786  				// Assume any kind of rule with the name "go_default_library" is some
   787  				// kind of go library. The old version of go_proto_library used this
   788  				// name, and it's possible with map_kind as well.
   789  				return goDefaultLibraryNamingConvention
   790  			} else if isGoLibrary(kind) && name == path.Base(r.AttrString("importpath")) {
   791  				return importNamingConvention
   792  			}
   793  		}
   794  		return unknownNamingConvention
   795  	}
   796  
   797  	detectInDir := func(dir, rel string) namingConvention {
   798  		var f *rule.File
   799  		for _, name := range c.ValidBuildFileNames {
   800  			fpath := filepath.Join(dir, name)
   801  			data, err := os.ReadFile(fpath)
   802  			if err != nil {
   803  				continue
   804  			}
   805  			f, err = rule.LoadData(fpath, rel, data)
   806  			if err != nil {
   807  				continue
   808  			}
   809  		}
   810  		if f == nil {
   811  			return unknownNamingConvention
   812  		}
   813  		return detectInFile(f)
   814  	}
   815  
   816  	nc := unknownNamingConvention
   817  	if rootFile != nil {
   818  		if rootNC := detectInFile(rootFile); rootNC != unknownNamingConvention {
   819  			return rootNC
   820  		}
   821  	}
   822  
   823  	ents, err := os.ReadDir(c.RepoRoot)
   824  	if err != nil {
   825  		return importNamingConvention
   826  	}
   827  	for _, ent := range ents {
   828  		if !ent.IsDir() {
   829  			continue
   830  		}
   831  		dirName := ent.Name()
   832  		dirNC := detectInDir(filepath.Join(c.RepoRoot, dirName), dirName)
   833  		if dirNC == unknownNamingConvention {
   834  			continue
   835  		}
   836  		if nc != unknownNamingConvention && dirNC != nc {
   837  			// Subdirectories use different conventions. Return the default.
   838  			return importNamingConvention
   839  		}
   840  		nc = dirNC
   841  	}
   842  	if nc == unknownNamingConvention {
   843  		return importNamingConvention
   844  	}
   845  	return nc
   846  }