-
Notifications
You must be signed in to change notification settings - Fork 20
/
resolver.go
215 lines (196 loc) · 8.27 KB
/
resolver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright 2020-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package protocompile
import (
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
"github.com/bufbuild/protocompile/ast"
"github.com/bufbuild/protocompile/parser"
)
// Resolver is used by the compiler to resolve a proto source file name
// into some unit that is usable by the compiler. The result could be source
// for a proto file or it could be an already-parsed AST or descriptor.
//
// Resolver implementations must be thread-safe as a single compilation
// operation could invoke FindFileByPath from multiple goroutines.
type Resolver interface {
// FindFileByPath searches for information for the given file path. If no
// result is available, it should return a non-nil error, such as
// protoregistry.NotFound.
FindFileByPath(path string) (SearchResult, error)
}
// SearchResult represents information about a proto source file. Only one of
// the various fields must be set, based on what is available for a file. If
// multiple fields are set, the compiler prefers them in opposite order listed:
// so it uses a descriptor if present and only falls back to source if nothing
// else is available.
type SearchResult struct {
// Represents source code for the file. This should be nil if source code
// is not available. If no field below is set, then the compiler will parse
// the source code into an AST.
Source io.Reader
// Represents the abstract syntax tree for the file. If no field below is
// set, then the compiler will convert the AST into a descriptor proto.
AST *ast.FileNode
// A descriptor proto that represents the file. If the field below is not
// set, then the compiler will link this proto with its dependencies to
// produce a linked descriptor.
Proto *descriptorpb.FileDescriptorProto
// A parse result for the file. This packages both an AST and a descriptor
// proto in one. When a parser result is available, it is more efficient
// than using an AST search result, since the descriptor proto need not be
// re-created. And it provides better error messages than a descriptor proto
// search result, since the AST has greater fidelity with regard to source
// positions (even if the descriptor proto includes source code info).
ParseResult parser.Result
// A fully linked descriptor that represents the file. If this field is set,
// then the compiler has little or no additional work to do for this file as
// it is already compiled. If this value implements linker.File, there is no
// additional work. Otherwise, the additional work is to compute an index of
// symbols in the file, for efficient lookup.
Desc protoreflect.FileDescriptor
}
// ResolverFunc is a simple function type that implements Resolver.
type ResolverFunc func(string) (SearchResult, error)
var _ Resolver = ResolverFunc(nil)
func (f ResolverFunc) FindFileByPath(path string) (SearchResult, error) {
return f(path)
}
// CompositeResolver is a slice of resolvers, which are consulted in order
// until one can supply a result. If none of the constituent resolvers can
// supply a result, the error returned by the first resolver is returned. If
// the slice of resolvers is empty, all operations return
// protoregistry.NotFound.
type CompositeResolver []Resolver
var _ Resolver = CompositeResolver(nil)
func (f CompositeResolver) FindFileByPath(path string) (SearchResult, error) {
if len(f) == 0 {
return SearchResult{}, protoregistry.NotFound
}
var firstErr error
for _, res := range f {
r, err := res.FindFileByPath(path)
if err == nil {
return r, nil
}
if firstErr == nil {
firstErr = err
}
}
return SearchResult{}, firstErr
}
// SourceResolver can resolve file names by returning source code. It uses
// an optional list of import paths to search. By default, it searches the
// file system.
type SourceResolver struct {
// Optional list of import paths. If present and not empty, then all
// file paths to find are assumed to be relative to one of these paths.
// If nil or empty, all file paths to find are assumed to be relative to
// the current working directory.
ImportPaths []string
// Optional function for returning a file's contents. If nil, then
// os.Open is used to open files on the file system.
//
// This function must be thread-safe as a single compilation operation
// could result in concurrent invocations of this function from
// multiple goroutines.
Accessor func(path string) (io.ReadCloser, error)
}
var _ Resolver = (*SourceResolver)(nil)
func (r *SourceResolver) FindFileByPath(path string) (SearchResult, error) {
if len(r.ImportPaths) == 0 {
reader, err := r.accessFile(path)
if err != nil {
return SearchResult{}, err
}
return SearchResult{Source: reader}, nil
}
var e error
for _, importPath := range r.ImportPaths {
reader, err := r.accessFile(filepath.Join(importPath, path))
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
e = err
continue
}
return SearchResult{}, err
}
return SearchResult{Source: reader}, nil
}
return SearchResult{}, e
}
func (r *SourceResolver) accessFile(path string) (io.ReadCloser, error) {
if r.Accessor != nil {
return r.Accessor(path)
}
return os.Open(path)
}
// SourceAccessorFromMap returns a function that can be used as the Accessor
// field of a SourceResolver that uses the given map to load source. The map
// keys are file names and the values are the corresponding file contents.
//
// The given map is used directly and not copied. Since accessor functions
// must be thread-safe, this means that the provided map must not be mutated
// once this accessor is provided to a compile operation.
func SourceAccessorFromMap(srcs map[string]string) func(string) (io.ReadCloser, error) {
return func(path string) (io.ReadCloser, error) {
src, ok := srcs[path]
if !ok {
return nil, os.ErrNotExist
}
return io.NopCloser(strings.NewReader(src)), nil
}
}
// WithStandardImports returns a new resolver that knows about the same standard
// imports that are included with protoc.
//
// Note that this uses the descriptors embedded in generated code in the packages
// of the Protobuf Go module, except for "google/protobuf/cpp_features.proto" and
// "google/protobuf/java_features.proto". For those two files, compiled descriptors
// are embedded in this module because there is no package in the Protobuf Go module
// that contains generated code for those files. This resolver also provides results
// for the "google/protobuf/go_features.proto", which is technically not a standard
// file (it is not included with protoc) but is included in generated code in the
// Protobuf Go module.
//
// As of v0.14.0 of this module (and v1.34.2 of the Protobuf Go module and v27.0 of
// Protobuf), the contents of the standard import "google/protobuf/descriptor.proto"
// contain extension declarations which are *absent* from the descriptors that this
// resolver returns. That is because extension declarations are only retained in
// source, not at runtime, which means they are not available in the embedded
// descriptors in generated code.
//
// To use versions of the standard imports that *do* include these extension
// declarations, see wellknownimports.WithStandardImports instead. As of this
// writing, the declarations are only needed to prevent source files from
// illegally re-defining the custom features for C++, Java, and Go.
func WithStandardImports(r Resolver) Resolver {
return ResolverFunc(func(name string) (SearchResult, error) {
res, err := r.FindFileByPath(name)
if err != nil {
// error from given resolver? see if it's a known standard file
if d, ok := standardImports[name]; ok {
return SearchResult{Desc: d}, nil
}
}
return res, err
})
}