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
92namespace google {
93namespace protobuf {
94namespace 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
104namespace {
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.
108using google::protobuf::io::win32::access;
109using google::protobuf::io::win32::close;
110using google::protobuf::io::win32::mkdir;
111using google::protobuf::io::win32::open;
112using google::protobuf::io::win32::setmode;
113using google::protobuf::io::win32::write;
114#endif
115
116static 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?
122static 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
131void 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
141void 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
151void AddTrailingSlash(std::string* path) {
152 if (!path->empty() && path->at(n: path->size() - 1) != '/') {
153 path->push_back(c: '/');
154 }
155}
156
157bool 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|.
172bool 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.
195bool 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.
230bool 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.
238void 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
277std::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.
287class 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.
383class 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
425class 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
485CommandLineInterface::GeneratorContextImpl::GeneratorContextImpl(
486 const std::vector<const FileDescriptor*>& parsed_files)
487 : parsed_files_(parsed_files), had_error_(false) {}
488
489bool 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
563bool 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
605void 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
615void 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
622io::ZeroCopyOutputStream* CommandLineInterface::GeneratorContextImpl::Open(
623 const std::string& filename) {
624 return new MemoryOutputStream(this, filename, false);
625}
626
627io::ZeroCopyOutputStream*
628CommandLineInterface::GeneratorContextImpl::OpenForAppend(
629 const std::string& filename) {
630 return new MemoryOutputStream(this, filename, true);
631}
632
633io::ZeroCopyOutputStream*
634CommandLineInterface::GeneratorContextImpl::OpenForInsert(
635 const std::string& filename, const std::string& insertion_point) {
636 return new MemoryOutputStream(this, filename, insertion_point);
637}
638
639io::ZeroCopyOutputStream*
640CommandLineInterface::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
648CommandLineInterface::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
656CommandLineInterface::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
664CommandLineInterface::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
673void 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
708void 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
775CommandLineInterface::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__)
893const char* const CommandLineInterface::kPathSeparator = ";";
894#else
895const char* const CommandLineInterface::kPathSeparator = ":";
896#endif
897
898CommandLineInterface::CommandLineInterface()
899 : direct_dependencies_violation_msg_(
900 kDefaultDirectDependenciesViolationMsg) {}
901
902CommandLineInterface::~CommandLineInterface() {}
903
904void 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
914void 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
926void CommandLineInterface::AllowPlugins(const std::string& exe_name_prefix) {
927 plugin_prefix_ = exe_name_prefix;
928}
929
930namespace {
931
932bool 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
946bool 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
959namespace {
960std::unique_ptr<SimpleDescriptorDatabase>
961PopulateSingleSimpleDescriptorDatabase(const std::string& descriptor_set_name);
962}
963
964int 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
1158bool 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
1175namespace {
1176std::unique_ptr<SimpleDescriptorDatabase>
1177PopulateSingleSimpleDescriptorDatabase(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
1220bool 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
1243bool 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
1314void 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
1338bool 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
1412bool 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
1425bool 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
1441CommandLineInterface::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
1596bool 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
1656CommandLineInterface::ParseArgumentStatus
1657CommandLineInterface::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
2007void 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"(
2011Parse 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
2112bool 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
2134bool 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
2185bool 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
2257bool 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
2352bool 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
2417bool 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
2482void 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
2509namespace {
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.
2542typedef std::pair<int, int> FieldRange;
2543void 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.
2577void 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
2608void 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