Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(313)

Side by Side Diff: tools/cmd/cproto/transform.go

Issue 1605363002: common/prpc, tools/cmd/cproto: prpc client (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-go@master
Patch Set: use text template for ast generation Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 }
OLDNEW
« server/prpc/encoding.go ('K') | « tools/cmd/cproto/testdata/twoFiles/2.pb.golden ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698