go-common/app/tool/liverpc/liverpcgen/main.go
2019-04-22 18:49:16 +08:00

301 lines
8.0 KiB
Go

package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/siddontang/go/ioutil2"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Name = "liverpcgen"
app.Usage = "Genproto generate files to call liverpc, include *.pb.go and *.liverpc.go\n" +
"EXAMPLE:\n liverpcgen APP_NAME"
app.Version = "1.0.0"
app.Commands = []cli.Command{
{
Name: "update",
Usage: "update the tool its self",
Action: actionUpdate,
},
}
app.Action = actionGenerate
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
func autoUpdate(ctx *cli.Context) (err error) {
tfile, e := ioutil.ReadFile("/tmp/liverpcgentime")
if e != nil {
tfile = []byte{'0'}
}
ts, _ := strconv.ParseInt(string(tfile), 0, 64)
current := time.Now().Unix()
if (current - int64(ts)) > 3600*12 { // 12 hours old
ioutil.WriteFile("/tmp/liverpcgentime", []byte(strconv.FormatInt(current, 10)), 0777)
err = actionUpdate(ctx)
if err != nil {
return
}
}
return
}
// actionGenerate invokes protoc to generate files
func actionGenerate(ctx *cli.Context) (err error) {
if err = autoUpdate(ctx); err != nil {
return
}
appName := ctx.Args().Get(0)
if appName == "" {
cli.ShowAppHelpAndExit(ctx, 1)
}
here := ctx.Args().Get(1)
goPath := initGopath()
goCommonPath := goPath + "/src/go-common"
if !fileExist(goCommonPath) {
return cli.NewExitError("go-common not exist : "+goCommonPath, 1)
}
var rpcPath = goCommonPath + "/app/service/live/" + appName + "/api/liverpc"
if here == "here" {
rpcPath, err = os.Getwd()
if err != nil {
return cli.NewExitError(err, 1)
}
}
if !fileExist(rpcPath) {
os.MkdirAll(rpcPath, 0755)
}
log.Println("Generate for path: " + rpcPath)
// find protos
hasProto := fileExistWithSuffix(rpcPath, ".proto")
if !hasProto {
return cli.NewExitError("No proto files found in path : "+rpcPath, 1)
}
files, err := ioutil.ReadDir(rpcPath)
if err != nil {
return cli.NewExitError("Cannot read dir : "+rpcPath+" error: "+err.Error(), 1)
}
for _, file := range files {
if file.IsDir() {
if strings.HasPrefix(file.Name(), "v") {
//if !file
p := rpcPath + "/" + file.Name()
if fileExistWithSuffix(p, ".proto") {
err = runCmd(fmt.Sprintf("cd %s && protoc -I%s -I%s -I. --gogofaster_out=. --liverpc_out=. %s/*.proto",
rpcPath, goCommonPath+"/vendor", goCommonPath, file.Name()))
if err != nil {
return cli.NewExitError("run protoc err: "+err.Error(), 1)
}
log.Println("generated for path: ", p)
} else {
log.Println("no protofiles found in " + p + " skip")
}
}
}
}
// generate client code
log.Println("generating client.go ...")
resetPrint()
var outputCliDeclares []string
var outputCliAssigns []string
var outputImports []string
split := strings.Split(rpcPath, "/")
clientPkg := split[len(split)-1]
ap("// Code generated by liverpcgen, DO NOT EDIT.")
ap("// source: *.proto files under this directory")
ap("// If you want to change this file, Please see README in go-common/app/tool/liverpc/protoc-gen-liverpc/")
ap("package " + clientPkg)
ap("")
ap("import (")
ap(` "go-common/library/net/rpc/liverpc"`)
pkgPrefix := strings.Replace(rpcPath, goPath+"/src/", "", 1)
files, err = ioutil.ReadDir(rpcPath)
if err != nil {
return cli.NewExitError("Cannot read dir : "+rpcPath+" error: "+err.Error(), 1)
}
for _, file := range files {
if strings.HasPrefix(file.Name(), "client.v") {
pkg := strings.Split(file.Name(), ".")[1]
pkgUpper := strings.ToUpper(pkg)
var b []byte
b, err = ioutil.ReadFile(rpcPath + "/" + file.Name())
fileContent := string(b)
if err != nil {
return cli.NewExitError("fail to read file: "+rpcPath+"/"+file.Name(), 1)
}
fileContent = strings.TrimSuffix(fileContent, "\n")
if fileContent != "" {
names := strings.Split(fileContent, "\n")
for _, name := range names {
apVar(&outputCliDeclares, fmt.Sprintf(" // %s%s presents the controller in liverpc",
pkgUpper, name))
apVar(&outputCliDeclares, fmt.Sprintf(" %s%s %s.%sRPCClient", pkgUpper, name, pkg, name))
apVar(&outputCliAssigns, fmt.Sprintf(" cli.%s%s = %s.New%sRPCClient(realCli)", pkgUpper, name, pkg, name))
}
log.Println("merge " + rpcPath + "/" + file.Name())
apVar(&outputImports, fmt.Sprintf(` "%s/%s"`, pkgPrefix, pkg))
}
os.Remove(rpcPath + "/" + file.Name())
}
}
ap(outputImports...)
ap(")")
ap("")
ap("// Client that represents a liverpc " + appName + " service api")
ap("type Client struct {")
ap(" cli *liverpc.Client")
ap(outputCliDeclares...)
ap("}")
ap("")
discoveryId := strings.Replace(appName, "-", "", -1)
discoveryId = strings.Replace(discoveryId, "_", "", -1)
discoveryId = "live." + discoveryId
ap("// DiscoveryAppId the discovery id is not the tree name")
ap(`var DiscoveryAppId = "` + discoveryId + `"`)
ap("")
ap("// New a Client that represents a liverpc " + discoveryId + " service api")
ap("// conf can be empty, and it will use discovery to find service by default")
ap("// conf.AppID will be overwrite by a fixed value DiscoveryAppId")
ap("// therefore is no need to set")
ap("func New(conf *liverpc.ClientConfig) *Client {")
ap(" if conf == nil {")
ap(" conf = &liverpc.ClientConfig{}")
ap(" }")
ap(" conf.AppID = DiscoveryAppId")
ap(" var realCli = liverpc.NewClient(conf)")
ap(" cli := &Client{cli:realCli}")
ap(" cli.clientInit(realCli)")
ap(" return cli")
ap("}")
ap("")
ap("")
ap("func (cli *Client)GetRawCli() *liverpc.Client {")
ap(" return cli.cli")
ap("}")
ap("")
ap("func (cli *Client)clientInit(realCli *liverpc.Client) {")
ap(outputCliAssigns...)
ap("}")
output := getOutput()
err = ioutil.WriteFile(rpcPath+"/client.go", []byte(output), 0755)
if err != nil {
return cli.NewExitError("write file err : "+err.Error()+"; path: "+rpcPath+"/client.go", 1)
}
runCmd("gofmt -s -w " + rpcPath + "/client.go")
return
}
var buf []string
func resetPrint() {
buf = make([]string, 0)
}
// append to default buf
func ap(args ...string) {
buf = append(buf, args...)
}
// append to var "b"
func apVar(b *[]string, str ...string) {
*b = append(*b, str...)
}
func getOutput() string {
return strings.Join(buf, "\n")
}
// equals to `ls dir/*ext` has result
func fileExistWithSuffix(dir string, ext string) bool {
hasFile := false
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
if strings.HasSuffix(info.Name(), ext) {
hasFile = true
return fmt.Errorf("stop")
}
}
return nil
})
return hasFile
}
// actionUpdate update the tools its self
func actionUpdate(ctx *cli.Context) (err error) {
log.Printf("Updating liverpcgen.....")
goPath := initGopath()
goCommonPath := goPath + "/src/go-common"
if !fileExist(goCommonPath) {
return cli.NewExitError("go-common not exist : "+goCommonPath, 1)
}
cmd1 := fmt.Sprintf("cd %s/app/tool/liverpc/protoc-gen-liverpc && go install", goCommonPath)
cmd2 := fmt.Sprintf("cd %s/app/tool/liverpc/liverpcgen && go install", goCommonPath)
err = runCmd(cmd1)
if err != nil {
return cli.NewExitError("update fail: "+err.Error(), 1)
}
err = runCmd(cmd2)
if err != nil {
return cli.NewExitError("update fail: "+err.Error(), 1)
}
log.Printf("Updated!")
return
}
// runCmd runs the cmd & print output (both stdout & stderr)
func runCmd(cmd string) (err error) {
fmt.Printf("CMD: %s\n", cmd)
out, err := exec.Command("/bin/bash", "-c", cmd).CombinedOutput()
fmt.Print(string(out))
return
}
func fileExist(file string) bool {
return ioutil2.FileExists(file)
}
func initGopath() string {
root, err := goPath()
if err != nil || root == "" {
log.Printf("can not read GOPATH, use ~/go as default GOPATH")
root = path.Join(os.Getenv("HOME"), "go")
}
return root
}
func goPath() (string, error) {
gopaths := strings.Split(os.Getenv("GOPATH"), ":")
if len(gopaths) == 1 {
return gopaths[0], nil
}
for _, gp := range gopaths {
absgp, err := filepath.Abs(gp)
if err != nil {
return "", err
}
if fileExist(absgp + "/src/go-common") {
return absgp, nil
}
}
return "", fmt.Errorf("can't found current gopath")
}