blob: b10edadbbea9068b5052e4a689741c4b8f49b4dc [file] [log] [blame]
Damien Neil220c2022018-08-15 11:24:18 -07001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package protogen provides support for writing protoc plugins.
6//
7// Plugins for protoc, the Protocol Buffers Compiler, are programs which read
8// a CodeGeneratorRequest protocol buffer from standard input and write a
9// CodeGeneratorResponse protocol buffer to standard output. This package
10// provides support for writing plugins which generate Go code.
11package protogen
12
13import (
14 "bytes"
15 "fmt"
16 "io/ioutil"
17 "os"
18 "path/filepath"
19 "strings"
20
21 "github.com/golang/protobuf/proto"
22 descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
23 pluginpb "github.com/golang/protobuf/protoc-gen-go/plugin"
24)
25
26// Run executes a function as a protoc plugin.
27//
28// It reads a CodeGeneratorRequest message from os.Stdin, invokes the plugin
29// function, and writes a CodeGeneratorResponse message to os.Stdout.
30//
31// If a failure occurs while reading or writing, Run prints an error to
32// os.Stderr and calls os.Exit(1).
33func Run(f func(*Plugin) error) {
34 if err := run(f); err != nil {
35 fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err)
36 os.Exit(1)
37 }
38}
39
40func run(f func(*Plugin) error) error {
41 in, err := ioutil.ReadAll(os.Stdin)
42 if err != nil {
43 return err
44 }
45 req := &pluginpb.CodeGeneratorRequest{}
46 if err := proto.Unmarshal(in, req); err != nil {
47 return err
48 }
49 gen, err := New(req)
50 if err != nil {
51 return err
52 }
53 if err := f(gen); err != nil {
54 // Errors from the plugin function are reported by setting the
55 // error field in the CodeGeneratorResponse.
56 //
57 // In contrast, errors that indicate a problem in protoc
58 // itself (unparsable input, I/O errors, etc.) are reported
59 // to stderr.
60 gen.Error(err)
61 }
62 resp := gen.Response()
63 out, err := proto.Marshal(resp)
64 if err != nil {
65 return err
66 }
67 if _, err := os.Stdout.Write(out); err != nil {
68 return err
69 }
70 return nil
71}
72
73// A Plugin is a protoc plugin invocation.
74type Plugin struct {
75 // Request is the CodeGeneratorRequest provided by protoc.
76 Request *pluginpb.CodeGeneratorRequest
77
78 // Files is the set of files to generate and everything they import.
79 // Files appear in topological order, so each file appears before any
80 // file that imports it.
81 Files []*File
82 filesByName map[string]*File
83
84 packageImportPath string // Go import path of the package we're generating code for.
85
86 genFiles []*GeneratedFile
87 err error
88}
89
90// New returns a new Plugin.
91func New(req *pluginpb.CodeGeneratorRequest) (*Plugin, error) {
92 gen := &Plugin{
93 Request: req,
94 filesByName: make(map[string]*File),
95 }
96
97 // TODO: Figure out how to pass parameters to the generator.
98 for _, param := range strings.Split(req.GetParameter(), ",") {
99 var value string
100 if i := strings.Index(param, "="); i >= 0 {
101 value = param[i+1:]
102 param = param[0:i]
103 }
104 switch param {
105 case "":
106 // Ignore.
107 case "import_prefix":
108 // TODO
109 case "import_path":
110 gen.packageImportPath = value
111 case "paths":
112 // TODO
113 case "plugins":
114 // TODO
115 case "annotate_code":
116 // TODO
117 default:
118 if param[0] != 'M' {
119 return nil, fmt.Errorf("unknown parameter %q", param)
120 }
121 // TODO
122 }
123 }
124
125 for _, fdesc := range gen.Request.ProtoFile {
126 f := newFile(gen, fdesc)
127 name := f.Desc.GetName()
128 if gen.filesByName[name] != nil {
129 return nil, fmt.Errorf("duplicate file name: %q", name)
130 }
131 gen.Files = append(gen.Files, f)
132 gen.filesByName[name] = f
133 }
134 for _, name := range gen.Request.FileToGenerate {
135 f, ok := gen.FileByName(name)
136 if !ok {
137 return nil, fmt.Errorf("no descriptor for generated file: %v", name)
138 }
139 f.Generate = true
140 }
141 return gen, nil
142}
143
144// Error records an error in code generation. The generator will report the
145// error back to protoc and will not produce output.
146func (gen *Plugin) Error(err error) {
147 if gen.err == nil {
148 gen.err = err
149 }
150}
151
152// Response returns the generator output.
153func (gen *Plugin) Response() *pluginpb.CodeGeneratorResponse {
154 resp := &pluginpb.CodeGeneratorResponse{}
155 if gen.err != nil {
156 resp.Error = proto.String(gen.err.Error())
157 return resp
158 }
159 for _, gf := range gen.genFiles {
160 resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{
161 Name: proto.String(gf.path),
162 Content: proto.String(string(gf.Content())),
163 })
164 }
165 return resp
166}
167
168// FileByName returns the file with the given name.
169func (gen *Plugin) FileByName(name string) (f *File, ok bool) {
170 f, ok = gen.filesByName[name]
171 return f, ok
172}
173
174// A File is a .proto source file.
175type File struct {
176 // TODO: Replace with protoreflect.FileDescriptor.
177 Desc *descpb.FileDescriptorProto
178
179 // Generate is true if the generator should generate code for this file.
180 Generate bool
181}
182
183func newFile(gen *Plugin, p *descpb.FileDescriptorProto) *File {
184 return &File{
185 Desc: p,
186 }
187}
188
189// A GeneratedFile is a generated file.
190type GeneratedFile struct {
191 path string
192 buf bytes.Buffer
193}
194
195// NewGeneratedFile creates a new generated file with the given path.
196func (gen *Plugin) NewGeneratedFile(path string) *GeneratedFile {
197 g := &GeneratedFile{
198 path: path,
199 }
200 gen.genFiles = append(gen.genFiles, g)
201 return g
202}
203
204// P prints a line to the generated output. It converts each parameter to a
205// string following the same rules as fmt.Print. It never inserts spaces
206// between parameters.
207//
208// TODO: .meta file annotations.
209func (g *GeneratedFile) P(v ...interface{}) {
210 for _, x := range v {
211 fmt.Fprint(&g.buf, x)
212 }
213 fmt.Fprintln(&g.buf)
214}
215
216// Write implements io.Writer.
217func (g *GeneratedFile) Write(p []byte) (n int, err error) {
218 return g.buf.Write(p)
219}
220
221// Content returns the contents of the generated file.
222func (g *GeneratedFile) Content() []byte {
223 return g.buf.Bytes()
224}