1 // Copyright 2016 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "grpc_tools/main.h"
16
17 #include <google/protobuf/compiler/code_generator.h>
18 #include <google/protobuf/compiler/command_line_interface.h>
19 #include <google/protobuf/compiler/importer.h>
20 #include <google/protobuf/compiler/python/generator.h>
21 #include <google/protobuf/compiler/python/pyi_generator.h>
22 #include <google/protobuf/descriptor.h>
23 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
24
25 #include <algorithm>
26 #include <map>
27 #include <string>
28 #include <tuple>
29 #include <unordered_set>
30 #include <vector>
31
32 #include "absl/strings/string_view.h"
33 #include "src/compiler/python_generator.h"
34
35 using ::google::protobuf::FileDescriptor;
36 using ::google::protobuf::compiler::CodeGenerator;
37 using ::google::protobuf::compiler::DiskSourceTree;
38 using ::google::protobuf::compiler::GeneratorContext;
39 using ::google::protobuf::compiler::Importer;
40 using ::google::protobuf::compiler::MultiFileErrorCollector;
41 using ::google::protobuf::io::StringOutputStream;
42 using ::google::protobuf::io::ZeroCopyOutputStream;
43
44 namespace grpc_tools {
protoc_main(int argc,char * argv[],char * version)45 int protoc_main(int argc, char* argv[], char* version) {
46 google::protobuf::compiler::CommandLineInterface cli;
47 cli.AllowPlugins("protoc-");
48
49 // Proto2 Python
50 google::protobuf::compiler::python::Generator py_generator;
51 cli.RegisterGenerator("--python_out", &py_generator,
52 "Generate Python source file.");
53
54 // pyi files for type checking
55 google::protobuf::compiler::python::PyiGenerator pyi_generator;
56 cli.RegisterGenerator("--pyi_out", &pyi_generator,
57 "Generate Python pyi stub.");
58
59 // Get grpc_tools version
60 std::string grpc_tools_version = version;
61
62 // gRPC Python
63 grpc_python_generator::GeneratorConfiguration grpc_py_config(
64 grpc_tools_version);
65 grpc_python_generator::PythonGrpcGenerator grpc_py_generator(grpc_py_config);
66 cli.RegisterGenerator("--grpc_python_out", &grpc_py_generator,
67 "Generate Python source file.");
68
69 return cli.Run(argc, argv);
70 }
71
72 namespace internal {
73
74 class GeneratorContextImpl : public GeneratorContext {
75 public:
GeneratorContextImpl(const std::vector<const FileDescriptor * > & parsed_files,std::vector<std::pair<std::string,std::string>> * files_out)76 GeneratorContextImpl(
77 const std::vector<const FileDescriptor*>& parsed_files,
78 std::vector<std::pair<std::string, std::string>>* files_out)
79 : files_(files_out), parsed_files_(parsed_files) {}
80
Open(const std::string & filename)81 ZeroCopyOutputStream* Open(const std::string& filename) {
82 files_->emplace_back(filename, "");
83 return new StringOutputStream(&(files_->back().second));
84 }
85
86 // NOTE(rbellevi): Equivalent to Open, since all files start out empty.
OpenForAppend(const std::string & filename)87 ZeroCopyOutputStream* OpenForAppend(const std::string& filename) {
88 return Open(filename);
89 }
90
91 // NOTE(rbellevi): Equivalent to Open, since all files start out empty.
OpenForInsert(const std::string & filename,const std::string & insertion_point)92 ZeroCopyOutputStream* OpenForInsert(const std::string& filename,
93 const std::string& insertion_point) {
94 return Open(filename);
95 }
96
ListParsedFiles(std::vector<const::google::protobuf::FileDescriptor * > * output)97 void ListParsedFiles(
98 std::vector<const ::google::protobuf::FileDescriptor*>* output) {
99 *output = parsed_files_;
100 }
101
102 private:
103 std::vector<std::pair<std::string, std::string>>* files_;
104 const std::vector<const FileDescriptor*>& parsed_files_;
105 };
106
107 class ErrorCollectorImpl : public MultiFileErrorCollector {
108 public:
ErrorCollectorImpl(std::vector<::grpc_tools::ProtocError> * errors,std::vector<::grpc_tools::ProtocWarning> * warnings)109 ErrorCollectorImpl(std::vector<::grpc_tools::ProtocError>* errors,
110 std::vector<::grpc_tools::ProtocWarning>* warnings)
111 : errors_(errors), warnings_(warnings) {}
112
RecordError(absl::string_view filename,int line,int column,absl::string_view message)113 void RecordError(absl::string_view filename, int line, int column,
114 absl::string_view message) override {
115 errors_->emplace_back(std::string(filename), line, column,
116 std::string(message));
117 }
118
RecordWarning(absl::string_view filename,int line,int column,absl::string_view message)119 void RecordWarning(absl::string_view filename, int line, int column,
120 absl::string_view message) override {
121 warnings_->emplace_back(std::string(filename), line, column,
122 std::string(message));
123 }
124
125 private:
126 std::vector<::grpc_tools::ProtocError>* errors_;
127 std::vector<::grpc_tools::ProtocWarning>* warnings_;
128 };
129
calculate_transitive_closure(const FileDescriptor * descriptor,std::vector<const FileDescriptor * > * transitive_closure,std::unordered_set<const::google::protobuf::FileDescriptor * > * visited)130 static void calculate_transitive_closure(
131 const FileDescriptor* descriptor,
132 std::vector<const FileDescriptor*>* transitive_closure,
133 std::unordered_set<const ::google::protobuf::FileDescriptor*>* visited) {
134 for (int i = 0; i < descriptor->dependency_count(); ++i) {
135 const FileDescriptor* dependency = descriptor->dependency(i);
136 if (visited->find(dependency) == visited->end()) {
137 calculate_transitive_closure(dependency, transitive_closure, visited);
138 }
139 }
140 transitive_closure->push_back(descriptor);
141 visited->insert(descriptor);
142 }
143
144 } // end namespace internal
145
generate_code(CodeGenerator * code_generator,char * protobuf_path,const std::vector<std::string> * include_paths,std::vector<std::pair<std::string,std::string>> * files_out,std::vector<::grpc_tools::ProtocError> * errors,std::vector<::grpc_tools::ProtocWarning> * warnings)146 static int generate_code(
147 CodeGenerator* code_generator, char* protobuf_path,
148 const std::vector<std::string>* include_paths,
149 std::vector<std::pair<std::string, std::string>>* files_out,
150 std::vector<::grpc_tools::ProtocError>* errors,
151 std::vector<::grpc_tools::ProtocWarning>* warnings) {
152 std::unique_ptr<internal::ErrorCollectorImpl> error_collector(
153 new internal::ErrorCollectorImpl(errors, warnings));
154 std::unique_ptr<DiskSourceTree> source_tree(new DiskSourceTree());
155 for (const auto& include_path : *include_paths) {
156 source_tree->MapPath("", include_path);
157 }
158 Importer importer(source_tree.get(), error_collector.get());
159 const FileDescriptor* parsed_file = importer.Import(protobuf_path);
160 if (parsed_file == nullptr) {
161 return 1;
162 }
163 std::vector<const FileDescriptor*> transitive_closure;
164 std::unordered_set<const FileDescriptor*> visited;
165 internal::calculate_transitive_closure(parsed_file, &transitive_closure,
166 &visited);
167 internal::GeneratorContextImpl generator_context(transitive_closure,
168 files_out);
169 std::string error;
170 for (const auto descriptor : transitive_closure) {
171 code_generator->Generate(descriptor, "", &generator_context, &error);
172 }
173 return 0;
174 }
175
protoc_get_protos(char * protobuf_path,const std::vector<std::string> * include_paths,std::vector<std::pair<std::string,std::string>> * files_out,std::vector<::grpc_tools::ProtocError> * errors,std::vector<::grpc_tools::ProtocWarning> * warnings)176 int protoc_get_protos(
177 char* protobuf_path, const std::vector<std::string>* include_paths,
178 std::vector<std::pair<std::string, std::string>>* files_out,
179 std::vector<::grpc_tools::ProtocError>* errors,
180 std::vector<::grpc_tools::ProtocWarning>* warnings) {
181 ::google::protobuf::compiler::python::Generator python_generator;
182 return generate_code(&python_generator, protobuf_path, include_paths,
183 files_out, errors, warnings);
184 }
185
protoc_get_services(char * protobuf_path,char * version,const std::vector<std::string> * include_paths,std::vector<std::pair<std::string,std::string>> * files_out,std::vector<::grpc_tools::ProtocError> * errors,std::vector<::grpc_tools::ProtocWarning> * warnings)186 int protoc_get_services(
187 char* protobuf_path, char* version,
188 const std::vector<std::string>* include_paths,
189 std::vector<std::pair<std::string, std::string>>* files_out,
190 std::vector<::grpc_tools::ProtocError>* errors,
191 std::vector<::grpc_tools::ProtocWarning>* warnings) {
192 std::string grpc_tools_version = version;
193 grpc_python_generator::GeneratorConfiguration grpc_py_config(
194 grpc_tools_version);
195 grpc_python_generator::PythonGrpcGenerator grpc_py_generator(grpc_py_config);
196 return generate_code(&grpc_py_generator, protobuf_path, include_paths,
197 files_out, errors, warnings);
198 }
199 } // end namespace grpc_tools
200