add protoc gen ecode (#274)

* add protoc gen ecode
* add protobuf example
pull/276/head
Felix Hao 5 years ago committed by GitHub
parent c1f9b5ca81
commit 1481e14c12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 37
      doc/wiki-cn/kratos-protoc.md
  3. 40
      example/protobuf/api.bm.go
  4. 17
      example/protobuf/api.ecode.go
  5. 1000
      example/protobuf/api.pb.go
  6. 33
      example/protobuf/api.proto
  7. 96
      example/protobuf/api.swagger.json
  8. 3
      example/protobuf/gen.sh
  9. 2
      tool/kratos-protoc/bm.go
  10. 25
      tool/kratos-protoc/ecode.go
  11. 5
      tool/kratos-protoc/main.go
  12. 12
      tool/kratos-protoc/protoc.go
  13. 2
      tool/kratos-protoc/swagger.go
  14. 11
      tool/protobuf/pkg/naming/naming.go
  15. 28
      tool/protobuf/protoc-gen-bm/generator/generator.go
  16. 2
      tool/protobuf/protoc-gen-bswagger/generator.go
  17. 117
      tool/protobuf/protoc-gen-ecode/generator/generator.go
  18. 27
      tool/protobuf/protoc-gen-ecode/generator/generator_test.go
  19. 23
      tool/protobuf/protoc-gen-ecode/main.go

1
.gitignore vendored

@ -28,4 +28,5 @@ tool/kratos-gen-bts/kratos-gen-bts
tool/kratos-gen-mc/kratos-gen-mc
tool/kratos/kratos-protoc/kratos-protoc
tool/kratos/protobuf/protoc-gen-bm/protoc-gen-bm
tool/kratos/protobuf/protoc-gen-ecode/protoc-gen-ecode
tool/kratos/protobuf/protoc-gen-bswagger/protoc-gen-bswagger

@ -1,38 +1,31 @@
### kratos tool protoc
```
// generate all
```shell
# generate all
kratos tool protoc api.proto
// generate gRPC
# generate gRPC
kratos tool protoc --grpc api.proto
// generate BM HTTP
# generate BM HTTP
kratos tool protoc --bm api.proto
// generate swagger
# generate ecode
kratos tool protoc --ecode api.proto
# generate swagger
kratos tool protoc --swagger api.proto
```
执行对应生成 `api.pb.go/api.bm.go/api.swagger.json` 源文档。
> 该工具在Windows/Linux下运行,需提前安装好 protobuf 工具
该工具实际是一段`shell`脚本,其中自动将`protoc`命令进行了拼接,识别了需要的`*.proto`文件和当前目录下的`proto`文件,最终会拼接为如下命令进行执行:
```shell
export $KRATOS_HOME = kratos路径
export $KRATOS_DEMO = 项目路径
执行生成如 `api.pb.go/api.bm.go/api.swagger.json/api.ecode.go` 的对应文件,需要注意的是:`ecode`生成有固定规则,需要首先是`enum`类型,且`enum`名字要以`ErrCode`结尾,如`enum UserErrCode`。详情可见:[example](https://github.com/bilibili/kratos/tree/master/example/protobuf)
// 生成:api.pb.go
protoc -I$GOPATH/src:$KRATOS_HOME/third_party:$KRATOS_DEMO/api --gofast_out=plugins=grpc:$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto
> 该工具在Windows/Linux下运行,需提前安装好 [protobuf](https://github.com/google/protobuf) 工具
// 生成:api.bm.go
protoc -I$GOPATH/src:$KRATOS_HOME/third_party:$KRATOS_DEMO/api --bm_out=$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto
`kratos tool protoc`本质上是拼接好了`protoc`命令然后进行执行,在执行时会打印出对应执行的`protoc`命令,如下可见:
// 生成:api.swagger.json
protoc -I$GOPATH/src:$KRATOS_HOME/third_party:$KRATOS_DEMO/api --bswagger_out=$KRATOS_DEMO/api $KRATOS_DEMO/api/api.proto
```shell
protoc --proto_path=$GOPATH --proto_path=$GOPATH/github.com/bilibili/kratos/third_party --proto_path=. --bm_out=:. api.proto
protoc --proto_path=$GOPATH --proto_path=$GOPATH/github.com/bilibili/kratos/third_party --proto_path=. --gofast_out=plugins=grpc:. api.proto
protoc --proto_path=$GOPATH --proto_path=$GOPATH/github.com/bilibili/kratos/third_party --proto_path=. --bswagger_out=:. api.proto
protoc --proto_path=$GOPATH --proto_path=$GOPATH/github.com/bilibili/kratos/third_party --proto_path=. --ecode_out=:. api.proto
```
大家也可以参考该命令进行`proto`生成,也可以参考 [protobuf](https://github.com/google/protobuf) 官方参数。
-------------
[文档目录树](summary.md)

@ -0,0 +1,40 @@
// Code generated by protoc-gen-bm v0.1, DO NOT EDIT.
// source: api.proto
package api
import (
"context"
bm "github.com/bilibili/kratos/pkg/net/http/blademaster"
"github.com/bilibili/kratos/pkg/net/http/blademaster/binding"
)
// to suppressed 'imported but not used warning'
var _ *bm.Context
var _ context.Context
var _ binding.StructValidator
var PathUserInfo = "/user.api.User/Info"
// UserBMServer is the server API for User service.
type UserBMServer interface {
Info(ctx context.Context, req *UserReq) (resp *InfoReply, err error)
}
var UserSvc UserBMServer
func userInfo(c *bm.Context) {
p := new(UserReq)
if err := c.BindWith(p, binding.Default(c.Request.Method, c.Request.Header.Get("Content-Type"))); err != nil {
return
}
resp, err := UserSvc.Info(c, p)
c.JSON(resp, err)
}
// RegisterUserBMServer Register the blademaster route
func RegisterUserBMServer(e *bm.Engine, server UserBMServer) {
UserSvc = server
e.GET("/user.api.User/Info", userInfo)
}

@ -0,0 +1,17 @@
// Code generated by protoc-gen-ecode v0.1, DO NOT EDIT.
// source: api.proto
package api
import (
"github.com/bilibili/kratos/pkg/ecode"
)
// to suppressed 'imported but not used warning'
var _ ecode.Codes
// UserErrCode ecode
var (
UserNotExist = ecode.New(-404)
UserUpdateNameFailed = ecode.New(10000)
)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,33 @@
syntax = "proto3";
package user.api;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option go_package = "api";
enum UserErrCode {
OK = 0;
UserNotExist = -404;
UserUpdateNameFailed = 10000;
}
message Info {
int64 mid = 1 [(gogoproto.jsontag) = "mid"];
string name = 2 [(gogoproto.jsontag) = "name"];
string sex = 3 [(gogoproto.jsontag) = "sex"];
string face = 4 [(gogoproto.jsontag) = "face"];
string sign = 5 [(gogoproto.jsontag) = "sign"];
}
message UserReq {
int64 mid = 1 [(gogoproto.moretags) = "validate:\"gt=0,required\""];
}
message InfoReply {
Info info = 1;
}
service User {
rpc Info(UserReq) returns (InfoReply);
}

@ -0,0 +1,96 @@
{
"swagger": "2.0",
"info": {
"title": "api.proto",
"version": ""
},
"schemes": [
"http",
"https"
],
"consumes": [
"application/json",
"multipart/form-data"
],
"produces": [
"application/json"
],
"paths": {
"/user.api.User/Info": {
"get": {
"summary": "/user.api.User/Info",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"message": {
"type": "string"
},
"data": {
"$ref": "#/definitions/.user.api.InfoReply"
}
}
}
}
},
"parameters": [
{
"name": "mid",
"in": "query",
"required": true,
"type": "integer"
}
],
"tags": [
"user.api.User"
]
}
}
},
"definitions": {
".user.api.Info": {
"type": "object",
"properties": {
"mid": {
"type": "integer"
},
"name": {
"type": "string"
},
"sex": {
"type": "string"
},
"face": {
"type": "string"
},
"sign": {
"type": "string"
}
}
},
".user.api.InfoReply": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/.user.api.Info"
}
}
},
".user.api.UserReq": {
"type": "object",
"properties": {
"mid": {
"type": "integer"
}
},
"required": [
"mid"
]
}
}
}

