diff --git a/doc/goctl-rpc.md b/doc/goctl-rpc.md index 3f3d68b9..7c5c2d50 100644 --- a/doc/goctl-rpc.md +++ b/doc/goctl-rpc.md @@ -7,6 +7,9 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot * 简单易用 * 快速提升开发效率 * 出错率低 +* 支持基于main proto作为相对路径的import +* 支持map、enum类型 +* 支持any类型 ## 快速开始 @@ -111,14 +114,12 @@ rpc一键生成常见问题解决见 常见问题 │   └── usermodel.go ├── user.go └── user.proto - ``` ## 准备工作 * 安装了go环境 * 安装了protoc&protoc-gen-go,并且已经设置环境变量 -* mockgen(可选,将移除) * 更多问题请见 注意事项 ## 用法 @@ -140,7 +141,6 @@ OPTIONS: --src value, -s value the file path of the proto source file --dir value, -d value the target path of the code,default path is "${pwd}". [option] --service value, --srv value the name of rpc service. [option] - --shared[已废弃] value the dir of the shared file,default path is "${pwd}/shared. [option]" --idea whether the command execution environment is from idea plugin. [option] ``` @@ -159,13 +159,13 @@ OPTIONS: ``` 则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。 -* --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。 > 注意:这里的shared文件夹名称将会是代码中的package名称。 * --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略 -## 开发人员需要做什么 + +### 开发人员需要做什么 关注业务代码编写,将重复性、与业务无关的工作交给goctl,生成好rpc服务代码后,开饭人员仅需要修改 @@ -173,14 +173,11 @@ OPTIONS: * 服务中业务逻辑编写(internal/logic/xxlogic.go) * 服务中资源上下文的编写(internal/svc/servicecontext.go) -## 扩展 -对于需要进行rpc mock的开发人员,在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。 - -## 注意事项 +### 注意事项 * `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2(see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即 - + ```shell script replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 ``` @@ -189,12 +186,76 @@ OPTIONS: * proto不支持外部依赖包引入,message不支持inline * 目前main文件、shared文件、handler文件会被强制覆盖,而和开发人员手动需要编写的则不会覆盖生成,这一类在代码头部均有 - ```shell script +```shell script // Code generated by goctl. DO NOT EDIT! // Source: xxx.proto - ``` - - 的标识,请注意不要将也写业务性代码写在里面。 +``` + +的标识,请注意不要将也写业务性代码写在里面。 + +## any和import支持 +* 支持any类型声明 +* 支持import其他proto文件 + + any类型固定import为`google/protobuf/any.proto`,且从${GOPATH}/src中查找,proto的import支持main proto的相对路径的import,且与proto文件对应的pb.go文件必须在proto目录中能被找到。不支持工程外的其他proto文件import。 + +> ⚠️注意: 不支持proto嵌套import,即:被import的proto文件不支持import。 + +### import书写格式 +import书写格式 +```golang +// @{package_of_pb} +import {proto_omport} +``` +@{package_of_pb}:pb文件的真实import目录。 +{proto_omport}:proto import + + +如:demo中的 + +```golang +// @greet/base +import "base/base.proto"; +``` + +工程目录结构如下 +``` +greet +│   ├── base +│   │   ├── base.pb.go +│   │   └── base.proto +│   ├── demo.proto +│   ├── go.mod +│   └── go.sum +``` + +demo +```golang +syntax = "proto3"; +import "google/protobuf/any.proto"; +// @greet/base +import "base/base.proto"; +package stream; + + +enum Gender{ + UNKNOWN = 0; + MAN = 1; + WOMAN = 2; +} + +message StreamResp{ + string name = 2; + Gender gender = 3; + google.protobuf.Any details = 5; + base.StreamReq req = 6; +} +service StreamGreeter { + rpc greet(base.StreamReq) returns (StreamResp); +} +``` + + ## 常见问题解决(go mod工程) diff --git a/go.mod b/go.mod index 49934070..9bb0507a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/alicebob/miniredis v2.5.0+incompatible github.com/dchest/siphash v1.2.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819 + github.com/emicklei/proto v1.9.0 github.com/fatih/color v1.9.0 // indirect github.com/frankban/quicktest v1.7.2 // indirect github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 @@ -56,7 +56,7 @@ require ( golang.org/x/tools v0.0.0-20200410132612-ae9902aceb98 // indirect google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f // indirect google.golang.org/grpc v1.29.1 - google.golang.org/protobuf v1.25.0 // indirect + google.golang.org/protobuf v1.25.0 gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/h2non/gock.v1 v1.0.15 gopkg.in/yaml.v2 v2.2.8 diff --git a/go.sum b/go.sum index 92e5167c..952b9131 100644 --- a/go.sum +++ b/go.sum @@ -50,10 +50,10 @@ github.com/dchest/siphash v1.2.1 h1:4cLinnzVJDKxTCl9B01807Yiy+W7ZzVHj/KIroQRvT4= github.com/dchest/siphash v1.2.1/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819 h1:9778zj477h/VauD8kHbOtbytW2KGQefJ/wUGE5w+mzw= -github.com/dsymonds/gotoc v0.0.0-20160928043926-5aebcfc91819/go.mod h1:MvzMVHq8BH2Ji/o8TGDocVA70byvLrAgFTxkEnmjO4Y= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/emicklei/proto v1.9.0 h1:l0QiNT6Qs7Yj0Mb4X6dnWBQer4ebei2BFcgQLbGqUDc= +github.com/emicklei/proto v1.9.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= diff --git a/tools/goctl/rpc/README.md b/tools/goctl/rpc/README.md index 010cee0d..7c5c2d50 100644 --- a/tools/goctl/rpc/README.md +++ b/tools/goctl/rpc/README.md @@ -7,6 +7,9 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot * 简单易用 * 快速提升开发效率 * 出错率低 +* 支持基于main proto作为相对路径的import +* 支持map、enum类型 +* 支持any类型 ## 快速开始 @@ -117,7 +120,6 @@ rpc一键生成常见问题解决见 常见问题 * 安装了go环境 * 安装了protoc&protoc-gen-go,并且已经设置环境变量 -* mockgen(可选,将移除) * 更多问题请见 注意事项 ## 用法 @@ -139,7 +141,6 @@ OPTIONS: --src value, -s value the file path of the proto source file --dir value, -d value the target path of the code,default path is "${pwd}". [option] --service value, --srv value the name of rpc service. [option] - --shared[已废弃] value the dir of the shared file,default path is "${pwd}/shared. [option]" --idea whether the command execution environment is from idea plugin. [option] ``` @@ -158,12 +159,12 @@ OPTIONS: ``` 则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。 -* --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。 > 注意:这里的shared文件夹名称将会是代码中的package名称。 * --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略 + ### 开发人员需要做什么 关注业务代码编写,将重复性、与业务无关的工作交给goctl,生成好rpc服务代码后,开饭人员仅需要修改 @@ -172,9 +173,6 @@ OPTIONS: * 服务中业务逻辑编写(internal/logic/xxlogic.go) * 服务中资源上下文的编写(internal/svc/servicecontext.go) -## 扩展 - -对于需要进行rpc mock的开发人员,在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。 ### 注意事项 @@ -195,6 +193,70 @@ OPTIONS: 的标识,请注意不要将也写业务性代码写在里面。 +## any和import支持 +* 支持any类型声明 +* 支持import其他proto文件 + + any类型固定import为`google/protobuf/any.proto`,且从${GOPATH}/src中查找,proto的import支持main proto的相对路径的import,且与proto文件对应的pb.go文件必须在proto目录中能被找到。不支持工程外的其他proto文件import。 + +> ⚠️注意: 不支持proto嵌套import,即:被import的proto文件不支持import。 + +### import书写格式 +import书写格式 +```golang +// @{package_of_pb} +import {proto_omport} +``` +@{package_of_pb}:pb文件的真实import目录。 +{proto_omport}:proto import + + +如:demo中的 + +```golang +// @greet/base +import "base/base.proto"; +``` + +工程目录结构如下 +``` +greet +│   ├── base +│   │   ├── base.pb.go +│   │   └── base.proto +│   ├── demo.proto +│   ├── go.mod +│   └── go.sum +``` + +demo +```golang +syntax = "proto3"; +import "google/protobuf/any.proto"; +// @greet/base +import "base/base.proto"; +package stream; + + +enum Gender{ + UNKNOWN = 0; + MAN = 1; + WOMAN = 2; +} + +message StreamResp{ + string name = 2; + Gender gender = 3; + google.protobuf.Any details = 5; + base.StreamReq req = 6; +} +service StreamGreeter { + rpc greet(base.StreamReq) returns (StreamResp); +} +``` + + + ## 常见问题解决(go mod工程) * 错误一: diff --git a/tools/goctl/rpc/base.pb.go b/tools/goctl/rpc/base.pb.go new file mode 100644 index 00000000..0f2653f9 --- /dev/null +++ b/tools/goctl/rpc/base.pb.go @@ -0,0 +1,108 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: base.proto + +package base + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type IdRequest struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *IdRequest) Reset() { *m = IdRequest{} } +func (m *IdRequest) String() string { return proto.CompactTextString(m) } +func (*IdRequest) ProtoMessage() {} +func (*IdRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_db1b6b0986796150, []int{0} +} + +func (m *IdRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_IdRequest.Unmarshal(m, b) +} +func (m *IdRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_IdRequest.Marshal(b, m, deterministic) +} +func (m *IdRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdRequest.Merge(m, src) +} +func (m *IdRequest) XXX_Size() int { + return xxx_messageInfo_IdRequest.Size(m) +} +func (m *IdRequest) XXX_DiscardUnknown() { + xxx_messageInfo_IdRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_IdRequest proto.InternalMessageInfo + +func (m *IdRequest) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +type EmptyResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EmptyResponse) Reset() { *m = EmptyResponse{} } +func (m *EmptyResponse) String() string { return proto.CompactTextString(m) } +func (*EmptyResponse) ProtoMessage() {} +func (*EmptyResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_db1b6b0986796150, []int{1} +} + +func (m *EmptyResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EmptyResponse.Unmarshal(m, b) +} +func (m *EmptyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EmptyResponse.Marshal(b, m, deterministic) +} +func (m *EmptyResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_EmptyResponse.Merge(m, src) +} +func (m *EmptyResponse) XXX_Size() int { + return xxx_messageInfo_EmptyResponse.Size(m) +} +func (m *EmptyResponse) XXX_DiscardUnknown() { + xxx_messageInfo_EmptyResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_EmptyResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*IdRequest)(nil), "base.IdRequest") + proto.RegisterType((*EmptyResponse)(nil), "base.EmptyResponse") +} + +func init() { proto.RegisterFile("base.proto", fileDescriptor_db1b6b0986796150) } + +var fileDescriptor_db1b6b0986796150 = []byte{ + // 91 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4a, 0x4a, 0x2c, 0x4e, + 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0xa4, 0xb9, 0x38, 0x3d, 0x53, + 0x82, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0xf8, 0xb8, 0x98, 0x32, 0x53, 0x24, 0x18, 0x15, + 0x18, 0x35, 0x38, 0x83, 0x98, 0x32, 0x53, 0x94, 0xf8, 0xb9, 0x78, 0x5d, 0x73, 0x0b, 0x4a, 0x2a, + 0x83, 0x52, 0x8b, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x93, 0xd8, 0xc0, 0x5a, 0x8d, 0x01, 0x01, 0x00, + 0x00, 0xff, 0xff, 0xe1, 0x39, 0x3c, 0x22, 0x48, 0x00, 0x00, 0x00, +} diff --git a/tools/goctl/rpc/base.proto b/tools/goctl/rpc/base.proto new file mode 100644 index 00000000..501f12f7 --- /dev/null +++ b/tools/goctl/rpc/base.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package base; + +message IdRequest { + string id = 1; +} + +message EmptyResponse { + +} diff --git a/tools/goctl/rpc/ctx/ctx.go b/tools/goctl/rpc/ctx/ctx.go index 2ad5e78d..5facee42 100644 --- a/tools/goctl/rpc/ctx/ctx.go +++ b/tools/goctl/rpc/ctx/ctx.go @@ -11,6 +11,7 @@ import ( "github.com/tal-tech/go-zero/tools/goctl/util/console" "github.com/tal-tech/go-zero/tools/goctl/util/project" "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/vars" "github.com/urfave/cli" ) @@ -58,7 +59,7 @@ func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *R } serviceNameString := stringx.From(serviceName) if serviceNameString.IsEmptyOrSpace() { - log.Fatalln("service name is not found") + log.Fatalln("service name not found") } info, err := project.Prepare(targetDir, true) @@ -80,7 +81,7 @@ func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *R func MustCreateRpcContextFromCli(ctx *cli.Context) *RpcContext { os := runtime.GOOS switch os { - case "darwin", "linux", "windows": + case vars.OsMac, vars.OsLinux, vars.OsWindows: default: logx.Must(fmt.Errorf("unexpected os: %s", os)) } diff --git a/tools/goctl/rpc/execx/execx.go b/tools/goctl/rpc/execx/execx.go index 327c5a35..039a94c7 100644 --- a/tools/goctl/rpc/execx/execx.go +++ b/tools/goctl/rpc/execx/execx.go @@ -6,15 +6,17 @@ import ( "fmt" "os/exec" "runtime" + + "github.com/tal-tech/go-zero/tools/goctl/vars" ) func Run(arg string, dir string) (string, error) { goos := runtime.GOOS var cmd *exec.Cmd switch goos { - case "darwin", "linux": + case vars.OsMac, vars.OsLinux: cmd = exec.Command("sh", "-c", arg) - case "windows": + case vars.OsWindows: cmd = exec.Command("cmd.exe", "/c", arg) default: return "", fmt.Errorf("unexpected os: %v", goos) diff --git a/tools/goctl/rpc/gen/gen.go b/tools/goctl/rpc/gen/gen.go index 12c32d69..8c9f3c0a 100644 --- a/tools/goctl/rpc/gen/gen.go +++ b/tools/goctl/rpc/gen/gen.go @@ -1,6 +1,7 @@ package gen import ( + "github.com/logrusorgru/aurora" "github.com/tal-tech/go-zero/tools/goctl/rpc/ctx" "github.com/tal-tech/go-zero/tools/goctl/rpc/parser" ) @@ -31,10 +32,11 @@ func NewDefaultRpcGenerator(ctx *ctx.RpcContext) *defaultRpcGenerator { } func (g *defaultRpcGenerator) Generate() (err error) { - g.Ctx.Info("generating code...") + g.Ctx.Info(aurora.Blue("-> goctl rpc reference documents: ").String() + "「https://github.com/tal-tech/go-zero/blob/master/doc/goctl-rpc.md」") + g.Ctx.Warning("-> generating rpc code ...") defer func() { if err == nil { - g.Ctx.Success("Done.") + g.Ctx.MarkDone() } }() err = g.createDir() diff --git a/tools/goctl/rpc/gen/gencall.go b/tools/goctl/rpc/gen/gencall.go index fac0ecfc..feaf534f 100644 --- a/tools/goctl/rpc/gen/gencall.go +++ b/tools/goctl/rpc/gen/gencall.go @@ -2,17 +2,16 @@ package gen import ( "fmt" - "os" - "os/exec" "path/filepath" "strings" - "github.com/tal-tech/go-zero/tools/goctl/rpc/execx" + "github.com/tal-tech/go-zero/core/collection" "github.com/tal-tech/go-zero/tools/goctl/rpc/parser" "github.com/tal-tech/go-zero/tools/goctl/util" ) const ( + typesFilename = "types.go" callTemplateText = `{{.head}} //go:generate mockgen -destination ./{{.name}}_mock.go -package {{.filePackage}} -source $GOFILE @@ -54,14 +53,17 @@ import "errors" var errJsonConvert = errors.New("json convert error") +{{.const}} + {{.types}} ` callInterfaceFunctionTemplate = `{{if .hasComment}}{{.comment}} {{end}}{{.method}}(ctx context.Context,in *{{.pbRequest}}) (*{{.pbResponse}},error)` + callFunctionTemplate = ` {{if .hasComment}}{{.comment}}{{end}} -func (m *default{{.rpcServiceName}}) {{.method}}(ctx context.Context,in *{{.pbRequest}}) (*{{.pbResponse}}, error) { - var request {{.package}}.{{.pbRequest}} +func (m *default{{.rpcServiceName}}) {{.method}}(ctx context.Context,in *{{.pbRequestName}}) (*{{.pbResponse}}, error) { + var request {{.pbRequest}} bts, err := jsonx.Marshal(in) if err != nil { return nil, errJsonConvert @@ -108,21 +110,23 @@ func (g *defaultRpcGenerator) genCall() error { return err } + constLit, err := file.GenEnumCode() + if err != nil { + return err + } + service := file.Service[0] callPath := filepath.Join(g.dirM[dirTarget], service.Name.Lower()) - if err = util.MkdirIfNotExist(callPath); err != nil { return err } - pbPkg := file.Package - remotePackage := fmt.Sprintf(`%v "%v"`, pbPkg, g.mustGetPackage(dirPb)) - filename := filepath.Join(callPath, "types.go") + filename := filepath.Join(callPath, typesFilename) head := util.GetHead(g.Ctx.ProtoSource) err = util.With("types").GoFmt(true).Parse(callTemplateTypes).SaveTo(map[string]interface{}{ "head": head, + "const": constLit, "filePackage": service.Name.Lower(), - "pbPkg": pbPkg, "serviceName": g.Ctx.ServiceName.Title(), "lowerStartServiceName": g.Ctx.ServiceName.UnTitle(), "types": typeCode, @@ -131,10 +135,8 @@ func (g *defaultRpcGenerator) genCall() error { return err } - _, err = exec.LookPath("mockgen") - mockGenInstalled := err == nil filename = filepath.Join(callPath, fmt.Sprintf("%s.go", service.Name.Lower())) - functions, err := g.getFuncs(service) + functions, importList, err := g.genFunction(service) if err != nil { return err } @@ -144,72 +146,56 @@ func (g *defaultRpcGenerator) genCall() error { return err } - mockFile := filepath.Join(callPath, fmt.Sprintf("%s_mock.go", service.Name.Lower())) - _ = os.Remove(mockFile) err = util.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{ "name": service.Name.Lower(), "head": head, "filePackage": service.Name.Lower(), - "pbPkg": pbPkg, - "package": remotePackage, + "package": strings.Join(importList, util.NL), "serviceName": service.Name.Title(), - "functions": strings.Join(functions, "\n"), - "interface": strings.Join(iFunctions, "\n"), + "functions": strings.Join(functions, util.NL), + "interface": strings.Join(iFunctions, util.NL), }, filename, true) - if err != nil { - return err - } - // if mockgen is already installed, it will generate code of gomock for shared files - // Deprecated: it will be removed - if mockGenInstalled && g.Ctx.IsInGoEnv { - _, _ = execx.Run(fmt.Sprintf("go generate %s", filename), "") - } - - return nil + return err } -func (g *defaultRpcGenerator) getFuncs(service *parser.RpcService) ([]string, error) { +func (g *defaultRpcGenerator) genFunction(service *parser.RpcService) ([]string, []string, error) { file := g.ast pkgName := file.Package functions := make([]string, 0) + imports := collection.NewSet() + imports.AddStr(fmt.Sprintf(`%v "%v"`, pkgName, g.mustGetPackage(dirPb))) for _, method := range service.Funcs { - var comment string - if len(method.Document) > 0 { - comment = method.Document[0] - } + imports.AddStr(g.ast.Imports[method.ParameterIn.Package]) buffer, err := util.With("sharedFn").Parse(callFunctionTemplate).Execute(map[string]interface{}{ "rpcServiceName": service.Name.Title(), "method": method.Name.Title(), "package": pkgName, - "pbRequest": method.InType, - "pbResponse": method.OutType, - "hasComment": len(method.Document) > 0, - "comment": comment, + "pbRequestName": method.ParameterIn.Name, + "pbRequest": method.ParameterIn.Expression, + "pbResponse": method.ParameterOut.Name, + "hasComment": method.HaveDoc(), + "comment": method.GetDoc(), }) if err != nil { - return nil, err + return nil, nil, err } functions = append(functions, buffer.String()) } - return functions, nil + return functions, imports.KeysStr(), nil } func (g *defaultRpcGenerator) getInterfaceFuncs(service *parser.RpcService) ([]string, error) { functions := make([]string, 0) for _, method := range service.Funcs { - var comment string - if len(method.Document) > 0 { - comment = method.Document[0] - } buffer, err := util.With("interfaceFn").Parse(callInterfaceFunctionTemplate).Execute( map[string]interface{}{ - "hasComment": len(method.Document) > 0, - "comment": comment, + "hasComment": method.HaveDoc(), + "comment": method.GetDoc(), "method": method.Name.Title(), - "pbRequest": method.InType, - "pbResponse": method.OutType, + "pbRequest": method.ParameterIn.Name, + "pbResponse": method.ParameterOut.Name, }) if err != nil { return nil, err diff --git a/tools/goctl/rpc/gen/gendir.go b/tools/goctl/rpc/gen/gendir.go index aa798e76..bb0bac22 100644 --- a/tools/goctl/rpc/gen/gendir.go +++ b/tools/goctl/rpc/gen/gendir.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/tal-tech/go-zero/tools/goctl/util" + "github.com/tal-tech/go-zero/tools/goctl/vars" ) // target @@ -43,9 +44,9 @@ func (g *defaultRpcGenerator) mustGetPackage(dir string) string { relativePath := strings.TrimPrefix(target, projectPath) os := runtime.GOOS switch os { - case "windows": + case vars.OsWindows: relativePath = filepath.ToSlash(relativePath) - case "darwin", "linux": + case vars.OsMac, vars.OsLinux: default: g.Ctx.Fatalln("unexpected os: %s", os) } diff --git a/tools/goctl/rpc/gen/genlogic.go b/tools/goctl/rpc/gen/genlogic.go index ca4468ca..b5e76435 100644 --- a/tools/goctl/rpc/gen/genlogic.go +++ b/tools/goctl/rpc/gen/genlogic.go @@ -37,10 +37,10 @@ func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logic {{.functions}} ` logicFunctionTemplate = `{{if .hasComment}}{{.comment}}{{end}} -func (l *{{.logicName}}) {{.method}} (in *{{.package}}.{{.request}}) (*{{.package}}.{{.response}}, error) { +func (l *{{.logicName}}) {{.method}} (in {{.request}}) ({{.response}}, error) { // todo: add your logic here and delete this line - return &{{.package}}.{{.response}}{}, nil + return &{{.responseType}}{}, nil } ` ) @@ -53,18 +53,18 @@ func (g *defaultRpcGenerator) genLogic() error { for _, method := range item.Funcs { logicName := fmt.Sprintf("%slogic.go", method.Name.Lower()) filename := filepath.Join(logicPath, logicName) - functions, err := genLogicFunction(protoPkg, method) + functions, importList, err := g.genLogicFunction(protoPkg, method) if err != nil { return err } imports := collection.NewSet() - pbImport := fmt.Sprintf(`%v "%v"`, protoPkg, g.mustGetPackage(dirPb)) svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc)) - imports.AddStr(pbImport, svcImport) + imports.AddStr(svcImport) + imports.AddStr(importList...) err = util.With("logic").GoFmt(true).Parse(logicTemplate).SaveTo(map[string]interface{}{ "logicName": fmt.Sprintf("%sLogic", method.Name.Title()), "functions": functions, - "imports": strings.Join(imports.KeysStr(), "\n"), + "imports": strings.Join(imports.KeysStr(), util.NL), }, filename, false) if err != nil { return err @@ -74,20 +74,26 @@ func (g *defaultRpcGenerator) genLogic() error { return nil } -func genLogicFunction(packageName string, method *parser.Func) (string, error) { +func (g *defaultRpcGenerator) genLogicFunction(packageName string, method *parser.Func) (string, []string, error) { var functions = make([]string, 0) + var imports = collection.NewSet() + if method.ParameterIn.Package == packageName || method.ParameterOut.Package == packageName { + imports.AddStr(fmt.Sprintf(`%v "%v"`, packageName, g.mustGetPackage(dirPb))) + } + imports.AddStr(g.ast.Imports[method.ParameterIn.Package]) + imports.AddStr(g.ast.Imports[method.ParameterOut.Package]) buffer, err := util.With("fun").Parse(logicFunctionTemplate).Execute(map[string]interface{}{ - "logicName": fmt.Sprintf("%sLogic", method.Name.Title()), - "method": method.Name.Title(), - "package": packageName, - "request": method.InType, - "response": method.OutType, - "hasComment": len(method.Document) > 0, - "comment": strings.Join(method.Document, "\n"), + "logicName": fmt.Sprintf("%sLogic", method.Name.Title()), + "method": method.Name.Title(), + "request": method.ParameterIn.StarExpression, + "response": method.ParameterOut.StarExpression, + "responseType": method.ParameterOut.Expression, + "hasComment": method.HaveDoc(), + "comment": method.GetDoc(), }) if err != nil { - return "", err + return "", nil, err } functions = append(functions, buffer.String()) - return strings.Join(functions, "\n"), nil + return strings.Join(functions, util.NL), imports.KeysStr(), nil } diff --git a/tools/goctl/rpc/gen/genmain.go b/tools/goctl/rpc/gen/genmain.go index 938c07da..33c412ac 100644 --- a/tools/goctl/rpc/gen/genmain.go +++ b/tools/goctl/rpc/gen/genmain.go @@ -65,7 +65,7 @@ func (g *defaultRpcGenerator) genMain() error { "serviceName": g.Ctx.ServiceName.Lower(), "srv": srv, "registers": registers, - "imports": strings.Join(imports, "\n"), + "imports": strings.Join(imports, util.NL), }, fileName, true) } @@ -77,5 +77,5 @@ func (g *defaultRpcGenerator) genServer(pkg string, list []*parser.RpcService) ( list1 = append(list1, fmt.Sprintf("%sSrv := server.New%sServer(ctx)", name, item.Name.Title())) list2 = append(list2, fmt.Sprintf("%s.Register%sServer(grpcServer, %sSrv)", pkg, item.Name.Title(), name)) } - return strings.Join(list1, "\n"), strings.Join(list2, "\n") + return strings.Join(list1, util.NL), strings.Join(list2, util.NL) } diff --git a/tools/goctl/rpc/gen/genpb.go b/tools/goctl/rpc/gen/genpb.go index 67296804..cd55d659 100644 --- a/tools/goctl/rpc/gen/genpb.go +++ b/tools/goctl/rpc/gen/genpb.go @@ -1,68 +1,37 @@ package gen import ( - "errors" + "bytes" "fmt" - "io/ioutil" "path/filepath" "strings" - "github.com/dsymonds/gotoc/parser" - - "github.com/tal-tech/go-zero/core/lang" + "github.com/tal-tech/go-zero/core/collection" "github.com/tal-tech/go-zero/tools/goctl/rpc/execx" - astParser "github.com/tal-tech/go-zero/tools/goctl/rpc/parser" - "github.com/tal-tech/go-zero/tools/goctl/util/stringx" + "github.com/tal-tech/go-zero/tools/goctl/rpc/parser" ) -func (g *defaultRpcGenerator) genPb() error { - importPath, filename := filepath.Split(g.Ctx.ProtoFileSrc) - tree, err := parser.ParseFiles([]string{filename}, []string{importPath}) - if err != nil { - return err - } - - if len(tree.Files) == 0 { - return errors.New("proto ast parse failed") - } - - file := tree.Files[0] - if len(file.Package) == 0 { - return errors.New("expected package, but nothing found") - } - - targetStruct := make(map[string]lang.PlaceholderType) - for _, item := range file.Messages { - if len(item.Messages) > 0 { - return fmt.Errorf(`line %v: unexpected inner message near: "%v""`, item.Messages[0].Position.Line, item.Messages[0].Name) - } - - name := stringx.From(item.Name) - if _, ok := targetStruct[name.Lower()]; ok { - return fmt.Errorf("line %v: duplicate %v", item.Position.Line, name) - } - targetStruct[name.Lower()] = lang.Placeholder - } +const ( + protocCmd = "protoc" + grpcPluginCmd = "--go_out=plugins=grpc" +) +func (g *defaultRpcGenerator) genPb() error { pbPath := g.dirM[dirPb] - protoFileName := filepath.Base(g.Ctx.ProtoFileSrc) - err = g.protocGenGo(pbPath) + imports, containsAny, err := parser.ParseImport(g.Ctx.ProtoFileSrc) if err != nil { return err } - pbGo := strings.TrimSuffix(protoFileName, ".proto") + ".pb.go" - pbFile := filepath.Join(pbPath, pbGo) - bts, err := ioutil.ReadFile(pbFile) + err = g.protocGenGo(pbPath, imports) if err != nil { return err } - - aspParser := astParser.NewAstParser(bts, targetStruct, g.Ctx.Console) - ast, err := aspParser.Parse() + ast, err := parser.Transfer(g.Ctx.ProtoFileSrc, pbPath, imports, g.Ctx.Console) if err != nil { return err } + ast.ContainsAny = containsAny if len(ast.Service) == 0 { return fmt.Errorf("service not found") @@ -71,10 +40,35 @@ func (g *defaultRpcGenerator) genPb() error { return nil } -func (g *defaultRpcGenerator) protocGenGo(target string) error { - src := filepath.Dir(g.Ctx.ProtoFileSrc) - sh := fmt.Sprintf(`protoc -I=%s --go_out=plugins=grpc:%s %s`, src, target, g.Ctx.ProtoFileSrc) - stdout, err := execx.Run(sh, "") +func (g *defaultRpcGenerator) protocGenGo(target string, imports []*parser.Import) error { + dir := filepath.Dir(g.Ctx.ProtoFileSrc) + // cmd join,see the document of proto generating class @https://developers.google.com/protocol-buffers/docs/proto3#generating + // template: protoc -I=${import_path} -I=${other_import_path} -I=${...} --go_out=plugins=grpc,M${pb_package_kv}, M${...} :${target_dir} + // eg: protoc -I=${GOPATH}/src -I=. example.proto --go_out=plugins=grpc,Mbase/base.proto=github.com/go-zero/base.proto:. + // note: the external import out of the project which are found in ${GOPATH}/src so far. + + buffer := new(bytes.Buffer) + buffer.WriteString(protocCmd + " ") + targetImportFiltered := collection.NewSet() + + for _, item := range imports { + buffer.WriteString(fmt.Sprintf("-I=%s ", item.OriginalDir)) + if len(item.BridgeImport) == 0 { + continue + } + targetImportFiltered.AddStr(item.BridgeImport) + + } + buffer.WriteString("-I=${GOPATH}/src ") + buffer.WriteString(fmt.Sprintf("-I=%s %s ", dir, g.Ctx.ProtoFileSrc)) + + buffer.WriteString(grpcPluginCmd) + if targetImportFiltered.Count() > 0 { + buffer.WriteString(fmt.Sprintf(",%v", strings.Join(targetImportFiltered.KeysStr(), ","))) + } + buffer.WriteString(":" + target) + g.Ctx.Debug("-> " + buffer.String()) + stdout, err := execx.Run(buffer.String(), "") if err != nil { return err } diff --git a/tools/goctl/rpc/gen/genserver.go b/tools/goctl/rpc/gen/genserver.go index 470c41a3..c9d61782 100644 --- a/tools/goctl/rpc/gen/genserver.go +++ b/tools/goctl/rpc/gen/genserver.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strings" + "github.com/tal-tech/go-zero/core/collection" "github.com/tal-tech/go-zero/tools/goctl/rpc/parser" "github.com/tal-tech/go-zero/tools/goctl/util" ) @@ -32,7 +33,7 @@ func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server { ` functionTemplate = ` {{if .hasComment}}{{.comment}}{{end}} -func (s *{{.server}}Server) {{.method}} (ctx context.Context, in *{{.package}}.{{.request}}) (*{{.package}}.{{.response}}, error) { +func (s *{{.server}}Server) {{.method}} (ctx context.Context, in {{.request}}) ({{.response}}, error) { l := logic.New{{.logicName}}(ctx,s.svcCtx) return l.{{.method}}(in) } @@ -45,29 +46,26 @@ func (s *{{.server}}Server) {{.method}} (ctx context.Context, in *{{.package}}.{ func (g *defaultRpcGenerator) genHandler() error { serverPath := g.dirM[dirServer] file := g.ast - pkg := file.Package - pbImport := fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb)) logicImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirLogic)) svcImport := fmt.Sprintf(`"%v"`, g.mustGetPackage(dirSvc)) - imports := []string{ - pbImport, - logicImport, - svcImport, - } + imports := collection.NewSet() + imports.AddStr(logicImport, svcImport) + head := util.GetHead(g.Ctx.ProtoSource) for _, service := range file.Service { filename := fmt.Sprintf("%vserver.go", service.Name.Lower()) serverFile := filepath.Join(serverPath, filename) - funcList, err := g.genFunctions(service) + funcList, importList, err := g.genFunctions(service) if err != nil { return err } + imports.AddStr(importList...) err = util.With("server").GoFmt(true).Parse(serverTemplate).SaveTo(map[string]interface{}{ "head": head, "types": fmt.Sprintf(typeFmt, service.Name.Title()), "server": service.Name.Title(), - "imports": strings.Join(imports, "\n\t"), - "funcs": strings.Join(funcList, "\n"), + "imports": strings.Join(imports.KeysStr(), util.NL), + "funcs": strings.Join(funcList, util.NL), }, serverFile, true) if err != nil { return err @@ -76,25 +74,31 @@ func (g *defaultRpcGenerator) genHandler() error { return nil } -func (g *defaultRpcGenerator) genFunctions(service *parser.RpcService) ([]string, error) { +func (g *defaultRpcGenerator) genFunctions(service *parser.RpcService) ([]string, []string, error) { file := g.ast pkg := file.Package var functionList []string + imports := collection.NewSet() for _, method := range service.Funcs { + if method.ParameterIn.Package == pkg || method.ParameterOut.Package == pkg { + imports.AddStr(fmt.Sprintf(`%v "%v"`, pkg, g.mustGetPackage(dirPb))) + } + imports.AddStr(g.ast.Imports[method.ParameterIn.Package]) + imports.AddStr(g.ast.Imports[method.ParameterOut.Package]) buffer, err := util.With("func").Parse(functionTemplate).Execute(map[string]interface{}{ "server": service.Name.Title(), "logicName": fmt.Sprintf("%sLogic", method.Name.Title()), "method": method.Name.Title(), "package": pkg, - "request": method.InType, - "response": method.OutType, - "hasComment": len(method.Document), - "comment": strings.Join(method.Document, "\n"), + "request": method.ParameterIn.StarExpression, + "response": method.ParameterOut.StarExpression, + "hasComment": method.HaveDoc(), + "comment": method.GetDoc(), }) if err != nil { - return nil, err + return nil, nil, err } functionList = append(functionList, buffer.String()) } - return functionList, nil + return functionList, imports.KeysStr(), nil } diff --git a/tools/goctl/rpc/gen/template.go b/tools/goctl/rpc/gen/template.go index eff5b1b7..d81c44a0 100644 --- a/tools/goctl/rpc/gen/template.go +++ b/tools/goctl/rpc/gen/template.go @@ -39,6 +39,8 @@ func NewRpcTemplate(out string, idea bool) *rpcTemplate { } func (r *rpcTemplate) MustGenerate(showState bool) { + r.Info("查看rpc生成请移步至「https://github.com/tal-tech/go-zero/blob/master/doc/goctl-rpc.md」") + r.Info("generating template...") protoFilename := filepath.Base(r.out) serviceName := stringx.From(strings.TrimSuffix(protoFilename, filepath.Ext(protoFilename))) err := util.With("t").Parse(rpcTemplateText).SaveTo(map[string]string{ diff --git a/tools/goctl/rpc/gen_test.go b/tools/goctl/rpc/gen_test.go new file mode 100644 index 00000000..e32e99b2 --- /dev/null +++ b/tools/goctl/rpc/gen_test.go @@ -0,0 +1,35 @@ +package base + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tal-tech/go-zero/tools/goctl/rpc/parser" + "github.com/tal-tech/go-zero/tools/goctl/util/console" +) + +func TestParseImport(t *testing.T) { + src, _ := filepath.Abs("./test.proto") + base, _ := filepath.Abs("./base.proto") + imports, containsAny, err := parser.ParseImport(src) + assert.Nil(t, err) + assert.Equal(t, true, containsAny) + assert.Equal(t, 1, len(imports)) + assert.Equal(t, "github.com/tal-tech/go-zero/tools/goctl/rpc", imports[0].PbImportName) + assert.Equal(t, base, imports[0].OriginalProtoPath) +} + +func TestTransfer(t *testing.T) { + src, _ := filepath.Abs("./test.proto") + abs, _ := filepath.Abs("./test") + imports, _, _ := parser.ParseImport(src) + proto, err := parser.Transfer(src, abs, imports, console.NewConsole(false)) + assert.Nil(t, err) + assert.Equal(t, 1, len(proto.Service)) + assert.Equal(t, "Greeter", proto.Service[0].Name.Source()) + assert.Equal(t, 5, len(proto.Structure)) + data, ok := proto.Structure["map"] + assert.Equal(t, true, ok) + assert.Equal(t, "M", data.Field[0].Name.Source()) +} diff --git a/tools/goctl/rpc/parser/parser.go b/tools/goctl/rpc/parser/parser.go new file mode 100644 index 00000000..16dec9fd --- /dev/null +++ b/tools/goctl/rpc/parser/parser.go @@ -0,0 +1,46 @@ +package parser + +import ( + "path/filepath" + "strings" + + "github.com/tal-tech/go-zero/core/lang" + "github.com/tal-tech/go-zero/tools/goctl/util/console" +) + +func Transfer(proto, target string, externalImport []*Import, console console.Console) (*PbAst, error) { + messageM := make(map[string]lang.PlaceholderType) + enumM := make(map[string]*Enum) + protoAst, err := parseProto(proto, messageM, enumM) + if err != nil { + return nil, err + } + for _, item := range externalImport { + err = checkImport(item.OriginalProtoPath) + if err != nil { + return nil, err + } + innerAst, err := parseProto(item.OriginalProtoPath, protoAst.Message, protoAst.Enum) + if err != nil { + return nil, err + } + for k, v := range innerAst.Message { + protoAst.Message[k] = v + } + for k, v := range innerAst.Enum { + protoAst.Enum[k] = v + } + } + protoAst.Import = externalImport + protoAst.PbSrc = filepath.Join(target, strings.TrimSuffix(filepath.Base(proto), ".proto")+".pb.go") + return transfer(protoAst, console) +} + +func transfer(proto *Proto, console console.Console) (*PbAst, error) { + parser := MustNewAstParser(proto, console) + parse, err := parser.Parse() + if err != nil { + return nil, err + } + return parse, nil +} diff --git a/tools/goctl/rpc/parser/pbast.go b/tools/goctl/rpc/parser/pbast.go index 2bcbaefd..6a5872f5 100644 --- a/tools/goctl/rpc/parser/pbast.go +++ b/tools/goctl/rpc/parser/pbast.go @@ -6,6 +6,7 @@ import ( "go/ast" "go/parser" "go/token" + "io/ioutil" "sort" "strings" @@ -18,8 +19,9 @@ import ( const ( flagStar = "*" + flagDot = "." suffixServer = "Server" - referenceContext = "context." + referenceContext = "context" unknownPrefix = "XXX_" ignoreJsonTagExpression = `json:"-"` ) @@ -34,19 +36,23 @@ var ( }` fieldTemplate = `{{if .hasDoc}}{{.doc}} {{end}}{{.name}} {{.type}} {{.tag}}{{if .hasComment}}{{.comment}}{{end}}` + + anyTypeTemplate = "Any struct {\n\tTypeUrl string `json:\"typeUrl\"`\n\tValue []byte `json:\"value\"`\n}" + objectM = make(map[string]*Struct) ) type ( astParser struct { - golang []byte filterStruct map[string]lang.PlaceholderType + filterEnum map[string]*Enum console.Console fileSet *token.FileSet + proto *Proto } Field struct { Name stringx.String - TypeName string + Type Type JsonTag string Document []string Comment []string @@ -57,13 +63,33 @@ type ( Comment []string Field []*Field } + ConstLit struct { + Name stringx.String + Document []string + Comment []string + Lit []*Lit + } + Lit struct { + Key string + Value int + } + Type struct { + // eg:context.Context + Expression string + // eg: *context.Context + StarExpression string + // Invoke Type Expression + InvokeTypeExpression string + // eg:context + Package string + // eg:Context + Name string + } Func struct { - Name stringx.String - InType string - InTypeName string // remove *Context,such as LoginRequest、UserRequest - OutTypeName string // remove *Context - OutType string - Document []string + Name stringx.String + ParameterIn Type + ParameterOut Type + Document []string } RpcService struct { Name stringx.String @@ -71,54 +97,98 @@ type ( } // parsing for rpc PbAst struct { - Package string - // external reference - Imports map[string]string - Strcuts map[string]*Struct - // rpc server's functions,not all functions - Service []*RpcService + ContainsAny bool + Imports map[string]string + Structure map[string]*Struct + Service []*RpcService + *Proto } ) -func NewAstParser(golang []byte, filterStruct map[string]lang.PlaceholderType, log console.Console) *astParser { +func MustNewAstParser(proto *Proto, log console.Console) *astParser { return &astParser{ - golang: golang, - filterStruct: filterStruct, + filterStruct: proto.Message, + filterEnum: proto.Enum, Console: log, fileSet: token.NewFileSet(), + proto: proto, } } func (a *astParser) Parse() (*PbAst, error) { - fSet := a.fileSet - f, err := parser.ParseFile(fSet, "", a.golang, parser.ParseComments) + var pbAst PbAst + pbAst.ContainsAny = a.proto.ContainsAny + pbAst.Proto = a.proto + pbAst.Structure = make(map[string]*Struct) + pbAst.Imports = make(map[string]string) + structure, imports, services, err := a.parse(a.proto.PbSrc) + if err != nil { + return nil, err + } + dependencyStructure, err := a.parseExternalDependency() if err != nil { return nil, err } + for k, v := range structure { + pbAst.Structure[k] = v + } + for k, v := range dependencyStructure { + pbAst.Structure[k] = v + } + for key, path := range imports { + pbAst.Imports[key] = path + } + pbAst.Service = append(pbAst.Service, services...) + return &pbAst, nil +} +func (a *astParser) parse(pbSrc string) (structure map[string]*Struct, imports map[string]string, services []*RpcService, retErr error) { + structure = make(map[string]*Struct) + imports = make(map[string]string) + data, err := ioutil.ReadFile(pbSrc) + if err != nil { + retErr = err + return + } + fSet := a.fileSet + f, err := parser.ParseFile(fSet, "", data, parser.ParseComments) + if err != nil { + retErr = err + return + } commentMap := ast.NewCommentMap(fSet, f, f.Comments) f.Comments = commentMap.Filter(f).Comments() - var pbAst PbAst - pbAst.Package = a.mustGetIndentName(f.Name) - imports := make(map[string]string) - for _, item := range f.Imports { - if item == nil { + strucs, function := a.mustScope(f.Scope, a.mustGetIndentName(f.Name)) + for k, v := range strucs { + if v == nil { continue } - if item.Path == nil { - continue + structure[k] = v + } + importList := f.Imports + for _, item := range importList { + name := a.mustGetIndentName(item.Name) + if item.Path != nil { + imports[name] = item.Path.Value } - key := a.mustGetIndentName(item.Name) - value := item.Path.Value - imports[key] = value } - structs, funcs := a.mustScope(f.Scope) - pbAst.Imports = imports - pbAst.Strcuts = structs - pbAst.Service = funcs - return &pbAst, nil + services = append(services, function...) + return +} +func (a *astParser) parseExternalDependency() (map[string]*Struct, error) { + m := make(map[string]*Struct) + for _, impo := range a.proto.Import { + ret, _, _, err := a.parse(impo.OriginalPbPath) + if err != nil { + return nil, err + } + for k, v := range ret { + m[k] = v + } + } + return m, nil } -func (a *astParser) mustScope(scope *ast.Scope) (map[string]*Struct, []*RpcService) { +func (a *astParser) mustScope(scope *ast.Scope, sourcePackage string) (map[string]*Struct, []*RpcService) { if scope == nil { return nil, nil } @@ -140,7 +210,7 @@ func (a *astParser) mustScope(scope *ast.Scope) (map[string]*Struct, []*RpcServi switch v := tp.(type) { case *ast.StructType: - st, err := a.parseObject(name, v) + st, err := a.parseObject(name, v, sourcePackage) a.Must(err) structs[st.Name.Lower()] = st @@ -148,7 +218,7 @@ func (a *astParser) mustScope(scope *ast.Scope) (map[string]*Struct, []*RpcServi if !strings.HasSuffix(name, suffixServer) { continue } - list := a.mustServerFunctions(v) + list := a.mustServerFunctions(v, sourcePackage) serviceList = append(serviceList, &RpcService{ Name: stringx.From(strings.TrimSuffix(name, suffixServer)), Funcs: list, @@ -163,7 +233,7 @@ func (a *astParser) mustScope(scope *ast.Scope) (map[string]*Struct, []*RpcServi return targetStruct, serviceList } -func (a *astParser) mustServerFunctions(v *ast.InterfaceType) []*Func { +func (a *astParser) mustServerFunctions(v *ast.InterfaceType, sourcePackage string) []*Func { funcs := make([]*Func, 0) methodObject := v.Methods if methodObject == nil { @@ -187,31 +257,27 @@ func (a *astParser) mustServerFunctions(v *ast.InterfaceType) []*Func { } params := v.Params if params != nil { - inList, err := a.parseFields(params.List, true) + inList, err := a.parseFields(params.List, true, sourcePackage) a.Must(err) for _, data := range inList { - if strings.HasPrefix(data.TypeName, referenceContext) { + if data.Type.Package == referenceContext { continue } - // currently,does not support external references - item.InTypeName = data.TypeName - item.InType = strings.TrimPrefix(data.TypeName, flagStar) + item.ParameterIn = data.Type break } } results := v.Results if results != nil { - outList, err := a.parseFields(results.List, true) + outList, err := a.parseFields(results.List, true, sourcePackage) a.Must(err) for _, data := range outList { - if strings.HasPrefix(data.TypeName, referenceContext) { + if data.Type.Package == referenceContext { continue } - // currently,does not support external references - item.OutTypeName = data.TypeName - item.OutType = strings.TrimPrefix(data.TypeName, flagStar) + item.ParameterOut = data.Type break } } @@ -220,7 +286,67 @@ func (a *astParser) mustServerFunctions(v *ast.InterfaceType) []*Func { return funcs } -func (a *astParser) parseObject(structName string, tp *ast.StructType) (*Struct, error) { +func (a *astParser) getFieldType(v string, sourcePackage string) Type { + var pkg, name, expression, starExpression, invokeTypeExpression string + + if strings.Contains(v, ".") { + starExpression = v + if strings.Contains(v, "*") { + leftIndex := strings.Index(v, "*") + rightIndex := strings.Index(v, ".") + if leftIndex >= 0 { + invokeTypeExpression = v[0:leftIndex+1] + v[rightIndex+1:] + } else { + invokeTypeExpression = v[rightIndex+1:] + } + } else { + if strings.HasPrefix(v, "map[") || strings.HasPrefix(v, "[]") { + leftIndex := strings.Index(v, "]") + rightIndex := strings.Index(v, ".") + invokeTypeExpression = v[0:leftIndex+1] + v[rightIndex+1:] + } else { + rightIndex := strings.Index(v, ".") + invokeTypeExpression = v[rightIndex+1:] + } + } + } else { + expression = strings.TrimPrefix(v, flagStar) + switch v { + case "double", "float", "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64", + "bool", "string", "bytes": + invokeTypeExpression = v + break + default: + name = expression + invokeTypeExpression = v + if strings.HasPrefix(v, "map[") || strings.HasPrefix(v, "[]") { + starExpression = strings.ReplaceAll(v, flagStar, flagStar+sourcePackage+".") + } else { + starExpression = fmt.Sprintf("*%v.%v", sourcePackage, name) + invokeTypeExpression = v + } + + } + } + expression = strings.TrimPrefix(starExpression, flagStar) + index := strings.LastIndex(expression, flagDot) + if index > 0 { + pkg = expression[0:index] + name = expression[index+1:] + } else { + pkg = sourcePackage + } + + return Type{ + Expression: expression, + StarExpression: starExpression, + InvokeTypeExpression: invokeTypeExpression, + Package: pkg, + Name: name, + } +} + +func (a *astParser) parseObject(structName string, tp *ast.StructType, sourcePackage string) (*Struct, error) { if data, ok := objectM[structName]; ok { return data, nil } @@ -237,7 +363,7 @@ func (a *astParser) parseObject(structName string, tp *ast.StructType) (*Struct, } fieldList := fields.List - members, err := a.parseFields(fieldList, false) + members, err := a.parseFields(fieldList, false, sourcePackage) if err != nil { return nil, err } @@ -245,7 +371,7 @@ func (a *astParser) parseObject(structName string, tp *ast.StructType) (*Struct, for _, m := range members { var field Field field.Name = m.Name - field.TypeName = m.TypeName + field.Type = m.Type field.JsonTag = m.JsonTag field.Document = m.Document field.Comment = m.Comment @@ -255,7 +381,7 @@ func (a *astParser) parseObject(structName string, tp *ast.StructType) (*Struct, return &st, nil } -func (a *astParser) parseFields(fields []*ast.Field, onlyType bool) ([]*Field, error) { +func (a *astParser) parseFields(fields []*ast.Field, onlyType bool, sourcePackage string) ([]*Field, error) { ret := make([]*Field, 0) for _, field := range fields { var item Field @@ -278,7 +404,7 @@ func (a *astParser) parseFields(fields []*ast.Field, onlyType bool) ([]*Field, e return nil, err } - item.TypeName = typeName + item.Type = a.getFieldType(typeName, sourcePackage) if onlyType { ret = append(ret, &item) continue @@ -414,10 +540,30 @@ func (a *astParser) wrapError(pos token.Pos, format string, arg ...interface{}) return fmt.Errorf("line %v: %s", file.Line, fmt.Sprintf(format, arg...)) } +func (f *Func) GetDoc() string { + return strings.Join(f.Document, util.NL) +} + +func (f *Func) HaveDoc() bool { + return len(f.Document) > 0 +} + +func (a *PbAst) GenEnumCode() (string, error) { + var element []string + for _, item := range a.Enum { + code, err := item.GenEnumCode() + if err != nil { + return "", err + } + element = append(element, code) + } + return strings.Join(element, util.NL), nil +} + func (a *PbAst) GenTypesCode() (string, error) { types := make([]string, 0) sts := make([]*Struct, 0) - for _, item := range a.Strcuts { + for _, item := range a.Structure { sts = append(sts, item) } sort.Slice(sts, func(i, j int) bool { @@ -434,8 +580,17 @@ func (a *PbAst) GenTypesCode() (string, error) { } types = append(types, structCode) } + types = append(types, a.genAnyCode()) + for _, item := range a.Enum { + typeCode, err := item.GenEnumTypeCode() + if err != nil { + return "", err + } + types = append(types, typeCode) + } + buffer, err := util.With("type").Parse(typeTemplate).Execute(map[string]interface{}{ - "types": strings.Join(types, "\n\n"), + "types": strings.Join(types, util.NL+util.NL), }) if err != nil { return "", err @@ -444,6 +599,13 @@ func (a *PbAst) GenTypesCode() (string, error) { return buffer.String(), nil } +func (a *PbAst) genAnyCode() string { + if !a.ContainsAny { + return "" + } + return anyTypeTemplate +} + func (s *Struct) genCode(containsTypeStatement bool) (string, error) { fields := make([]string, 0) for _, f := range s.Field { @@ -451,10 +613,10 @@ func (s *Struct) genCode(containsTypeStatement bool) (string, error) { if len(f.Comment) > 0 { comment = f.Comment[0] } - doc = strings.Join(f.Document, "\n") + doc = strings.Join(f.Document, util.NL) buffer, err := util.With(sx.Rand()).Parse(fieldTemplate).Execute(map[string]interface{}{ "name": f.Name.Title(), - "type": f.TypeName, + "type": f.Type.InvokeTypeExpression, "tag": f.JsonTag, "hasDoc": len(f.Document) > 0, "doc": doc, @@ -470,7 +632,7 @@ func (s *Struct) genCode(containsTypeStatement bool) (string, error) { buffer, err := util.With("struct").Parse(structTemplate).Execute(map[string]interface{}{ "type": containsTypeStatement, "name": s.Name.Title(), - "fields": strings.Join(fields, "\n"), + "fields": strings.Join(fields, util.NL), }) if err != nil { return "", err diff --git a/tools/goctl/rpc/parser/proto.go b/tools/goctl/rpc/parser/proto.go new file mode 100644 index 00000000..e0e6f947 --- /dev/null +++ b/tools/goctl/rpc/parser/proto.go @@ -0,0 +1,294 @@ +package parser + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/emicklei/proto" + "github.com/tal-tech/go-zero/core/collection" + "github.com/tal-tech/go-zero/core/lang" + "github.com/tal-tech/go-zero/tools/goctl/util" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" +) + +const ( + AnyImport = "google/protobuf/any.proto" +) + +var ( + enumTypeTemplate = `{{.name}} int32` + enumTemplate = `const ( + {{.element}} +)` + enumFiledTemplate = `{{.key}} {{.name}} = {{.value}}` +) + +type ( + MessageField struct { + Type string + Name stringx.String + } + Message struct { + Name stringx.String + Element []*MessageField + *proto.Message + } + Enum struct { + Name stringx.String + Element []*EnumField + *proto.Enum + } + EnumField struct { + Key string + Value int + } + + Proto struct { + Package string + Import []*Import + PbSrc string + ContainsAny bool + Message map[string]lang.PlaceholderType + Enum map[string]*Enum + } + Import struct { + ProtoImportName string + PbImportName string + OriginalDir string + OriginalProtoPath string + OriginalPbPath string + BridgeImport string + exists bool + //xx.proto + protoName string + // xx.pb.go + pbName string + } +) + +func checkImport(src string) error { + r, err := os.Open(src) + if err != nil { + return err + } + defer r.Close() + + parser := proto.NewParser(r) + parseRet, err := parser.Parse() + if err != nil { + return err + } + var base = filepath.Base(src) + proto.Walk(parseRet, proto.WithImport(func(i *proto.Import) { + if err != nil { + return + } + err = fmt.Errorf("%v:%v the external proto cannot import other proto files", base, i.Position.Line) + })) + if err != nil { + return err + } + return nil +} +func ParseImport(src string) ([]*Import, bool, error) { + bridgeImportM := make(map[string]string) + r, err := os.Open(src) + if err != nil { + return nil, false, err + } + defer r.Close() + + workDir := filepath.Dir(src) + parser := proto.NewParser(r) + parseRet, err := parser.Parse() + if err != nil { + return nil, false, err + } + protoImportSet := collection.NewSet() + var containsAny bool + proto.Walk(parseRet, proto.WithImport(func(i *proto.Import) { + if i.Filename == AnyImport { + containsAny = true + return + } + protoImportSet.AddStr(i.Filename) + if i.Comment != nil { + lines := i.Comment.Lines + for _, line := range lines { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, "@") { + continue + } + line = strings.TrimPrefix(line, "@") + bridgeImportM[i.Filename] = line + } + } + })) + var importList []*Import + + for _, item := range protoImportSet.KeysStr() { + pb := strings.TrimSuffix(filepath.Base(item), filepath.Ext(item)) + ".pb.go" + var pbImportName, brideImport string + if v, ok := bridgeImportM[item]; ok { + pbImportName = v + brideImport = "M" + item + "=" + v + } else { + pbImportName = item + } + var impo = Import{ + ProtoImportName: item, + PbImportName: pbImportName, + BridgeImport: brideImport, + } + protoSource := filepath.Join(workDir, item) + pbSource := filepath.Join(filepath.Dir(protoSource), pb) + if util.FileExists(protoSource) && util.FileExists(pbSource) { + impo.OriginalProtoPath = protoSource + impo.OriginalPbPath = pbSource + impo.OriginalDir = filepath.Dir(protoSource) + impo.exists = true + impo.protoName = filepath.Base(item) + impo.pbName = pb + } else { + return nil, false, fmt.Errorf("「%v」: import must be found in the relative directory of 「%v」", item, filepath.Base(src)) + } + importList = append(importList, &impo) + } + + return importList, containsAny, nil +} + +func parseProto(src string, messageM map[string]lang.PlaceholderType, enumM map[string]*Enum) (*Proto, error) { + if !filepath.IsAbs(src) { + return nil, fmt.Errorf("expected absolute path,but found: %v", src) + } + + r, err := os.Open(src) + if err != nil { + return nil, err + } + defer r.Close() + + parser := proto.NewParser(r) + parseRet, err := parser.Parse() + if err != nil { + return nil, err + } + + // xx.proto + fileBase := filepath.Base(src) + var resp Proto + + proto.Walk(parseRet, proto.WithPackage(func(p *proto.Package) { + if err != nil { + return + } + + if len(resp.Package) != 0 { + err = fmt.Errorf("%v:%v duplicate package「%v」", fileBase, p.Position.Line, p.Name) + } + + if len(p.Name) == 0 { + err = errors.New("package not found") + } + + resp.Package = p.Name + }), proto.WithMessage(func(message *proto.Message) { + if err != nil { + return + } + + for _, item := range message.Elements { + switch item.(type) { + case *proto.NormalField, *proto.MapField, *proto.Comment: + continue + default: + err = fmt.Errorf("%v: unsupport inline declaration", fileBase) + return + } + } + name := stringx.From(message.Name) + if _, ok := messageM[name.Lower()]; ok { + err = fmt.Errorf("%v:%v duplicate message 「%v」", fileBase, message.Position.Line, message.Name) + return + } + + messageM[name.Lower()] = lang.Placeholder + }), proto.WithEnum(func(enum *proto.Enum) { + if err != nil { + return + } + + var node Enum + node.Enum = enum + node.Name = stringx.From(enum.Name) + for _, item := range enum.Elements { + v, ok := item.(*proto.EnumField) + if !ok { + continue + } + node.Element = append(node.Element, &EnumField{ + Key: v.Name, + Value: v.Integer, + }) + } + if _, ok := enumM[node.Name.Lower()]; ok { + err = fmt.Errorf("%v:%v duplicate enum 「%v」", fileBase, node.Position.Line, node.Name.Source()) + return + } + + lower := stringx.From(enum.Name).Lower() + enumM[lower] = &node + })) + + if err != nil { + return nil, err + } + resp.Message = messageM + resp.Enum = enumM + + return &resp, nil +} + +func (e *Enum) GenEnumCode() (string, error) { + var element []string + for _, item := range e.Element { + code, err := item.GenEnumFieldCode(e.Name.Source()) + if err != nil { + return "", err + } + element = append(element, code) + } + buffer, err := util.With("enum").Parse(enumTemplate).Execute(map[string]interface{}{ + "element": strings.Join(element, util.NL), + }) + if err != nil { + return "", err + } + return buffer.String(), nil +} + +func (e *Enum) GenEnumTypeCode() (string, error) { + buffer, err := util.With("enumAlias").Parse(enumTypeTemplate).Execute(map[string]interface{}{ + "name": e.Name.Source(), + }) + if err != nil { + return "", err + } + return buffer.String(), nil +} + +func (e *EnumField) GenEnumFieldCode(parentName string) (string, error) { + buffer, err := util.With("enumField").Parse(enumFiledTemplate).Execute(map[string]interface{}{ + "key": e.Key, + "name": parentName, + "value": e.Value, + }) + if err != nil { + return "", err + } + return buffer.String(), nil +} diff --git a/tools/goctl/rpc/test.proto b/tools/goctl/rpc/test.proto new file mode 100644 index 00000000..d98ec884 --- /dev/null +++ b/tools/goctl/rpc/test.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; +// protoc -I=${GOPATH}/src -I=. test.proto --go_out=plugins=grpc,Mbase.proto=github.com/tal-tech/go-zero/tools/goctl/rpc:./test +package test; +// @github.com/tal-tech/go-zero/tools/goctl/rpc +import "base.proto"; +import "google/protobuf/any.proto"; + +message request { + string name = 1; +} +enum Gender{ + UNKNOWN = 0; + MALE = 1; + FEMALE = 2; +} +message response { + string greet = 1; + google.protobuf.Any data = 2; + +} +message map { + map m = 1; +} + +service Greeter { + rpc greet(request) returns (response); + rpc idRequest(base.IdRequest)returns(base.EmptyResponse); +} \ No newline at end of file diff --git a/tools/goctl/rpc/test/test.pb.go b/tools/goctl/rpc/test/test.pb.go new file mode 100644 index 00000000..298ec08d --- /dev/null +++ b/tools/goctl/rpc/test/test.pb.go @@ -0,0 +1,331 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: test.proto + +// protoc -I=${GOPATH}/src -I=. test.proto --go_out=plugins=grpc,Mbase.proto=github.com/tal-tech/go-zero/tools/goctl/rpc:./test + +package test + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + rpc "github.com/tal-tech/go-zero/tools/goctl/rpc" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + anypb "google.golang.org/protobuf/types/known/anypb" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package + +type Gender int32 + +const ( + Gender_UNKNOWN Gender = 0 + Gender_MALE Gender = 1 + Gender_FEMALE Gender = 2 +) + +var Gender_name = map[int32]string{ + 0: "UNKNOWN", + 1: "MALE", + 2: "FEMALE", +} + +var Gender_value = map[string]int32{ + "UNKNOWN": 0, + "MALE": 1, + "FEMALE": 2, +} + +func (x Gender) String() string { + return proto.EnumName(Gender_name, int32(x)) +} + +func (Gender) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_c161fcfdc0c3ff1e, []int{0} +} + +type Request struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Request) Reset() { *m = Request{} } +func (m *Request) String() string { return proto.CompactTextString(m) } +func (*Request) ProtoMessage() {} +func (*Request) Descriptor() ([]byte, []int) { + return fileDescriptor_c161fcfdc0c3ff1e, []int{0} +} + +func (m *Request) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Request.Unmarshal(m, b) +} +func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Request.Marshal(b, m, deterministic) +} +func (m *Request) XXX_Merge(src proto.Message) { + xxx_messageInfo_Request.Merge(m, src) +} +func (m *Request) XXX_Size() int { + return xxx_messageInfo_Request.Size(m) +} +func (m *Request) XXX_DiscardUnknown() { + xxx_messageInfo_Request.DiscardUnknown(m) +} + +var xxx_messageInfo_Request proto.InternalMessageInfo + +func (m *Request) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type Response struct { + Greet string `protobuf:"bytes,1,opt,name=greet,proto3" json:"greet,omitempty"` + Data *anypb.Any `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} +func (*Response) Descriptor() ([]byte, []int) { + return fileDescriptor_c161fcfdc0c3ff1e, []int{1} +} + +func (m *Response) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Response.Unmarshal(m, b) +} +func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Response.Marshal(b, m, deterministic) +} +func (m *Response) XXX_Merge(src proto.Message) { + xxx_messageInfo_Response.Merge(m, src) +} +func (m *Response) XXX_Size() int { + return xxx_messageInfo_Response.Size(m) +} +func (m *Response) XXX_DiscardUnknown() { + xxx_messageInfo_Response.DiscardUnknown(m) +} + +var xxx_messageInfo_Response proto.InternalMessageInfo + +func (m *Response) GetGreet() string { + if m != nil { + return m.Greet + } + return "" +} + +func (m *Response) GetData() *anypb.Any { + if m != nil { + return m.Data + } + return nil +} + +type Map struct { + M map[string]string `protobuf:"bytes,1,rep,name=m,proto3" json:"m,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Map) Reset() { *m = Map{} } +func (m *Map) String() string { return proto.CompactTextString(m) } +func (*Map) ProtoMessage() {} +func (*Map) Descriptor() ([]byte, []int) { + return fileDescriptor_c161fcfdc0c3ff1e, []int{2} +} + +func (m *Map) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Map.Unmarshal(m, b) +} +func (m *Map) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Map.Marshal(b, m, deterministic) +} +func (m *Map) XXX_Merge(src proto.Message) { + xxx_messageInfo_Map.Merge(m, src) +} +func (m *Map) XXX_Size() int { + return xxx_messageInfo_Map.Size(m) +} +func (m *Map) XXX_DiscardUnknown() { + xxx_messageInfo_Map.DiscardUnknown(m) +} + +var xxx_messageInfo_Map proto.InternalMessageInfo + +func (m *Map) GetM() map[string]string { + if m != nil { + return m.M + } + return nil +} + +func init() { + proto.RegisterEnum("test.Gender", Gender_name, Gender_value) + proto.RegisterType((*Request)(nil), "test.request") + proto.RegisterType((*Response)(nil), "test.response") + proto.RegisterType((*Map)(nil), "test.map") + proto.RegisterMapType((map[string]string)(nil), "test.map.MEntry") +} + +func init() { proto.RegisterFile("test.proto", fileDescriptor_c161fcfdc0c3ff1e) } + +var fileDescriptor_c161fcfdc0c3ff1e = []byte{ + // 301 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x34, 0x90, 0x4f, 0x4b, 0xc3, 0x40, + 0x10, 0xc5, 0xdd, 0x36, 0xa6, 0xed, 0x14, 0x35, 0x8c, 0x3d, 0xd4, 0x80, 0x52, 0x7a, 0x90, 0xa0, + 0xb0, 0xc5, 0xea, 0x41, 0xbc, 0xf5, 0x10, 0x8b, 0x7f, 0x5a, 0x21, 0x20, 0x1e, 0x3c, 0x6d, 0xc9, + 0x58, 0xc4, 0xee, 0x26, 0x6e, 0xb6, 0x42, 0xbe, 0xbd, 0x64, 0x77, 0x73, 0x7b, 0xbf, 0xd9, 0x59, + 0xde, 0x9b, 0x07, 0x60, 0xa8, 0x32, 0xbc, 0xd4, 0x85, 0x29, 0x30, 0x68, 0x74, 0x0c, 0x1b, 0x51, + 0x91, 0x9b, 0xc4, 0x67, 0xdb, 0xa2, 0xd8, 0xee, 0x68, 0x66, 0x69, 0xb3, 0xff, 0x9a, 0x09, 0x55, + 0xbb, 0xa7, 0xe9, 0x39, 0xf4, 0x34, 0xfd, 0xee, 0xa9, 0x32, 0x88, 0x10, 0x28, 0x21, 0x69, 0xcc, + 0x26, 0x2c, 0x19, 0x64, 0x56, 0x4f, 0x9f, 0xa1, 0xaf, 0xa9, 0x2a, 0x0b, 0x55, 0x11, 0x8e, 0xe0, + 0x70, 0xab, 0x89, 0x8c, 0x5f, 0x70, 0x80, 0x09, 0x04, 0xb9, 0x30, 0x62, 0xdc, 0x99, 0xb0, 0x64, + 0x38, 0x1f, 0x71, 0x67, 0xc5, 0x5b, 0x2b, 0xbe, 0x50, 0x75, 0x66, 0x37, 0xa6, 0x9f, 0xd0, 0x95, + 0xa2, 0xc4, 0x0b, 0x60, 0x72, 0xcc, 0x26, 0xdd, 0x64, 0x38, 0x8f, 0xb8, 0x8d, 0x2d, 0x45, 0xc9, + 0x57, 0xa9, 0x32, 0xba, 0xce, 0x98, 0x8c, 0xef, 0x20, 0x74, 0x80, 0x11, 0x74, 0x7f, 0xa8, 0xf6, + 0x76, 0x8d, 0x6c, 0x22, 0xfc, 0x89, 0xdd, 0x9e, 0xac, 0xdb, 0x20, 0x73, 0xf0, 0xd0, 0xb9, 0x67, + 0x57, 0xd7, 0x10, 0x2e, 0x49, 0xe5, 0xa4, 0x71, 0x08, 0xbd, 0xf7, 0xf5, 0xcb, 0xfa, 0xed, 0x63, + 0x1d, 0x1d, 0x60, 0x1f, 0x82, 0xd5, 0xe2, 0x35, 0x8d, 0x18, 0x02, 0x84, 0x8f, 0xa9, 0xd5, 0x9d, + 0x79, 0x0e, 0xbd, 0x65, 0x13, 0x9e, 0x34, 0x5e, 0xfa, 0xa3, 0xf0, 0xc8, 0x65, 0xf1, 0x65, 0xc4, + 0xc7, 0x2d, 0xfa, 0xe3, 0x6f, 0x60, 0xf0, 0x9d, 0x67, 0xbe, 0xa9, 0x13, 0x6e, 0xcb, 0x7d, 0x6a, + 0x07, 0xf1, 0xa9, 0x1b, 0xa4, 0xb2, 0x34, 0x75, 0xe6, 0xbf, 0x6c, 0x42, 0xdb, 0xc1, 0xed, 0x7f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x48, 0x7a, 0x3b, 0x55, 0x9c, 0x01, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// GreeterClient is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type GreeterClient interface { + Greet(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + IdRequest(ctx context.Context, in *rpc.IdRequest, opts ...grpc.CallOption) (*rpc.EmptyResponse, error) +} + +type greeterClient struct { + cc *grpc.ClientConn +} + +func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) Greet(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/test.Greeter/greet", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *greeterClient) IdRequest(ctx context.Context, in *rpc.IdRequest, opts ...grpc.CallOption) (*rpc.EmptyResponse, error) { + out := new(rpc.EmptyResponse) + err := c.cc.Invoke(ctx, "/test.Greeter/idRequest", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GreeterServer is the server API for Greeter service. +type GreeterServer interface { + Greet(context.Context, *Request) (*Response, error) + IdRequest(context.Context, *rpc.IdRequest) (*rpc.EmptyResponse, error) +} + +// UnimplementedGreeterServer can be embedded to have forward compatible implementations. +type UnimplementedGreeterServer struct { +} + +func (*UnimplementedGreeterServer) Greet(ctx context.Context, req *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Greet not implemented") +} +func (*UnimplementedGreeterServer) IdRequest(ctx context.Context, req *rpc.IdRequest) (*rpc.EmptyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method IdRequest not implemented") +} + +func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { + s.RegisterService(&_Greeter_serviceDesc, srv) +} + +func _Greeter_Greet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).Greet(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/test.Greeter/Greet", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).Greet(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _Greeter_IdRequest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(rpc.IdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).IdRequest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/test.Greeter/IdRequest", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).IdRequest(ctx, req.(*rpc.IdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Greeter_serviceDesc = grpc.ServiceDesc{ + ServiceName: "test.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "greet", + Handler: _Greeter_Greet_Handler, + }, + { + MethodName: "idRequest", + Handler: _Greeter_IdRequest_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "test.proto", +} diff --git a/tools/goctl/util/console/console.go b/tools/goctl/util/console/console.go index 73bd212f..e2fb09d5 100644 --- a/tools/goctl/util/console/console.go +++ b/tools/goctl/util/console/console.go @@ -11,9 +11,11 @@ type ( Console interface { Success(format string, a ...interface{}) Info(format string, a ...interface{}) + Debug(format string, a ...interface{}) Warning(format string, a ...interface{}) Error(format string, a ...interface{}) Fatalln(format string, a ...interface{}) + MarkDone() Must(err error) } colorConsole struct { @@ -39,6 +41,11 @@ func (c *colorConsole) Info(format string, a ...interface{}) { fmt.Println(msg) } +func (c *colorConsole) Debug(format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + fmt.Println(aurora.Blue(msg)) +} + func (c *colorConsole) Success(format string, a ...interface{}) { msg := fmt.Sprintf(format, a...) fmt.Println(aurora.Green(msg)) @@ -59,6 +66,10 @@ func (c *colorConsole) Fatalln(format string, a ...interface{}) { os.Exit(1) } +func (c *colorConsole) MarkDone() { + c.Success("Done.") +} + func (c *colorConsole) Must(err error) { if err != nil { c.Fatalln("%+v", err) @@ -74,6 +85,11 @@ func (i *ideaConsole) Info(format string, a ...interface{}) { fmt.Println(msg) } +func (i *ideaConsole) Debug(format string, a ...interface{}) { + msg := fmt.Sprintf(format, a...) + fmt.Println(aurora.Blue(msg)) +} + func (i *ideaConsole) Success(format string, a ...interface{}) { msg := fmt.Sprintf(format, a...) fmt.Println("[SUCCESS]: ", msg) @@ -94,6 +110,10 @@ func (i *ideaConsole) Fatalln(format string, a ...interface{}) { os.Exit(1) } +func (i *ideaConsole) MarkDone() { + i.Success("Done.") +} + func (i *ideaConsole) Must(err error) { if err != nil { i.Fatalln("%+v", err) diff --git a/tools/goctl/util/file.go b/tools/goctl/util/file.go index 3cfe604c..cadb5980 100644 --- a/tools/goctl/util/file.go +++ b/tools/goctl/util/file.go @@ -10,6 +10,10 @@ import ( "github.com/logrusorgru/aurora" ) +const ( + NL = "\n" +) + func CreateIfNotExist(file string) (*os.File, error) { _, err := os.Stat(file) if !os.IsNotExist(err) { diff --git a/tools/goctl/util/project/project.go b/tools/goctl/util/project/project.go index b9f0b976..b363fa19 100644 --- a/tools/goctl/util/project/project.go +++ b/tools/goctl/util/project/project.go @@ -1,6 +1,7 @@ package project import ( + "fmt" "io/ioutil" "os" "os/exec" @@ -38,18 +39,18 @@ type ( func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) { _, err := exec.LookPath(constGo) if err != nil { - return nil, err + return nil, fmt.Errorf("please install go first,reference documents:「https://golang.org/doc/install」") } if checkGrpcEnv { _, err = exec.LookPath(constProtoC) if err != nil { - return nil, err + return nil, fmt.Errorf("please install protoc first,reference documents:「https://github.com/golang/protobuf」") } _, err = exec.LookPath(constProtoCGenGo) if err != nil { - return nil, err + return nil, fmt.Errorf("please install plugin protoc-gen-go first,reference documents:「https://github.com/golang/protobuf」") } } diff --git a/tools/goctl/vars/settings.go b/tools/goctl/vars/settings.go index c5c6a00f..9b11c60e 100644 --- a/tools/goctl/vars/settings.go +++ b/tools/goctl/vars/settings.go @@ -3,4 +3,7 @@ package vars const ( ProjectName = "zero" ProjectOpenSourceUrl = "github.com/tal-tech/go-zero" + OsWindows = "windows" + OsMac = "darwin" + OsLinux = "linux" )