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 }