github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/config/config.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 config provides extensible configuration for Gazelle libraries.
    17  //
    18  // Packages may define Configurers which add support for new command-line
    19  // options and directive comments in build files. Note that the
    20  // language.Language interface embeds Configurer, so each language extension
    21  // has the opportunity
    22  //
    23  // When Gazelle walks the directory trees in a repository, it calls the
    24  // Configure method of each Configurer to produce a Config object.
    25  // Config objects are passed as arguments to most functions in Gazelle, so
    26  // this mechanism may be used to control many aspects of Gazelle's behavior.
    27  package config
    28  
    29  import (
    30  	"flag"
    31  	"fmt"
    32  	"log"
    33  	"os"
    34  	"path/filepath"
    35  	"strings"
    36  
    37  	"github.com/bazelbuild/bazel-gazelle/internal/module"
    38  	"github.com/bazelbuild/bazel-gazelle/internal/wspace"
    39  	"github.com/bazelbuild/bazel-gazelle/rule"
    40  )
    41  
    42  // Config holds information about how Gazelle should run. This is based on
    43  // command line arguments, directives, other hints in build files.
    44  //
    45  // A Config applies to a single directory. A Config is created for the
    46  // repository root directory, then copied and modified for each subdirectory.
    47  //
    48  // Config itself contains only general information. Most configuration
    49  // information is language-specific and is stored in Exts. This information
    50  // is modified by extensions that implement Configurer.
    51  type Config struct {
    52  	// WorkDir is the effective working directory, used to resolve relative
    53  	// paths on the command line. When Gazelle is invoked with 'bazel run',
    54  	// this is set by BUILD_WORKSPACE_DIRECTORY.
    55  	WorkDir string
    56  
    57  	// RepoRoot is the absolute, canonical path to the root directory of the
    58  	// repository with all symlinks resolved.
    59  	RepoRoot string
    60  
    61  	// RepoName is the name of the repository.
    62  	RepoName string
    63  
    64  	// ReadBuildFilesDir is the absolute path to a directory where
    65  	// build files should be read from instead of RepoRoot.
    66  	ReadBuildFilesDir string
    67  
    68  	// WriteBuildFilesDir is the absolute path to a directory where
    69  	// build files should be written to instead of RepoRoot.
    70  	WriteBuildFilesDir string
    71  
    72  	// ValidBuildFileNames is a list of base names that are considered valid
    73  	// build files. Some repositories may have files named "BUILD" that are not
    74  	// used by Bazel and should be ignored. Must contain at least one string.
    75  	ValidBuildFileNames []string
    76  
    77  	// ShouldFix determines whether Gazelle attempts to remove and replace
    78  	// usage of deprecated rules.
    79  	ShouldFix bool
    80  
    81  	// Strict determines how Gazelle handles build file and directive errors. When
    82  	// set, Gazelle will exit with non-zero value after logging such errors.
    83  	Strict bool
    84  
    85  	// IndexLibraries determines whether Gazelle should build an index of
    86  	// libraries in the workspace for dependency resolution
    87  	IndexLibraries bool
    88  
    89  	// KindMap maps from a kind name to its replacement. It provides a way for
    90  	// users to customize the kind of rules created by Gazelle, via
    91  	// # gazelle:map_kind.
    92  	KindMap map[string]MappedKind
    93  
    94  	// Repos is a list of repository rules declared in the main WORKSPACE file
    95  	// or in macros called by the main WORKSPACE file. This may affect rule
    96  	// generation and dependency resolution.
    97  	Repos []*rule.Rule
    98  
    99  	// Langs is a list of language names which Gazelle should process.
   100  	// An empty list means "all languages".
   101  	Langs []string
   102  
   103  	// Exts is a set of configurable extensions. Generally, each language
   104  	// has its own set of extensions, but other modules may provide their own
   105  	// extensions as well. Values in here may be populated by command line
   106  	// arguments, directives in build files, or other mechanisms.
   107  	Exts map[string]interface{}
   108  
   109  	// Whether Gazelle is loaded as a Bzlmod 'bazel_dep'.
   110  	Bzlmod bool
   111  
   112  	// ModuleToApparentName is a function that maps the name of a Bazel module
   113  	// to the apparent name (repo_name) specified in the MODULE.bazel file. It
   114  	// returns the empty string if the module is not found.
   115  	ModuleToApparentName func(string) string
   116  }
   117  
   118  // MappedKind describes a replacement to use for a built-in kind.
   119  type MappedKind struct {
   120  	FromKind, KindName, KindLoad string
   121  }
   122  
   123  func New() *Config {
   124  	return &Config{
   125  		ValidBuildFileNames: DefaultValidBuildFileNames,
   126  		Exts:                make(map[string]interface{}),
   127  	}
   128  }
   129  
   130  // Clone creates a copy of the configuration for use in a subdirectory.
   131  // Note that the Exts map is copied, but its contents are not.
   132  // Configurer.Configure should do this, if needed.
   133  func (c *Config) Clone() *Config {
   134  	cc := *c
   135  	cc.Exts = make(map[string]interface{})
   136  	for k, v := range c.Exts {
   137  		cc.Exts[k] = v
   138  	}
   139  	cc.KindMap = make(map[string]MappedKind)
   140  	for k, v := range c.KindMap {
   141  		cc.KindMap[k] = v
   142  	}
   143  	return &cc
   144  }
   145  
   146  var DefaultValidBuildFileNames = []string{"BUILD.bazel", "BUILD"}
   147  
   148  // IsValidBuildFileName returns true if a file with the given base name
   149  // should be treated as a build file.
   150  func (c *Config) IsValidBuildFileName(name string) bool {
   151  	for _, n := range c.ValidBuildFileNames {
   152  		if name == n {
   153  			return true
   154  		}
   155  	}
   156  	return false
   157  }
   158  
   159  // DefaultBuildFileName returns the base name used to create new build files.
   160  func (c *Config) DefaultBuildFileName() string {
   161  	return c.ValidBuildFileNames[0]
   162  }
   163  
   164  // Configurer is the interface for language or library-specific configuration
   165  // extensions. Most (ideally all) modifications to Config should happen
   166  // via this interface.
   167  type Configurer interface {
   168  	// RegisterFlags registers command-line flags used by the extension. This
   169  	// method is called once with the root configuration when Gazelle
   170  	// starts. RegisterFlags may set an initial values in Config.Exts. When flags
   171  	// are set, they should modify these values.
   172  	RegisterFlags(fs *flag.FlagSet, cmd string, c *Config)
   173  
   174  	// CheckFlags validates the configuration after command line flags are parsed.
   175  	// This is called once with the root configuration when Gazelle starts.
   176  	// CheckFlags may set default values in flags or make implied changes.
   177  	CheckFlags(fs *flag.FlagSet, c *Config) error
   178  
   179  	// KnownDirectives returns a list of directive keys that this Configurer can
   180  	// interpret. Gazelle prints errors for directives that are not recoginized by
   181  	// any Configurer.
   182  	KnownDirectives() []string
   183  
   184  	// Configure modifies the configuration using directives and other information
   185  	// extracted from a build file. Configure is called in each directory.
   186  	//
   187  	// c is the configuration for the current directory. It starts out as a copy
   188  	// of the configuration for the parent directory.
   189  	//
   190  	// rel is the slash-separated relative path from the repository root to
   191  	// the current directory. It is "" for the root directory itself.
   192  	//
   193  	// f is the build file for the current directory or nil if there is no
   194  	// existing build file.
   195  	Configure(c *Config, rel string, f *rule.File)
   196  }
   197  
   198  // CommonConfigurer handles language-agnostic command-line flags and directives,
   199  // i.e., those that apply to Config itself and not to Config.Exts.
   200  type CommonConfigurer struct {
   201  	repoRoot, buildFileNames, readBuildFilesDir, writeBuildFilesDir string
   202  	indexLibraries, strict                                          bool
   203  	langCsv                                                         string
   204  	bzlmod                                                          bool
   205  }
   206  
   207  func (cc *CommonConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *Config) {
   208  	fs.StringVar(&cc.repoRoot, "repo_root", "", "path to a directory which corresponds to go_prefix, otherwise gazelle searches for it.")
   209  	fs.StringVar(&cc.buildFileNames, "build_file_name", strings.Join(DefaultValidBuildFileNames, ","), "comma-separated list of valid build file names.\nThe first element of the list is the name of output build files to generate.")
   210  	fs.BoolVar(&cc.indexLibraries, "index", true, "when true, gazelle will build an index of libraries in the workspace for dependency resolution")
   211  	fs.BoolVar(&cc.strict, "strict", false, "when true, gazelle will exit with none-zero value for build file syntax errors or unknown directives")
   212  	fs.StringVar(&cc.readBuildFilesDir, "experimental_read_build_files_dir", "", "path to a directory where build files should be read from (instead of -repo_root)")
   213  	fs.StringVar(&cc.writeBuildFilesDir, "experimental_write_build_files_dir", "", "path to a directory where build files should be written to (instead of -repo_root)")
   214  	fs.StringVar(&cc.langCsv, "lang", "", "if non-empty, process only these languages (e.g. \"go,proto\")")
   215  	fs.BoolVar(&cc.bzlmod, "bzlmod", false, "for internal usage only")
   216  }
   217  
   218  func (cc *CommonConfigurer) CheckFlags(fs *flag.FlagSet, c *Config) error {
   219  	var err error
   220  	if cc.repoRoot == "" {
   221  		if wsDir := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); wsDir != "" {
   222  			cc.repoRoot = wsDir
   223  		} else if parent, err := wspace.FindRepoRoot(c.WorkDir); err == nil {
   224  			cc.repoRoot = parent
   225  		} else {
   226  			return fmt.Errorf("-repo_root not specified, and WORKSPACE cannot be found: %v", err)
   227  		}
   228  	}
   229  	if filepath.IsAbs(cc.repoRoot) {
   230  		c.RepoRoot = cc.repoRoot
   231  	} else {
   232  		c.RepoRoot = filepath.Join(c.WorkDir, cc.repoRoot)
   233  	}
   234  	c.RepoRoot, err = filepath.EvalSymlinks(c.RepoRoot)
   235  	if err != nil {
   236  		return fmt.Errorf("%s: failed to resolve symlinks: %v", cc.repoRoot, err)
   237  	}
   238  	c.ValidBuildFileNames = strings.Split(cc.buildFileNames, ",")
   239  	if cc.readBuildFilesDir != "" {
   240  		if filepath.IsAbs(cc.readBuildFilesDir) {
   241  			c.ReadBuildFilesDir = cc.readBuildFilesDir
   242  		} else {
   243  			c.ReadBuildFilesDir = filepath.Join(c.WorkDir, cc.readBuildFilesDir)
   244  		}
   245  	}
   246  	if cc.writeBuildFilesDir != "" {
   247  		if filepath.IsAbs(cc.writeBuildFilesDir) {
   248  			c.WriteBuildFilesDir = cc.writeBuildFilesDir
   249  		} else {
   250  			c.WriteBuildFilesDir = filepath.Join(c.WorkDir, cc.writeBuildFilesDir)
   251  		}
   252  	}
   253  	c.IndexLibraries = cc.indexLibraries
   254  	c.Strict = cc.strict
   255  	if len(cc.langCsv) > 0 {
   256  		c.Langs = strings.Split(cc.langCsv, ",")
   257  	}
   258  	c.Bzlmod = cc.bzlmod
   259  	c.ModuleToApparentName, err = module.ExtractModuleToApparentNameMapping(c.RepoRoot)
   260  	if err != nil {
   261  		return fmt.Errorf("failed to parse MODULE.bazel: %v", err)
   262  	}
   263  	return nil
   264  }
   265  
   266  func (cc *CommonConfigurer) KnownDirectives() []string {
   267  	return []string{"build_file_name", "map_kind", "lang"}
   268  }
   269  
   270  func (cc *CommonConfigurer) Configure(c *Config, rel string, f *rule.File) {
   271  	if f == nil {
   272  		return
   273  	}
   274  	for _, d := range f.Directives {
   275  		switch d.Key {
   276  		case "build_file_name":
   277  			c.ValidBuildFileNames = strings.Split(d.Value, ",")
   278  
   279  		case "map_kind":
   280  			vals := strings.Fields(d.Value)
   281  			if len(vals) != 3 {
   282  				log.Printf("expected three arguments (gazelle:map_kind from_kind to_kind load_file), got %v", vals)
   283  				continue
   284  			}
   285  			if c.KindMap == nil {
   286  				c.KindMap = make(map[string]MappedKind)
   287  			}
   288  			c.KindMap[vals[0]] = MappedKind{
   289  				FromKind: vals[0],
   290  				KindName: vals[1],
   291  				KindLoad: vals[2],
   292  			}
   293  
   294  		case "lang":
   295  			if len(d.Value) > 0 {
   296  				c.Langs = strings.Split(d.Value, ",")
   297  			} else {
   298  				c.Langs = nil
   299  			}
   300  		}
   301  	}
   302  }