diff --git a/tools/goctl/rpc/cli/cli.go b/tools/goctl/rpc/cli/cli.go index 9574fd15..a97fdbd6 100644 --- a/tools/goctl/rpc/cli/cli.go +++ b/tools/goctl/rpc/cli/cli.go @@ -58,6 +58,9 @@ func prepare() error { if _, err := env.LookUpProtoc(); err != nil { return err } + if _, err := env.LookUpProtocGenGo(); err != nil { + return err + } return nil } diff --git a/tools/goctl/rpc/generator/defaultgenerator.go b/tools/goctl/rpc/generator/defaultgenerator.go index d70f96ca..61725161 100644 --- a/tools/goctl/rpc/generator/defaultgenerator.go +++ b/tools/goctl/rpc/generator/defaultgenerator.go @@ -1,6 +1,8 @@ package generator import ( + "os/exec" + "github.com/tal-tech/go-zero/tools/goctl/util/console" ) @@ -19,3 +21,21 @@ func NewDefaultGenerator() Generator { log: log, } } + +// Prepare provides environment detection generated by rpc service, +// including go environment, protoc, whether protoc-gen-go is installed or not +func (g *DefaultGenerator) Prepare() error { + _, err := exec.LookPath("go") + if err != nil { + return err + } + + _, err = exec.LookPath("protoc") + if err != nil { + return err + } + + _, err = exec.LookPath("protoc-gen-go") + + return err +} diff --git a/tools/goctl/rpc/generator/gen.go b/tools/goctl/rpc/generator/gen.go index 2ca559d6..02b32b74 100644 --- a/tools/goctl/rpc/generator/gen.go +++ b/tools/goctl/rpc/generator/gen.go @@ -47,6 +47,11 @@ func (g *RPCGenerator) Generate(src, target string, protoImportPath []string, go return err } + err = g.g.Prepare() + if err != nil { + return err + } + projectCtx, err := ctx.Prepare(abs) if err != nil { return err diff --git a/tools/goctl/rpc/generator/gen_test.go b/tools/goctl/rpc/generator/gen_test.go index 3970fcae..948cb1e0 100644 --- a/tools/goctl/rpc/generator/gen_test.go +++ b/tools/goctl/rpc/generator/gen_test.go @@ -1,11 +1,9 @@ package generator import ( - "fmt" "go/build" "os" "path/filepath" - "runtime" "strings" "testing" @@ -15,7 +13,6 @@ import ( "github.com/tal-tech/go-zero/core/stringx" conf "github.com/tal-tech/go-zero/tools/goctl/config" "github.com/tal-tech/go-zero/tools/goctl/rpc/execx" - "github.com/tal-tech/go-zero/tools/goctl/util/env" ) var cfg = &conf.Config{ @@ -25,7 +22,7 @@ var cfg = &conf.Config{ func TestRpcGenerate(t *testing.T) { _ = Clean() dispatcher := NewDefaultGenerator() - err := prepare() + err := dispatcher.Prepare() if err != nil { logx.Error(err) return @@ -92,16 +89,3 @@ func TestRpcGenerate(t *testing.T) { } }) } - -func prepare() error { - if !env.CanExec() { - return fmt.Errorf("%s: can not start new processes using os.StartProcess or exec.Command", runtime.GOOS) - } - if _, err := env.LookUpGo(); err != nil { - return err - } - if _, err := env.LookUpProtoc(); err != nil { - return err - } - return nil -} diff --git a/tools/goctl/rpc/generator/generator.go b/tools/goctl/rpc/generator/generator.go index deade549..cad3895b 100644 --- a/tools/goctl/rpc/generator/generator.go +++ b/tools/goctl/rpc/generator/generator.go @@ -7,6 +7,7 @@ import ( // Generator defines a generator interface to describe how to generate rpc service type Generator interface { + Prepare() error GenMain(ctx DirContext, proto parser.Proto, cfg *conf.Config) error GenCall(ctx DirContext, proto parser.Proto, cfg *conf.Config) error GenEtc(ctx DirContext, proto parser.Proto, cfg *conf.Config) error diff --git a/tools/goctl/rpc/generator/genpb.go b/tools/goctl/rpc/generator/genpb.go index 181ae01d..683fe4c5 100644 --- a/tools/goctl/rpc/generator/genpb.go +++ b/tools/goctl/rpc/generator/genpb.go @@ -2,36 +2,45 @@ package generator import ( "bytes" - "fmt" - "io/ioutil" - "os" + "errors" "path/filepath" - "runtime" "strings" "github.com/tal-tech/go-zero/core/collection" conf "github.com/tal-tech/go-zero/tools/goctl/config" "github.com/tal-tech/go-zero/tools/goctl/rpc/execx" "github.com/tal-tech/go-zero/tools/goctl/rpc/parser" - "github.com/tal-tech/go-zero/tools/goctl/util" - "github.com/tal-tech/go-zero/tools/goctl/vars" ) +const googleProtocGenGoErr = `--go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC` + // GenPb generates the pb.go file, which is a layer of packaging for protoc to generate gprc, // but the commands and flags in protoc are not completely joined in goctl. At present, proto_path(-I) is introduced func (g *DefaultGenerator) GenPb(ctx DirContext, protoImportPath []string, proto parser.Proto, _ *conf.Config, goOptions ...string) error { dir := ctx.GetPb() cw := new(bytes.Buffer) - directory, _ := filepath.Split(proto.Src) + directory, base := filepath.Split(proto.Src) directory = filepath.Clean(directory) cw.WriteString("protoc ") protoImportPathSet := collection.NewSet() + isSamePackage := true for _, ip := range protoImportPath { pip := " --proto_path=" + ip if protoImportPathSet.Contains(pip) { continue } + abs, err := filepath.Abs(ip) + if err != nil { + return err + } + + if abs == directory { + isSamePackage = true + } else { + isSamePackage = false + } + protoImportPathSet.AddStr(pip) cw.WriteString(pip) } @@ -47,88 +56,50 @@ func (g *DefaultGenerator) GenPb(ctx DirContext, protoImportPath []string, proto cw.WriteString(" --go_out=plugins=grpc:" + dir.Filename) } - return g.generatePbWithVersion132(cw.String()) -} + // Compatible with version 1.4.0,github.com/golang/protobuf/protoc-gen-go@v1.4.0 + // --go_opt usage please see https://developers.google.com/protocol-buffers/docs/reference/go-generated#package + optSet := collection.NewSet() + for _, op := range goOptions { + opt := " --go_opt=" + op + if optSet.Contains(opt) { + continue + } -// generatePbWithVersion132 generates pb.go by specifying protoc-gen-go@1.3.2 version -func (g *DefaultGenerator) generatePbWithVersion132(cmd string) error { - goctlHome, err := util.GetGoctlHome() - if err != nil { - return err + optSet.AddStr(op) + cw.WriteString(" --go_opt=" + op) } - err = util.MkdirIfNotExist(goctlHome) - if err != nil { - return err - } - - goctlHomeBin := filepath.Join(goctlHome, "bin") - err = util.MkdirIfNotExist(goctlHomeBin) - if err != nil { - return err - } - - protocGenGo := filepath.Join(goctlHome, "bin", "protoc-gen-go") - g.log.Debug("checking protoc-gen-go state ...") - goGetCmd := "\ngo install github.com/golang/protobuf/protoc-gen-go@v1.3.2" - - if util.FileExists(protocGenGo) { - g.log.Success("protoc-gen-go exists ...") - goGetCmd = "" + var currentFileOpt string + if !isSamePackage || (len(proto.GoPackage) > 0 && proto.GoPackage != proto.Package.Name) { + if filepath.IsAbs(proto.GoPackage) { + currentFileOpt = " --go_opt=M" + base + "=" + proto.GoPackage + } else if strings.Contains(proto.GoPackage, string(filepath.Separator)) { + currentFileOpt = " --go_opt=M" + base + "=./" + proto.GoPackage + } else { + currentFileOpt = " --go_opt=M" + base + "=../" + proto.GoPackage + } } else { - g.log.Error("missing protoc-gen-go: downloading ...") + currentFileOpt = " --go_opt=M" + base + "=." } - goos := runtime.GOOS - switch goos { - case vars.OsLinux, vars.OsMac: - cmd = getUnixLikeCmd(goctlHome, goctlHomeBin, goGetCmd, cmd) - g.log.Debug("%s", cmd) - case vars.OsWindows: - cmd = getWindowsCmd(goctlHome, goctlHomeBin, goGetCmd, cmd) - // Do not support to execute commands in context, the solution is created - // a batch file to execute it on Windows. - batFile, err := createBatchFile(goctlHome, cmd) - if err != nil { - return err + if !optSet.Contains(currentFileOpt) { + cw.WriteString(currentFileOpt) + } + + command := cw.String() + g.log.Debug(command) + _, err := execx.Run(command, "") + if err != nil { + if strings.Contains(err.Error(), googleProtocGenGoErr) { + return errors.New(`Unsupported plugin protoc-gen-go which installed from the following source: +google.golang.org/protobuf/cmd/protoc-gen-go, +github.com/protocolbuffers/protobuf-go/cmd/protoc-gen-go; + +Please replace it by the following command, we recommend to use version before v1.3.5: +go get -u github.com/golang/protobuf/protoc-gen-go`) } - g.log.Debug("%s", cmd) - cmd = batFile - default: - return fmt.Errorf("unsupported os: %s", goos) + return err } - - _, err = execx.Run(cmd, "") - return err -} - -func getUnixLikeCmd(goctlHome, goctlHomeBin, goGetCmd, cmd string) string { - return fmt.Sprintf(`export GOPATH=%s -export GOBIN=%s -export PATH=$PATH:$GOPATH:$GOBIN -export GO111MODULE=on -export GOPROXY=https://goproxy.cn %s -%s`, goctlHome, goctlHomeBin, goGetCmd, cmd) -} - -func getWindowsCmd(goctlHome, goctlHomeBin, goGetCmd, cmd string) string { - return fmt.Sprintf(`set GOPATH=%s -set GOBIN=%s -set path=%s -set GO111MODULE=on -set GOPROXY=https://goproxy.cn %s -%s`, goctlHome, goctlHomeBin, "%path%;"+goctlHome+";"+goctlHomeBin, goGetCmd, cmd) -} - -func createBatchFile(goctlHome, cmd string) (string, error) { - batFile := filepath.Join(goctlHome, ".generate.bat") - if !util.FileExists(batFile) { - err := ioutil.WriteFile(batFile, []byte(cmd), os.ModePerm) - if err != nil { - return "", err - } - } - - return batFile, nil + return nil } diff --git a/tools/goctl/util/path.go b/tools/goctl/util/path.go index 3c94e1a8..d4ae2c24 100644 --- a/tools/goctl/util/path.go +++ b/tools/goctl/util/path.go @@ -5,7 +5,6 @@ import ( "os" "path" "path/filepath" - "runtime" "strings" "github.com/tal-tech/go-zero/tools/goctl/vars" @@ -116,12 +115,6 @@ func FindProjectPath(loc string) (string, bool) { // ReadLink returns the destination of the named symbolic link recursively. func ReadLink(name string) (string, error) { - goos := runtime.GOOS - switch goos { - case vars.OsWindows: - return name, nil - } - name, err := filepath.Abs(name) if err != nil { return "", err