zrpc.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package cli
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "github.com/emicklei/proto"
  9. "github.com/urfave/cli"
  10. "github.com/zeromicro/go-zero/tools/goctl/rpc/generator"
  11. "github.com/zeromicro/go-zero/tools/goctl/util"
  12. "github.com/zeromicro/go-zero/tools/goctl/util/pathx"
  13. )
  14. var (
  15. errInvalidGrpcOutput = errors.New("ZRPC: missing grpc output")
  16. errInvalidZrpcOutput = errors.New("ZRPC: missing zrpc output, please use --zrpc_out to specify the output")
  17. errInvalidInput = errors.New("ZRPC: missing source")
  18. errMultiInput = errors.New("ZRPC: only one source is expected")
  19. )
  20. const (
  21. optImport = "import"
  22. optSourceRelative = "source_relative"
  23. )
  24. // ZRPC generates grpc code directly by protoc and generates
  25. // zrpc code by goctl.
  26. func ZRPC(c *cli.Context) error {
  27. args := c.Parent().Args()
  28. protocArgs := removeGoctlFlag(args)
  29. pwd, err := os.Getwd()
  30. if err != nil {
  31. return err
  32. }
  33. source, err := getSourceProto(c.Args(), pwd)
  34. if err != nil {
  35. return err
  36. }
  37. src := filepath.Dir(source)
  38. goPackage, protoPkg, err := getGoPackage(source)
  39. if err != nil {
  40. return err
  41. }
  42. grpcOut := c.String("go-grpc_out")
  43. goOut := c.String("go_out")
  44. goOpt := c.String("go_opt")
  45. grpcOpt := c.String("go-grpc_opt")
  46. zrpcOut := c.String("zrpc_out")
  47. style := c.String("style")
  48. home := c.String("home")
  49. remote := c.String("remote")
  50. if len(remote) > 0 {
  51. repo, _ := util.CloneIntoGitHome(remote)
  52. if len(repo) > 0 {
  53. home = repo
  54. }
  55. }
  56. if len(home) > 0 {
  57. pathx.RegisterGoctlHome(home)
  58. }
  59. if len(goOut) == 0 {
  60. return errInvalidGrpcOutput
  61. }
  62. if len(zrpcOut) == 0 {
  63. return errInvalidZrpcOutput
  64. }
  65. if !filepath.IsAbs(zrpcOut) {
  66. zrpcOut = filepath.Join(pwd, zrpcOut)
  67. }
  68. goOut = removePluginFlag(goOut)
  69. goOut, err = parseOut(src, goOut, goOpt, goPackage)
  70. if err != nil {
  71. return err
  72. }
  73. var isGooglePlugin = len(grpcOut) > 0
  74. // If grpcOut is not empty means that user generates grpc code by
  75. // https://google.golang.org/protobuf/cmd/protoc-gen-go and
  76. // https://google.golang.org/grpc/cmd/protoc-gen-go-grpc,
  77. // for details please see https://grpc.io/docs/languages/go/quickstart/
  78. if isGooglePlugin {
  79. grpcOut, err = parseOut(src, grpcOut, grpcOpt, goPackage)
  80. if err != nil {
  81. return err
  82. }
  83. } else {
  84. // Else it means that user generates grpc code by
  85. // https://github.com/golang/protobuf/tree/master/protoc-gen-go
  86. grpcOut = goOut
  87. }
  88. goOut, err = filepath.Abs(goOut)
  89. if err != nil {
  90. return err
  91. }
  92. grpcOut, err = filepath.Abs(grpcOut)
  93. if err != nil {
  94. return err
  95. }
  96. zrpcOut, err = filepath.Abs(zrpcOut)
  97. if err != nil {
  98. return err
  99. }
  100. if isGooglePlugin && grpcOut != goOut {
  101. return fmt.Errorf("the --go_out and --go-grpc_out must be the same")
  102. }
  103. if goOut == zrpcOut || grpcOut == zrpcOut {
  104. recommendName := goPackage
  105. if len(recommendName) == 0 {
  106. recommendName = protoPkg
  107. }
  108. return fmt.Errorf("the zrpc and grpc output can not be the same, it is recommended to output grpc to the %q",
  109. filepath.Join(goOut, recommendName))
  110. }
  111. var ctx generator.ZRpcContext
  112. ctx.Src = source
  113. ctx.ProtoGenGoDir = goOut
  114. ctx.ProtoGenGrpcDir = grpcOut
  115. ctx.Output = zrpcOut
  116. ctx.ProtocCmd = strings.Join(protocArgs, " ")
  117. g, err := generator.NewDefaultRPCGenerator(style, generator.WithZRpcContext(&ctx))
  118. if err != nil {
  119. return err
  120. }
  121. return g.Generate(source, zrpcOut, nil)
  122. }
  123. // parseOut calculates the output place to grpc code, about to calculate logic for details
  124. // please see https://developers.google.com/protocol-buffers/docs/reference/go-generated#invocation.
  125. func parseOut(sourceDir, grpcOut, grpcOpt, goPackage string) (string, error) {
  126. if !filepath.IsAbs(grpcOut) {
  127. grpcOut = filepath.Join(sourceDir, grpcOut)
  128. }
  129. switch grpcOpt {
  130. case "", optImport:
  131. grpcOut = filepath.Join(grpcOut, goPackage)
  132. case optSourceRelative:
  133. grpcOut = filepath.Join(grpcOut)
  134. default:
  135. return "", fmt.Errorf("parseAndSetGrpcOut: unknown path type %q: want %q or %q",
  136. grpcOpt, optImport, optSourceRelative)
  137. }
  138. return grpcOut, nil
  139. }
  140. func getGoPackage(source string) (string, string, error) {
  141. r, err := os.Open(source)
  142. if err != nil {
  143. return "", "", err
  144. }
  145. defer func() {
  146. _ = r.Close()
  147. }()
  148. parser := proto.NewParser(r)
  149. set, err := parser.Parse()
  150. if err != nil {
  151. return "", "", err
  152. }
  153. var goPackage, protoPkg string
  154. proto.Walk(set, proto.WithOption(func(option *proto.Option) {
  155. if option.Name == "go_package" {
  156. goPackage = option.Constant.Source
  157. }
  158. }), proto.WithPackage(func(p *proto.Package) {
  159. protoPkg = p.Name
  160. }))
  161. return goPackage, protoPkg, nil
  162. }
  163. func removeGoctlFlag(args []string) []string {
  164. var ret []string
  165. var step int
  166. for step < len(args) {
  167. arg := args[step]
  168. switch {
  169. case arg == "--style", arg == "--home", arg == "--zrpc_out":
  170. step += 2
  171. continue
  172. case strings.HasPrefix(arg, "--style="),
  173. strings.HasPrefix(arg, "--home="),
  174. strings.HasPrefix(arg, "--zrpc_out="):
  175. step += 1
  176. continue
  177. }
  178. step += 1
  179. ret = append(ret, arg)
  180. }
  181. return ret
  182. }
  183. func getSourceProto(args []string, pwd string) (string, error) {
  184. var source []string
  185. for _, p := range args {
  186. if strings.HasSuffix(p, ".proto") {
  187. source = append(source, p)
  188. }
  189. }
  190. switch len(source) {
  191. case 0:
  192. return "", errInvalidInput
  193. case 1:
  194. isAbs := filepath.IsAbs(source[0])
  195. if isAbs {
  196. return source[0], nil
  197. }
  198. abs := filepath.Join(pwd, source[0])
  199. return abs, nil
  200. default:
  201. return "", errMultiInput
  202. }
  203. }
  204. func removePluginFlag(goOut string) string {
  205. goOut = strings.ReplaceAll(goOut, "plugins=", "")
  206. index := strings.LastIndex(goOut, ":")
  207. if index < 0 {
  208. return goOut
  209. }
  210. return goOut[index+1:]
  211. }