github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/pkg/protoc/package_config.go (about) 1 package protoc 2 3 import ( 4 "fmt" 5 "log" 6 "sort" 7 "strings" 8 9 "github.com/bazelbuild/bazel-gazelle/config" 10 "github.com/bazelbuild/bazel-gazelle/rule" 11 ) 12 13 const ( 14 // RuleDirective is the directive for toggling rule generation. 15 RuleDirective = "proto_rule" 16 // LanguageDirective tells gazelle which languages a package should 17 // produce and how it is configured. 18 LanguageDirective = "proto_language" 19 // PluginDirective created an association between proto_lang 20 // and the label of a proto_plugin. 21 PluginDirective = "proto_plugin" 22 // importpathPrefixDirective is the same as 'gazelle:prefix' 23 importpathPrefixDirective = "prefix" 24 ) 25 26 // PackageConfig represents the config extension for the protobuf language. 27 type PackageConfig struct { 28 // config is the parent gazelle config. 29 Config *config.Config 30 // the gazelle:prefix for golang 31 importpathPrefix string 32 // configured languages for this package 33 langs map[string]*LanguageConfig 34 // exclude patterns for rules that should be skipped for this package. 35 plugins map[string]*LanguagePluginConfig 36 // exclude patterns for rules that should be skipped for this package. 37 rules map[string]*LanguageRuleConfig 38 // IMPORTANT! Adding new fields here? Don't forget to copy it in the Clone 39 // method! 40 } 41 42 // GetPackageConfig returns the associated package config. 43 func GetPackageConfig(config *config.Config) *PackageConfig { 44 if cfg, ok := config.Exts["protobuf"].(*PackageConfig); ok { 45 return cfg 46 } 47 return nil 48 } 49 50 // NewPackageConfig initializes a new PackageConfig. 51 func NewPackageConfig(config *config.Config) *PackageConfig { 52 return &PackageConfig{ 53 Config: config, 54 langs: make(map[string]*LanguageConfig), 55 plugins: make(map[string]*LanguagePluginConfig), 56 rules: make(map[string]*LanguageRuleConfig), 57 } 58 } 59 60 // Plugin returns a readonly copy of the plugin configuration having the given 61 // name. If the plugin is not known the bool return arg is false. 62 func (c *PackageConfig) Plugin(name string) (LanguagePluginConfig, bool) { 63 if c.plugins == nil { 64 return LanguagePluginConfig{}, false 65 } 66 if plugin, ok := c.plugins[name]; ok { 67 return *plugin, true 68 } else { 69 return LanguagePluginConfig{}, false 70 } 71 } 72 73 // Clone copies this config to a new one. 74 func (c *PackageConfig) Clone() *PackageConfig { 75 clone := NewPackageConfig(c.Config) 76 clone.importpathPrefix = c.importpathPrefix 77 78 for k, v := range c.rules { 79 clone.rules[k] = v.clone() 80 } 81 for k, v := range c.langs { 82 clone.langs[k] = v.clone() 83 } 84 for k, v := range c.plugins { 85 clone.plugins[k] = v.clone() 86 } 87 88 return clone 89 } 90 91 // ParseDirectives is called in each directory visited by gazelle. The relative 92 // directory name is given by 'rel' and the list of directives in the BUILD file 93 // are specified by 'directives'. 94 func (c *PackageConfig) ParseDirectives(rel string, directives []rule.Directive) (err error) { 95 for _, d := range directives { 96 switch d.Key { 97 case importpathPrefixDirective: 98 err = c.parsePrefixDirective(d) 99 case PluginDirective: 100 err = c.parsePluginDirective(d) 101 case RuleDirective: 102 err = c.parseRuleDirective(d) 103 case LanguageDirective: 104 err = c.parseLanguageDirective(d) 105 } 106 if err != nil { 107 return fmt.Errorf("parse %v: %w", d, err) 108 } 109 } 110 return 111 } 112 113 func (c *PackageConfig) parsePrefixDirective(d rule.Directive) error { 114 c.importpathPrefix = strings.TrimSpace(d.Value) 115 return nil 116 } 117 118 func (c *PackageConfig) parseLanguageDirective(d rule.Directive) error { 119 fields := strings.Fields(d.Value) 120 if len(fields) != 3 { 121 return fmt.Errorf("invalid directive %v: expected three fields, got %d", d, len(fields)) 122 } 123 name, param, value := fields[0], fields[1], fields[2] 124 lang, ok := c.langs[name] 125 if !ok { 126 lang = newLanguageConfig(name) 127 c.langs[name] = lang 128 } 129 return lang.parseDirective(c, name, param, value) 130 } 131 132 func (c *PackageConfig) parsePluginDirective(d rule.Directive) error { 133 fields := strings.Fields(d.Value) 134 if len(fields) != 3 { 135 return fmt.Errorf("invalid directive %v: expected three fields, got %d", d, len(fields)) 136 } 137 name, param, value := fields[0], fields[1], fields[2] 138 plugin, err := c.getOrCreateLanguagePluginConfig(name) 139 if err != nil { 140 return fmt.Errorf("invalid proto_plugin directive %+v: %w", d, err) 141 } 142 return plugin.parseDirective(c, name, param, value) 143 } 144 145 func (c *PackageConfig) parseRuleDirective(d rule.Directive) error { 146 fields := strings.Fields(d.Value) 147 if len(fields) < 3 { 148 return fmt.Errorf("invalid directive %v: expected three or more fields, got %d", d, len(fields)) 149 } 150 name, param, value := fields[0], fields[1], strings.Join(fields[2:], " ") 151 r, err := c.getOrCreateLanguageRuleConfig(c.Config, name) 152 if err != nil { 153 return fmt.Errorf("invalid proto_rule directive %+v: %w", d, err) 154 } 155 return r.parseDirective(c, name, param, value) 156 } 157 158 func (c *PackageConfig) getOrCreateLanguagePluginConfig(name string) (*LanguagePluginConfig, error) { 159 plugin, ok := c.plugins[name] 160 if !ok { 161 plugin = newLanguagePluginConfig(name) 162 c.plugins[name] = plugin 163 } 164 return plugin, nil 165 } 166 167 func (c *PackageConfig) getOrCreateLanguageRuleConfig(config *config.Config, name string) (*LanguageRuleConfig, error) { 168 r, ok := c.rules[name] 169 if !ok { 170 r = NewLanguageRuleConfig(config, name) 171 r.Implementation = name 172 c.rules[name] = r 173 } 174 return r, nil 175 } 176 177 // configuredLangs returns a determinstic ordered list of configured 178 // langs 179 func (c *PackageConfig) configuredLangs() []*LanguageConfig { 180 names := make([]string, 0) 181 for name := range c.langs { 182 names = append(names, name) 183 } 184 sort.Strings(names) 185 langs := make([]*LanguageConfig, 0) 186 for _, name := range names { 187 langs = append(langs, c.langs[name]) 188 } 189 return langs 190 } 191 192 func (c *PackageConfig) LoadYConfig(y *YConfig) error { 193 for _, starlarkPlugin := range y.StarlarkPlugin { 194 if err := c.loadYStarlarkPlugin(starlarkPlugin); err != nil { 195 return err 196 } 197 } 198 for _, starlarkRule := range y.StarlarkRule { 199 if err := c.loadYStarlarkRule(starlarkRule); err != nil { 200 return err 201 } 202 } 203 for _, plugin := range y.Plugin { 204 if err := c.loadYPlugin(plugin); err != nil { 205 return err 206 } 207 } 208 for _, rule := range y.Rule { 209 if err := c.loadYRule(rule); err != nil { 210 return err 211 } 212 } 213 for _, lang := range y.Language { 214 if err := c.loadYLanguage(lang); err != nil { 215 return err 216 } 217 } 218 return nil 219 } 220 221 func (c *PackageConfig) loadYStarlarkPlugin(y string) error { 222 return RegisterStarlarkPlugin(c.Config, y) 223 } 224 225 func (c *PackageConfig) loadYStarlarkRule(y string) error { 226 return RegisterStarlarkRule(c.Config, y) 227 } 228 229 func (c *PackageConfig) loadYPlugin(y *YPlugin) error { 230 if y.Name == "" { 231 return fmt.Errorf("yaml plugin name missing in: %+v", y) 232 } 233 plugin, err := c.getOrCreateLanguagePluginConfig(y.Name) 234 if err != nil { 235 return err 236 } 237 return plugin.fromYAML(y) 238 } 239 240 func (c *PackageConfig) loadYRule(y *YRule) error { 241 if y.Name == "" { 242 return fmt.Errorf("yaml rule name missing in: %+v", y) 243 } 244 rule, err := c.getOrCreateLanguageRuleConfig(c.Config, y.Name) 245 if err != nil { 246 return err 247 } 248 return rule.fromYAML(y) 249 } 250 251 func (c *PackageConfig) loadYLanguage(y *YLanguage) error { 252 if y.Name == "" { 253 return fmt.Errorf("yaml language name missing in: %+v", y) 254 } 255 lang, ok := c.langs[y.Name] 256 if !ok { 257 lang = newLanguageConfig(y.Name) 258 c.langs[y.Name] = lang 259 } 260 return lang.fromYAML(y) 261 } 262 263 func RegisterStarlarkPlugin(c *config.Config, starlarkPlugin string) error { 264 parts := strings.Split(starlarkPlugin, "%") 265 if len(parts) != 2 { 266 return fmt.Errorf("invalid starlark plugin name %q", starlarkPlugin) 267 } 268 fileName := parts[0] 269 ruleName := parts[1] 270 impl, err := LoadStarlarkPluginFromFile(c.WorkDir, fileName, ruleName, func(msg string) { 271 log.Printf("%s: %v", starlarkPlugin, msg) 272 }, func(err error) { 273 log.Fatalf("starlark plugin configuration error (plugin %q will not be registered): %v", starlarkPlugin, err) 274 }) 275 if err != nil { 276 return err 277 } 278 Plugins().RegisterPlugin(starlarkPlugin, impl) 279 return nil 280 } 281 282 func RegisterStarlarkRule(c *config.Config, starlarkRule string) error { 283 parts := strings.Split(starlarkRule, "%") 284 if len(parts) != 2 { 285 return fmt.Errorf("invalid starlark rule name %q", starlarkRule) 286 } 287 fileName := parts[0] 288 ruleName := parts[1] 289 290 impl, err := LoadStarlarkLanguageRuleFromFile(c.WorkDir, fileName, ruleName, func(msg string) { 291 }, func(err error) { 292 log.Panicf("starlark rule configuration error (rule %q will not be registered): %v", starlarkRule, err) 293 }) 294 if err != nil { 295 return err 296 } 297 Rules().MustRegisterRule(starlarkRule, impl) 298 return nil 299 }