1 | // Protocol Buffers - Google's data interchange format |
2 | // Copyright 2008 Google Inc. All rights reserved. |
3 | // https://developers.google.com/protocol-buffers/ |
4 | // |
5 | // Redistribution and use in source and binary forms, with or without |
6 | // modification, are permitted provided that the following conditions are |
7 | // met: |
8 | // |
9 | // * Redistributions of source code must retain the above copyright |
10 | // notice, this list of conditions and the following disclaimer. |
11 | // * Redistributions in binary form must reproduce the above |
12 | // copyright notice, this list of conditions and the following disclaimer |
13 | // in the documentation and/or other materials provided with the |
14 | // distribution. |
15 | // * Neither the name of Google Inc. nor the names of its |
16 | // contributors may be used to endorse or promote products derived from |
17 | // this software without specific prior written permission. |
18 | // |
19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | |
31 | // Author: kenton@google.com (Kenton Varda) |
32 | // Based on original Protocol Buffers design by |
33 | // Sanjay Ghemawat, Jeff Dean, and others. |
34 | |
35 | #include <google/protobuf/compiler/command_line_interface.h> |
36 | |
37 | #include <google/protobuf/stubs/platform_macros.h> |
38 | |
39 | #include <stdio.h> |
40 | #include <sys/types.h> |
41 | #ifdef major |
42 | #undef major |
43 | #endif |
44 | #ifdef minor |
45 | #undef minor |
46 | #endif |
47 | #include <fcntl.h> |
48 | #include <sys/stat.h> |
49 | #ifndef _MSC_VER |
50 | #include <unistd.h> |
51 | #endif |
52 | #include <ctype.h> |
53 | #include <errno.h> |
54 | |
55 | #include <fstream> |
56 | #include <iostream> |
57 | |
58 | #include <limits.h> // For PATH_MAX |
59 | |
60 | #include <memory> |
61 | |
62 | #if defined(__APPLE__) |
63 | #include <mach-o/dyld.h> |
64 | #elif defined(__FreeBSD__) |
65 | #include <sys/sysctl.h> |
66 | #endif |
67 | |
68 | #include <google/protobuf/stubs/common.h> |
69 | #include <google/protobuf/stubs/logging.h> |
70 | #include <google/protobuf/compiler/subprocess.h> |
71 | #include <google/protobuf/compiler/plugin.pb.h> |
72 | #include <google/protobuf/stubs/strutil.h> |
73 | #include <google/protobuf/stubs/stringprintf.h> |
74 | #include <google/protobuf/stubs/substitute.h> |
75 | #include <google/protobuf/compiler/code_generator.h> |
76 | #include <google/protobuf/compiler/importer.h> |
77 | #include <google/protobuf/compiler/zip_writer.h> |
78 | #include <google/protobuf/descriptor.h> |
79 | #include <google/protobuf/dynamic_message.h> |
80 | #include <google/protobuf/io/coded_stream.h> |
81 | #include <google/protobuf/io/io_win32.h> |
82 | #include <google/protobuf/io/printer.h> |
83 | #include <google/protobuf/io/zero_copy_stream_impl.h> |
84 | #include <google/protobuf/text_format.h> |
85 | #include <google/protobuf/stubs/map_util.h> |
86 | #include <google/protobuf/stubs/stl_util.h> |
87 | |
88 | |
89 | // Must be included last. |
90 | #include <google/protobuf/port_def.inc> |
91 | |
92 | namespace google { |
93 | namespace protobuf { |
94 | namespace compiler { |
95 | |
96 | #ifndef O_BINARY |
97 | #ifdef _O_BINARY |
98 | #define O_BINARY _O_BINARY |
99 | #else |
100 | #define O_BINARY 0 // If this isn't defined, the platform doesn't need it. |
101 | #endif |
102 | #endif |
103 | |
104 | namespace { |
105 | #if defined(_WIN32) |
106 | // DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import |
107 | // them like we do below. |
108 | using google::protobuf::io::win32::access; |
109 | using google::protobuf::io::win32::close; |
110 | using google::protobuf::io::win32::mkdir; |
111 | using google::protobuf::io::win32::open; |
112 | using google::protobuf::io::win32::setmode; |
113 | using google::protobuf::io::win32::write; |
114 | #endif |
115 | |
116 | static const char* kDefaultDirectDependenciesViolationMsg = |
117 | "File is imported but not declared in --direct_dependencies: %s" ; |
118 | |
119 | // Returns true if the text looks like a Windows-style absolute path, starting |
120 | // with a drive letter. Example: "C:\foo". TODO(kenton): Share this with |
121 | // copy in importer.cc? |
122 | static bool IsWindowsAbsolutePath(const std::string& text) { |
123 | #if defined(_WIN32) || defined(__CYGWIN__) |
124 | return text.size() >= 3 && text[1] == ':' && isalpha(text[0]) && |
125 | (text[2] == '/' || text[2] == '\\') && text.find_last_of(':') == 1; |
126 | #else |
127 | return false; |
128 | #endif |
129 | } |
130 | |
131 | void SetFdToTextMode(int fd) { |
132 | #ifdef _WIN32 |
133 | if (setmode(fd, _O_TEXT) == -1) { |
134 | // This should never happen, I think. |
135 | GOOGLE_LOG(WARNING) << "setmode(" << fd << ", _O_TEXT): " << strerror(errno); |
136 | } |
137 | #endif |
138 | // (Text and binary are the same on non-Windows platforms.) |
139 | } |
140 | |
141 | void SetFdToBinaryMode(int fd) { |
142 | #ifdef _WIN32 |
143 | if (setmode(fd, _O_BINARY) == -1) { |
144 | // This should never happen, I think. |
145 | GOOGLE_LOG(WARNING) << "setmode(" << fd << ", _O_BINARY): " << strerror(errno); |
146 | } |
147 | #endif |
148 | // (Text and binary are the same on non-Windows platforms.) |
149 | } |
150 | |
151 | void AddTrailingSlash(std::string* path) { |
152 | if (!path->empty() && path->at(n: path->size() - 1) != '/') { |
153 | path->push_back(c: '/'); |
154 | } |
155 | } |
156 | |
157 | bool VerifyDirectoryExists(const std::string& path) { |
158 | if (path.empty()) return true; |
159 | |
160 | if (access(name: path.c_str(), F_OK) == -1) { |
161 | std::cerr << path << ": " << strerror(errno) << std::endl; |
162 | return false; |
163 | } else { |
164 | return true; |
165 | } |
166 | } |
167 | |
168 | // Try to create the parent directory of the given file, creating the parent's |
169 | // parent if necessary, and so on. The full file name is actually |
170 | // (prefix + filename), but we assume |prefix| already exists and only create |
171 | // directories listed in |filename|. |
172 | bool TryCreateParentDirectory(const std::string& prefix, |
173 | const std::string& filename) { |
174 | // Recursively create parent directories to the output file. |
175 | // On Windows, both '/' and '\' are valid path separators. |
176 | std::vector<std::string> parts = |
177 | Split(full: filename, delim: "/\\" , skip_empty: true); |
178 | std::string path_so_far = prefix; |
179 | for (int i = 0; i < parts.size() - 1; i++) { |
180 | path_so_far += parts[i]; |
181 | if (mkdir(path: path_so_far.c_str(), mode: 0777) != 0) { |
182 | if (errno != EEXIST) { |
183 | std::cerr << filename << ": while trying to create directory " |
184 | << path_so_far << ": " << strerror(errno) << std::endl; |
185 | return false; |
186 | } |
187 | } |
188 | path_so_far += '/'; |
189 | } |
190 | |
191 | return true; |
192 | } |
193 | |
194 | // Get the absolute path of this protoc binary. |
195 | bool GetProtocAbsolutePath(std::string* path) { |
196 | #ifdef _WIN32 |
197 | char buffer[MAX_PATH]; |
198 | int len = GetModuleFileNameA(nullptr, buffer, MAX_PATH); |
199 | #elif defined(__APPLE__) |
200 | char buffer[PATH_MAX]; |
201 | int len = 0; |
202 | |
203 | char dirtybuffer[PATH_MAX]; |
204 | uint32_t size = sizeof(dirtybuffer); |
205 | if (_NSGetExecutablePath(dirtybuffer, &size) == 0) { |
206 | realpath(dirtybuffer, buffer); |
207 | len = strlen(buffer); |
208 | } |
209 | #elif defined(__FreeBSD__) |
210 | char buffer[PATH_MAX]; |
211 | size_t len = PATH_MAX; |
212 | int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; |
213 | if (sysctl(mib, 4, &buffer, &len, nullptr, 0) != 0) { |
214 | len = 0; |
215 | } |
216 | #else |
217 | char buffer[PATH_MAX]; |
218 | int len = readlink(path: "/proc/self/exe" , buf: buffer, PATH_MAX); |
219 | #endif |
220 | if (len > 0) { |
221 | path->assign(s: buffer, n: len); |
222 | return true; |
223 | } else { |
224 | return false; |
225 | } |
226 | } |
227 | |
228 | // Whether a path is where google/protobuf/descriptor.proto and other well-known |
229 | // type protos are installed. |
230 | bool IsInstalledProtoPath(const std::string& path) { |
231 | // Checking the descriptor.proto file should be good enough. |
232 | std::string file_path = path + "/google/protobuf/descriptor.proto" ; |
233 | return access(name: file_path.c_str(), F_OK) != -1; |
234 | } |
235 | |
236 | // Add the paths where google/protobuf/descriptor.proto and other well-known |
237 | // type protos are installed. |
238 | void AddDefaultProtoPaths( |
239 | std::vector<std::pair<std::string, std::string>>* paths) { |
240 | // TODO(xiaofeng): The code currently only checks relative paths of where |
241 | // the protoc binary is installed. We probably should make it handle more |
242 | // cases than that. |
243 | std::string path; |
244 | if (!GetProtocAbsolutePath(path: &path)) { |
245 | return; |
246 | } |
247 | // Strip the binary name. |
248 | size_t pos = path.find_last_of(s: "/\\" ); |
249 | if (pos == std::string::npos || pos == 0) { |
250 | return; |
251 | } |
252 | path = path.substr(pos: 0, n: pos); |
253 | // Check the binary's directory. |
254 | if (IsInstalledProtoPath(path)) { |
255 | paths->push_back(x: std::pair<std::string, std::string>("" , path)); |
256 | return; |
257 | } |
258 | // Check if there is an include subdirectory. |
259 | if (IsInstalledProtoPath(path: path + "/include" )) { |
260 | paths->push_back( |
261 | x: std::pair<std::string, std::string>("" , path + "/include" )); |
262 | return; |
263 | } |
264 | // Check if the upper level directory has an "include" subdirectory. |
265 | pos = path.find_last_of(s: "/\\" ); |
266 | if (pos == std::string::npos || pos == 0) { |
267 | return; |
268 | } |
269 | path = path.substr(pos: 0, n: pos); |
270 | if (IsInstalledProtoPath(path: path + "/include" )) { |
271 | paths->push_back( |
272 | x: std::pair<std::string, std::string>("" , path + "/include" )); |
273 | return; |
274 | } |
275 | } |
276 | |
277 | std::string PluginName(const std::string& plugin_prefix, |
278 | const std::string& directive) { |
279 | // Assuming the directive starts with "--" and ends with "_out" or "_opt", |
280 | // strip the "--" and "_out/_opt" and add the plugin prefix. |
281 | return plugin_prefix + "gen-" + directive.substr(pos: 2, n: directive.size() - 6); |
282 | } |
283 | |
284 | } // namespace |
285 | |
286 | // A MultiFileErrorCollector that prints errors to stderr. |
287 | class CommandLineInterface::ErrorPrinter |
288 | : public MultiFileErrorCollector, |
289 | public io::ErrorCollector, |
290 | public DescriptorPool::ErrorCollector { |
291 | public: |
292 | ErrorPrinter(ErrorFormat format, DiskSourceTree* tree = nullptr) |
293 | : format_(format), |
294 | tree_(tree), |
295 | found_errors_(false), |
296 | found_warnings_(false) {} |
297 | ~ErrorPrinter() override {} |
298 | |
299 | // implements MultiFileErrorCollector ------------------------------ |
300 | void AddError(const std::string& filename, int line, int column, |
301 | const std::string& message) override { |
302 | found_errors_ = true; |
303 | AddErrorOrWarning(filename, line, column, message, type: "error" , out&: std::cerr); |
304 | } |
305 | |
306 | void AddWarning(const std::string& filename, int line, int column, |
307 | const std::string& message) override { |
308 | found_warnings_ = true; |
309 | AddErrorOrWarning(filename, line, column, message, type: "warning" , out&: std::clog); |
310 | } |
311 | |
312 | // implements io::ErrorCollector ----------------------------------- |
313 | void AddError(int line, int column, const std::string& message) override { |
314 | AddError(filename: "input" , line, column, message); |
315 | } |
316 | |
317 | void AddWarning(int line, int column, const std::string& message) override { |
318 | AddErrorOrWarning(filename: "input" , line, column, message, type: "warning" , out&: std::clog); |
319 | } |
320 | |
321 | // implements DescriptorPool::ErrorCollector------------------------- |
322 | void AddError(const std::string& filename, const std::string& element_name, |
323 | const Message* descriptor, ErrorLocation location, |
324 | const std::string& message) override { |
325 | AddErrorOrWarning(filename, line: -1, column: -1, message, type: "error" , out&: std::cerr); |
326 | } |
327 | |
328 | void AddWarning(const std::string& filename, const std::string& element_name, |
329 | const Message* descriptor, ErrorLocation location, |
330 | const std::string& message) override { |
331 | AddErrorOrWarning(filename, line: -1, column: -1, message, type: "warning" , out&: std::clog); |
332 | } |
333 | |
334 | bool FoundErrors() const { return found_errors_; } |
335 | |
336 | bool FoundWarnings() const { return found_warnings_; } |
337 | |
338 | private: |
339 | void AddErrorOrWarning(const std::string& filename, int line, int column, |
340 | const std::string& message, const std::string& type, |
341 | std::ostream& out) { |
342 | // Print full path when running under MSVS |
343 | std::string dfile; |
344 | if (format_ == CommandLineInterface::ERROR_FORMAT_MSVS && |
345 | tree_ != nullptr && tree_->VirtualFileToDiskFile(virtual_file: filename, disk_file: &dfile)) { |
346 | out << dfile; |
347 | } else { |
348 | out << filename; |
349 | } |
350 | |
351 | // Users typically expect 1-based line/column numbers, so we add 1 |
352 | // to each here. |
353 | if (line != -1) { |
354 | // Allow for both GCC- and Visual-Studio-compatible output. |
355 | switch (format_) { |
356 | case CommandLineInterface::ERROR_FORMAT_GCC: |
357 | out << ":" << (line + 1) << ":" << (column + 1); |
358 | break; |
359 | case CommandLineInterface::ERROR_FORMAT_MSVS: |
360 | out << "(" << (line + 1) << ") : " << type |
361 | << " in column=" << (column + 1); |
362 | break; |
363 | } |
364 | } |
365 | |
366 | if (type == "warning" ) { |
367 | out << ": warning: " << message << std::endl; |
368 | } else { |
369 | out << ": " << message << std::endl; |
370 | } |
371 | } |
372 | |
373 | const ErrorFormat format_; |
374 | DiskSourceTree* tree_; |
375 | bool found_errors_; |
376 | bool found_warnings_; |
377 | }; |
378 | |
379 | // ------------------------------------------------------------------- |
380 | |
381 | // A GeneratorContext implementation that buffers files in memory, then dumps |
382 | // them all to disk on demand. |
383 | class CommandLineInterface::GeneratorContextImpl : public GeneratorContext { |
384 | public: |
385 | GeneratorContextImpl(const std::vector<const FileDescriptor*>& parsed_files); |
386 | |
387 | // Write all files in the directory to disk at the given output location, |
388 | // which must end in a '/'. |
389 | bool WriteAllToDisk(const std::string& prefix); |
390 | |
391 | // Write the contents of this directory to a ZIP-format archive with the |
392 | // given name. |
393 | bool WriteAllToZip(const std::string& filename); |
394 | |
395 | // Add a boilerplate META-INF/MANIFEST.MF file as required by the Java JAR |
396 | // format, unless one has already been written. |
397 | void AddJarManifest(); |
398 | |
399 | // Get name of all output files. |
400 | void GetOutputFilenames(std::vector<std::string>* output_filenames); |
401 | |
402 | // implements GeneratorContext -------------------------------------- |
403 | io::ZeroCopyOutputStream* Open(const std::string& filename) override; |
404 | io::ZeroCopyOutputStream* OpenForAppend(const std::string& filename) override; |
405 | io::ZeroCopyOutputStream* OpenForInsert( |
406 | const std::string& filename, const std::string& insertion_point) override; |
407 | io::ZeroCopyOutputStream* OpenForInsertWithGeneratedCodeInfo( |
408 | const std::string& filename, const std::string& insertion_point, |
409 | const google::protobuf::GeneratedCodeInfo& info) override; |
410 | void ListParsedFiles(std::vector<const FileDescriptor*>* output) override { |
411 | *output = parsed_files_; |
412 | } |
413 | |
414 | private: |
415 | friend class MemoryOutputStream; |
416 | |
417 | // The files_ field maps from path keys to file content values. It's a map |
418 | // instead of an unordered_map so that files are written in order (good when |
419 | // writing zips). |
420 | std::map<std::string, std::string> files_; |
421 | const std::vector<const FileDescriptor*>& parsed_files_; |
422 | bool had_error_; |
423 | }; |
424 | |
425 | class CommandLineInterface::MemoryOutputStream |
426 | : public io::ZeroCopyOutputStream { |
427 | public: |
428 | MemoryOutputStream(GeneratorContextImpl* directory, |
429 | const std::string& filename, bool append_mode); |
430 | MemoryOutputStream(GeneratorContextImpl* directory, |
431 | const std::string& filename, |
432 | const std::string& insertion_point); |
433 | MemoryOutputStream(GeneratorContextImpl* directory, |
434 | const std::string& filename, |
435 | const std::string& insertion_point, |
436 | const google::protobuf::GeneratedCodeInfo& info); |
437 | ~MemoryOutputStream() override; |
438 | |
439 | // implements ZeroCopyOutputStream --------------------------------- |
440 | bool Next(void** data, int* size) override { |
441 | return inner_->Next(data, size); |
442 | } |
443 | void BackUp(int count) override { inner_->BackUp(count); } |
444 | int64_t ByteCount() const override { return inner_->ByteCount(); } |
445 | |
446 | private: |
447 | // Checks to see if "filename_.pb.meta" exists in directory_; if so, fixes the |
448 | // offsets in that GeneratedCodeInfo record to reflect bytes inserted in |
449 | // filename_ at original offset insertion_offset with length insertion_length. |
450 | // Also adds in the data from info_to_insert_ with updated offsets governed by |
451 | // insertion_offset and indent_length. We assume that insertions will not |
452 | // occur within any given annotated span of text. insertion_content must end |
453 | // with an endline. |
454 | void UpdateMetadata(const std::string& insertion_content, |
455 | size_t insertion_offset, size_t insertion_length, |
456 | size_t indent_length); |
457 | |
458 | // Inserts info_to_insert_ into target_info, assuming that the relevant |
459 | // insertion was made at insertion_offset in file_content with the given |
460 | // indent_length. insertion_content must end with an endline. |
461 | void InsertShiftedInfo(const std::string& insertion_content, |
462 | size_t insertion_offset, size_t indent_length, |
463 | google::protobuf::GeneratedCodeInfo& target_info); |
464 | |
465 | // Where to insert the string when it's done. |
466 | GeneratorContextImpl* directory_; |
467 | std::string filename_; |
468 | std::string insertion_point_; |
469 | |
470 | // The string we're building. |
471 | std::string data_; |
472 | |
473 | // Whether we should append the output stream to the existing file. |
474 | bool append_mode_; |
475 | |
476 | // StringOutputStream writing to data_. |
477 | std::unique_ptr<io::StringOutputStream> inner_; |
478 | |
479 | // The GeneratedCodeInfo to insert at the insertion point. |
480 | google::protobuf::GeneratedCodeInfo info_to_insert_; |
481 | }; |
482 | |
483 | // ------------------------------------------------------------------- |
484 | |
485 | CommandLineInterface::GeneratorContextImpl::GeneratorContextImpl( |
486 | const std::vector<const FileDescriptor*>& parsed_files) |
487 | : parsed_files_(parsed_files), had_error_(false) {} |
488 | |
489 | bool CommandLineInterface::GeneratorContextImpl::WriteAllToDisk( |
490 | const std::string& prefix) { |
491 | if (had_error_) { |
492 | return false; |
493 | } |
494 | |
495 | if (!VerifyDirectoryExists(path: prefix)) { |
496 | return false; |
497 | } |
498 | |
499 | for (const auto& pair : files_) { |
500 | const std::string& relative_filename = pair.first; |
501 | const char* data = pair.second.data(); |
502 | int size = pair.second.size(); |
503 | |
504 | if (!TryCreateParentDirectory(prefix, filename: relative_filename)) { |
505 | return false; |
506 | } |
507 | std::string filename = prefix + relative_filename; |
508 | |
509 | // Create the output file. |
510 | int file_descriptor; |
511 | do { |
512 | file_descriptor = |
513 | open(file: filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); |
514 | } while (file_descriptor < 0 && errno == EINTR); |
515 | |
516 | if (file_descriptor < 0) { |
517 | int error = errno; |
518 | std::cerr << filename << ": " << strerror(errnum: error); |
519 | return false; |
520 | } |
521 | |
522 | // Write the file. |
523 | while (size > 0) { |
524 | int write_result; |
525 | do { |
526 | write_result = write(fd: file_descriptor, buf: data, n: size); |
527 | } while (write_result < 0 && errno == EINTR); |
528 | |
529 | if (write_result <= 0) { |
530 | // Write error. |
531 | |
532 | // FIXME(kenton): According to the man page, if write() returns zero, |
533 | // there was no error; write() simply did not write anything. It's |
534 | // unclear under what circumstances this might happen, but presumably |
535 | // errno won't be set in this case. I am confused as to how such an |
536 | // event should be handled. For now I'm treating it as an error, |
537 | // since retrying seems like it could lead to an infinite loop. I |
538 | // suspect this never actually happens anyway. |
539 | |
540 | if (write_result < 0) { |
541 | int error = errno; |
542 | std::cerr << filename << ": write: " << strerror(errnum: error); |
543 | } else { |
544 | std::cerr << filename << ": write() returned zero?" << std::endl; |
545 | } |
546 | return false; |
547 | } |
548 | |
549 | data += write_result; |
550 | size -= write_result; |
551 | } |
552 | |
553 | if (close(fd: file_descriptor) != 0) { |
554 | int error = errno; |
555 | std::cerr << filename << ": close: " << strerror(errnum: error); |
556 | return false; |
557 | } |
558 | } |
559 | |
560 | return true; |
561 | } |
562 | |
563 | bool CommandLineInterface::GeneratorContextImpl::WriteAllToZip( |
564 | const std::string& filename) { |
565 | if (had_error_) { |
566 | return false; |
567 | } |
568 | |
569 | // Create the output file. |
570 | int file_descriptor; |
571 | do { |
572 | file_descriptor = |
573 | open(file: filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); |
574 | } while (file_descriptor < 0 && errno == EINTR); |
575 | |
576 | if (file_descriptor < 0) { |
577 | int error = errno; |
578 | std::cerr << filename << ": " << strerror(errnum: error); |
579 | return false; |
580 | } |
581 | |
582 | // Create the ZipWriter |
583 | io::FileOutputStream stream(file_descriptor); |
584 | ZipWriter zip_writer(&stream); |
585 | |
586 | for (const auto& pair : files_) { |
587 | zip_writer.Write(filename: pair.first, contents: pair.second); |
588 | } |
589 | |
590 | zip_writer.WriteDirectory(); |
591 | |
592 | if (stream.GetErrno() != 0) { |
593 | std::cerr << filename << ": " << strerror(errnum: stream.GetErrno()) << std::endl; |
594 | return false; |
595 | } |
596 | |
597 | if (!stream.Close()) { |
598 | std::cerr << filename << ": " << strerror(errnum: stream.GetErrno()) << std::endl; |
599 | return false; |
600 | } |
601 | |
602 | return true; |
603 | } |
604 | |
605 | void CommandLineInterface::GeneratorContextImpl::AddJarManifest() { |
606 | auto pair = files_.insert(x: {"META-INF/MANIFEST.MF" , "" }); |
607 | if (pair.second) { |
608 | pair.first->second = |
609 | "Manifest-Version: 1.0\n" |
610 | "Created-By: 1.6.0 (protoc)\n" |
611 | "\n" ; |
612 | } |
613 | } |
614 | |
615 | void CommandLineInterface::GeneratorContextImpl::GetOutputFilenames( |
616 | std::vector<std::string>* output_filenames) { |
617 | for (const auto& pair : files_) { |
618 | output_filenames->push_back(x: pair.first); |
619 | } |
620 | } |
621 | |
622 | io::ZeroCopyOutputStream* CommandLineInterface::GeneratorContextImpl::Open( |
623 | const std::string& filename) { |
624 | return new MemoryOutputStream(this, filename, false); |
625 | } |
626 | |
627 | io::ZeroCopyOutputStream* |
628 | CommandLineInterface::GeneratorContextImpl::OpenForAppend( |
629 | const std::string& filename) { |
630 | return new MemoryOutputStream(this, filename, true); |
631 | } |
632 | |
633 | io::ZeroCopyOutputStream* |
634 | CommandLineInterface::GeneratorContextImpl::OpenForInsert( |
635 | const std::string& filename, const std::string& insertion_point) { |
636 | return new MemoryOutputStream(this, filename, insertion_point); |
637 | } |
638 | |
639 | io::ZeroCopyOutputStream* |
640 | CommandLineInterface::GeneratorContextImpl::OpenForInsertWithGeneratedCodeInfo( |
641 | const std::string& filename, const std::string& insertion_point, |
642 | const google::protobuf::GeneratedCodeInfo& info) { |
643 | return new MemoryOutputStream(this, filename, insertion_point, info); |
644 | } |
645 | |
646 | // ------------------------------------------------------------------- |
647 | |
648 | CommandLineInterface::MemoryOutputStream::MemoryOutputStream( |
649 | GeneratorContextImpl* directory, const std::string& filename, |
650 | bool append_mode) |
651 | : directory_(directory), |
652 | filename_(filename), |
653 | append_mode_(append_mode), |
654 | inner_(new io::StringOutputStream(&data_)) {} |
655 | |
656 | CommandLineInterface::MemoryOutputStream::MemoryOutputStream( |
657 | GeneratorContextImpl* directory, const std::string& filename, |
658 | const std::string& insertion_point) |
659 | : directory_(directory), |
660 | filename_(filename), |
661 | insertion_point_(insertion_point), |
662 | inner_(new io::StringOutputStream(&data_)) {} |
663 | |
664 | CommandLineInterface::MemoryOutputStream::MemoryOutputStream( |
665 | GeneratorContextImpl* directory, const std::string& filename, |
666 | const std::string& insertion_point, const google::protobuf::GeneratedCodeInfo& info) |
667 | : directory_(directory), |
668 | filename_(filename), |
669 | insertion_point_(insertion_point), |
670 | inner_(new io::StringOutputStream(&data_)), |
671 | info_to_insert_(info) {} |
672 | |
673 | void CommandLineInterface::MemoryOutputStream::InsertShiftedInfo( |
674 | const std::string& insertion_content, size_t insertion_offset, |
675 | size_t indent_length, google::protobuf::GeneratedCodeInfo& target_info) { |
676 | // Keep track of how much extra data was added for indents before the |
677 | // current annotation being inserted. `pos` and `source_annotation.begin()` |
678 | // are offsets in `insertion_content`. `insertion_offset` is updated so that |
679 | // it can be added to an annotation's `begin` field to reflect that |
680 | // annotation's updated location after `insertion_content` was inserted into |
681 | // the target file. |
682 | size_t pos = 0; |
683 | insertion_offset += indent_length; |
684 | for (const auto& source_annotation : info_to_insert_.annotation()) { |
685 | GeneratedCodeInfo::Annotation* annotation = target_info.add_annotation(); |
686 | int inner_indent = 0; |
687 | // insertion_content is guaranteed to end in an endline. This last endline |
688 | // has no effect on indentation. |
689 | for (; pos < source_annotation.end() && pos < insertion_content.size() - 1; |
690 | ++pos) { |
691 | if (insertion_content[pos] == '\n') { |
692 | if (pos >= source_annotation.begin()) { |
693 | // The beginning of the annotation is at insertion_offset, but the end |
694 | // can still move further in the target file. |
695 | inner_indent += indent_length; |
696 | } else { |
697 | insertion_offset += indent_length; |
698 | } |
699 | } |
700 | } |
701 | *annotation = source_annotation; |
702 | annotation->set_begin(annotation->begin() + insertion_offset); |
703 | insertion_offset += inner_indent; |
704 | annotation->set_end(annotation->end() + insertion_offset); |
705 | } |
706 | } |
707 | |
708 | void CommandLineInterface::MemoryOutputStream::UpdateMetadata( |
709 | const std::string& insertion_content, size_t insertion_offset, |
710 | size_t insertion_length, size_t indent_length) { |
711 | auto it = directory_->files_.find(x: filename_ + ".pb.meta" ); |
712 | if (it == directory_->files_.end() && info_to_insert_.annotation().empty()) { |
713 | // No metadata was recorded for this file. |
714 | return; |
715 | } |
716 | GeneratedCodeInfo metadata; |
717 | bool is_text_format = false; |
718 | std::string* encoded_data = nullptr; |
719 | if (it != directory_->files_.end()) { |
720 | encoded_data = &it->second; |
721 | // Try to decode a GeneratedCodeInfo proto from the .pb.meta file. It may be |
722 | // in wire or text format. Keep the same format when the data is written out |
723 | // later. |
724 | if (!metadata.ParseFromString(data: *encoded_data)) { |
725 | if (!TextFormat::ParseFromString(input: *encoded_data, output: &metadata)) { |
726 | // The metadata is invalid. |
727 | std::cerr |
728 | << filename_ |
729 | << ".pb.meta: Could not parse metadata as wire or text format." |
730 | << std::endl; |
731 | return; |
732 | } |
733 | // Generators that use the public plugin interface emit text-format |
734 | // metadata (because in the public plugin protocol, file content must be |
735 | // UTF8-encoded strings). |
736 | is_text_format = true; |
737 | } |
738 | } else { |
739 | // Create a new file to store the new metadata in info_to_insert_. |
740 | encoded_data = |
741 | &directory_->files_.insert(x: {filename_ + ".pb.meta" , "" }).first->second; |
742 | } |
743 | GeneratedCodeInfo new_metadata; |
744 | bool crossed_offset = false; |
745 | size_t to_add = 0; |
746 | for (const auto& source_annotation : metadata.annotation()) { |
747 | // The first time an annotation at or after the insertion point is found, |
748 | // insert the new metadata from info_to_insert_. Shift all annotations |
749 | // after the new metadata by the length of the text that was inserted |
750 | // (including any additional indent length). |
751 | if (source_annotation.begin() >= insertion_offset && !crossed_offset) { |
752 | crossed_offset = true; |
753 | InsertShiftedInfo(insertion_content, insertion_offset, indent_length, |
754 | target_info&: new_metadata); |
755 | to_add += insertion_length; |
756 | } |
757 | GeneratedCodeInfo::Annotation* annotation = new_metadata.add_annotation(); |
758 | *annotation = source_annotation; |
759 | annotation->set_begin(annotation->begin() + to_add); |
760 | annotation->set_end(annotation->end() + to_add); |
761 | } |
762 | // If there were never any annotations at or after the insertion point, |
763 | // make sure to still insert the new metadata from info_to_insert_. |
764 | if (!crossed_offset) { |
765 | InsertShiftedInfo(insertion_content, insertion_offset, indent_length, |
766 | target_info&: new_metadata); |
767 | } |
768 | if (is_text_format) { |
769 | TextFormat::PrintToString(message: new_metadata, output: encoded_data); |
770 | } else { |
771 | new_metadata.SerializeToString(output: encoded_data); |
772 | } |
773 | } |
774 | |
775 | CommandLineInterface::MemoryOutputStream::~MemoryOutputStream() { |
776 | // Make sure all data has been written. |
777 | inner_.reset(); |
778 | |
779 | // Insert into the directory. |
780 | auto pair = directory_->files_.insert(x: {filename_, "" }); |
781 | auto it = pair.first; |
782 | bool already_present = !pair.second; |
783 | |
784 | if (insertion_point_.empty()) { |
785 | // This was just a regular Open(). |
786 | if (already_present) { |
787 | if (append_mode_) { |
788 | it->second.append(str: data_); |
789 | } else { |
790 | std::cerr << filename_ << ": Tried to write the same file twice." |
791 | << std::endl; |
792 | directory_->had_error_ = true; |
793 | } |
794 | return; |
795 | } |
796 | |
797 | it->second.swap(s&: data_); |
798 | } else { |
799 | // This was an OpenForInsert(). |
800 | |
801 | // If the data doesn't end with a clean line break, add one. |
802 | if (!data_.empty() && data_[data_.size() - 1] != '\n') { |
803 | data_.push_back(c: '\n'); |
804 | } |
805 | |
806 | // Find the file we are going to insert into. |
807 | if (!already_present) { |
808 | std::cerr << filename_ |
809 | << ": Tried to insert into file that doesn't exist." |
810 | << std::endl; |
811 | directory_->had_error_ = true; |
812 | return; |
813 | } |
814 | std::string* target = &it->second; |
815 | |
816 | // Find the insertion point. |
817 | std::string magic_string = |
818 | strings::Substitute(format: "@@protoc_insertion_point($0)" , arg0: insertion_point_); |
819 | std::string::size_type pos = target->find(str: magic_string); |
820 | |
821 | if (pos == std::string::npos) { |
822 | std::cerr << filename_ << ": insertion point \"" << insertion_point_ |
823 | << "\" not found." << std::endl; |
824 | directory_->had_error_ = true; |
825 | return; |
826 | } |
827 | |
828 | if ((pos > 3) && (target->substr(pos: pos - 3, n: 2) == "/*" )) { |
829 | // Support for inline "/* @@protoc_insertion_point() */" |
830 | pos = pos - 3; |
831 | } else { |
832 | // Seek backwards to the beginning of the line, which is where we will |
833 | // insert the data. Note that this has the effect of pushing the |
834 | // insertion point down, so the data is inserted before it. This is |
835 | // intentional because it means that multiple insertions at the same point |
836 | // will end up in the expected order in the final output. |
837 | pos = target->find_last_of(c: '\n', pos: pos); |
838 | if (pos == std::string::npos) { |
839 | // Insertion point is on the first line. |
840 | pos = 0; |
841 | } else { |
842 | // Advance to character after '\n'. |
843 | ++pos; |
844 | } |
845 | } |
846 | |
847 | // Extract indent. |
848 | std::string indent_(*target, pos, |
849 | target->find_first_not_of(s: " \t" , pos: pos) - pos); |
850 | |
851 | if (indent_.empty()) { |
852 | // No indent. This makes things easier. |
853 | target->insert(pos1: pos, str: data_); |
854 | UpdateMetadata(insertion_content: data_, insertion_offset: pos, insertion_length: data_.size(), indent_length: 0); |
855 | } else { |
856 | // Calculate how much space we need. |
857 | int indent_size = 0; |
858 | for (int i = 0; i < data_.size(); i++) { |
859 | if (data_[i] == '\n') indent_size += indent_.size(); |
860 | } |
861 | |
862 | // Make a hole for it. |
863 | target->insert(pos: pos, n: data_.size() + indent_size, c: '\0'); |
864 | |
865 | // Now copy in the data. |
866 | std::string::size_type data_pos = 0; |
867 | char* target_ptr = ::google::protobuf::string_as_array(str: target) + pos; |
868 | while (data_pos < data_.size()) { |
869 | // Copy indent. |
870 | memcpy(dest: target_ptr, src: indent_.data(), n: indent_.size()); |
871 | target_ptr += indent_.size(); |
872 | |
873 | // Copy line from data_. |
874 | // We already guaranteed that data_ ends with a newline (above), so this |
875 | // search can't fail. |
876 | std::string::size_type line_length = |
877 | data_.find_first_of(c: '\n', pos: data_pos) + 1 - data_pos; |
878 | memcpy(dest: target_ptr, src: data_.data() + data_pos, n: line_length); |
879 | target_ptr += line_length; |
880 | data_pos += line_length; |
881 | } |
882 | UpdateMetadata(insertion_content: data_, insertion_offset: pos, insertion_length: data_.size() + indent_size, indent_length: indent_.size()); |
883 | |
884 | GOOGLE_CHECK_EQ(target_ptr, |
885 | ::google::protobuf::string_as_array(target) + pos + data_.size() + indent_size); |
886 | } |
887 | } |
888 | } |
889 | |
890 | // =================================================================== |
891 | |
892 | #if defined(_WIN32) && !defined(__CYGWIN__) |
893 | const char* const CommandLineInterface::kPathSeparator = ";" ; |
894 | #else |
895 | const char* const CommandLineInterface::kPathSeparator = ":" ; |
896 | #endif |
897 | |
898 | CommandLineInterface::CommandLineInterface() |
899 | : direct_dependencies_violation_msg_( |
900 | kDefaultDirectDependenciesViolationMsg) {} |
901 | |
902 | CommandLineInterface::~CommandLineInterface() {} |
903 | |
904 | void CommandLineInterface::RegisterGenerator(const std::string& flag_name, |
905 | CodeGenerator* generator, |
906 | const std::string& help_text) { |
907 | GeneratorInfo info; |
908 | info.flag_name = flag_name; |
909 | info.generator = generator; |
910 | info.help_text = help_text; |
911 | generators_by_flag_name_[flag_name] = info; |
912 | } |
913 | |
914 | void CommandLineInterface::RegisterGenerator( |
915 | const std::string& flag_name, const std::string& option_flag_name, |
916 | CodeGenerator* generator, const std::string& help_text) { |
917 | GeneratorInfo info; |
918 | info.flag_name = flag_name; |
919 | info.option_flag_name = option_flag_name; |
920 | info.generator = generator; |
921 | info.help_text = help_text; |
922 | generators_by_flag_name_[flag_name] = info; |
923 | generators_by_option_name_[option_flag_name] = info; |
924 | } |
925 | |
926 | void CommandLineInterface::AllowPlugins(const std::string& exe_name_prefix) { |
927 | plugin_prefix_ = exe_name_prefix; |
928 | } |
929 | |
930 | namespace { |
931 | |
932 | bool ContainsProto3Optional(const Descriptor* desc) { |
933 | for (int i = 0; i < desc->field_count(); i++) { |
934 | if (desc->field(index: i)->has_optional_keyword()) { |
935 | return true; |
936 | } |
937 | } |
938 | for (int i = 0; i < desc->nested_type_count(); i++) { |
939 | if (ContainsProto3Optional(desc: desc->nested_type(index: i))) { |
940 | return true; |
941 | } |
942 | } |
943 | return false; |
944 | } |
945 | |
946 | bool ContainsProto3Optional(const FileDescriptor* file) { |
947 | if (file->syntax() == FileDescriptor::SYNTAX_PROTO3) { |
948 | for (int i = 0; i < file->message_type_count(); i++) { |
949 | if (ContainsProto3Optional(desc: file->message_type(index: i))) { |
950 | return true; |
951 | } |
952 | } |
953 | } |
954 | return false; |
955 | } |
956 | |
957 | } // namespace |
958 | |
959 | namespace { |
960 | std::unique_ptr<SimpleDescriptorDatabase> |
961 | PopulateSingleSimpleDescriptorDatabase(const std::string& descriptor_set_name); |
962 | } |
963 | |
964 | int CommandLineInterface::Run(int argc, const char* const argv[]) { |
965 | Clear(); |
966 | switch (ParseArguments(argc, argv)) { |
967 | case PARSE_ARGUMENT_DONE_AND_EXIT: |
968 | return 0; |
969 | case PARSE_ARGUMENT_FAIL: |
970 | return 1; |
971 | case PARSE_ARGUMENT_DONE_AND_CONTINUE: |
972 | break; |
973 | } |
974 | |
975 | std::vector<const FileDescriptor*> parsed_files; |
976 | std::unique_ptr<DiskSourceTree> disk_source_tree; |
977 | std::unique_ptr<ErrorPrinter> error_collector; |
978 | std::unique_ptr<DescriptorPool> descriptor_pool; |
979 | |
980 | // The SimpleDescriptorDatabases here are the constituents of the |
981 | // MergedDescriptorDatabase descriptor_set_in_database, so this vector is for |
982 | // managing their lifetimes. Its scope should match descriptor_set_in_database |
983 | std::vector<std::unique_ptr<SimpleDescriptorDatabase>> |
984 | databases_per_descriptor_set; |
985 | std::unique_ptr<MergedDescriptorDatabase> descriptor_set_in_database; |
986 | |
987 | std::unique_ptr<SourceTreeDescriptorDatabase> source_tree_database; |
988 | |
989 | // Any --descriptor_set_in FileDescriptorSet objects will be used as a |
990 | // fallback to input_files on command line, so create that db first. |
991 | if (!descriptor_set_in_names_.empty()) { |
992 | for (const std::string& name : descriptor_set_in_names_) { |
993 | std::unique_ptr<SimpleDescriptorDatabase> database_for_descriptor_set = |
994 | PopulateSingleSimpleDescriptorDatabase(descriptor_set_name: name); |
995 | if (!database_for_descriptor_set) { |
996 | return EXIT_FAILURE; |
997 | } |
998 | databases_per_descriptor_set.push_back( |
999 | x: std::move(database_for_descriptor_set)); |
1000 | } |
1001 | |
1002 | std::vector<DescriptorDatabase*> raw_databases_per_descriptor_set; |
1003 | raw_databases_per_descriptor_set.reserve( |
1004 | n: databases_per_descriptor_set.size()); |
1005 | for (const std::unique_ptr<SimpleDescriptorDatabase>& db : |
1006 | databases_per_descriptor_set) { |
1007 | raw_databases_per_descriptor_set.push_back(x: db.get()); |
1008 | } |
1009 | descriptor_set_in_database.reset( |
1010 | p: new MergedDescriptorDatabase(raw_databases_per_descriptor_set)); |
1011 | } |
1012 | |
1013 | if (proto_path_.empty()) { |
1014 | // If there are no --proto_path flags, then just look in the specified |
1015 | // --descriptor_set_in files. But first, verify that the input files are |
1016 | // there. |
1017 | if (!VerifyInputFilesInDescriptors(fallback_database: descriptor_set_in_database.get())) { |
1018 | return 1; |
1019 | } |
1020 | |
1021 | error_collector.reset(p: new ErrorPrinter(error_format_)); |
1022 | descriptor_pool.reset(p: new DescriptorPool(descriptor_set_in_database.get(), |
1023 | error_collector.get())); |
1024 | } else { |
1025 | disk_source_tree.reset(p: new DiskSourceTree()); |
1026 | if (!InitializeDiskSourceTree(source_tree: disk_source_tree.get(), |
1027 | fallback_database: descriptor_set_in_database.get())) { |
1028 | return 1; |
1029 | } |
1030 | |
1031 | error_collector.reset( |
1032 | p: new ErrorPrinter(error_format_, disk_source_tree.get())); |
1033 | |
1034 | source_tree_database.reset(p: new SourceTreeDescriptorDatabase( |
1035 | disk_source_tree.get(), descriptor_set_in_database.get())); |
1036 | source_tree_database->RecordErrorsTo(error_collector: error_collector.get()); |
1037 | |
1038 | descriptor_pool.reset(p: new DescriptorPool( |
1039 | source_tree_database.get(), |
1040 | source_tree_database->GetValidationErrorCollector())); |
1041 | } |
1042 | |
1043 | descriptor_pool->EnforceWeakDependencies(enforce: true); |
1044 | if (!ParseInputFiles(descriptor_pool: descriptor_pool.get(), source_tree: disk_source_tree.get(), |
1045 | parsed_files: &parsed_files)) { |
1046 | return 1; |
1047 | } |
1048 | |
1049 | |
1050 | // We construct a separate GeneratorContext for each output location. Note |
1051 | // that two code generators may output to the same location, in which case |
1052 | // they should share a single GeneratorContext so that OpenForInsert() works. |
1053 | GeneratorContextMap output_directories; |
1054 | |
1055 | // Generate output. |
1056 | if (mode_ == MODE_COMPILE) { |
1057 | for (int i = 0; i < output_directives_.size(); i++) { |
1058 | std::string output_location = output_directives_[i].output_location; |
1059 | if (!HasSuffixString(str: output_location, suffix: ".zip" ) && |
1060 | !HasSuffixString(str: output_location, suffix: ".jar" ) && |
1061 | !HasSuffixString(str: output_location, suffix: ".srcjar" )) { |
1062 | AddTrailingSlash(path: &output_location); |
1063 | } |
1064 | |
1065 | auto& generator = output_directories[output_location]; |
1066 | |
1067 | if (!generator) { |
1068 | // First time we've seen this output location. |
1069 | generator.reset(p: new GeneratorContextImpl(parsed_files)); |
1070 | } |
1071 | |
1072 | if (!GenerateOutput(parsed_files, output_directive: output_directives_[i], |
1073 | generator_context: generator.get())) { |
1074 | return 1; |
1075 | } |
1076 | } |
1077 | } |
1078 | |
1079 | // Write all output to disk. |
1080 | for (const auto& pair : output_directories) { |
1081 | const std::string& location = pair.first; |
1082 | GeneratorContextImpl* directory = pair.second.get(); |
1083 | if (HasSuffixString(str: location, suffix: "/" )) { |
1084 | if (!directory->WriteAllToDisk(prefix: location)) { |
1085 | return 1; |
1086 | } |
1087 | } else { |
1088 | if (HasSuffixString(str: location, suffix: ".jar" )) { |
1089 | directory->AddJarManifest(); |
1090 | } |
1091 | |
1092 | if (!directory->WriteAllToZip(filename: location)) { |
1093 | return 1; |
1094 | } |
1095 | } |
1096 | } |
1097 | |
1098 | if (!dependency_out_name_.empty()) { |
1099 | GOOGLE_DCHECK(disk_source_tree.get()); |
1100 | if (!GenerateDependencyManifestFile(parsed_files, output_directories, |
1101 | source_tree: disk_source_tree.get())) { |
1102 | return 1; |
1103 | } |
1104 | } |
1105 | |
1106 | if (!descriptor_set_out_name_.empty()) { |
1107 | if (!WriteDescriptorSet(parsed_files)) { |
1108 | return 1; |
1109 | } |
1110 | } |
1111 | |
1112 | if (mode_ == MODE_ENCODE || mode_ == MODE_DECODE) { |
1113 | if (codec_type_.empty()) { |
1114 | // HACK: Define an EmptyMessage type to use for decoding. |
1115 | DescriptorPool pool; |
1116 | FileDescriptorProto file; |
1117 | file.set_name("empty_message.proto" ); |
1118 | file.add_message_type()->set_name("EmptyMessage" ); |
1119 | GOOGLE_CHECK(pool.BuildFile(file) != nullptr); |
1120 | codec_type_ = "EmptyMessage" ; |
1121 | if (!EncodeOrDecode(pool: &pool)) { |
1122 | return 1; |
1123 | } |
1124 | } else { |
1125 | if (!EncodeOrDecode(pool: descriptor_pool.get())) { |
1126 | return 1; |
1127 | } |
1128 | } |
1129 | } |
1130 | |
1131 | if (error_collector->FoundErrors() || |
1132 | (fatal_warnings_ && error_collector->FoundWarnings())) { |
1133 | return 1; |
1134 | } |
1135 | |
1136 | if (mode_ == MODE_PRINT) { |
1137 | switch (print_mode_) { |
1138 | case PRINT_FREE_FIELDS: |
1139 | for (int i = 0; i < parsed_files.size(); ++i) { |
1140 | const FileDescriptor* fd = parsed_files[i]; |
1141 | for (int j = 0; j < fd->message_type_count(); ++j) { |
1142 | PrintFreeFieldNumbers(descriptor: fd->message_type(index: j)); |
1143 | } |
1144 | } |
1145 | break; |
1146 | case PRINT_NONE: |
1147 | GOOGLE_LOG(ERROR) << "If the code reaches here, it usually means a bug of " |
1148 | "flag parsing in the CommandLineInterface." ; |
1149 | return 1; |
1150 | |
1151 | // Do not add a default case. |
1152 | } |
1153 | } |
1154 | |
1155 | return 0; |
1156 | } |
1157 | |
1158 | bool CommandLineInterface::InitializeDiskSourceTree( |
1159 | DiskSourceTree* source_tree, DescriptorDatabase* fallback_database) { |
1160 | AddDefaultProtoPaths(paths: &proto_path_); |
1161 | |
1162 | // Set up the source tree. |
1163 | for (int i = 0; i < proto_path_.size(); i++) { |
1164 | source_tree->MapPath(virtual_path: proto_path_[i].first, disk_path: proto_path_[i].second); |
1165 | } |
1166 | |
1167 | // Map input files to virtual paths if possible. |
1168 | if (!MakeInputsBeProtoPathRelative(source_tree, fallback_database)) { |
1169 | return false; |
1170 | } |
1171 | |
1172 | return true; |
1173 | } |
1174 | |
1175 | namespace { |
1176 | std::unique_ptr<SimpleDescriptorDatabase> |
1177 | PopulateSingleSimpleDescriptorDatabase(const std::string& descriptor_set_name) { |
1178 | int fd; |
1179 | do { |
1180 | fd = open(file: descriptor_set_name.c_str(), O_RDONLY | O_BINARY); |
1181 | } while (fd < 0 && errno == EINTR); |
1182 | if (fd < 0) { |
1183 | std::cerr << descriptor_set_name << ": " << strerror(ENOENT) << std::endl; |
1184 | return nullptr; |
1185 | } |
1186 | |
1187 | FileDescriptorSet file_descriptor_set; |
1188 | bool parsed = file_descriptor_set.ParseFromFileDescriptor(file_descriptor: fd); |
1189 | if (close(fd: fd) != 0) { |
1190 | std::cerr << descriptor_set_name << ": close: " << strerror(errno) |
1191 | << std::endl; |
1192 | return nullptr; |
1193 | } |
1194 | |
1195 | if (!parsed) { |
1196 | std::cerr << descriptor_set_name << ": Unable to parse." << std::endl; |
1197 | return nullptr; |
1198 | } |
1199 | |
1200 | std::unique_ptr<SimpleDescriptorDatabase> database{ |
1201 | new SimpleDescriptorDatabase()}; |
1202 | |
1203 | for (int j = 0; j < file_descriptor_set.file_size(); j++) { |
1204 | FileDescriptorProto previously_added_file_descriptor_proto; |
1205 | if (database->FindFileByName(filename: file_descriptor_set.file(index: j).name(), |
1206 | output: &previously_added_file_descriptor_proto)) { |
1207 | // already present - skip |
1208 | continue; |
1209 | } |
1210 | if (!database->Add(file: file_descriptor_set.file(index: j))) { |
1211 | return nullptr; |
1212 | } |
1213 | } |
1214 | return database; |
1215 | } |
1216 | |
1217 | } // namespace |
1218 | |
1219 | |
1220 | bool CommandLineInterface::VerifyInputFilesInDescriptors( |
1221 | DescriptorDatabase* database) { |
1222 | for (const auto& input_file : input_files_) { |
1223 | FileDescriptorProto file_descriptor; |
1224 | if (!database->FindFileByName(filename: input_file, output: &file_descriptor)) { |
1225 | std::cerr << "Could not find file in descriptor database: " << input_file |
1226 | << ": " << strerror(ENOENT) << std::endl; |
1227 | return false; |
1228 | } |
1229 | |
1230 | // Enforce --disallow_services. |
1231 | if (disallow_services_ && file_descriptor.service_size() > 0) { |
1232 | std::cerr << file_descriptor.name() |
1233 | << ": This file contains services, but " |
1234 | "--disallow_services was used." |
1235 | << std::endl; |
1236 | return false; |
1237 | } |
1238 | |
1239 | } |
1240 | return true; |
1241 | } |
1242 | |
1243 | bool CommandLineInterface::ParseInputFiles( |
1244 | DescriptorPool* descriptor_pool, DiskSourceTree* source_tree, |
1245 | std::vector<const FileDescriptor*>* parsed_files) { |
1246 | |
1247 | if (!proto_path_.empty()) { |
1248 | // Track unused imports in all source files that were loaded from the |
1249 | // filesystem. We do not track unused imports for files loaded from |
1250 | // descriptor sets as they may be programmatically generated in which case |
1251 | // exerting this level of rigor is less desirable. We're also making the |
1252 | // assumption that the initial parse of the proto from the filesystem |
1253 | // was rigorous in checking unused imports and that the descriptor set |
1254 | // being parsed was produced then and that it was subsequent mutations |
1255 | // of that descriptor set that left unused imports. |
1256 | // |
1257 | // Note that relying on proto_path exclusively is limited in that we may |
1258 | // be loading descriptors from both the filesystem and descriptor sets |
1259 | // depending on the invocation. At least for invocations that are |
1260 | // exclusively reading from descriptor sets, we can eliminate this failure |
1261 | // condition. |
1262 | for (const auto& input_file : input_files_) { |
1263 | descriptor_pool->AddUnusedImportTrackFile(file_name: input_file); |
1264 | } |
1265 | } |
1266 | |
1267 | bool result = true; |
1268 | // Parse each file. |
1269 | for (const auto& input_file : input_files_) { |
1270 | // Import the file. |
1271 | const FileDescriptor* parsed_file = |
1272 | descriptor_pool->FindFileByName(name: input_file); |
1273 | if (parsed_file == nullptr) { |
1274 | result = false; |
1275 | break; |
1276 | } |
1277 | parsed_files->push_back(x: parsed_file); |
1278 | |
1279 | // Enforce --disallow_services. |
1280 | if (disallow_services_ && parsed_file->service_count() > 0) { |
1281 | std::cerr << parsed_file->name() |
1282 | << ": This file contains services, but " |
1283 | "--disallow_services was used." |
1284 | << std::endl; |
1285 | result = false; |
1286 | break; |
1287 | } |
1288 | |
1289 | |
1290 | // Enforce --direct_dependencies |
1291 | if (direct_dependencies_explicitly_set_) { |
1292 | bool indirect_imports = false; |
1293 | for (int i = 0; i < parsed_file->dependency_count(); i++) { |
1294 | if (direct_dependencies_.find(x: parsed_file->dependency(index: i)->name()) == |
1295 | direct_dependencies_.end()) { |
1296 | indirect_imports = true; |
1297 | std::cerr << parsed_file->name() << ": " |
1298 | << StringReplace(s: direct_dependencies_violation_msg_, oldsub: "%s" , |
1299 | newsub: parsed_file->dependency(index: i)->name(), |
1300 | replace_all: true /* replace_all */) |
1301 | << std::endl; |
1302 | } |
1303 | } |
1304 | if (indirect_imports) { |
1305 | result = false; |
1306 | break; |
1307 | } |
1308 | } |
1309 | } |
1310 | descriptor_pool->ClearUnusedImportTrackFiles(); |
1311 | return result; |
1312 | } |
1313 | |
1314 | void CommandLineInterface::Clear() { |
1315 | // Clear all members that are set by Run(). Note that we must not clear |
1316 | // members which are set by other methods before Run() is called. |
1317 | executable_name_.clear(); |
1318 | proto_path_.clear(); |
1319 | input_files_.clear(); |
1320 | direct_dependencies_.clear(); |
1321 | direct_dependencies_violation_msg_ = kDefaultDirectDependenciesViolationMsg; |
1322 | output_directives_.clear(); |
1323 | codec_type_.clear(); |
1324 | descriptor_set_in_names_.clear(); |
1325 | descriptor_set_out_name_.clear(); |
1326 | dependency_out_name_.clear(); |
1327 | |
1328 | |
1329 | mode_ = MODE_COMPILE; |
1330 | print_mode_ = PRINT_NONE; |
1331 | imports_in_descriptor_set_ = false; |
1332 | source_info_in_descriptor_set_ = false; |
1333 | disallow_services_ = false; |
1334 | direct_dependencies_explicitly_set_ = false; |
1335 | deterministic_output_ = false; |
1336 | } |
1337 | |
1338 | bool CommandLineInterface::MakeProtoProtoPathRelative( |
1339 | DiskSourceTree* source_tree, std::string* proto, |
1340 | DescriptorDatabase* fallback_database) { |
1341 | // If it's in the fallback db, don't report non-existent file errors. |
1342 | FileDescriptorProto fallback_file; |
1343 | bool in_fallback_database = |
1344 | fallback_database != nullptr && |
1345 | fallback_database->FindFileByName(filename: *proto, output: &fallback_file); |
1346 | |
1347 | // If the input file path is not a physical file path, it must be a virtual |
1348 | // path. |
1349 | if (access(name: proto->c_str(), F_OK) < 0) { |
1350 | std::string disk_file; |
1351 | if (source_tree->VirtualFileToDiskFile(virtual_file: *proto, disk_file: &disk_file) || |
1352 | in_fallback_database) { |
1353 | return true; |
1354 | } else { |
1355 | std::cerr << "Could not make proto path relative: " << *proto << ": " |
1356 | << strerror(ENOENT) << std::endl; |
1357 | return false; |
1358 | } |
1359 | } |
1360 | |
1361 | std::string virtual_file, shadowing_disk_file; |
1362 | switch (source_tree->DiskFileToVirtualFile(disk_file: *proto, virtual_file: &virtual_file, |
1363 | shadowing_disk_file: &shadowing_disk_file)) { |
1364 | case DiskSourceTree::SUCCESS: |
1365 | *proto = virtual_file; |
1366 | break; |
1367 | case DiskSourceTree::SHADOWED: |
1368 | std::cerr << *proto << ": Input is shadowed in the --proto_path by \"" |
1369 | << shadowing_disk_file |
1370 | << "\". Either use the latter file as your input or reorder " |
1371 | "the --proto_path so that the former file's location " |
1372 | "comes first." |
1373 | << std::endl; |
1374 | return false; |
1375 | case DiskSourceTree::CANNOT_OPEN: { |
1376 | if (in_fallback_database) { |
1377 | return true; |
1378 | } |
1379 | std::string error_str = source_tree->GetLastErrorMessage().empty() |
1380 | ? strerror(errno) |
1381 | : source_tree->GetLastErrorMessage(); |
1382 | std::cerr << "Could not map to virtual file: " << *proto << ": " |
1383 | << error_str << std::endl; |
1384 | return false; |
1385 | } |
1386 | case DiskSourceTree::NO_MAPPING: { |
1387 | // Try to interpret the path as a virtual path. |
1388 | std::string disk_file; |
1389 | if (source_tree->VirtualFileToDiskFile(virtual_file: *proto, disk_file: &disk_file) || |
1390 | in_fallback_database) { |
1391 | return true; |
1392 | } else { |
1393 | // The input file path can't be mapped to any --proto_path and it also |
1394 | // can't be interpreted as a virtual path. |
1395 | std::cerr |
1396 | << *proto |
1397 | << ": File does not reside within any path " |
1398 | "specified using --proto_path (or -I). You must specify a " |
1399 | "--proto_path which encompasses this file. Note that the " |
1400 | "proto_path must be an exact prefix of the .proto file " |
1401 | "names -- protoc is too dumb to figure out when two paths " |
1402 | "(e.g. absolute and relative) are equivalent (it's harder " |
1403 | "than you think)." |
1404 | << std::endl; |
1405 | return false; |
1406 | } |
1407 | } |
1408 | } |
1409 | return true; |
1410 | } |
1411 | |
1412 | bool CommandLineInterface::MakeInputsBeProtoPathRelative( |
1413 | DiskSourceTree* source_tree, DescriptorDatabase* fallback_database) { |
1414 | for (auto& input_file : input_files_) { |
1415 | if (!MakeProtoProtoPathRelative(source_tree, proto: &input_file, |
1416 | fallback_database)) { |
1417 | return false; |
1418 | } |
1419 | } |
1420 | |
1421 | return true; |
1422 | } |
1423 | |
1424 | |
1425 | bool CommandLineInterface::ExpandArgumentFile( |
1426 | const std::string& file, std::vector<std::string>* arguments) { |
1427 | // The argument file is searched in the working directory only. We don't |
1428 | // use the proto import path here. |
1429 | std::ifstream file_stream(file.c_str()); |
1430 | if (!file_stream.is_open()) { |
1431 | return false; |
1432 | } |
1433 | std::string argument; |
1434 | // We don't support any kind of shell expansion right now. |
1435 | while (std::getline(is&: file_stream, str&: argument)) { |
1436 | arguments->push_back(x: argument); |
1437 | } |
1438 | return true; |
1439 | } |
1440 | |
1441 | CommandLineInterface::ParseArgumentStatus CommandLineInterface::ParseArguments( |
1442 | int argc, const char* const argv[]) { |
1443 | executable_name_ = argv[0]; |
1444 | |
1445 | std::vector<std::string> arguments; |
1446 | for (int i = 1; i < argc; ++i) { |
1447 | if (argv[i][0] == '@') { |
1448 | if (!ExpandArgumentFile(file: argv[i] + 1, arguments: &arguments)) { |
1449 | std::cerr << "Failed to open argument file: " << (argv[i] + 1) |
1450 | << std::endl; |
1451 | return PARSE_ARGUMENT_FAIL; |
1452 | } |
1453 | continue; |
1454 | } |
1455 | arguments.push_back(x: argv[i]); |
1456 | } |
1457 | |
1458 | // if no arguments are given, show help |
1459 | if (arguments.empty()) { |
1460 | PrintHelpText(); |
1461 | return PARSE_ARGUMENT_DONE_AND_EXIT; // Exit without running compiler. |
1462 | } |
1463 | |
1464 | // Iterate through all arguments and parse them. |
1465 | for (int i = 0; i < arguments.size(); ++i) { |
1466 | std::string name, value; |
1467 | |
1468 | if (ParseArgument(arg: arguments[i].c_str(), name: &name, value: &value)) { |
1469 | // Returned true => Use the next argument as the flag value. |
1470 | if (i + 1 == arguments.size() || arguments[i + 1][0] == '-') { |
1471 | std::cerr << "Missing value for flag: " << name << std::endl; |
1472 | if (name == "--decode" ) { |
1473 | std::cerr << "To decode an unknown message, use --decode_raw." |
1474 | << std::endl; |
1475 | } |
1476 | return PARSE_ARGUMENT_FAIL; |
1477 | } else { |
1478 | ++i; |
1479 | value = arguments[i]; |
1480 | } |
1481 | } |
1482 | |
1483 | ParseArgumentStatus status = InterpretArgument(name, value); |
1484 | if (status != PARSE_ARGUMENT_DONE_AND_CONTINUE) return status; |
1485 | } |
1486 | |
1487 | // Make sure each plugin option has a matching plugin output. |
1488 | bool foundUnknownPluginOption = false; |
1489 | for (std::map<std::string, std::string>::const_iterator i = |
1490 | plugin_parameters_.begin(); |
1491 | i != plugin_parameters_.end(); ++i) { |
1492 | if (plugins_.find(x: i->first) != plugins_.end()) { |
1493 | continue; |
1494 | } |
1495 | bool foundImplicitPlugin = false; |
1496 | for (std::vector<OutputDirective>::const_iterator j = |
1497 | output_directives_.begin(); |
1498 | j != output_directives_.end(); ++j) { |
1499 | if (j->generator == nullptr) { |
1500 | std::string plugin_name = PluginName(plugin_prefix: plugin_prefix_, directive: j->name); |
1501 | if (plugin_name == i->first) { |
1502 | foundImplicitPlugin = true; |
1503 | break; |
1504 | } |
1505 | } |
1506 | } |
1507 | if (!foundImplicitPlugin) { |
1508 | std::cerr << "Unknown flag: " |
1509 | // strip prefix + "gen-" and add back "_opt" |
1510 | << "--" + i->first.substr(pos: plugin_prefix_.size() + 4) + "_opt" |
1511 | << std::endl; |
1512 | foundUnknownPluginOption = true; |
1513 | } |
1514 | } |
1515 | if (foundUnknownPluginOption) { |
1516 | return PARSE_ARGUMENT_FAIL; |
1517 | } |
1518 | |
1519 | // The --proto_path & --descriptor_set_in flags both specify places to look |
1520 | // for proto files. If neither were given, use the current working directory. |
1521 | if (proto_path_.empty() && descriptor_set_in_names_.empty()) { |
1522 | // Don't use make_pair as the old/default standard library on Solaris |
1523 | // doesn't support it without explicit template parameters, which are |
1524 | // incompatible with C++0x's make_pair. |
1525 | proto_path_.push_back(x: std::pair<std::string, std::string>("" , "." )); |
1526 | } |
1527 | |
1528 | // Check error cases that span multiple flag values. |
1529 | bool missing_proto_definitions = false; |
1530 | switch (mode_) { |
1531 | case MODE_COMPILE: |
1532 | missing_proto_definitions = input_files_.empty(); |
1533 | break; |
1534 | case MODE_DECODE: |
1535 | // Handle --decode_raw separately, since it requires that no proto |
1536 | // definitions are specified. |
1537 | if (codec_type_.empty()) { |
1538 | if (!input_files_.empty() || !descriptor_set_in_names_.empty()) { |
1539 | std::cerr |
1540 | << "When using --decode_raw, no input files should be given." |
1541 | << std::endl; |
1542 | return PARSE_ARGUMENT_FAIL; |
1543 | } |
1544 | missing_proto_definitions = false; |
1545 | break; // only for --decode_raw |
1546 | } |
1547 | // --decode (not raw) is handled the same way as the rest of the modes. |
1548 | PROTOBUF_FALLTHROUGH_INTENDED; |
1549 | case MODE_ENCODE: |
1550 | case MODE_PRINT: |
1551 | missing_proto_definitions = |
1552 | input_files_.empty() && descriptor_set_in_names_.empty(); |
1553 | break; |
1554 | default: |
1555 | GOOGLE_LOG(FATAL) << "Unexpected mode: " << mode_; |
1556 | } |
1557 | if (missing_proto_definitions) { |
1558 | std::cerr << "Missing input file." << std::endl; |
1559 | return PARSE_ARGUMENT_FAIL; |
1560 | } |
1561 | if (mode_ == MODE_COMPILE && output_directives_.empty() && |
1562 | descriptor_set_out_name_.empty()) { |
1563 | std::cerr << "Missing output directives." << std::endl; |
1564 | return PARSE_ARGUMENT_FAIL; |
1565 | } |
1566 | if (mode_ != MODE_COMPILE && !dependency_out_name_.empty()) { |
1567 | std::cerr << "Can only use --dependency_out=FILE when generating code." |
1568 | << std::endl; |
1569 | return PARSE_ARGUMENT_FAIL; |
1570 | } |
1571 | if (mode_ != MODE_ENCODE && deterministic_output_) { |
1572 | std::cerr << "Can only use --deterministic_output with --encode." |
1573 | << std::endl; |
1574 | return PARSE_ARGUMENT_FAIL; |
1575 | } |
1576 | if (!dependency_out_name_.empty() && input_files_.size() > 1) { |
1577 | std::cerr |
1578 | << "Can only process one input file when using --dependency_out=FILE." |
1579 | << std::endl; |
1580 | return PARSE_ARGUMENT_FAIL; |
1581 | } |
1582 | if (imports_in_descriptor_set_ && descriptor_set_out_name_.empty()) { |
1583 | std::cerr << "--include_imports only makes sense when combined with " |
1584 | "--descriptor_set_out." |
1585 | << std::endl; |
1586 | } |
1587 | if (source_info_in_descriptor_set_ && descriptor_set_out_name_.empty()) { |
1588 | std::cerr << "--include_source_info only makes sense when combined with " |
1589 | "--descriptor_set_out." |
1590 | << std::endl; |
1591 | } |
1592 | |
1593 | return PARSE_ARGUMENT_DONE_AND_CONTINUE; |
1594 | } |
1595 | |
1596 | bool CommandLineInterface::ParseArgument(const char* arg, std::string* name, |
1597 | std::string* value) { |
1598 | bool parsed_value = false; |
1599 | |
1600 | if (arg[0] != '-') { |
1601 | // Not a flag. |
1602 | name->clear(); |
1603 | parsed_value = true; |
1604 | *value = arg; |
1605 | } else if (arg[1] == '-') { |
1606 | // Two dashes: Multi-character name, with '=' separating name and |
1607 | // value. |
1608 | const char* equals_pos = strchr(s: arg, c: '='); |
1609 | if (equals_pos != nullptr) { |
1610 | *name = std::string(arg, equals_pos - arg); |
1611 | *value = equals_pos + 1; |
1612 | parsed_value = true; |
1613 | } else { |
1614 | *name = arg; |
1615 | } |
1616 | } else { |
1617 | // One dash: One-character name, all subsequent characters are the |
1618 | // value. |
1619 | if (arg[1] == '\0') { |
1620 | // arg is just "-". We treat this as an input file, except that at |
1621 | // present this will just lead to a "file not found" error. |
1622 | name->clear(); |
1623 | *value = arg; |
1624 | parsed_value = true; |
1625 | } else { |
1626 | *name = std::string(arg, 2); |
1627 | *value = arg + 2; |
1628 | parsed_value = !value->empty(); |
1629 | } |
1630 | } |
1631 | |
1632 | // Need to return true iff the next arg should be used as the value for this |
1633 | // one, false otherwise. |
1634 | |
1635 | if (parsed_value) { |
1636 | // We already parsed a value for this flag. |
1637 | return false; |
1638 | } |
1639 | |
1640 | if (*name == "-h" || *name == "--help" || *name == "--disallow_services" || |
1641 | *name == "--include_imports" || *name == "--include_source_info" || |
1642 | *name == "--version" || *name == "--decode_raw" || |
1643 | *name == "--print_free_field_numbers" || |
1644 | *name == "--experimental_allow_proto3_optional" || |
1645 | *name == "--deterministic_output" || *name == "--fatal_warnings" ) { |
1646 | // HACK: These are the only flags that don't take a value. |
1647 | // They probably should not be hard-coded like this but for now it's |
1648 | // not worth doing better. |
1649 | return false; |
1650 | } |
1651 | |
1652 | // Next argument is the flag value. |
1653 | return true; |
1654 | } |
1655 | |
1656 | CommandLineInterface::ParseArgumentStatus |
1657 | CommandLineInterface::InterpretArgument(const std::string& name, |
1658 | const std::string& value) { |
1659 | if (name.empty()) { |
1660 | // Not a flag. Just a filename. |
1661 | if (value.empty()) { |
1662 | std::cerr |
1663 | << "You seem to have passed an empty string as one of the " |
1664 | "arguments to " |
1665 | << executable_name_ |
1666 | << ". This is actually " |
1667 | "sort of hard to do. Congrats. Unfortunately it is not valid " |
1668 | "input so the program is going to die now." |
1669 | << std::endl; |
1670 | return PARSE_ARGUMENT_FAIL; |
1671 | } |
1672 | |
1673 | #if defined(_WIN32) |
1674 | // On Windows, the shell (typically cmd.exe) does not expand wildcards in |
1675 | // file names (e.g. foo\*.proto), so we do it ourselves. |
1676 | switch (google::protobuf::io::win32::ExpandWildcards( |
1677 | value, [this](const std::string& path) { |
1678 | this->input_files_.push_back(path); |
1679 | })) { |
1680 | case google::protobuf::io::win32::ExpandWildcardsResult::kSuccess: |
1681 | break; |
1682 | case google::protobuf::io::win32::ExpandWildcardsResult:: |
1683 | kErrorNoMatchingFile: |
1684 | // Path does not exist, is not a file, or it's longer than MAX_PATH and |
1685 | // long path handling is disabled. |
1686 | std::cerr << "Invalid file name pattern or missing input file \"" |
1687 | << value << "\"" << std::endl; |
1688 | return PARSE_ARGUMENT_FAIL; |
1689 | default: |
1690 | std::cerr << "Cannot convert path \"" << value |
1691 | << "\" to or from Windows style" << std::endl; |
1692 | return PARSE_ARGUMENT_FAIL; |
1693 | } |
1694 | #else // not _WIN32 |
1695 | // On other platforms than Windows (e.g. Linux, Mac OS) the shell (typically |
1696 | // Bash) expands wildcards. |
1697 | input_files_.push_back(x: value); |
1698 | #endif // _WIN32 |
1699 | |
1700 | } else if (name == "-I" || name == "--proto_path" ) { |
1701 | // Java's -classpath (and some other languages) delimits path components |
1702 | // with colons. Let's accept that syntax too just to make things more |
1703 | // intuitive. |
1704 | std::vector<std::string> parts = Split( |
1705 | full: value, delim: CommandLineInterface::kPathSeparator, |
1706 | skip_empty: true); |
1707 | |
1708 | for (int i = 0; i < parts.size(); i++) { |
1709 | std::string virtual_path; |
1710 | std::string disk_path; |
1711 | |
1712 | std::string::size_type equals_pos = parts[i].find_first_of(c: '='); |
1713 | if (equals_pos == std::string::npos) { |
1714 | virtual_path = "" ; |
1715 | disk_path = parts[i]; |
1716 | } else { |
1717 | virtual_path = parts[i].substr(pos: 0, n: equals_pos); |
1718 | disk_path = parts[i].substr(pos: equals_pos + 1); |
1719 | } |
1720 | |
1721 | if (disk_path.empty()) { |
1722 | std::cerr |
1723 | << "--proto_path passed empty directory name. (Use \".\" for " |
1724 | "current directory.)" |
1725 | << std::endl; |
1726 | return PARSE_ARGUMENT_FAIL; |
1727 | } |
1728 | |
1729 | // Make sure disk path exists, warn otherwise. |
1730 | if (access(name: disk_path.c_str(), F_OK) < 0) { |
1731 | // Try the original path; it may have just happened to have a '=' in it. |
1732 | if (access(name: parts[i].c_str(), F_OK) < 0) { |
1733 | std::cerr << disk_path << ": warning: directory does not exist." |
1734 | << std::endl; |
1735 | } else { |
1736 | virtual_path = "" ; |
1737 | disk_path = parts[i]; |
1738 | } |
1739 | } |
1740 | |
1741 | // Don't use make_pair as the old/default standard library on Solaris |
1742 | // doesn't support it without explicit template parameters, which are |
1743 | // incompatible with C++0x's make_pair. |
1744 | proto_path_.push_back( |
1745 | x: std::pair<std::string, std::string>(virtual_path, disk_path)); |
1746 | } |
1747 | |
1748 | } else if (name == "--direct_dependencies" ) { |
1749 | if (direct_dependencies_explicitly_set_) { |
1750 | std::cerr << name |
1751 | << " may only be passed once. To specify multiple " |
1752 | "direct dependencies, pass them all as a single " |
1753 | "parameter separated by ':'." |
1754 | << std::endl; |
1755 | return PARSE_ARGUMENT_FAIL; |
1756 | } |
1757 | |
1758 | direct_dependencies_explicitly_set_ = true; |
1759 | std::vector<std::string> direct = |
1760 | Split(full: value, delim: ":" , skip_empty: true); |
1761 | GOOGLE_DCHECK(direct_dependencies_.empty()); |
1762 | direct_dependencies_.insert(first: direct.begin(), last: direct.end()); |
1763 | |
1764 | } else if (name == "--direct_dependencies_violation_msg" ) { |
1765 | direct_dependencies_violation_msg_ = value; |
1766 | |
1767 | } else if (name == "--descriptor_set_in" ) { |
1768 | if (!descriptor_set_in_names_.empty()) { |
1769 | std::cerr << name |
1770 | << " may only be passed once. To specify multiple " |
1771 | "descriptor sets, pass them all as a single " |
1772 | "parameter separated by '" |
1773 | << CommandLineInterface::kPathSeparator << "'." << std::endl; |
1774 | return PARSE_ARGUMENT_FAIL; |
1775 | } |
1776 | if (value.empty()) { |
1777 | std::cerr << name << " requires a non-empty value." << std::endl; |
1778 | return PARSE_ARGUMENT_FAIL; |
1779 | } |
1780 | if (!dependency_out_name_.empty()) { |
1781 | std::cerr << name << " cannot be used with --dependency_out." |
1782 | << std::endl; |
1783 | return PARSE_ARGUMENT_FAIL; |
1784 | } |
1785 | |
1786 | descriptor_set_in_names_ = Split( |
1787 | full: value, delim: CommandLineInterface::kPathSeparator, |
1788 | skip_empty: true); |
1789 | |
1790 | } else if (name == "-o" || name == "--descriptor_set_out" ) { |
1791 | if (!descriptor_set_out_name_.empty()) { |
1792 | std::cerr << name << " may only be passed once." << std::endl; |
1793 | return PARSE_ARGUMENT_FAIL; |
1794 | } |
1795 | if (value.empty()) { |
1796 | std::cerr << name << " requires a non-empty value." << std::endl; |
1797 | return PARSE_ARGUMENT_FAIL; |
1798 | } |
1799 | if (mode_ != MODE_COMPILE) { |
1800 | std::cerr |
1801 | << "Cannot use --encode or --decode and generate descriptors at the " |
1802 | "same time." |
1803 | << std::endl; |
1804 | return PARSE_ARGUMENT_FAIL; |
1805 | } |
1806 | descriptor_set_out_name_ = value; |
1807 | |
1808 | } else if (name == "--dependency_out" ) { |
1809 | if (!dependency_out_name_.empty()) { |
1810 | std::cerr << name << " may only be passed once." << std::endl; |
1811 | return PARSE_ARGUMENT_FAIL; |
1812 | } |
1813 | if (value.empty()) { |
1814 | std::cerr << name << " requires a non-empty value." << std::endl; |
1815 | return PARSE_ARGUMENT_FAIL; |
1816 | } |
1817 | if (!descriptor_set_in_names_.empty()) { |
1818 | std::cerr << name << " cannot be used with --descriptor_set_in." |
1819 | << std::endl; |
1820 | return PARSE_ARGUMENT_FAIL; |
1821 | } |
1822 | dependency_out_name_ = value; |
1823 | |
1824 | } else if (name == "--include_imports" ) { |
1825 | if (imports_in_descriptor_set_) { |
1826 | std::cerr << name << " may only be passed once." << std::endl; |
1827 | return PARSE_ARGUMENT_FAIL; |
1828 | } |
1829 | imports_in_descriptor_set_ = true; |
1830 | |
1831 | } else if (name == "--include_source_info" ) { |
1832 | if (source_info_in_descriptor_set_) { |
1833 | std::cerr << name << " may only be passed once." << std::endl; |
1834 | return PARSE_ARGUMENT_FAIL; |
1835 | } |
1836 | source_info_in_descriptor_set_ = true; |
1837 | |
1838 | } else if (name == "-h" || name == "--help" ) { |
1839 | PrintHelpText(); |
1840 | return PARSE_ARGUMENT_DONE_AND_EXIT; // Exit without running compiler. |
1841 | |
1842 | } else if (name == "--version" ) { |
1843 | if (!version_info_.empty()) { |
1844 | std::cout << version_info_ << std::endl; |
1845 | } |
1846 | std::cout << "libprotoc " << internal::VersionString(PROTOBUF_VERSION) |
1847 | << PROTOBUF_VERSION_SUFFIX << std::endl; |
1848 | return PARSE_ARGUMENT_DONE_AND_EXIT; // Exit without running compiler. |
1849 | |
1850 | } else if (name == "--disallow_services" ) { |
1851 | disallow_services_ = true; |
1852 | |
1853 | |
1854 | } else if (name == "--experimental_allow_proto3_optional" ) { |
1855 | // Flag is no longer observed, but we allow it for backward compat. |
1856 | } else if (name == "--encode" || name == "--decode" || |
1857 | name == "--decode_raw" ) { |
1858 | if (mode_ != MODE_COMPILE) { |
1859 | std::cerr << "Only one of --encode and --decode can be specified." |
1860 | << std::endl; |
1861 | return PARSE_ARGUMENT_FAIL; |
1862 | } |
1863 | if (!output_directives_.empty() || !descriptor_set_out_name_.empty()) { |
1864 | std::cerr << "Cannot use " << name |
1865 | << " and generate code or descriptors at the same time." |
1866 | << std::endl; |
1867 | return PARSE_ARGUMENT_FAIL; |
1868 | } |
1869 | |
1870 | mode_ = (name == "--encode" ) ? MODE_ENCODE : MODE_DECODE; |
1871 | |
1872 | if (value.empty() && name != "--decode_raw" ) { |
1873 | std::cerr << "Type name for " << name << " cannot be blank." << std::endl; |
1874 | if (name == "--decode" ) { |
1875 | std::cerr << "To decode an unknown message, use --decode_raw." |
1876 | << std::endl; |
1877 | } |
1878 | return PARSE_ARGUMENT_FAIL; |
1879 | } else if (!value.empty() && name == "--decode_raw" ) { |
1880 | std::cerr << "--decode_raw does not take a parameter." << std::endl; |
1881 | return PARSE_ARGUMENT_FAIL; |
1882 | } |
1883 | |
1884 | codec_type_ = value; |
1885 | |
1886 | } else if (name == "--deterministic_output" ) { |
1887 | deterministic_output_ = true; |
1888 | |
1889 | } else if (name == "--error_format" ) { |
1890 | if (value == "gcc" ) { |
1891 | error_format_ = ERROR_FORMAT_GCC; |
1892 | } else if (value == "msvs" ) { |
1893 | error_format_ = ERROR_FORMAT_MSVS; |
1894 | } else { |
1895 | std::cerr << "Unknown error format: " << value << std::endl; |
1896 | return PARSE_ARGUMENT_FAIL; |
1897 | } |
1898 | |
1899 | } else if (name == "--fatal_warnings" ) { |
1900 | if (fatal_warnings_) { |
1901 | std::cerr << name << " may only be passed once." << std::endl; |
1902 | return PARSE_ARGUMENT_FAIL; |
1903 | } |
1904 | fatal_warnings_ = true; |
1905 | } else if (name == "--plugin" ) { |
1906 | if (plugin_prefix_.empty()) { |
1907 | std::cerr << "This compiler does not support plugins." << std::endl; |
1908 | return PARSE_ARGUMENT_FAIL; |
1909 | } |
1910 | |
1911 | std::string plugin_name; |
1912 | std::string path; |
1913 | |
1914 | std::string::size_type equals_pos = value.find_first_of(c: '='); |
1915 | if (equals_pos == std::string::npos) { |
1916 | // Use the basename of the file. |
1917 | std::string::size_type slash_pos = value.find_last_of(c: '/'); |
1918 | if (slash_pos == std::string::npos) { |
1919 | plugin_name = value; |
1920 | } else { |
1921 | plugin_name = value.substr(pos: slash_pos + 1); |
1922 | } |
1923 | path = value; |
1924 | } else { |
1925 | plugin_name = value.substr(pos: 0, n: equals_pos); |
1926 | path = value.substr(pos: equals_pos + 1); |
1927 | } |
1928 | |
1929 | plugins_[plugin_name] = path; |
1930 | |
1931 | } else if (name == "--print_free_field_numbers" ) { |
1932 | if (mode_ != MODE_COMPILE) { |
1933 | std::cerr << "Cannot use " << name |
1934 | << " and use --encode, --decode or print " |
1935 | << "other info at the same time." << std::endl; |
1936 | return PARSE_ARGUMENT_FAIL; |
1937 | } |
1938 | if (!output_directives_.empty() || !descriptor_set_out_name_.empty()) { |
1939 | std::cerr << "Cannot use " << name |
1940 | << " and generate code or descriptors at the same time." |
1941 | << std::endl; |
1942 | return PARSE_ARGUMENT_FAIL; |
1943 | } |
1944 | mode_ = MODE_PRINT; |
1945 | print_mode_ = PRINT_FREE_FIELDS; |
1946 | } else { |
1947 | // Some other flag. Look it up in the generators list. |
1948 | const GeneratorInfo* generator_info = |
1949 | FindOrNull(collection&: generators_by_flag_name_, key: name); |
1950 | if (generator_info == nullptr && |
1951 | (plugin_prefix_.empty() || !HasSuffixString(str: name, suffix: "_out" ))) { |
1952 | // Check if it's a generator option flag. |
1953 | generator_info = FindOrNull(collection&: generators_by_option_name_, key: name); |
1954 | if (generator_info != nullptr) { |
1955 | std::string* parameters = |
1956 | &generator_parameters_[generator_info->flag_name]; |
1957 | if (!parameters->empty()) { |
1958 | parameters->append(s: "," ); |
1959 | } |
1960 | parameters->append(str: value); |
1961 | } else if (HasPrefixString(str: name, prefix: "--" ) && HasSuffixString(str: name, suffix: "_opt" )) { |
1962 | std::string* parameters = |
1963 | &plugin_parameters_[PluginName(plugin_prefix: plugin_prefix_, directive: name)]; |
1964 | if (!parameters->empty()) { |
1965 | parameters->append(s: "," ); |
1966 | } |
1967 | parameters->append(str: value); |
1968 | } else { |
1969 | std::cerr << "Unknown flag: " << name << std::endl; |
1970 | return PARSE_ARGUMENT_FAIL; |
1971 | } |
1972 | } else { |
1973 | // It's an output flag. Add it to the output directives. |
1974 | if (mode_ != MODE_COMPILE) { |
1975 | std::cerr << "Cannot use --encode, --decode or print .proto info and " |
1976 | "generate code at the same time." |
1977 | << std::endl; |
1978 | return PARSE_ARGUMENT_FAIL; |
1979 | } |
1980 | |
1981 | OutputDirective directive; |
1982 | directive.name = name; |
1983 | if (generator_info == nullptr) { |
1984 | directive.generator = nullptr; |
1985 | } else { |
1986 | directive.generator = generator_info->generator; |
1987 | } |
1988 | |
1989 | // Split value at ':' to separate the generator parameter from the |
1990 | // filename. However, avoid doing this if the colon is part of a valid |
1991 | // Windows-style absolute path. |
1992 | std::string::size_type colon_pos = value.find_first_of(c: ':'); |
1993 | if (colon_pos == std::string::npos || IsWindowsAbsolutePath(text: value)) { |
1994 | directive.output_location = value; |
1995 | } else { |
1996 | directive.parameter = value.substr(pos: 0, n: colon_pos); |
1997 | directive.output_location = value.substr(pos: colon_pos + 1); |
1998 | } |
1999 | |
2000 | output_directives_.push_back(x: directive); |
2001 | } |
2002 | } |
2003 | |
2004 | return PARSE_ARGUMENT_DONE_AND_CONTINUE; |
2005 | } |
2006 | |
2007 | void CommandLineInterface::PrintHelpText() { |
2008 | // Sorry for indentation here; line wrapping would be uglier. |
2009 | std::cout << "Usage: " << executable_name_ << " [OPTION] PROTO_FILES" ; |
2010 | std::cout << R"( |
2011 | Parse PROTO_FILES and generate output based on the options given: |
2012 | -IPATH, --proto_path=PATH Specify the directory in which to search for |
2013 | imports. May be specified multiple times; |
2014 | directories will be searched in order. If not |
2015 | given, the current working directory is used. |
2016 | If not found in any of the these directories, |
2017 | the --descriptor_set_in descriptors will be |
2018 | checked for required proto file. |
2019 | --version Show version info and exit. |
2020 | -h, --help Show this text and exit. |
2021 | --encode=MESSAGE_TYPE Read a text-format message of the given type |
2022 | from standard input and write it in binary |
2023 | to standard output. The message type must |
2024 | be defined in PROTO_FILES or their imports. |
2025 | --deterministic_output When using --encode, ensure map fields are |
2026 | deterministically ordered. Note that this order |
2027 | is not canonical, and changes across builds or |
2028 | releases of protoc. |
2029 | --decode=MESSAGE_TYPE Read a binary message of the given type from |
2030 | standard input and write it in text format |
2031 | to standard output. The message type must |
2032 | be defined in PROTO_FILES or their imports. |
2033 | --decode_raw Read an arbitrary protocol message from |
2034 | standard input and write the raw tag/value |
2035 | pairs in text format to standard output. No |
2036 | PROTO_FILES should be given when using this |
2037 | flag. |
2038 | --descriptor_set_in=FILES Specifies a delimited list of FILES |
2039 | each containing a FileDescriptorSet (a |
2040 | protocol buffer defined in descriptor.proto). |
2041 | The FileDescriptor for each of the PROTO_FILES |
2042 | provided will be loaded from these |
2043 | FileDescriptorSets. If a FileDescriptor |
2044 | appears multiple times, the first occurrence |
2045 | will be used. |
2046 | -oFILE, Writes a FileDescriptorSet (a protocol buffer, |
2047 | --descriptor_set_out=FILE defined in descriptor.proto) containing all of |
2048 | the input files to FILE. |
2049 | --include_imports When using --descriptor_set_out, also include |
2050 | all dependencies of the input files in the |
2051 | set, so that the set is self-contained. |
2052 | --include_source_info When using --descriptor_set_out, do not strip |
2053 | SourceCodeInfo from the FileDescriptorProto. |
2054 | This results in vastly larger descriptors that |
2055 | include information about the original |
2056 | location of each decl in the source file as |
2057 | well as surrounding comments. |
2058 | --dependency_out=FILE Write a dependency output file in the format |
2059 | expected by make. This writes the transitive |
2060 | set of input file paths to FILE |
2061 | --error_format=FORMAT Set the format in which to print errors. |
2062 | FORMAT may be 'gcc' (the default) or 'msvs' |
2063 | (Microsoft Visual Studio format). |
2064 | --fatal_warnings Make warnings be fatal (similar to -Werr in |
2065 | gcc). This flag will make protoc return |
2066 | with a non-zero exit code if any warnings |
2067 | are generated. |
2068 | --print_free_field_numbers Print the free field numbers of the messages |
2069 | defined in the given proto files. Groups share |
2070 | the same field number space with the parent |
2071 | message. Extension ranges are counted as |
2072 | occupied fields numbers.)" ; |
2073 | if (!plugin_prefix_.empty()) { |
2074 | std::cout << R"( |
2075 | --plugin=EXECUTABLE Specifies a plugin executable to use. |
2076 | Normally, protoc searches the PATH for |
2077 | plugins, but you may specify additional |
2078 | executables not in the path using this flag. |
2079 | Additionally, EXECUTABLE may be of the form |
2080 | NAME=PATH, in which case the given plugin name |
2081 | is mapped to the given executable even if |
2082 | the executable's own name differs.)" ; |
2083 | } |
2084 | |
2085 | for (GeneratorMap::iterator iter = generators_by_flag_name_.begin(); |
2086 | iter != generators_by_flag_name_.end(); ++iter) { |
2087 | // FIXME(kenton): If the text is long enough it will wrap, which is ugly, |
2088 | // but fixing this nicely (e.g. splitting on spaces) is probably more |
2089 | // trouble than it's worth. |
2090 | std::cout << std::endl |
2091 | << " " << iter->first << "=OUT_DIR " |
2092 | << std::string(19 - iter->first.size(), |
2093 | ' ') // Spaces for alignment. |
2094 | << iter->second.help_text; |
2095 | } |
2096 | std::cout << R"( |
2097 | @<filename> Read options and filenames from file. If a |
2098 | relative file path is specified, the file |
2099 | will be searched in the working directory. |
2100 | The --proto_path option will not affect how |
2101 | this argument file is searched. Content of |
2102 | the file will be expanded in the position of |
2103 | @<filename> as in the argument list. Note |
2104 | that shell expansion is not applied to the |
2105 | content of the file (i.e., you cannot use |
2106 | quotes, wildcards, escapes, commands, etc.). |
2107 | Each line corresponds to a single argument, |
2108 | even if it contains spaces.)" ; |
2109 | std::cout << std::endl; |
2110 | } |
2111 | |
2112 | bool CommandLineInterface::EnforceProto3OptionalSupport( |
2113 | const std::string& codegen_name, uint64_t supported_features, |
2114 | const std::vector<const FileDescriptor*>& parsed_files) const { |
2115 | bool supports_proto3_optional = |
2116 | supported_features & CodeGenerator::FEATURE_PROTO3_OPTIONAL; |
2117 | if (!supports_proto3_optional) { |
2118 | for (const auto fd : parsed_files) { |
2119 | if (ContainsProto3Optional(file: fd)) { |
2120 | std::cerr << fd->name() |
2121 | << ": is a proto3 file that contains optional fields, but " |
2122 | "code generator " |
2123 | << codegen_name |
2124 | << " hasn't been updated to support optional fields in " |
2125 | "proto3. Please ask the owner of this code generator to " |
2126 | "support proto3 optional." ; |
2127 | return false; |
2128 | } |
2129 | } |
2130 | } |
2131 | return true; |
2132 | } |
2133 | |
2134 | bool CommandLineInterface::GenerateOutput( |
2135 | const std::vector<const FileDescriptor*>& parsed_files, |
2136 | const OutputDirective& output_directive, |
2137 | GeneratorContext* generator_context) { |
2138 | // Call the generator. |
2139 | std::string error; |
2140 | if (output_directive.generator == nullptr) { |
2141 | // This is a plugin. |
2142 | GOOGLE_CHECK(HasPrefixString(output_directive.name, "--" ) && |
2143 | HasSuffixString(output_directive.name, "_out" )) |
2144 | << "Bad name for plugin generator: " << output_directive.name; |
2145 | |
2146 | std::string plugin_name = PluginName(plugin_prefix: plugin_prefix_, directive: output_directive.name); |
2147 | std::string parameters = output_directive.parameter; |
2148 | if (!plugin_parameters_[plugin_name].empty()) { |
2149 | if (!parameters.empty()) { |
2150 | parameters.append(s: "," ); |
2151 | } |
2152 | parameters.append(str: plugin_parameters_[plugin_name]); |
2153 | } |
2154 | if (!GeneratePluginOutput(parsed_files, plugin_name, parameter: parameters, |
2155 | generator_context, error: &error)) { |
2156 | std::cerr << output_directive.name << ": " << error << std::endl; |
2157 | return false; |
2158 | } |
2159 | } else { |
2160 | // Regular generator. |
2161 | std::string parameters = output_directive.parameter; |
2162 | if (!generator_parameters_[output_directive.name].empty()) { |
2163 | if (!parameters.empty()) { |
2164 | parameters.append(s: "," ); |
2165 | } |
2166 | parameters.append(str: generator_parameters_[output_directive.name]); |
2167 | } |
2168 | if (!EnforceProto3OptionalSupport( |
2169 | codegen_name: output_directive.name, |
2170 | supported_features: output_directive.generator->GetSupportedFeatures(), parsed_files)) { |
2171 | return false; |
2172 | } |
2173 | |
2174 | if (!output_directive.generator->GenerateAll(files: parsed_files, parameter: parameters, |
2175 | generator_context, error: &error)) { |
2176 | // Generator returned an error. |
2177 | std::cerr << output_directive.name << ": " << error << std::endl; |
2178 | return false; |
2179 | } |
2180 | } |
2181 | |
2182 | return true; |
2183 | } |
2184 | |
2185 | bool CommandLineInterface::GenerateDependencyManifestFile( |
2186 | const std::vector<const FileDescriptor*>& parsed_files, |
2187 | const GeneratorContextMap& output_directories, |
2188 | DiskSourceTree* source_tree) { |
2189 | FileDescriptorSet file_set; |
2190 | |
2191 | std::set<const FileDescriptor*> already_seen; |
2192 | for (int i = 0; i < parsed_files.size(); i++) { |
2193 | GetTransitiveDependencies(file: parsed_files[i], include_json_name: false, include_source_code_info: false, already_seen: &already_seen, |
2194 | output: file_set.mutable_file()); |
2195 | } |
2196 | |
2197 | std::vector<std::string> output_filenames; |
2198 | for (const auto& pair : output_directories) { |
2199 | const std::string& location = pair.first; |
2200 | GeneratorContextImpl* directory = pair.second.get(); |
2201 | std::vector<std::string> relative_output_filenames; |
2202 | directory->GetOutputFilenames(output_filenames: &relative_output_filenames); |
2203 | for (int i = 0; i < relative_output_filenames.size(); i++) { |
2204 | std::string output_filename = location + relative_output_filenames[i]; |
2205 | if (output_filename.compare(pos: 0, n1: 2, s: "./" ) == 0) { |
2206 | output_filename = output_filename.substr(pos: 2); |
2207 | } |
2208 | output_filenames.push_back(x: output_filename); |
2209 | } |
2210 | } |
2211 | |
2212 | if (!descriptor_set_out_name_.empty()) { |
2213 | output_filenames.push_back(x: descriptor_set_out_name_); |
2214 | } |
2215 | |
2216 | int fd; |
2217 | do { |
2218 | fd = open(file: dependency_out_name_.c_str(), |
2219 | O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); |
2220 | } while (fd < 0 && errno == EINTR); |
2221 | |
2222 | if (fd < 0) { |
2223 | perror(s: dependency_out_name_.c_str()); |
2224 | return false; |
2225 | } |
2226 | |
2227 | io::FileOutputStream out(fd); |
2228 | io::Printer printer(&out, '$'); |
2229 | |
2230 | for (int i = 0; i < output_filenames.size(); i++) { |
2231 | printer.Print(text: output_filenames[i].c_str()); |
2232 | if (i == output_filenames.size() - 1) { |
2233 | printer.Print(text: ":" ); |
2234 | } else { |
2235 | printer.Print(text: " \\\n" ); |
2236 | } |
2237 | } |
2238 | |
2239 | for (int i = 0; i < file_set.file_size(); i++) { |
2240 | const FileDescriptorProto& file = file_set.file(index: i); |
2241 | const std::string& virtual_file = file.name(); |
2242 | std::string disk_file; |
2243 | if (source_tree && |
2244 | source_tree->VirtualFileToDiskFile(virtual_file, disk_file: &disk_file)) { |
2245 | printer.Print(text: " $disk_file$" , args: "disk_file" , args: disk_file); |
2246 | if (i < file_set.file_size() - 1) printer.Print(text: "\\\n" ); |
2247 | } else { |
2248 | std::cerr << "Unable to identify path for file " << virtual_file |
2249 | << std::endl; |
2250 | return false; |
2251 | } |
2252 | } |
2253 | |
2254 | return true; |
2255 | } |
2256 | |
2257 | bool CommandLineInterface::GeneratePluginOutput( |
2258 | const std::vector<const FileDescriptor*>& parsed_files, |
2259 | const std::string& plugin_name, const std::string& parameter, |
2260 | GeneratorContext* generator_context, std::string* error) { |
2261 | CodeGeneratorRequest request; |
2262 | CodeGeneratorResponse response; |
2263 | std::string processed_parameter = parameter; |
2264 | |
2265 | |
2266 | // Build the request. |
2267 | if (!processed_parameter.empty()) { |
2268 | request.set_parameter(processed_parameter); |
2269 | } |
2270 | |
2271 | |
2272 | std::set<const FileDescriptor*> already_seen; |
2273 | for (int i = 0; i < parsed_files.size(); i++) { |
2274 | request.add_file_to_generate(value: parsed_files[i]->name()); |
2275 | GetTransitiveDependencies(file: parsed_files[i], |
2276 | include_json_name: true, // Include json_name for plugins. |
2277 | include_source_code_info: true, // Include source code info. |
2278 | already_seen: &already_seen, output: request.mutable_proto_file()); |
2279 | } |
2280 | |
2281 | google::protobuf::compiler::Version* version = |
2282 | request.mutable_compiler_version(); |
2283 | version->set_major(PROTOBUF_VERSION / 1000000); |
2284 | version->set_minor(PROTOBUF_VERSION / 1000 % 1000); |
2285 | version->set_patch(PROTOBUF_VERSION % 1000); |
2286 | version->set_suffix(PROTOBUF_VERSION_SUFFIX); |
2287 | |
2288 | // Invoke the plugin. |
2289 | Subprocess subprocess; |
2290 | |
2291 | if (plugins_.count(x: plugin_name) > 0) { |
2292 | subprocess.Start(program: plugins_[plugin_name], search_mode: Subprocess::EXACT_NAME); |
2293 | } else { |
2294 | subprocess.Start(program: plugin_name, search_mode: Subprocess::SEARCH_PATH); |
2295 | } |
2296 | |
2297 | std::string communicate_error; |
2298 | if (!subprocess.Communicate(input: request, output: &response, error: &communicate_error)) { |
2299 | *error = strings::Substitute(format: "$0: $1" , arg0: plugin_name, arg1: communicate_error); |
2300 | return false; |
2301 | } |
2302 | |
2303 | // Write the files. We do this even if there was a generator error in order |
2304 | // to match the behavior of a compiled-in generator. |
2305 | std::unique_ptr<io::ZeroCopyOutputStream> current_output; |
2306 | for (int i = 0; i < response.file_size(); i++) { |
2307 | const CodeGeneratorResponse::File& output_file = response.file(index: i); |
2308 | |
2309 | if (!output_file.insertion_point().empty()) { |
2310 | std::string filename = output_file.name(); |
2311 | // Open a file for insert. |
2312 | // We reset current_output to nullptr first so that the old file is closed |
2313 | // before the new one is opened. |
2314 | current_output.reset(); |
2315 | current_output.reset( |
2316 | p: generator_context->OpenForInsertWithGeneratedCodeInfo( |
2317 | filename, insertion_point: output_file.insertion_point(), |
2318 | info: output_file.generated_code_info())); |
2319 | } else if (!output_file.name().empty()) { |
2320 | // Starting a new file. Open it. |
2321 | // We reset current_output to nullptr first so that the old file is closed |
2322 | // before the new one is opened. |
2323 | current_output.reset(); |
2324 | current_output.reset(p: generator_context->Open(filename: output_file.name())); |
2325 | } else if (current_output == nullptr) { |
2326 | *error = strings::Substitute( |
2327 | format: "$0: First file chunk returned by plugin did not specify a file " |
2328 | "name." , |
2329 | arg0: plugin_name); |
2330 | return false; |
2331 | } |
2332 | |
2333 | // Use CodedOutputStream for convenience; otherwise we'd need to provide |
2334 | // our own buffer-copying loop. |
2335 | io::CodedOutputStream writer(current_output.get()); |
2336 | writer.WriteString(str: output_file.content()); |
2337 | } |
2338 | |
2339 | // Check for errors. |
2340 | if (!response.error().empty()) { |
2341 | // Generator returned an error. |
2342 | *error = response.error(); |
2343 | return false; |
2344 | } else if (!EnforceProto3OptionalSupport( |
2345 | codegen_name: plugin_name, supported_features: response.supported_features(), parsed_files)) { |
2346 | return false; |
2347 | } |
2348 | |
2349 | return true; |
2350 | } |
2351 | |
2352 | bool CommandLineInterface::EncodeOrDecode(const DescriptorPool* pool) { |
2353 | // Look up the type. |
2354 | const Descriptor* type = pool->FindMessageTypeByName(name: codec_type_); |
2355 | if (type == nullptr) { |
2356 | std::cerr << "Type not defined: " << codec_type_ << std::endl; |
2357 | return false; |
2358 | } |
2359 | |
2360 | DynamicMessageFactory dynamic_factory(pool); |
2361 | std::unique_ptr<Message> message(dynamic_factory.GetPrototype(type)->New()); |
2362 | |
2363 | if (mode_ == MODE_ENCODE) { |
2364 | SetFdToTextMode(STDIN_FILENO); |
2365 | SetFdToBinaryMode(STDOUT_FILENO); |
2366 | } else { |
2367 | SetFdToBinaryMode(STDIN_FILENO); |
2368 | SetFdToTextMode(STDOUT_FILENO); |
2369 | } |
2370 | |
2371 | io::FileInputStream in(STDIN_FILENO); |
2372 | io::FileOutputStream out(STDOUT_FILENO); |
2373 | |
2374 | if (mode_ == MODE_ENCODE) { |
2375 | // Input is text. |
2376 | ErrorPrinter error_collector(error_format_); |
2377 | TextFormat::Parser parser; |
2378 | parser.RecordErrorsTo(error_collector: &error_collector); |
2379 | parser.AllowPartialMessage(allow: true); |
2380 | |
2381 | if (!parser.Parse(input: &in, output: message.get())) { |
2382 | std::cerr << "Failed to parse input." << std::endl; |
2383 | return false; |
2384 | } |
2385 | } else { |
2386 | // Input is binary. |
2387 | if (!message->ParsePartialFromZeroCopyStream(input: &in)) { |
2388 | std::cerr << "Failed to parse input." << std::endl; |
2389 | return false; |
2390 | } |
2391 | } |
2392 | |
2393 | if (!message->IsInitialized()) { |
2394 | std::cerr << "warning: Input message is missing required fields: " |
2395 | << message->InitializationErrorString() << std::endl; |
2396 | } |
2397 | |
2398 | if (mode_ == MODE_ENCODE) { |
2399 | // Output is binary. |
2400 | io::CodedOutputStream coded_out(&out); |
2401 | coded_out.SetSerializationDeterministic(deterministic_output_); |
2402 | if (!message->SerializePartialToCodedStream(output: &coded_out)) { |
2403 | std::cerr << "output: I/O error." << std::endl; |
2404 | return false; |
2405 | } |
2406 | } else { |
2407 | // Output is text. |
2408 | if (!TextFormat::Print(message: *message, output: &out)) { |
2409 | std::cerr << "output: I/O error." << std::endl; |
2410 | return false; |
2411 | } |
2412 | } |
2413 | |
2414 | return true; |
2415 | } |
2416 | |
2417 | bool CommandLineInterface::WriteDescriptorSet( |
2418 | const std::vector<const FileDescriptor*>& parsed_files) { |
2419 | FileDescriptorSet file_set; |
2420 | |
2421 | std::set<const FileDescriptor*> already_seen; |
2422 | if (!imports_in_descriptor_set_) { |
2423 | // Since we don't want to output transitive dependencies, but we do want |
2424 | // things to be in dependency order, add all dependencies that aren't in |
2425 | // parsed_files to already_seen. This will short circuit the recursion |
2426 | // in GetTransitiveDependencies. |
2427 | std::set<const FileDescriptor*> to_output; |
2428 | to_output.insert(first: parsed_files.begin(), last: parsed_files.end()); |
2429 | for (int i = 0; i < parsed_files.size(); i++) { |
2430 | const FileDescriptor* file = parsed_files[i]; |
2431 | for (int j = 0; j < file->dependency_count(); j++) { |
2432 | const FileDescriptor* dependency = file->dependency(index: j); |
2433 | // if the dependency isn't in parsed files, mark it as already seen |
2434 | if (to_output.find(x: dependency) == to_output.end()) { |
2435 | already_seen.insert(x: dependency); |
2436 | } |
2437 | } |
2438 | } |
2439 | } |
2440 | for (int i = 0; i < parsed_files.size(); i++) { |
2441 | GetTransitiveDependencies(file: parsed_files[i], |
2442 | include_json_name: true, // Include json_name |
2443 | include_source_code_info: source_info_in_descriptor_set_, already_seen: &already_seen, |
2444 | output: file_set.mutable_file()); |
2445 | } |
2446 | |
2447 | int fd; |
2448 | do { |
2449 | fd = open(file: descriptor_set_out_name_.c_str(), |
2450 | O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); |
2451 | } while (fd < 0 && errno == EINTR); |
2452 | |
2453 | if (fd < 0) { |
2454 | perror(s: descriptor_set_out_name_.c_str()); |
2455 | return false; |
2456 | } |
2457 | |
2458 | io::FileOutputStream out(fd); |
2459 | |
2460 | { |
2461 | io::CodedOutputStream coded_out(&out); |
2462 | // Determinism is useful here because build outputs are sometimes checked |
2463 | // into version control. |
2464 | coded_out.SetSerializationDeterministic(true); |
2465 | if (!file_set.SerializeToCodedStream(output: &coded_out)) { |
2466 | std::cerr << descriptor_set_out_name_ << ": " << strerror(errnum: out.GetErrno()) |
2467 | << std::endl; |
2468 | out.Close(); |
2469 | return false; |
2470 | } |
2471 | } |
2472 | |
2473 | if (!out.Close()) { |
2474 | std::cerr << descriptor_set_out_name_ << ": " << strerror(errnum: out.GetErrno()) |
2475 | << std::endl; |
2476 | return false; |
2477 | } |
2478 | |
2479 | return true; |
2480 | } |
2481 | |
2482 | void CommandLineInterface::GetTransitiveDependencies( |
2483 | const FileDescriptor* file, bool include_json_name, |
2484 | bool include_source_code_info, |
2485 | std::set<const FileDescriptor*>* already_seen, |
2486 | RepeatedPtrField<FileDescriptorProto>* output) { |
2487 | if (!already_seen->insert(x: file).second) { |
2488 | // Already saw this file. Skip. |
2489 | return; |
2490 | } |
2491 | |
2492 | // Add all dependencies. |
2493 | for (int i = 0; i < file->dependency_count(); i++) { |
2494 | GetTransitiveDependencies(file: file->dependency(index: i), include_json_name, |
2495 | include_source_code_info, already_seen, output); |
2496 | } |
2497 | |
2498 | // Add this file. |
2499 | FileDescriptorProto* new_descriptor = output->Add(); |
2500 | file->CopyTo(proto: new_descriptor); |
2501 | if (include_json_name) { |
2502 | file->CopyJsonNameTo(proto: new_descriptor); |
2503 | } |
2504 | if (include_source_code_info) { |
2505 | file->CopySourceCodeInfoTo(proto: new_descriptor); |
2506 | } |
2507 | } |
2508 | |
2509 | namespace { |
2510 | |
2511 | // Utility function for PrintFreeFieldNumbers. |
2512 | // Stores occupied ranges into the ranges parameter, and next level of sub |
2513 | // message types into the nested_messages parameter. The FieldRange is left |
2514 | // inclusive, right exclusive. i.e. [a, b). |
2515 | // |
2516 | // Nested Messages: |
2517 | // Note that it only stores the nested message type, iff the nested type is |
2518 | // either a direct child of the given descriptor, or the nested type is a |
2519 | // descendant of the given descriptor and all the nodes between the |
2520 | // nested type and the given descriptor are group types. e.g. |
2521 | // |
2522 | // message Foo { |
2523 | // message Bar { |
2524 | // message NestedBar {} |
2525 | // } |
2526 | // group Baz = 1 { |
2527 | // group NestedBazGroup = 2 { |
2528 | // message Quz { |
2529 | // message NestedQuz {} |
2530 | // } |
2531 | // } |
2532 | // message NestedBaz {} |
2533 | // } |
2534 | // } |
2535 | // |
2536 | // In this case, Bar, Quz and NestedBaz will be added into the nested types. |
2537 | // Since free field numbers of group types will not be printed, this makes sure |
2538 | // the nested message types in groups will not be dropped. The nested_messages |
2539 | // parameter will contain the direct children (when groups are ignored in the |
2540 | // tree) of the given descriptor for the caller to traverse. The declaration |
2541 | // order of the nested messages is also preserved. |
2542 | typedef std::pair<int, int> FieldRange; |
2543 | void GatherOccupiedFieldRanges( |
2544 | const Descriptor* descriptor, std::set<FieldRange>* ranges, |
2545 | std::vector<const Descriptor*>* nested_messages) { |
2546 | std::set<const Descriptor*> groups; |
2547 | for (int i = 0; i < descriptor->field_count(); ++i) { |
2548 | const FieldDescriptor* fd = descriptor->field(index: i); |
2549 | ranges->insert(x: FieldRange(fd->number(), fd->number() + 1)); |
2550 | if (fd->type() == FieldDescriptor::TYPE_GROUP) { |
2551 | groups.insert(x: fd->message_type()); |
2552 | } |
2553 | } |
2554 | for (int i = 0; i < descriptor->extension_range_count(); ++i) { |
2555 | ranges->insert(x: FieldRange(descriptor->extension_range(index: i)->start, |
2556 | descriptor->extension_range(index: i)->end)); |
2557 | } |
2558 | for (int i = 0; i < descriptor->reserved_range_count(); ++i) { |
2559 | ranges->insert(x: FieldRange(descriptor->reserved_range(index: i)->start, |
2560 | descriptor->reserved_range(index: i)->end)); |
2561 | } |
2562 | // Handle the nested messages/groups in declaration order to make it |
2563 | // post-order strict. |
2564 | for (int i = 0; i < descriptor->nested_type_count(); ++i) { |
2565 | const Descriptor* nested_desc = descriptor->nested_type(index: i); |
2566 | if (groups.find(x: nested_desc) != groups.end()) { |
2567 | GatherOccupiedFieldRanges(descriptor: nested_desc, ranges, nested_messages); |
2568 | } else { |
2569 | nested_messages->push_back(x: nested_desc); |
2570 | } |
2571 | } |
2572 | } |
2573 | |
2574 | // Utility function for PrintFreeFieldNumbers. |
2575 | // Actually prints the formatted free field numbers for given message name and |
2576 | // occupied ranges. |
2577 | void FormatFreeFieldNumbers(const std::string& name, |
2578 | const std::set<FieldRange>& ranges) { |
2579 | std::string output; |
2580 | StringAppendF(dst: &output, format: "%-35s free:" , name.c_str()); |
2581 | int next_free_number = 1; |
2582 | for (std::set<FieldRange>::const_iterator i = ranges.begin(); |
2583 | i != ranges.end(); ++i) { |
2584 | // This happens when groups re-use parent field numbers, in which |
2585 | // case we skip the FieldRange entirely. |
2586 | if (next_free_number >= i->second) continue; |
2587 | |
2588 | if (next_free_number < i->first) { |
2589 | if (next_free_number + 1 == i->first) { |
2590 | // Singleton |
2591 | StringAppendF(dst: &output, format: " %d" , next_free_number); |
2592 | } else { |
2593 | // Range |
2594 | StringAppendF(dst: &output, format: " %d-%d" , next_free_number, |
2595 | i->first - 1); |
2596 | } |
2597 | } |
2598 | next_free_number = i->second; |
2599 | } |
2600 | if (next_free_number <= FieldDescriptor::kMaxNumber) { |
2601 | StringAppendF(dst: &output, format: " %d-INF" , next_free_number); |
2602 | } |
2603 | std::cout << output << std::endl; |
2604 | } |
2605 | |
2606 | } // namespace |
2607 | |
2608 | void CommandLineInterface::PrintFreeFieldNumbers(const Descriptor* descriptor) { |
2609 | std::set<FieldRange> ranges; |
2610 | std::vector<const Descriptor*> nested_messages; |
2611 | GatherOccupiedFieldRanges(descriptor, ranges: &ranges, nested_messages: &nested_messages); |
2612 | |
2613 | for (int i = 0; i < nested_messages.size(); ++i) { |
2614 | PrintFreeFieldNumbers(descriptor: nested_messages[i]); |
2615 | } |
2616 | FormatFreeFieldNumbers(name: descriptor->full_name(), ranges); |
2617 | } |
2618 | |
2619 | |
2620 | } // namespace compiler |
2621 | } // namespace protobuf |
2622 | } // namespace google |
2623 | |