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 }