@ -0,0 +1,3 @@
#!/bin/bash
kratos tool protoc api.proto

@ -8,7 +8,7 @@ import (
const (
_getBMGen = "go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-bm"
_bmProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bm_out=explicit_http=true:."
_bmProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bm_out=:."
)
func installBMGen() error {

@ -0,0 +1,25 @@
package main
import (
"os/exec"
"github.com/urfave/cli"
)
const (
_getEcodeGen = "go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-ecode"
_ecodeProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --ecode_out=:."
)
func installEcodeGen() error {
if _, err := exec.LookPath("protoc-gen-ecode"); err != nil {
if err := goget(_getEcodeGen); err != nil {
return err
}
}
return nil
}
func genEcode(ctx *cli.Context) error {
return generate(ctx, _ecodeProtoc)
}

@ -27,6 +27,11 @@ func main() {
Usage: "whether to use swagger for generation",
Destination: &withSwagger,
},
cli.BoolFlag{
Name: "ecode",
Usage: "whether to use ecode for generation",
Destination: &withEcode,
},
}
app.Action = func(c *cli.Context) error {
return protocAction(c)

@ -20,16 +20,18 @@ var (
withBM bool
withGRPC bool
withSwagger bool
withEcode bool
)
func protocAction(ctx *cli.Context) (err error) {
if err = checkProtoc(); err != nil {
return err
}
if !withGRPC && !withBM && !withSwagger {
if !withGRPC && !withBM && !withSwagger && !withEcode {
withBM = true
withGRPC = true
withSwagger = true
withEcode = true
}
if withBM {
if err = installBMGen(); err != nil {
@ -55,6 +57,14 @@ func protocAction(ctx *cli.Context) (err error) {
return
}
}
if withEcode {
if err = installEcodeGen(); err != nil {
return
}
if err = genEcode(ctx); err != nil {
return
}
}
log.Printf("generate %v success.\n", ctx.Args())
return nil
}

@ -8,7 +8,7 @@ import (
const (
_getSwaggerGen = "go get -u github.com/bilibili/kratos/tool/protobuf/protoc-gen-bswagger"
_swaggerProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bswagger_out=explicit_http=true:."
_swaggerProtoc = "protoc --proto_path=%s --proto_path=%s --proto_path=%s --bswagger_out=:."
)
func installSwaggerGen() error {

@ -2,6 +2,7 @@ package naming
import (
"os"
"path"
"path/filepath"
"strings"
@ -25,6 +26,16 @@ func GetVersionPrefix(pkg string) string {
return ""
}
// GenFileName returns the output name for the generated Go file.
func GenFileName(f *descriptor.FileDescriptorProto, suffix string) string {
name := *f.Name
if ext := path.Ext(name); ext == ".pb" || ext == ".proto" || ext == ".protodevel" {
name = name[:len(name)-len(ext)]
}
name += suffix
return name
}
func ServiceName(service *descriptor.ServiceDescriptorProto) string {
return utils.CamelCase(service.GetName())
}

@ -44,9 +44,6 @@ func (t *bm) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResp
func (t *bm) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File {
resp := new(plugin.CodeGeneratorResponse_File)
//if len(file.Service) == 0 {
// return nil
//}
t.generateFileHeader(file, t.GenPkgName)
t.generateImports(file)
@ -56,11 +53,8 @@ func (t *bm) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeG
count += t.generateBMInterface(file, service)
t.generateBMRoute(file, service, i)
}
//if count == 0 {
// return nil
//}
resp.Name = proto.String(naming.GoFileName(file, ".bm.go"))
resp.Name = proto.String(naming.GenFileName(file, ".bm.go"))
resp.Content = proto.String(t.FormattedOutput())
t.Output.Reset()
@ -88,13 +82,13 @@ func (t *bm) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName st
t.P("// source: ", file.GetName())
t.P()
if t.filesHandled == 0 {
// doc for the first file
t.P("/*")
t.P("Package ", t.GenPkgName, " is a generated blademaster stub package.")
t.P("This code was generated with kratos/tool/protobuf/protoc-gen-bm ", generator.Version, ".")
t.P()
comment, err := t.Reg.FileComments(file)
if err == nil && comment.Leading != "" {
// doc for the first file
t.P("/*")
t.P("Package ", t.GenPkgName, " is a generated blademaster stub package.")
t.P("This code was generated with kratos/tool/protobuf/protoc-gen-bm ", generator.Version, ".")
t.P()
for _, line := range strings.Split(comment.Leading, "\n") {
line = strings.TrimPrefix(line, " ")
// ensure we don't escape from the block comment
@ -102,12 +96,12 @@ func (t *bm) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName st
t.P(line)
}
t.P()
t.P("It is generated from these files:")
for _, f := range t.GenFiles {
t.P("\t", f.GetName())
}
t.P("*/")
}
t.P("It is generated from these files:")
for _, f := range t.GenFiles {
t.P("\t", f.GetName())
}
t.P("*/")
}
t.P(`package `, pkgName)
t.P()

@ -66,7 +66,7 @@ func (t *swaggerGen) generateSwagger(file *descriptor.FileDescriptorProto) *plug
t.defsMap = map[string]*typemap.MessageDefinition{}
out := &plugin.CodeGeneratorResponse_File{}
name := naming.GoFileName(file, ".swagger.json")
name := naming.GenFileName(file, ".swagger.json")
for _, svc := range file.Service {
for _, meth := range svc.Method {
if !t.ShouldGenForMethod(file, svc, meth) {

@ -0,0 +1,117 @@
package generator
import (
"strconv"
"strings"
"github.com/bilibili/kratos/tool/protobuf/pkg/generator"
"github.com/bilibili/kratos/tool/protobuf/pkg/naming"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)
type ecode struct {
generator.Base
filesHandled int
}
// EcodeGenerator ecode generator.
func EcodeGenerator() *ecode {
t := &ecode{}
return t
}
// Generate ...
func (t *ecode) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse {
t.Setup(in)
// Showtime! Generate the response.
resp := new(plugin.CodeGeneratorResponse)
for _, f := range t.GenFiles {
respFile := t.generateForFile(f)
if respFile != nil {
resp.File = append(resp.File, respFile)
}
}
return resp
}
func (t *ecode) generateForFile(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File {
var enums []*descriptor.EnumDescriptorProto
for _, enum := range file.EnumType {
if strings.HasSuffix(*enum.Name, "ErrCode") {
enums = append(enums, enum)
}
}
if len(enums) == 0 {
return nil
}
resp := new(plugin.CodeGeneratorResponse_File)
t.generateFileHeader(file, t.GenPkgName)
t.generateImports(file)
for _, enum := range enums {
t.generateEcode(file, enum)
}
resp.Name = proto.String(naming.GenFileName(file, ".ecode.go"))
resp.Content = proto.String(t.FormattedOutput())
t.Output.Reset()
t.filesHandled++
return resp
}
func (t *ecode) generateFileHeader(file *descriptor.FileDescriptorProto, pkgName string) {
t.P("// Code generated by protoc-gen-ecode ", generator.Version, ", DO NOT EDIT.")
t.P("// source: ", file.GetName())
t.P()
if t.filesHandled == 0 {
comment, err := t.Reg.FileComments(file)
if err == nil && comment.Leading != "" {
// doc for the first file
t.P("/*")
t.P("Package ", t.GenPkgName, " is a generated ecode package.")
t.P("This code was generated with kratos/tool/protobuf/protoc-gen-ecode ", generator.Version, ".")
t.P()
for _, line := range strings.Split(comment.Leading, "\n") {
line = strings.TrimPrefix(line, " ")
// ensure we don't escape from the block comment
line = strings.Replace(line, "*/", "* /", -1)
t.P(line)
}
t.P()
t.P("It is generated from these files:")
for _, f := range t.GenFiles {
t.P("\t", f.GetName())
}
t.P("*/")
}
}
t.P(`package `, pkgName)
t.P()
}
func (t *ecode) generateImports(file *descriptor.FileDescriptorProto) {
t.P(`import (`)
t.P(` "github.com/bilibili/kratos/pkg/ecode"`)
t.P(`)`)
t.P()
t.P(`// to suppressed 'imported but not used warning'`)
t.P(`var _ ecode.Codes`)
}
func (t *ecode) generateEcode(file *descriptor.FileDescriptorProto, enum *descriptor.EnumDescriptorProto) {
t.P("// ", *enum.Name, " ecode")
t.P("var (")
for _, item := range enum.Value {
if *item.Number == 0 {
continue
}
// NOTE: eg: t.P("UserNotExist = New(-404) ")
t.P(*item.Name, " = ", "ecode.New(", strconv.Itoa(int(*item.Number)), ")")
}
t.P(")")
}

@ -0,0 +1,27 @@
package generator
import (
"os"
"os/exec"
"testing"
"github.com/golang/protobuf/proto"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)
func TestGenerateParseCommandLineParamsError(t *testing.T) {
if os.Getenv("BE_CRASHER") == "1" {
g := &ecode{}
g.Generate(&plugin.CodeGeneratorRequest{
Parameter: proto.String("invalid"),
})
return
}
cmd := exec.Command(os.Args[0], "-test.run=TestGenerateParseCommandLineParamsError")
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
err := cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}

@ -0,0 +1,23 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/bilibili/kratos/tool/protobuf/pkg/gen"
"github.com/bilibili/kratos/tool/protobuf/pkg/generator"
ecodegen "github.com/bilibili/kratos/tool/protobuf/protoc-gen-ecode/generator"
)
func main() {
versionFlag := flag.Bool("version", false, "print version and exit")
flag.Parse()
if *versionFlag {
fmt.Println(generator.Version)
os.Exit(0)
}
g := ecodegen.EcodeGenerator()
gen.Main(g)
}
Loading…
Cancel
Save