Skip to content

Commit

Permalink
feat: introduce bufbuild/connect-es (#338)
Browse files Browse the repository at this point in the history
* feat: add bufbuild es-connect
* show working example again
* feat: add gazelle extension plugin for bufbuild
* fix: gazelle extension creates both plugins
* chore: set options on generated bufbuild rules
* chore: restore original example which conflicts with bufbuild/connect usage
* chore: minify delta
  • Loading branch information
alexeagle committed Feb 19, 2024
1 parent 8296592 commit 9fffedb
Show file tree
Hide file tree
Showing 15 changed files with 476 additions and 276 deletions.
1 change: 1 addition & 0 deletions .bazelignore
@@ -1,4 +1,5 @@
docs/
node_modules
plugin/bufbuild/node_modules
plugin/stephenh/ts-proto/node_modules
rules/ts/node_modules
1 change: 1 addition & 0 deletions language/protobuf/BUILD.bazel
Expand Up @@ -7,6 +7,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/language/protobuf",
"//pkg/plugin/bufbuild",
"//pkg/plugin/builtin",
"//pkg/plugin/gogo/protobuf",
"//pkg/plugin/golang/protobuf",
Expand Down
1 change: 1 addition & 0 deletions language/protobuf/protobuf.go
Expand Up @@ -16,6 +16,7 @@ import (
_ "github.com/stackb/rules_proto/pkg/plugin/grpcecosystem/grpcgateway"
_ "github.com/stackb/rules_proto/pkg/plugin/scalapb/scalapb"
_ "github.com/stackb/rules_proto/pkg/plugin/stackb/grpc_js"
_ "github.com/stackb/rules_proto/pkg/plugin/bufbuild"
_ "github.com/stackb/rules_proto/pkg/plugin/stephenh/ts-proto"
_ "github.com/stackb/rules_proto/pkg/rule/rules_cc"
_ "github.com/stackb/rules_proto/pkg/rule/rules_closure"
Expand Down
3 changes: 0 additions & 3 deletions package.json
@@ -1,7 +1,4 @@
{
"dependencies": {
"ts-proto": "1.156.0"
},
"devDependencies": {
"@nestjs/common": "10.1.3",
"@nestjs/core": "10.1.3",
Expand Down
1 change: 1 addition & 0 deletions pkg/BUILD.bazel
Expand Up @@ -4,6 +4,7 @@ filegroup(
srcs = [
"//pkg/language/protobuf:all_files",
"//pkg/plugin/akka/akka_grpc:all_files",
"//pkg/plugin/bufbuild:all_files",
"//pkg/plugin/builtin:all_files",
"//pkg/plugin/gogo/protobuf:all_files",
"//pkg/plugin/golang/protobuf:all_files",
Expand Down
33 changes: 33 additions & 0 deletions pkg/plugin/bufbuild/BUILD.bazel
@@ -0,0 +1,33 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "bufbuild",
srcs = [
"connect_es_plugin.go",
"es_plugin.go",
],
importpath = "github.com/stackb/rules_proto/pkg/plugin/bufbuild",
visibility = ["//visibility:public"],
deps = [
"//pkg/protoc",
"@bazel_gazelle//label:go_default_library",
],
)

go_test(
name = "bufbuild_test",
srcs = ["es_plugin_test.go"],
deps = [
":bufbuild",
"//pkg/plugintest",
],
)

filegroup(
name = "all_files",
testonly = True,
srcs = [
"BUILD.bazel",
] + glob(["*.go"]),
visibility = ["//pkg:__pkg__"],
)
84 changes: 84 additions & 0 deletions pkg/plugin/bufbuild/connect_es_plugin.go
@@ -0,0 +1,84 @@
package bufbuild

import (
"flag"
"log"
"path"
"path/filepath"
"strings"

"github.com/bazelbuild/bazel-gazelle/label"
"github.com/stackb/rules_proto/pkg/protoc"
)

func init() {
protoc.Plugins().MustRegisterPlugin(&ConnectEsProto{})
}

// ConnectEsProto implements Plugin for the bufbuild/connect-es plugin.
type ConnectEsProto struct{}

// Name implements part of the Plugin interface.
func (p *ConnectEsProto) Name() string {
return "bufbuild:connect-es"
}

// Configure implements part of the Plugin interface.
func (p *ConnectEsProto) Configure(ctx *protoc.PluginContext) *protoc.PluginConfiguration {
flags := parseConnectEsProtoOptions(p.Name(), ctx.PluginConfig.GetFlags())
imports := make(map[string]bool)
for _, file := range ctx.ProtoLibrary.Files() {
for _, imp := range file.Imports() {
imports[imp.Filename] = true
}
}
// TODO: get target option from directive
var options = []string{"keep_empty_files=true", "target=ts"}
tsFiles := make([]string, 0)
for _, file := range ctx.ProtoLibrary.Files() {
// TODO: outputs should be conditional on which target= value is used
tsFile := file.Name + "_connect.ts"
if flags.excludeOutput[filepath.Base(tsFile)] {
continue
}
if ctx.Rel != "" {
tsFile = path.Join(ctx.Rel, tsFile)
}
tsFiles = append(tsFiles, tsFile)
}

pc := &protoc.PluginConfiguration{
Label: label.New("build_stack_rules_proto", "plugin/bufbuild", "connect-es"),
Outputs: protoc.DeduplicateAndSort(tsFiles),
Options: protoc.DeduplicateAndSort(options),
}
if len(pc.Outputs) == 0 {
pc.Outputs = nil
}
return pc
}

// ConnectEsProtoOptions represents the parsed flag configuration for the
// ConnectEsProto implementation.
type ConnectEsProtoOptions struct {
excludeOutput map[string]bool
}

func parseConnectEsProtoOptions(kindName string, args []string) *ConnectEsProtoOptions {
flags := flag.NewFlagSet(kindName, flag.ExitOnError)

var excludeOutput string
flags.StringVar(&excludeOutput, "exclude_output", "", "--exclude_output=foo.ts suppresses the file 'foo.ts' from the output list")

if err := flags.Parse(args); err != nil {
log.Fatalf("failed to parse flags for %q: %v", kindName, err)
}
config := &ConnectEsProtoOptions{
excludeOutput: make(map[string]bool),
}
for _, value := range strings.Split(excludeOutput, ",") {
config.excludeOutput[value] = true
}

return config
}
84 changes: 84 additions & 0 deletions pkg/plugin/bufbuild/es_plugin.go
@@ -0,0 +1,84 @@
package bufbuild

import (
"flag"
"log"
"path"
"path/filepath"
"strings"

"github.com/bazelbuild/bazel-gazelle/label"
"github.com/stackb/rules_proto/pkg/protoc"
)

func init() {
protoc.Plugins().MustRegisterPlugin(&EsProto{})
}

// EsProto implements Plugin for the bufbuild/protoc-gen-es plugin.
type EsProto struct{}

// Name implements part of the Plugin interface.
func (p *EsProto) Name() string {
return "bufbuild:es"
}

// Configure implements part of the Plugin interface.
func (p *EsProto) Configure(ctx *protoc.PluginContext) *protoc.PluginConfiguration {
flags := parseEsProtoOptions(p.Name(), ctx.PluginConfig.GetFlags())
imports := make(map[string]bool)
for _, file := range ctx.ProtoLibrary.Files() {
for _, imp := range file.Imports() {
imports[imp.Filename] = true
}
}
// TODO: get target option from directive
var options = []string{"keep_empty_files=true", "target=ts"}
tsFiles := make([]string, 0)
for _, file := range ctx.ProtoLibrary.Files() {
// TODO: outputs should be conditional on which target= value is used
tsFile := file.Name + "_pb.ts"
if flags.excludeOutput[filepath.Base(tsFile)] {
continue
}
if ctx.Rel != "" {
tsFile = path.Join(ctx.Rel, tsFile)
}
tsFiles = append(tsFiles, tsFile)
}

pc := &protoc.PluginConfiguration{
Label: label.New("build_stack_rules_proto", "plugin/bufbuild", "es"),
Outputs: protoc.DeduplicateAndSort(tsFiles),
Options: protoc.DeduplicateAndSort(options),
}
if len(pc.Outputs) == 0 {
pc.Outputs = nil
}
return pc
}

// EsProtoOptions represents the parsed flag configuration for the
// EsProto implementation.
type EsProtoOptions struct {
excludeOutput map[string]bool
}

func parseEsProtoOptions(kindName string, args []string) *EsProtoOptions {
flags := flag.NewFlagSet(kindName, flag.ExitOnError)

var excludeOutput string
flags.StringVar(&excludeOutput, "exclude_output", "", "--exclude_output=foo.ts suppresses the file 'foo.ts' from the output list")

if err := flags.Parse(args); err != nil {
log.Fatalf("failed to parse flags for %q: %v", kindName, err)
}
config := &EsProtoOptions{
excludeOutput: make(map[string]bool),
}
for _, value := range strings.Split(excludeOutput, ",") {
config.excludeOutput[value] = true
}

return config
}
26 changes: 26 additions & 0 deletions pkg/plugin/bufbuild/es_plugin_test.go
@@ -0,0 +1,26 @@
package bufbuild_test

import (
"testing"

"github.com/stackb/rules_proto/pkg/plugin/bufbuild"
"github.com/stackb/rules_proto/pkg/plugintest"
)

func TestProtocGenTsProtoPlugin(t *testing.T) {
plugintest.Cases(t, &bufbuild.EsProto{}, map[string]plugintest.Case{
"simple": {
Input: "message M{}",
Directives: plugintest.WithDirectives(
"proto_plugin", "es implementation bufbuild:connect-es",
),
PluginName: "es",
Configuration: plugintest.WithConfiguration(
plugintest.WithLabel(t, "@build_stack_rules_proto//plugin/bufbuild:es"),
plugintest.WithOptions("keep_empty_files=true", "target=ts"),
plugintest.WithOutputs("test_pb.ts"),
),
SkipIntegration: true,
},
})
}
1 change: 1 addition & 0 deletions plugin/BUILD.bazel
Expand Up @@ -4,6 +4,7 @@ filegroup(
srcs = [
"BUILD.bazel",
"//plugin/akka/akka-grpc:all_files",
"//plugin/bufbuild:all_files",
"//plugin/builtin:all_files",
"//plugin/gogo/protobuf:all_files",
"//plugin/golang/protobuf:all_files",
Expand Down
30 changes: 30 additions & 0 deletions plugin/bufbuild/BUILD.bazel
@@ -0,0 +1,30 @@
load("@build_stack_rules_proto//rules:proto_plugin.bzl", "proto_plugin")
load("@npm_ts_proto//plugin/bufbuild:@bufbuild/protoc-gen-es/package_json.bzl", gen_bin = "bin")
load("@npm_ts_proto//plugin/bufbuild:@bufbuild/protoc-gen-connect-es/package_json.bzl", connect_bin = "bin")

gen_bin.protoc_gen_es_binary(
name = "protoc-gen-es",
)

connect_bin.protoc_gen_connect_es_binary(
name = "protoc-gen-connect",
)

proto_plugin(
name = "connect-es",
tool = ":protoc-gen-connect",
visibility = ["//visibility:public"],
)

proto_plugin(
name = "es",
tool = ":protoc-gen-es",
visibility = ["//visibility:public"],
)

filegroup(
name = "all_files",
testonly = True,
srcs = ["BUILD.bazel"],
visibility = ["//plugin:__pkg__"],
)
7 changes: 7 additions & 0 deletions plugin/bufbuild/package.json
@@ -0,0 +1,7 @@
{
"dependencies": {
"@bufbuild/protobuf": "1.3.0",
"@bufbuild/protoc-gen-es": "1.3.0",
"@bufbuild/protoc-gen-connect-es": "0.12.0"
}
}
5 changes: 5 additions & 0 deletions plugin/stephenh/ts-proto/package.json
@@ -0,0 +1,5 @@
{
"dependencies": {
"ts-proto": "1.156.0"
}
}

0 comments on commit 9fffedb

Please sign in to comment.