github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/pkg/protoc/starlark_plugin.go (about)

     1  package protoc
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"github.com/bazelbuild/bazel-gazelle/config"
     8  	"github.com/bazelbuild/bazel-gazelle/label"
     9  	"github.com/bazelbuild/bazel-gazelle/rule"
    10  	"github.com/emicklei/proto"
    11  	"go.starlark.net/starlark"
    12  	"go.starlark.net/starlarkstruct"
    13  )
    14  
    15  func LoadStarlarkPluginFromFile(workDir, filename, name string, reporter func(msg string), errorReporter func(err error)) (Plugin, error) {
    16  	filename, err := resolveStarlarkFilename(workDir, filename)
    17  	if err != nil {
    18  		return nil, err
    19  	}
    20  
    21  	f, err := os.Open(filename)
    22  	if err != nil {
    23  		return nil, fmt.Errorf("failed to open plugin file %q: %w", filename, err)
    24  	}
    25  	defer f.Close()
    26  
    27  	return loadStarlarkPlugin(name, filename, f, reporter, errorReporter)
    28  }
    29  
    30  func loadStarlarkPlugin(name, filename string, src interface{}, reporter func(msg string), errorReporter func(err error)) (Plugin, error) {
    31  
    32  	newErrorf := func(msg string, args ...interface{}) error {
    33  		err := fmt.Errorf(filename+": "+msg, args...)
    34  		errorReporter(err)
    35  		return err
    36  	}
    37  
    38  	plugins := make(map[string]*starlarkstruct.Struct)
    39  	rules := make(map[string]*starlarkstruct.Struct)
    40  	predeclared := newPredeclared(plugins, rules)
    41  
    42  	_, thread, err := loadStarlarkProgram(filename, src, predeclared, reporter, errorReporter)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	if plugin, ok := plugins[name]; !ok {
    48  		return nil, newErrorf("plugin %q was never declared", name)
    49  	} else {
    50  		return &starlarkPlugin{
    51  			name:          name,
    52  			plugin:        plugin,
    53  			reporter:      thread.Print,
    54  			errorReporter: newErrorf,
    55  		}, nil
    56  	}
    57  }
    58  
    59  // starlarkPlugin is an adapter for starlark code that implements the protoc
    60  // plugin interface.
    61  type starlarkPlugin struct {
    62  	name          string
    63  	reporter      func(thread *starlark.Thread, msg string)
    64  	errorReporter func(msg string, args ...interface{}) error
    65  	plugin        *starlarkstruct.Struct
    66  }
    67  
    68  func (p *starlarkPlugin) Name() string {
    69  	return p.name
    70  }
    71  
    72  func (p *starlarkPlugin) Configure(ctx *PluginContext) *PluginConfiguration {
    73  
    74  	var result *PluginConfiguration
    75  
    76  	configure, err := p.plugin.Attr("configure")
    77  	if err != nil {
    78  		p.errorReporter("plugin %q has no configure function", p.name)
    79  		return nil
    80  	}
    81  
    82  	thread := new(starlark.Thread)
    83  	thread.Print = p.reporter
    84  	value, err := starlark.Call(thread, configure, starlark.Tuple{
    85  		newPluginContextStruct(ctx),
    86  	}, []starlark.Tuple{})
    87  	if err != nil {
    88  		p.errorReporter("plugin %q configure failed: %v", p.name, err)
    89  		return nil
    90  	}
    91  
    92  	switch value := value.(type) {
    93  	case *starlarkstruct.Struct:
    94  		labelValue, err := value.Attr("label")
    95  		if err != nil {
    96  			p.errorReporter("PluginConfiguration.label get value: %v", err)
    97  			return nil
    98  		}
    99  		lbl := label.NoLabel
   100  		labelStr := labelValue.(starlark.String).GoString()
   101  		if labelStr != "" {
   102  			var err error
   103  			lbl, err = label.Parse(labelStr)
   104  			if err != nil {
   105  				p.errorReporter("PluginConfiguration.label parse: %v", err)
   106  				return nil
   107  			}
   108  		}
   109  		outputsValue, err := value.Attr("outputs")
   110  		if err != nil {
   111  			p.errorReporter("PluginConfiguration.outputs get value: %v", err)
   112  		}
   113  		outputsList := outputsValue.(*starlark.List)
   114  		outputs := make([]string, outputsList.Len())
   115  		for i := 0; i < outputsList.Len(); i++ {
   116  			outputs[i] = outputsList.Index(i).(starlark.String).GoString()
   117  		}
   118  
   119  		optionsValue, err := value.Attr("options")
   120  		if err != nil {
   121  			p.errorReporter("PluginConfiguration.options get value: %v", err)
   122  		}
   123  		optionsList := optionsValue.(*starlark.List)
   124  		options := make([]string, optionsList.Len())
   125  		for i := 0; i < optionsList.Len(); i++ {
   126  			options[i] = optionsList.Index(i).(starlark.String).GoString()
   127  		}
   128  
   129  		outValue, err := value.Attr("out")
   130  		if err != nil {
   131  			p.errorReporter("PluginConfiguration.out get value: %v", err)
   132  		}
   133  		var out string
   134  		if outString, ok := outValue.(starlark.String); ok {
   135  			out = outString.GoString()
   136  		}
   137  
   138  		result = &PluginConfiguration{
   139  			Label:   lbl,
   140  			Outputs: outputs,
   141  			Out:     out,
   142  			Options: options,
   143  		}
   144  	default:
   145  		p.errorReporter("plugin %q configure returned invalid type: %T", p.name, value)
   146  		return nil
   147  	}
   148  
   149  	return result
   150  }
   151  
   152  func newStarlarkPluginConfiguration() goStarlarkFunction {
   153  	return func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   154  		var labelStr string
   155  		var out string
   156  		outputs := &starlark.List{}
   157  		options := &starlark.List{}
   158  
   159  		if err := starlark.UnpackArgs("PluginConfiguration", args, kwargs,
   160  			"label", &labelStr,
   161  			"outputs", &outputs,
   162  			"out?", &out,
   163  			"options?", &options,
   164  		); err != nil {
   165  			return nil, err
   166  		}
   167  
   168  		return starlarkstruct.FromStringDict(
   169  			Symbol("PluginConfiguration"),
   170  			starlark.StringDict{
   171  				"label":   starlark.String(labelStr),
   172  				"outputs": outputs,
   173  				"out":     starlark.String(out),
   174  				"options": options,
   175  			},
   176  		), nil
   177  	}
   178  }
   179  
   180  func newStarlarkPluginFunction(plugins map[string]*starlarkstruct.Struct) goStarlarkFunction {
   181  	return func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   182  		var name string
   183  		var configure starlark.Callable
   184  
   185  		if err := starlark.UnpackArgs("Plugin", args, kwargs,
   186  			"name", &name,
   187  			"configure", &configure,
   188  		); err != nil {
   189  			return nil, err
   190  		}
   191  
   192  		plugin := starlarkstruct.FromStringDict(
   193  			Symbol("Plugin"),
   194  			starlark.StringDict{
   195  				"name":      starlark.String(name),
   196  				"configure": configure,
   197  			},
   198  		)
   199  
   200  		plugins[name] = plugin
   201  		return plugin, nil
   202  	}
   203  }
   204  
   205  func newPluginContextStruct(ctx *PluginContext) *starlarkstruct.Struct {
   206  	return starlarkstruct.FromStringDict(
   207  		Symbol("PluginContext"),
   208  		starlark.StringDict{
   209  			"rel":            starlark.String(ctx.Rel),
   210  			"plugin_config":  newLanguagePluginConfigStruct(ctx.PluginConfig),
   211  			"package_config": newPackageConfigStruct(&ctx.PackageConfig),
   212  			"proto_library":  newProtoLibraryStruct(ctx.ProtoLibrary),
   213  		},
   214  	)
   215  }
   216  
   217  func newLanguagePluginConfigStruct(cfg LanguagePluginConfig) *starlarkstruct.Struct {
   218  	var labelStr string
   219  	if cfg.Label != label.NoLabel {
   220  		labelStr = cfg.Label.String()
   221  	}
   222  	return starlarkstruct.FromStringDict(
   223  		Symbol("LanguagePluginConfig"),
   224  		starlark.StringDict{
   225  			"name":           starlark.String(cfg.Name),
   226  			"implementation": starlark.String(cfg.Implementation),
   227  			"label":          starlark.String(labelStr),
   228  			"options":        newStringList(cfg.GetOptions()),
   229  			"deps":           newStringList(cfg.GetDeps()),
   230  			"enabled":        starlark.Bool(cfg.Enabled),
   231  		},
   232  	)
   233  }
   234  
   235  func newPackageConfigStruct(cfg *PackageConfig) *starlarkstruct.Struct {
   236  	if cfg == nil {
   237  		return starlarkstruct.FromStringDict(
   238  			Symbol("PackageConfig"),
   239  			starlark.StringDict{
   240  				"config": newConfigStruct(&config.Config{}),
   241  			},
   242  		)
   243  	}
   244  	return starlarkstruct.FromStringDict(
   245  		Symbol("PackageConfig"),
   246  		starlark.StringDict{
   247  			"config": newConfigStruct(cfg.Config),
   248  		},
   249  	)
   250  }
   251  
   252  func newConfigStruct(c *config.Config) *starlarkstruct.Struct {
   253  	if c == nil {
   254  		return starlarkstruct.FromStringDict(
   255  			Symbol("Config"),
   256  			starlark.StringDict{
   257  				"work_dir":  starlark.String(""),
   258  				"repo_root": starlark.String(""),
   259  				"repo_name": starlark.String(""),
   260  			},
   261  		)
   262  	}
   263  	return starlarkstruct.FromStringDict(
   264  		Symbol("Config"),
   265  		starlark.StringDict{
   266  			"work_dir":  starlark.String(c.WorkDir),
   267  			"repo_root": starlark.String(c.RepoRoot),
   268  			"repo_name": starlark.String(c.RepoName),
   269  		},
   270  	)
   271  }
   272  
   273  func newProtoLibraryStruct(p ProtoLibrary) *starlarkstruct.Struct {
   274  	if p == nil {
   275  		return starlarkstruct.FromStringDict(
   276  			Symbol("ProtoLibrary"),
   277  			starlark.StringDict{
   278  				"name":                starlark.String(""),
   279  				"base_name":           starlark.String(""),
   280  				"strip_import_prefix": starlark.String(""),
   281  				"srcs":                &starlark.List{},
   282  				"deps":                &starlark.List{},
   283  				"imports":             &starlark.List{},
   284  				"files":               &starlark.List{},
   285  			},
   286  		)
   287  	}
   288  	return starlarkstruct.FromStringDict(
   289  		Symbol("ProtoLibrary"),
   290  		starlark.StringDict{
   291  			"name":                starlark.String(p.Name()),
   292  			"base_name":           starlark.String(p.BaseName()),
   293  			"strip_import_prefix": starlark.String(p.StripImportPrefix()),
   294  			"srcs":                newStringList(p.Srcs()),
   295  			"deps":                newStringList(p.Deps()),
   296  			"imports":             newStringList(p.Imports()),
   297  			"files":               newProtoFileList(p.Files()),
   298  			"rule":                newStarlarkProtoLibraryRuleStruct(p.Rule()),
   299  		},
   300  	)
   301  }
   302  
   303  func newProtoFileList(in []*File) *starlark.List {
   304  	values := make([]starlark.Value, 0, len(in))
   305  	for _, v := range in {
   306  		if v == nil {
   307  			continue
   308  		}
   309  		values = append(values, newProtoFileStruct(*v))
   310  	}
   311  	return starlark.NewList(values)
   312  }
   313  
   314  func newProtoFileStruct(f File) *starlarkstruct.Struct {
   315  	return starlarkstruct.FromStringDict(
   316  		Symbol("ProtoFile"),
   317  		starlark.StringDict{
   318  			"dir":          starlark.String(f.Dir),
   319  			"basename":     starlark.String(f.Basename),
   320  			"name":         starlark.String(f.Name),
   321  			"relname":      starlark.String(f.Relname()),
   322  			"pkg":          newProtoPackageStruct(f.pkg),
   323  			"imports":      newProtoImportList(f.imports),
   324  			"options":      newProtoOptionList(f.options),
   325  			"messages":     newProtoMessageList(f.messages),
   326  			"services":     newProtoServiceList(f.services),
   327  			"enums":        newProtoEnumList(f.enums),
   328  			"enum_options": newProtoEnumOptionList(f.enumOptions),
   329  		},
   330  	)
   331  }
   332  
   333  func newProtoPackageStruct(p proto.Package) *starlarkstruct.Struct {
   334  	return starlarkstruct.FromStringDict(
   335  		Symbol("ProtoPackage"),
   336  		starlark.StringDict{
   337  			"name": starlark.String(p.Name),
   338  		},
   339  	)
   340  }
   341  
   342  func newProtoImportList(in []proto.Import) *starlark.List {
   343  	values := make([]starlark.Value, len(in))
   344  	for i, v := range in {
   345  		values[i] = newProtoImportStruct(v)
   346  	}
   347  	return starlark.NewList(values)
   348  }
   349  
   350  func newProtoImportStruct(i proto.Import) *starlarkstruct.Struct {
   351  	return starlarkstruct.FromStringDict(
   352  		Symbol("ProtoImport"),
   353  		starlark.StringDict{
   354  			"filename": starlark.String(i.Filename),
   355  			"kind":     starlark.String(i.Kind),
   356  		},
   357  	)
   358  }
   359  
   360  func newProtoOptionList(in []proto.Option) *starlark.List {
   361  	values := make([]starlark.Value, len(in))
   362  	for i, v := range in {
   363  		values[i] = newProtoOptionStruct(v)
   364  	}
   365  	return starlark.NewList(values)
   366  }
   367  
   368  func newProtoOptionStruct(o proto.Option) *starlarkstruct.Struct {
   369  	return starlarkstruct.FromStringDict(
   370  		Symbol("ProtoOption"),
   371  		starlark.StringDict{
   372  			"name":     starlark.String(o.Name),
   373  			"constant": starlark.String(o.Constant.Source),
   374  		},
   375  	)
   376  }
   377  
   378  func newProtoMessageList(in []proto.Message) *starlark.List {
   379  	values := make([]starlark.Value, len(in))
   380  	for i, v := range in {
   381  		values[i] = newProtoMessageStruct(v)
   382  	}
   383  	return starlark.NewList(values)
   384  }
   385  
   386  func newProtoMessageStruct(m proto.Message) *starlarkstruct.Struct {
   387  	return starlarkstruct.FromStringDict(
   388  		Symbol("ProtoMessage"),
   389  		starlark.StringDict{
   390  			"name":      starlark.String(m.Name),
   391  			"is_extend": starlark.Bool(m.IsExtend),
   392  		},
   393  	)
   394  }
   395  
   396  func newProtoServiceList(in []proto.Service) *starlark.List {
   397  	values := make([]starlark.Value, len(in))
   398  	for i, v := range in {
   399  		values[i] = newProtoServiceStruct(v)
   400  	}
   401  	return starlark.NewList(values)
   402  }
   403  
   404  func newProtoServiceStruct(s proto.Service) *starlarkstruct.Struct {
   405  	return starlarkstruct.FromStringDict(
   406  		Symbol("ProtoService"),
   407  		starlark.StringDict{
   408  			"name": starlark.String(s.Name),
   409  		},
   410  	)
   411  }
   412  
   413  func newProtoEnumList(in []proto.Enum) *starlark.List {
   414  	values := make([]starlark.Value, len(in))
   415  	for i, v := range in {
   416  		values[i] = newProtoEnumStruct(v)
   417  	}
   418  	return starlark.NewList(values)
   419  }
   420  
   421  func newProtoEnumStruct(e proto.Enum) *starlarkstruct.Struct {
   422  	return starlarkstruct.FromStringDict(
   423  		Symbol("ProtoEnum"),
   424  		starlark.StringDict{
   425  			"name": starlark.String(e.Name),
   426  		},
   427  	)
   428  }
   429  
   430  func newProtoEnumOptionList(in []proto.Option) *starlark.List {
   431  	values := make([]starlark.Value, len(in))
   432  	for i, v := range in {
   433  		values[i] = newProtoEnumOptionStruct(v)
   434  	}
   435  	return starlark.NewList(values)
   436  }
   437  
   438  func newProtoEnumOptionStruct(e proto.Option) *starlarkstruct.Struct {
   439  	return starlarkstruct.FromStringDict(
   440  		Symbol("ProtoEnumOption"),
   441  		starlark.StringDict{
   442  			"name":     starlark.String(e.Name),
   443  			"constant": starlark.String(e.Constant.Source),
   444  		},
   445  	)
   446  }
   447  
   448  func newStarlarkProtoLibraryRuleStruct(r *rule.Rule) *starlarkstruct.Struct {
   449  	if r == nil {
   450  		return starlarkstruct.FromStringDict(
   451  			Symbol("Rule"),
   452  			starlark.StringDict{
   453  				"name":       starlark.String(""),
   454  				"kind":       starlark.String(""),
   455  				"srcs":       &starlark.List{},
   456  				"deps":       &starlark.List{},
   457  				"tags":       &starlark.List{},
   458  				"visibility": &starlark.List{},
   459  			},
   460  		)
   461  	}
   462  	return starlarkstruct.FromStringDict(
   463  		Symbol("Rule"),
   464  		starlark.StringDict{
   465  			"name":       starlark.String(r.Name()),
   466  			"kind":       starlark.String(r.Kind()),
   467  			"srcs":       newStringList(r.AttrStrings("srcs")),
   468  			"deps":       newStringList(r.AttrStrings("deps")),
   469  			"tags":       newStringList(r.AttrStrings("tags")),
   470  			"visibility": newStringList(r.AttrStrings("visibility")),
   471  		},
   472  	)
   473  
   474  }