| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // This file implements .go code transformation. | 5 // This file implements .go code transformation. |
| 6 | 6 |
| 7 package main | 7 package main |
| 8 | 8 |
| 9 import ( | 9 import ( |
| 10 "bytes" | 10 "bytes" |
| 11 "fmt" |
| 12 "io/ioutil" |
| 13 "strings" |
| 14 "text/template" |
| 15 "unicode/utf8" |
| 16 |
| 11 "go/ast" | 17 "go/ast" |
| 18 "go/format" |
| 12 "go/parser" | 19 "go/parser" |
| 13 "go/printer" | 20 "go/printer" |
| 14 "go/token" | 21 "go/token" |
| 15 "io/ioutil" | |
| 16 "strings" | |
| 17 ) | 22 ) |
| 18 | 23 |
| 19 const ( | 24 const ( |
| 20 » prpcPackagePath = `github.com/luci/luci-go/server/prpc` | 25 » serverPrpcPackagePath = `github.com/luci/luci-go/server/prpc` |
| 26 » commonPrpcPackagePath = `github.com/luci/luci-go/common/prpc` |
| 21 ) | 27 ) |
| 22 | 28 |
| 23 var ( | 29 var ( |
| 24 » prpcPkg = ast.NewIdent("prpc") | 30 » serverPrpcPkg = ast.NewIdent("prpc") |
| 25 » registrarName = ast.NewIdent("Registrar") | 31 » commonPrpcPkg = ast.NewIdent("prpccommon") |
| 26 ) | 32 ) |
| 27 | 33 |
| 28 type transformer struct { | 34 type transformer struct { |
| 35 fset *token.FileSet |
| 29 inPRPCPackage bool | 36 inPRPCPackage bool |
| 30 PackageName string | 37 PackageName string |
| 31 } | 38 } |
| 32 | 39 |
| 33 // transformGoFile rewrites a .go file to work with prpc. | 40 // transformGoFile rewrites a .go file to work with prpc. |
| 34 func (t *transformer) transformGoFile(filename string) error { | 41 func (t *transformer) transformGoFile(filename string) error { |
| 35 » fset := token.NewFileSet() | 42 » t.fset = token.NewFileSet() |
| 36 » file, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) | 43 » file, err := parser.ParseFile(t.fset, filename, nil, parser.ParseComment
s) |
| 37 if err != nil { | 44 if err != nil { |
| 38 return err | 45 return err |
| 39 } | 46 } |
| 40 | 47 |
| 41 t.PackageName = file.Name.Name | 48 t.PackageName = file.Name.Name |
| 42 » t.inPRPCPackage, err = isInPackage(filename, prpcPackagePath) | 49 » t.inPRPCPackage, err = isInPackage(filename, serverPrpcPackagePath) |
| 43 if err != nil { | 50 if err != nil { |
| 44 return err | 51 return err |
| 45 } | 52 } |
| 46 » t.transformFile(file) | 53 |
| 54 » if err := t.transformFile(file); err != nil { |
| 55 » » return err |
| 56 » } |
| 47 | 57 |
| 48 var buf bytes.Buffer | 58 var buf bytes.Buffer |
| 49 » if err := printer.Fprint(&buf, fset, file); err != nil { | 59 » if err := printer.Fprint(&buf, t.fset, file); err != nil { |
| 50 return err | 60 return err |
| 51 } | 61 } |
| 52 formatted, err := gofmt(buf.Bytes()) | 62 formatted, err := gofmt(buf.Bytes()) |
| 53 if err != nil { | 63 if err != nil { |
| 54 return err | 64 return err |
| 55 } | 65 } |
| 56 | 66 |
| 57 return ioutil.WriteFile(filename, formatted, 0666) | 67 return ioutil.WriteFile(filename, formatted, 0666) |
| 58 } | 68 } |
| 59 | 69 |
| 60 func (t *transformer) transformFile(file *ast.File) { | 70 func (t *transformer) transformFile(file *ast.File) error { |
| 61 if t.transformRegisterServerFuncs(file) && !t.inPRPCPackage { | 71 if t.transformRegisterServerFuncs(file) && !t.inPRPCPackage { |
| 62 » » t.insertPrpcImport(file) | 72 » » t.insertImport(file, serverPrpcPkg, serverPrpcPackagePath) |
| 63 } | 73 } |
| 74 changed, err := t.generateClients(file) |
| 75 if err != nil { |
| 76 return err |
| 77 } |
| 78 if changed { |
| 79 t.insertImport(file, commonPrpcPkg, commonPrpcPackagePath) |
| 80 } |
| 81 return nil |
| 64 } | 82 } |
| 65 | 83 |
| 66 // transformRegisterServerFuncs finds RegisterXXXServer functions and | 84 // transformRegisterServerFuncs finds RegisterXXXServer functions and |
| 67 // checks its first parameter type to prpc.Registrar. | 85 // checks its first parameter type to prpc.Registrar. |
| 68 // Returns true if modified ast. | 86 // Returns true if modified ast. |
| 69 func (t *transformer) transformRegisterServerFuncs(file *ast.File) bool { | 87 func (t *transformer) transformRegisterServerFuncs(file *ast.File) bool { |
| 88 registrarName := ast.NewIdent("Registrar") |
| 70 var registrarType ast.Expr = registrarName | 89 var registrarType ast.Expr = registrarName |
| 71 if !t.inPRPCPackage { | 90 if !t.inPRPCPackage { |
| 72 » » registrarType = &ast.SelectorExpr{prpcPkg, registrarName} | 91 » » registrarType = &ast.SelectorExpr{serverPrpcPkg, registrarName} |
| 73 } | 92 } |
| 74 | 93 |
| 75 changed := false | 94 changed := false |
| 76 for _, decl := range file.Decls { | 95 for _, decl := range file.Decls { |
| 77 funcDecl, ok := decl.(*ast.FuncDecl) | 96 funcDecl, ok := decl.(*ast.FuncDecl) |
| 78 if !ok { | 97 if !ok { |
| 79 continue | 98 continue |
| 80 } | 99 } |
| 81 name := funcDecl.Name.Name | 100 name := funcDecl.Name.Name |
| 82 if !strings.HasPrefix(name, "Register") || !strings.HasSuffix(na
me, "Server") { | 101 if !strings.HasPrefix(name, "Register") || !strings.HasSuffix(na
me, "Server") { |
| 83 continue | 102 continue |
| 84 } | 103 } |
| 85 params := funcDecl.Type.Params | 104 params := funcDecl.Type.Params |
| 86 if params == nil || len(params.List) != 2 { | 105 if params == nil || len(params.List) != 2 { |
| 87 continue | 106 continue |
| 88 } | 107 } |
| 89 | 108 |
| 90 params.List[0].Type = registrarType | 109 params.List[0].Type = registrarType |
| 91 changed = true | 110 changed = true |
| 92 } | 111 } |
| 93 return changed | 112 return changed |
| 94 } | 113 } |
| 95 | 114 |
| 96 func (t *transformer) insertPrpcImport(file *ast.File) { | 115 // generateClients finds client interface declarations |
| 116 // and inserts pRPC implementations after them. |
| 117 func (t *transformer) generateClients(file *ast.File) (bool, error) { |
| 118 » changed := false |
| 119 » for i := len(file.Decls) - 1; i >= 0; i-- { |
| 120 » » genDecl, ok := file.Decls[i].(*ast.GenDecl) |
| 121 » » if !ok || genDecl.Tok != token.TYPE { |
| 122 » » » continue |
| 123 » » } |
| 124 » » for _, spec := range genDecl.Specs { |
| 125 » » » spec := spec.(*ast.TypeSpec) |
| 126 » » » const suffix = "Client" |
| 127 » » » if !strings.HasSuffix(spec.Name.Name, suffix) { |
| 128 » » » » continue |
| 129 » » » } |
| 130 » » » serviceName := strings.TrimSuffix(spec.Name.Name, suffix
) |
| 131 |
| 132 » » » iface, ok := spec.Type.(*ast.InterfaceType) |
| 133 » » » if !ok { |
| 134 » » » » continue |
| 135 » » » } |
| 136 |
| 137 » » » newDecls, err := t.generateClient(file.Name.Name, servic
eName, iface) |
| 138 » » » if err != nil { |
| 139 » » » » return false, err |
| 140 » » » } |
| 141 » » » file.Decls = append(file.Decls[:i+1], append(newDecls, f
ile.Decls[i+1:]...)...) |
| 142 » » » changed = true |
| 143 » » } |
| 144 » } |
| 145 » return changed, nil |
| 146 } |
| 147 |
| 148 var clientCodeTemplate = template.Must(template.New("").Parse(` |
| 149 package template |
| 150 |
| 151 type {{$.StructName}} struct { |
| 152 » client *prpccommon.Client |
| 153 } |
| 154 |
| 155 func New{{.Service}}PRPCClient(client *prpccommon.Client) {{.Service}}Client { |
| 156 » return &{{$.StructName}}{client} |
| 157 } |
| 158 |
| 159 {{range .Methods}} |
| 160 func (c *{{$.StructName}}) {{.Name}}(ctx context.Context, in *{{.InputMessage}},
opts ...grpc.CallOption) (*{{.OutputMessage}}, error) { |
| 161 » out := new({{.OutputMessage}}) |
| 162 » err := c.client.Call(ctx, "{{$.Pkg}}.{{$.Service}}", "{{.Name}}", in, ou
t, opts...) |
| 163 » if err != nil { |
| 164 » » return nil, err |
| 165 » } |
| 166 » return out, nil |
| 167 } |
| 168 {{end}} |
| 169 `)) |
| 170 |
| 171 // generateClient generates pRPC implementation of a client interface. |
| 172 func (t *transformer) generateClient(packageName, serviceName string, iface *ast
.InterfaceType) ([]ast.Decl, error) { |
| 173 » // This function used to construct an AST. It was a lot of code. |
| 174 » // Now it generates code via a template and parses back to AST. |
| 175 » // Slower, but saner and easier to make changes. |
| 176 |
| 177 » type Method struct { |
| 178 » » Name string |
| 179 » » InputMessage string |
| 180 » » OutputMessage string |
| 181 » } |
| 182 » methods := make([]Method, 0, len(iface.Methods.List)) |
| 183 |
| 184 » var buf bytes.Buffer |
| 185 » toGoCode := func(n ast.Node) (string, error) { |
| 186 » » defer buf.Reset() |
| 187 » » err := format.Node(&buf, t.fset, n) |
| 188 » » if err != nil { |
| 189 » » » return "", err |
| 190 » » } |
| 191 » » return buf.String(), nil |
| 192 » } |
| 193 |
| 194 » for _, m := range iface.Methods.List { |
| 195 » » signature, ok := m.Type.(*ast.FuncType) |
| 196 » » if !ok { |
| 197 » » » return nil, fmt.Errorf("unexpected embedded interface in
%sClient", serviceName) |
| 198 » » } |
| 199 |
| 200 » » inStructPtr := signature.Params.List[1].Type.(*ast.StarExpr) |
| 201 » » inStruct, err := toGoCode(inStructPtr.X) |
| 202 » » if err != nil { |
| 203 » » » return nil, err |
| 204 » » } |
| 205 |
| 206 » » outStructPtr := signature.Results.List[0].Type.(*ast.StarExpr) |
| 207 » » outStruct, err := toGoCode(outStructPtr.X) |
| 208 » » if err != nil { |
| 209 » » » return nil, err |
| 210 » » } |
| 211 |
| 212 » » methods = append(methods, Method{ |
| 213 » » » Name: m.Names[0].Name, |
| 214 » » » InputMessage: inStruct, |
| 215 » » » OutputMessage: outStruct, |
| 216 » » }) |
| 217 » } |
| 218 |
| 219 » err := clientCodeTemplate.Execute(&buf, map[string]interface{}{ |
| 220 » » "Pkg": packageName, |
| 221 » » "Service": serviceName, |
| 222 » » "StructName": firstLower(serviceName) + "PRPCClient", |
| 223 » » "Methods": methods, |
| 224 » }) |
| 225 » if err != nil { |
| 226 » » return nil, fmt.Errorf("client template execution: %s", err) |
| 227 » } |
| 228 |
| 229 » f, err := parser.ParseFile(t.fset, "", buf.String(), 0) |
| 230 » if err != nil { |
| 231 » » return nil, fmt.Errorf("client template result parsing: %s. Code
: %#v", err, buf.String()) |
| 232 » } |
| 233 » return f.Decls, nil |
| 234 } |
| 235 |
| 236 func (t *transformer) insertImport(file *ast.File, name *ast.Ident, path string)
{ |
| 97 spec := &ast.ImportSpec{ | 237 spec := &ast.ImportSpec{ |
| 98 » » Name: prpcPkg, | 238 » » Name: name, |
| 99 Path: &ast.BasicLit{ | 239 Path: &ast.BasicLit{ |
| 100 Kind: token.STRING, | 240 Kind: token.STRING, |
| 101 » » » Value: `"` + prpcPackagePath + `"`, | 241 » » » Value: `"` + path + `"`, |
| 102 }, | 242 }, |
| 103 } | 243 } |
| 104 importDecl := &ast.GenDecl{ | 244 importDecl := &ast.GenDecl{ |
| 105 Tok: token.IMPORT, | 245 Tok: token.IMPORT, |
| 106 Specs: []ast.Spec{spec}, | 246 Specs: []ast.Spec{spec}, |
| 107 } | 247 } |
| 108 file.Decls = append([]ast.Decl{importDecl}, file.Decls...) | 248 file.Decls = append([]ast.Decl{importDecl}, file.Decls...) |
| 109 } | 249 } |
| 250 |
| 251 func firstLower(s string) string { |
| 252 _, w := utf8.DecodeRuneInString(s) |
| 253 return strings.ToLower(s[:w]) + s[w:] |
| 254 } |
| OLD | NEW |