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 |