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  }