1// Protocol Buffers - Google's data interchange format 2// Copyright 2008 Google Inc. All rights reserved. 3// http://code.google.com/p/protobuf/ 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 <stdio.h> 38#include <sys/types.h> 39#include <sys/stat.h> 40#include <fcntl.h> 41#ifdef _MSC_VER 42#include <io.h> 43#include <direct.h> 44#else 45#include <unistd.h> 46#endif 47#include <errno.h> 48#include <iostream> 49#include <ctype.h> 50 51#include <google/protobuf/stubs/hash.h> 52 53#include <google/protobuf/stubs/common.h> 54#include <google/protobuf/compiler/importer.h> 55#include <google/protobuf/compiler/code_generator.h> 56#include <google/protobuf/compiler/plugin.pb.h> 57#include <google/protobuf/compiler/subprocess.h> 58#include <google/protobuf/compiler/zip_writer.h> 59#include <google/protobuf/descriptor.h> 60#include <google/protobuf/text_format.h> 61#include <google/protobuf/dynamic_message.h> 62#include <google/protobuf/io/coded_stream.h> 63#include <google/protobuf/io/zero_copy_stream_impl.h> 64#include <google/protobuf/io/printer.h> 65#include <google/protobuf/stubs/strutil.h> 66#include <google/protobuf/stubs/substitute.h> 67#include <google/protobuf/stubs/map-util.h> 68#include <google/protobuf/stubs/stl_util.h> 69 70 71namespace google { 72namespace protobuf { 73namespace compiler { 74 75#if defined(_WIN32) 76#define mkdir(name, mode) mkdir(name) 77#ifndef W_OK 78#define W_OK 02 // not defined by MSVC for whatever reason 79#endif 80#ifndef F_OK 81#define F_OK 00 // not defined by MSVC for whatever reason 82#endif 83#ifndef STDIN_FILENO 84#define STDIN_FILENO 0 85#endif 86#ifndef STDOUT_FILENO 87#define STDOUT_FILENO 1 88#endif 89#endif 90 91#ifndef O_BINARY 92#ifdef _O_BINARY 93#define O_BINARY _O_BINARY 94#else 95#define O_BINARY 0 // If this isn't defined, the platform doesn't need it. 96#endif 97#endif 98 99namespace { 100#if defined(_WIN32) && !defined(__CYGWIN__) 101static const char* kPathSeparator = ";"; 102#else 103static const char* kPathSeparator = ":"; 104#endif 105 106// Returns true if the text looks like a Windows-style absolute path, starting 107// with a drive letter. Example: "C:\foo". TODO(kenton): Share this with 108// copy in importer.cc? 109static bool IsWindowsAbsolutePath(const string& text) { 110#if defined(_WIN32) || defined(__CYGWIN__) 111 return text.size() >= 3 && text[1] == ':' && 112 isalpha(text[0]) && 113 (text[2] == '/' || text[2] == '\\') && 114 text.find_last_of(':') == 1; 115#else 116 return false; 117#endif 118} 119 120void SetFdToTextMode(int fd) { 121#ifdef _WIN32 122 if (_setmode(fd, _O_TEXT) == -1) { 123 // This should never happen, I think. 124 GOOGLE_LOG(WARNING) << "_setmode(" << fd << ", _O_TEXT): " << strerror(errno); 125 } 126#endif 127 // (Text and binary are the same on non-Windows platforms.) 128} 129 130void SetFdToBinaryMode(int fd) { 131#ifdef _WIN32 132 if (_setmode(fd, _O_BINARY) == -1) { 133 // This should never happen, I think. 134 GOOGLE_LOG(WARNING) << "_setmode(" << fd << ", _O_BINARY): " << strerror(errno); 135 } 136#endif 137 // (Text and binary are the same on non-Windows platforms.) 138} 139 140void AddTrailingSlash(string* path) { 141 if (!path->empty() && path->at(path->size() - 1) != '/') { 142 path->push_back('/'); 143 } 144} 145 146bool VerifyDirectoryExists(const string& path) { 147 if (path.empty()) return true; 148 149 if (access(path.c_str(), F_OK) == -1) { 150 cerr << path << ": " << strerror(errno) << endl; 151 return false; 152 } else { 153 return true; 154 } 155} 156 157// Try to create the parent directory of the given file, creating the parent's 158// parent if necessary, and so on. The full file name is actually 159// (prefix + filename), but we assume |prefix| already exists and only create 160// directories listed in |filename|. 161bool TryCreateParentDirectory(const string& prefix, const string& filename) { 162 // Recursively create parent directories to the output file. 163 vector<string> parts; 164 SplitStringUsing(filename, "/", &parts); 165 string path_so_far = prefix; 166 for (int i = 0; i < parts.size() - 1; i++) { 167 path_so_far += parts[i]; 168 if (mkdir(path_so_far.c_str(), 0777) != 0) { 169 if (errno != EEXIST) { 170 cerr << filename << ": while trying to create directory " 171 << path_so_far << ": " << strerror(errno) << endl; 172 return false; 173 } 174 } 175 path_so_far += '/'; 176 } 177 178 return true; 179} 180 181} // namespace 182 183// A MultiFileErrorCollector that prints errors to stderr. 184class CommandLineInterface::ErrorPrinter : public MultiFileErrorCollector, 185 public io::ErrorCollector { 186 public: 187 ErrorPrinter(ErrorFormat format, DiskSourceTree *tree = NULL) 188 : format_(format), tree_(tree) {} 189 ~ErrorPrinter() {} 190 191 // implements MultiFileErrorCollector ------------------------------ 192 void AddError(const string& filename, int line, int column, 193 const string& message) { 194 195 // Print full path when running under MSVS 196 string dfile; 197 if (format_ == CommandLineInterface::ERROR_FORMAT_MSVS && 198 tree_ != NULL && 199 tree_->VirtualFileToDiskFile(filename, &dfile)) { 200 cerr << dfile; 201 } else { 202 cerr << filename; 203 } 204 205 // Users typically expect 1-based line/column numbers, so we add 1 206 // to each here. 207 if (line != -1) { 208 // Allow for both GCC- and Visual-Studio-compatible output. 209 switch (format_) { 210 case CommandLineInterface::ERROR_FORMAT_GCC: 211 cerr << ":" << (line + 1) << ":" << (column + 1); 212 break; 213 case CommandLineInterface::ERROR_FORMAT_MSVS: 214 cerr << "(" << (line + 1) << ") : error in column=" << (column + 1); 215 break; 216 } 217 } 218 219 cerr << ": " << message << endl; 220 } 221 222 // implements io::ErrorCollector ----------------------------------- 223 void AddError(int line, int column, const string& message) { 224 AddError("input", line, column, message); 225 } 226 227 private: 228 const ErrorFormat format_; 229 DiskSourceTree *tree_; 230}; 231 232// ------------------------------------------------------------------- 233 234// A GeneratorContext implementation that buffers files in memory, then dumps 235// them all to disk on demand. 236class CommandLineInterface::GeneratorContextImpl : public GeneratorContext { 237 public: 238 GeneratorContextImpl(const vector<const FileDescriptor*>& parsed_files); 239 ~GeneratorContextImpl(); 240 241 // Write all files in the directory to disk at the given output location, 242 // which must end in a '/'. 243 bool WriteAllToDisk(const string& prefix); 244 245 // Write the contents of this directory to a ZIP-format archive with the 246 // given name. 247 bool WriteAllToZip(const string& filename); 248 249 // Add a boilerplate META-INF/MANIFEST.MF file as required by the Java JAR 250 // format, unless one has already been written. 251 void AddJarManifest(); 252 253 // implements GeneratorContext -------------------------------------- 254 io::ZeroCopyOutputStream* Open(const string& filename); 255 io::ZeroCopyOutputStream* OpenForInsert( 256 const string& filename, const string& insertion_point); 257 void ListParsedFiles(vector<const FileDescriptor*>* output) { 258 *output = parsed_files_; 259 } 260 261 private: 262 friend class MemoryOutputStream; 263 264 // map instead of hash_map so that files are written in order (good when 265 // writing zips). 266 map<string, string*> files_; 267 const vector<const FileDescriptor*>& parsed_files_; 268 bool had_error_; 269}; 270 271class CommandLineInterface::MemoryOutputStream 272 : public io::ZeroCopyOutputStream { 273 public: 274 MemoryOutputStream(GeneratorContextImpl* directory, const string& filename); 275 MemoryOutputStream(GeneratorContextImpl* directory, const string& filename, 276 const string& insertion_point); 277 virtual ~MemoryOutputStream(); 278 279 // implements ZeroCopyOutputStream --------------------------------- 280 virtual bool Next(void** data, int* size) { return inner_->Next(data, size); } 281 virtual void BackUp(int count) { inner_->BackUp(count); } 282 virtual int64 ByteCount() const { return inner_->ByteCount(); } 283 284 private: 285 // Where to insert the string when it's done. 286 GeneratorContextImpl* directory_; 287 string filename_; 288 string insertion_point_; 289 290 // The string we're building. 291 string data_; 292 293 // StringOutputStream writing to data_. 294 scoped_ptr<io::StringOutputStream> inner_; 295}; 296 297// ------------------------------------------------------------------- 298 299CommandLineInterface::GeneratorContextImpl::GeneratorContextImpl( 300 const vector<const FileDescriptor*>& parsed_files) 301 : parsed_files_(parsed_files), 302 had_error_(false) { 303} 304 305CommandLineInterface::GeneratorContextImpl::~GeneratorContextImpl() { 306 STLDeleteValues(&files_); 307} 308 309bool CommandLineInterface::GeneratorContextImpl::WriteAllToDisk( 310 const string& prefix) { 311 if (had_error_) { 312 return false; 313 } 314 315 if (!VerifyDirectoryExists(prefix)) { 316 return false; 317 } 318 319 for (map<string, string*>::const_iterator iter = files_.begin(); 320 iter != files_.end(); ++iter) { 321 const string& relative_filename = iter->first; 322 const char* data = iter->second->data(); 323 int size = iter->second->size(); 324 325 if (!TryCreateParentDirectory(prefix, relative_filename)) { 326 return false; 327 } 328 string filename = prefix + relative_filename; 329 330 // Create the output file. 331 int file_descriptor; 332 do { 333 file_descriptor = 334 open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); 335 } while (file_descriptor < 0 && errno == EINTR); 336 337 if (file_descriptor < 0) { 338 int error = errno; 339 cerr << filename << ": " << strerror(error); 340 return false; 341 } 342 343 // Write the file. 344 while (size > 0) { 345 int write_result; 346 do { 347 write_result = write(file_descriptor, data, size); 348 } while (write_result < 0 && errno == EINTR); 349 350 if (write_result <= 0) { 351 // Write error. 352 353 // FIXME(kenton): According to the man page, if write() returns zero, 354 // there was no error; write() simply did not write anything. It's 355 // unclear under what circumstances this might happen, but presumably 356 // errno won't be set in this case. I am confused as to how such an 357 // event should be handled. For now I'm treating it as an error, 358 // since retrying seems like it could lead to an infinite loop. I 359 // suspect this never actually happens anyway. 360 361 if (write_result < 0) { 362 int error = errno; 363 cerr << filename << ": write: " << strerror(error); 364 } else { 365 cerr << filename << ": write() returned zero?" << endl; 366 } 367 return false; 368 } 369 370 data += write_result; 371 size -= write_result; 372 } 373 374 if (close(file_descriptor) != 0) { 375 int error = errno; 376 cerr << filename << ": close: " << strerror(error); 377 return false; 378 } 379 } 380 381 return true; 382} 383 384bool CommandLineInterface::GeneratorContextImpl::WriteAllToZip( 385 const string& filename) { 386 if (had_error_) { 387 return false; 388 } 389 390 // Create the output file. 391 int file_descriptor; 392 do { 393 file_descriptor = 394 open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); 395 } while (file_descriptor < 0 && errno == EINTR); 396 397 if (file_descriptor < 0) { 398 int error = errno; 399 cerr << filename << ": " << strerror(error); 400 return false; 401 } 402 403 // Create the ZipWriter 404 io::FileOutputStream stream(file_descriptor); 405 ZipWriter zip_writer(&stream); 406 407 for (map<string, string*>::const_iterator iter = files_.begin(); 408 iter != files_.end(); ++iter) { 409 zip_writer.Write(iter->first, *iter->second); 410 } 411 412 zip_writer.WriteDirectory(); 413 414 if (stream.GetErrno() != 0) { 415 cerr << filename << ": " << strerror(stream.GetErrno()) << endl; 416 } 417 418 if (!stream.Close()) { 419 cerr << filename << ": " << strerror(stream.GetErrno()) << endl; 420 } 421 422 return true; 423} 424 425void CommandLineInterface::GeneratorContextImpl::AddJarManifest() { 426 string** map_slot = &files_["META-INF/MANIFEST.MF"]; 427 if (*map_slot == NULL) { 428 *map_slot = new string( 429 "Manifest-Version: 1.0\n" 430 "Created-By: 1.6.0 (protoc)\n" 431 "\n"); 432 } 433} 434 435io::ZeroCopyOutputStream* CommandLineInterface::GeneratorContextImpl::Open( 436 const string& filename) { 437 return new MemoryOutputStream(this, filename); 438} 439 440io::ZeroCopyOutputStream* 441CommandLineInterface::GeneratorContextImpl::OpenForInsert( 442 const string& filename, const string& insertion_point) { 443 return new MemoryOutputStream(this, filename, insertion_point); 444} 445 446// ------------------------------------------------------------------- 447 448CommandLineInterface::MemoryOutputStream::MemoryOutputStream( 449 GeneratorContextImpl* directory, const string& filename) 450 : directory_(directory), 451 filename_(filename), 452 inner_(new io::StringOutputStream(&data_)) { 453} 454 455CommandLineInterface::MemoryOutputStream::MemoryOutputStream( 456 GeneratorContextImpl* directory, const string& filename, 457 const string& insertion_point) 458 : directory_(directory), 459 filename_(filename), 460 insertion_point_(insertion_point), 461 inner_(new io::StringOutputStream(&data_)) { 462} 463 464CommandLineInterface::MemoryOutputStream::~MemoryOutputStream() { 465 // Make sure all data has been written. 466 inner_.reset(); 467 468 // Insert into the directory. 469 string** map_slot = &directory_->files_[filename_]; 470 471 if (insertion_point_.empty()) { 472 // This was just a regular Open(). 473 if (*map_slot != NULL) { 474 cerr << filename_ << ": Tried to write the same file twice." << endl; 475 directory_->had_error_ = true; 476 return; 477 } 478 479 *map_slot = new string; 480 (*map_slot)->swap(data_); 481 } else { 482 // This was an OpenForInsert(). 483 484 // If the data doens't end with a clean line break, add one. 485 if (!data_.empty() && data_[data_.size() - 1] != '\n') { 486 data_.push_back('\n'); 487 } 488 489 // Find the file we are going to insert into. 490 if (*map_slot == NULL) { 491 cerr << filename_ << ": Tried to insert into file that doesn't exist." 492 << endl; 493 directory_->had_error_ = true; 494 return; 495 } 496 string* target = *map_slot; 497 498 // Find the insertion point. 499 string magic_string = strings::Substitute( 500 "@@protoc_insertion_point($0)", insertion_point_); 501 string::size_type pos = target->find(magic_string); 502 503 if (pos == string::npos) { 504 cerr << filename_ << ": insertion point \"" << insertion_point_ 505 << "\" not found." << endl; 506 directory_->had_error_ = true; 507 return; 508 } 509 510 // Seek backwards to the beginning of the line, which is where we will 511 // insert the data. Note that this has the effect of pushing the insertion 512 // point down, so the data is inserted before it. This is intentional 513 // because it means that multiple insertions at the same point will end 514 // up in the expected order in the final output. 515 pos = target->find_last_of('\n', pos); 516 if (pos == string::npos) { 517 // Insertion point is on the first line. 518 pos = 0; 519 } else { 520 // Advance to character after '\n'. 521 ++pos; 522 } 523 524 // Extract indent. 525 string indent_(*target, pos, target->find_first_not_of(" \t", pos) - pos); 526 527 if (indent_.empty()) { 528 // No indent. This makes things easier. 529 target->insert(pos, data_); 530 } else { 531 // Calculate how much space we need. 532 int indent_size = 0; 533 for (int i = 0; i < data_.size(); i++) { 534 if (data_[i] == '\n') indent_size += indent_.size(); 535 } 536 537 // Make a hole for it. 538 target->insert(pos, data_.size() + indent_size, '\0'); 539 540 // Now copy in the data. 541 string::size_type data_pos = 0; 542 char* target_ptr = string_as_array(target) + pos; 543 while (data_pos < data_.size()) { 544 // Copy indent. 545 memcpy(target_ptr, indent_.data(), indent_.size()); 546 target_ptr += indent_.size(); 547 548 // Copy line from data_. 549 // We already guaranteed that data_ ends with a newline (above), so this 550 // search can't fail. 551 string::size_type line_length = 552 data_.find_first_of('\n', data_pos) + 1 - data_pos; 553 memcpy(target_ptr, data_.data() + data_pos, line_length); 554 target_ptr += line_length; 555 data_pos += line_length; 556 } 557 558 GOOGLE_CHECK_EQ(target_ptr, 559 string_as_array(target) + pos + data_.size() + indent_size); 560 } 561 } 562} 563 564// =================================================================== 565 566CommandLineInterface::CommandLineInterface() 567 : mode_(MODE_COMPILE), 568 error_format_(ERROR_FORMAT_GCC), 569 imports_in_descriptor_set_(false), 570 source_info_in_descriptor_set_(false), 571 disallow_services_(false), 572 inputs_are_proto_path_relative_(false) {} 573CommandLineInterface::~CommandLineInterface() {} 574 575void CommandLineInterface::RegisterGenerator(const string& flag_name, 576 CodeGenerator* generator, 577 const string& help_text) { 578 GeneratorInfo info; 579 info.flag_name = flag_name; 580 info.generator = generator; 581 info.help_text = help_text; 582 generators_by_flag_name_[flag_name] = info; 583} 584 585void CommandLineInterface::RegisterGenerator(const string& flag_name, 586 const string& option_flag_name, 587 CodeGenerator* generator, 588 const string& help_text) { 589 GeneratorInfo info; 590 info.flag_name = flag_name; 591 info.option_flag_name = option_flag_name; 592 info.generator = generator; 593 info.help_text = help_text; 594 generators_by_flag_name_[flag_name] = info; 595 generators_by_option_name_[option_flag_name] = info; 596} 597 598void CommandLineInterface::AllowPlugins(const string& exe_name_prefix) { 599 plugin_prefix_ = exe_name_prefix; 600} 601 602int CommandLineInterface::Run(int argc, const char* const argv[]) { 603 Clear(); 604 switch (ParseArguments(argc, argv)) { 605 case PARSE_ARGUMENT_DONE_AND_EXIT: 606 return 0; 607 case PARSE_ARGUMENT_FAIL: 608 return 1; 609 case PARSE_ARGUMENT_DONE_AND_CONTINUE: 610 break; 611 } 612 613 // Set up the source tree. 614 DiskSourceTree source_tree; 615 for (int i = 0; i < proto_path_.size(); i++) { 616 source_tree.MapPath(proto_path_[i].first, proto_path_[i].second); 617 } 618 619 // Map input files to virtual paths if necessary. 620 if (!inputs_are_proto_path_relative_) { 621 if (!MakeInputsBeProtoPathRelative(&source_tree)) { 622 return 1; 623 } 624 } 625 626 // Allocate the Importer. 627 ErrorPrinter error_collector(error_format_, &source_tree); 628 Importer importer(&source_tree, &error_collector); 629 630 vector<const FileDescriptor*> parsed_files; 631 632 // Parse each file. 633 for (int i = 0; i < input_files_.size(); i++) { 634 // Import the file. 635 const FileDescriptor* parsed_file = importer.Import(input_files_[i]); 636 if (parsed_file == NULL) return 1; 637 parsed_files.push_back(parsed_file); 638 639 // Enforce --disallow_services. 640 if (disallow_services_ && parsed_file->service_count() > 0) { 641 cerr << parsed_file->name() << ": This file contains services, but " 642 "--disallow_services was used." << endl; 643 return 1; 644 } 645 } 646 647 // We construct a separate GeneratorContext for each output location. Note 648 // that two code generators may output to the same location, in which case 649 // they should share a single GeneratorContext so that OpenForInsert() works. 650 typedef hash_map<string, GeneratorContextImpl*> GeneratorContextMap; 651 GeneratorContextMap output_directories; 652 653 // Generate output. 654 if (mode_ == MODE_COMPILE) { 655 for (int i = 0; i < output_directives_.size(); i++) { 656 string output_location = output_directives_[i].output_location; 657 if (!HasSuffixString(output_location, ".zip") && 658 !HasSuffixString(output_location, ".jar")) { 659 AddTrailingSlash(&output_location); 660 } 661 GeneratorContextImpl** map_slot = &output_directories[output_location]; 662 663 if (*map_slot == NULL) { 664 // First time we've seen this output location. 665 *map_slot = new GeneratorContextImpl(parsed_files); 666 } 667 668 if (!GenerateOutput(parsed_files, output_directives_[i], *map_slot)) { 669 STLDeleteValues(&output_directories); 670 return 1; 671 } 672 } 673 } 674 675 // Write all output to disk. 676 for (GeneratorContextMap::iterator iter = output_directories.begin(); 677 iter != output_directories.end(); ++iter) { 678 const string& location = iter->first; 679 GeneratorContextImpl* directory = iter->second; 680 if (HasSuffixString(location, "/")) { 681 if (!directory->WriteAllToDisk(location)) { 682 STLDeleteValues(&output_directories); 683 return 1; 684 } 685 } else { 686 if (HasSuffixString(location, ".jar")) { 687 directory->AddJarManifest(); 688 } 689 690 if (!directory->WriteAllToZip(location)) { 691 STLDeleteValues(&output_directories); 692 return 1; 693 } 694 } 695 } 696 697 STLDeleteValues(&output_directories); 698 699 if (!descriptor_set_name_.empty()) { 700 if (!WriteDescriptorSet(parsed_files)) { 701 return 1; 702 } 703 } 704 705 if (mode_ == MODE_ENCODE || mode_ == MODE_DECODE) { 706 if (codec_type_.empty()) { 707 // HACK: Define an EmptyMessage type to use for decoding. 708 DescriptorPool pool; 709 FileDescriptorProto file; 710 file.set_name("empty_message.proto"); 711 file.add_message_type()->set_name("EmptyMessage"); 712 GOOGLE_CHECK(pool.BuildFile(file) != NULL); 713 codec_type_ = "EmptyMessage"; 714 if (!EncodeOrDecode(&pool)) { 715 return 1; 716 } 717 } else { 718 if (!EncodeOrDecode(importer.pool())) { 719 return 1; 720 } 721 } 722 } 723 724 return 0; 725} 726 727void CommandLineInterface::Clear() { 728 // Clear all members that are set by Run(). Note that we must not clear 729 // members which are set by other methods before Run() is called. 730 executable_name_.clear(); 731 proto_path_.clear(); 732 input_files_.clear(); 733 output_directives_.clear(); 734 codec_type_.clear(); 735 descriptor_set_name_.clear(); 736 737 mode_ = MODE_COMPILE; 738 imports_in_descriptor_set_ = false; 739 source_info_in_descriptor_set_ = false; 740 disallow_services_ = false; 741} 742 743bool CommandLineInterface::MakeInputsBeProtoPathRelative( 744 DiskSourceTree* source_tree) { 745 for (int i = 0; i < input_files_.size(); i++) { 746 string virtual_file, shadowing_disk_file; 747 switch (source_tree->DiskFileToVirtualFile( 748 input_files_[i], &virtual_file, &shadowing_disk_file)) { 749 case DiskSourceTree::SUCCESS: 750 input_files_[i] = virtual_file; 751 break; 752 case DiskSourceTree::SHADOWED: 753 cerr << input_files_[i] << ": Input is shadowed in the --proto_path " 754 "by \"" << shadowing_disk_file << "\". Either use the latter " 755 "file as your input or reorder the --proto_path so that the " 756 "former file's location comes first." << endl; 757 return false; 758 case DiskSourceTree::CANNOT_OPEN: 759 cerr << input_files_[i] << ": " << strerror(errno) << endl; 760 return false; 761 case DiskSourceTree::NO_MAPPING: 762 // First check if the file exists at all. 763 if (access(input_files_[i].c_str(), F_OK) < 0) { 764 // File does not even exist. 765 cerr << input_files_[i] << ": " << strerror(ENOENT) << endl; 766 } else { 767 cerr << input_files_[i] << ": File does not reside within any path " 768 "specified using --proto_path (or -I). You must specify a " 769 "--proto_path which encompasses this file. Note that the " 770 "proto_path must be an exact prefix of the .proto file " 771 "names -- protoc is too dumb to figure out when two paths " 772 "(e.g. absolute and relative) are equivalent (it's harder " 773 "than you think)." << endl; 774 } 775 return false; 776 } 777 } 778 779 return true; 780} 781 782CommandLineInterface::ParseArgumentStatus 783CommandLineInterface::ParseArguments(int argc, const char* const argv[]) { 784 executable_name_ = argv[0]; 785 786 // Iterate through all arguments and parse them. 787 for (int i = 1; i < argc; i++) { 788 string name, value; 789 790 if (ParseArgument(argv[i], &name, &value)) { 791 // Returned true => Use the next argument as the flag value. 792 if (i + 1 == argc || argv[i+1][0] == '-') { 793 cerr << "Missing value for flag: " << name << endl; 794 if (name == "--decode") { 795 cerr << "To decode an unknown message, use --decode_raw." << endl; 796 } 797 return PARSE_ARGUMENT_FAIL; 798 } else { 799 ++i; 800 value = argv[i]; 801 } 802 } 803 804 ParseArgumentStatus status = InterpretArgument(name, value); 805 if (status != PARSE_ARGUMENT_DONE_AND_CONTINUE) 806 return status; 807 } 808 809 // If no --proto_path was given, use the current working directory. 810 if (proto_path_.empty()) { 811 // Don't use make_pair as the old/default standard library on Solaris 812 // doesn't support it without explicit template parameters, which are 813 // incompatible with C++0x's make_pair. 814 proto_path_.push_back(pair<string, string>("", ".")); 815 } 816 817 // Check some errror cases. 818 bool decoding_raw = (mode_ == MODE_DECODE) && codec_type_.empty(); 819 if (decoding_raw && !input_files_.empty()) { 820 cerr << "When using --decode_raw, no input files should be given." << endl; 821 return PARSE_ARGUMENT_FAIL; 822 } else if (!decoding_raw && input_files_.empty()) { 823 cerr << "Missing input file." << endl; 824 return PARSE_ARGUMENT_FAIL; 825 } 826 if (mode_ == MODE_COMPILE && output_directives_.empty() && 827 descriptor_set_name_.empty()) { 828 cerr << "Missing output directives." << endl; 829 return PARSE_ARGUMENT_FAIL; 830 } 831 if (imports_in_descriptor_set_ && descriptor_set_name_.empty()) { 832 cerr << "--include_imports only makes sense when combined with " 833 "--descriptor_set_out." << endl; 834 } 835 if (source_info_in_descriptor_set_ && descriptor_set_name_.empty()) { 836 cerr << "--include_source_info only makes sense when combined with " 837 "--descriptor_set_out." << endl; 838 } 839 840 return PARSE_ARGUMENT_DONE_AND_CONTINUE; 841} 842 843bool CommandLineInterface::ParseArgument(const char* arg, 844 string* name, string* value) { 845 bool parsed_value = false; 846 847 if (arg[0] != '-') { 848 // Not a flag. 849 name->clear(); 850 parsed_value = true; 851 *value = arg; 852 } else if (arg[1] == '-') { 853 // Two dashes: Multi-character name, with '=' separating name and 854 // value. 855 const char* equals_pos = strchr(arg, '='); 856 if (equals_pos != NULL) { 857 *name = string(arg, equals_pos - arg); 858 *value = equals_pos + 1; 859 parsed_value = true; 860 } else { 861 *name = arg; 862 } 863 } else { 864 // One dash: One-character name, all subsequent characters are the 865 // value. 866 if (arg[1] == '\0') { 867 // arg is just "-". We treat this as an input file, except that at 868 // present this will just lead to a "file not found" error. 869 name->clear(); 870 *value = arg; 871 parsed_value = true; 872 } else { 873 *name = string(arg, 2); 874 *value = arg + 2; 875 parsed_value = !value->empty(); 876 } 877 } 878 879 // Need to return true iff the next arg should be used as the value for this 880 // one, false otherwise. 881 882 if (parsed_value) { 883 // We already parsed a value for this flag. 884 return false; 885 } 886 887 if (*name == "-h" || *name == "--help" || 888 *name == "--disallow_services" || 889 *name == "--include_imports" || 890 *name == "--include_source_info" || 891 *name == "--version" || 892 *name == "--decode_raw") { 893 // HACK: These are the only flags that don't take a value. 894 // They probably should not be hard-coded like this but for now it's 895 // not worth doing better. 896 return false; 897 } 898 899 // Next argument is the flag value. 900 return true; 901} 902 903CommandLineInterface::ParseArgumentStatus 904CommandLineInterface::InterpretArgument(const string& name, 905 const string& value) { 906 if (name.empty()) { 907 // Not a flag. Just a filename. 908 if (value.empty()) { 909 cerr << "You seem to have passed an empty string as one of the " 910 "arguments to " << executable_name_ << ". This is actually " 911 "sort of hard to do. Congrats. Unfortunately it is not valid " 912 "input so the program is going to die now." << endl; 913 return PARSE_ARGUMENT_FAIL; 914 } 915 916 input_files_.push_back(value); 917 918 } else if (name == "-I" || name == "--proto_path") { 919 // Java's -classpath (and some other languages) delimits path components 920 // with colons. Let's accept that syntax too just to make things more 921 // intuitive. 922 vector<string> parts; 923 SplitStringUsing(value, kPathSeparator, &parts); 924 925 for (int i = 0; i < parts.size(); i++) { 926 string virtual_path; 927 string disk_path; 928 929 string::size_type equals_pos = parts[i].find_first_of('='); 930 if (equals_pos == string::npos) { 931 virtual_path = ""; 932 disk_path = parts[i]; 933 } else { 934 virtual_path = parts[i].substr(0, equals_pos); 935 disk_path = parts[i].substr(equals_pos + 1); 936 } 937 938 if (disk_path.empty()) { 939 cerr << "--proto_path passed empty directory name. (Use \".\" for " 940 "current directory.)" << endl; 941 return PARSE_ARGUMENT_FAIL; 942 } 943 944 // Make sure disk path exists, warn otherwise. 945 if (access(disk_path.c_str(), F_OK) < 0) { 946 cerr << disk_path << ": warning: directory does not exist." << endl; 947 } 948 949 // Don't use make_pair as the old/default standard library on Solaris 950 // doesn't support it without explicit template parameters, which are 951 // incompatible with C++0x's make_pair. 952 proto_path_.push_back(pair<string, string>(virtual_path, disk_path)); 953 } 954 955 } else if (name == "-o" || name == "--descriptor_set_out") { 956 if (!descriptor_set_name_.empty()) { 957 cerr << name << " may only be passed once." << endl; 958 return PARSE_ARGUMENT_FAIL; 959 } 960 if (value.empty()) { 961 cerr << name << " requires a non-empty value." << endl; 962 return PARSE_ARGUMENT_FAIL; 963 } 964 if (mode_ != MODE_COMPILE) { 965 cerr << "Cannot use --encode or --decode and generate descriptors at the " 966 "same time." << endl; 967 return PARSE_ARGUMENT_FAIL; 968 } 969 descriptor_set_name_ = value; 970 971 } else if (name == "--include_imports") { 972 if (imports_in_descriptor_set_) { 973 cerr << name << " may only be passed once." << endl; 974 return PARSE_ARGUMENT_FAIL; 975 } 976 imports_in_descriptor_set_ = true; 977 978 } else if (name == "--include_source_info") { 979 if (source_info_in_descriptor_set_) { 980 cerr << name << " may only be passed once." << endl; 981 return PARSE_ARGUMENT_FAIL; 982 } 983 source_info_in_descriptor_set_ = true; 984 985 } else if (name == "-h" || name == "--help") { 986 PrintHelpText(); 987 return PARSE_ARGUMENT_DONE_AND_EXIT; // Exit without running compiler. 988 989 } else if (name == "--version") { 990 if (!version_info_.empty()) { 991 cout << version_info_ << endl; 992 } 993 cout << "libprotoc " 994 << protobuf::internal::VersionString(GOOGLE_PROTOBUF_VERSION) 995 << endl; 996 return PARSE_ARGUMENT_DONE_AND_EXIT; // Exit without running compiler. 997 998 } else if (name == "--disallow_services") { 999 disallow_services_ = true; 1000 1001 } else if (name == "--encode" || name == "--decode" || 1002 name == "--decode_raw") { 1003 if (mode_ != MODE_COMPILE) { 1004 cerr << "Only one of --encode and --decode can be specified." << endl; 1005 return PARSE_ARGUMENT_FAIL; 1006 } 1007 if (!output_directives_.empty() || !descriptor_set_name_.empty()) { 1008 cerr << "Cannot use " << name 1009 << " and generate code or descriptors at the same time." << endl; 1010 return PARSE_ARGUMENT_FAIL; 1011 } 1012 1013 mode_ = (name == "--encode") ? MODE_ENCODE : MODE_DECODE; 1014 1015 if (value.empty() && name != "--decode_raw") { 1016 cerr << "Type name for " << name << " cannot be blank." << endl; 1017 if (name == "--decode") { 1018 cerr << "To decode an unknown message, use --decode_raw." << endl; 1019 } 1020 return PARSE_ARGUMENT_FAIL; 1021 } else if (!value.empty() && name == "--decode_raw") { 1022 cerr << "--decode_raw does not take a parameter." << endl; 1023 return PARSE_ARGUMENT_FAIL; 1024 } 1025 1026 codec_type_ = value; 1027 1028 } else if (name == "--error_format") { 1029 if (value == "gcc") { 1030 error_format_ = ERROR_FORMAT_GCC; 1031 } else if (value == "msvs") { 1032 error_format_ = ERROR_FORMAT_MSVS; 1033 } else { 1034 cerr << "Unknown error format: " << value << endl; 1035 return PARSE_ARGUMENT_FAIL; 1036 } 1037 1038 } else if (name == "--plugin") { 1039 if (plugin_prefix_.empty()) { 1040 cerr << "This compiler does not support plugins." << endl; 1041 return PARSE_ARGUMENT_FAIL; 1042 } 1043 1044 string plugin_name; 1045 string path; 1046 1047 string::size_type equals_pos = value.find_first_of('='); 1048 if (equals_pos == string::npos) { 1049 // Use the basename of the file. 1050 string::size_type slash_pos = value.find_last_of('/'); 1051 if (slash_pos == string::npos) { 1052 plugin_name = value; 1053 } else { 1054 plugin_name = value.substr(slash_pos + 1); 1055 } 1056 path = value; 1057 } else { 1058 plugin_name = value.substr(0, equals_pos); 1059 path = value.substr(equals_pos + 1); 1060 } 1061 1062 plugins_[plugin_name] = path; 1063 1064 } else { 1065 // Some other flag. Look it up in the generators list. 1066 const GeneratorInfo* generator_info = 1067 FindOrNull(generators_by_flag_name_, name); 1068 if (generator_info == NULL && 1069 (plugin_prefix_.empty() || !HasSuffixString(name, "_out"))) { 1070 // Check if it's a generator option flag. 1071 generator_info = FindOrNull(generators_by_option_name_, name); 1072 if (generator_info == NULL) { 1073 cerr << "Unknown flag: " << name << endl; 1074 return PARSE_ARGUMENT_FAIL; 1075 } else { 1076 string* parameters = &generator_parameters_[generator_info->flag_name]; 1077 if (!parameters->empty()) { 1078 parameters->append(","); 1079 } 1080 parameters->append(value); 1081 } 1082 } else { 1083 // It's an output flag. Add it to the output directives. 1084 if (mode_ != MODE_COMPILE) { 1085 cerr << "Cannot use --encode or --decode and generate code at the " 1086 "same time." << endl; 1087 return PARSE_ARGUMENT_FAIL; 1088 } 1089 1090 OutputDirective directive; 1091 directive.name = name; 1092 if (generator_info == NULL) { 1093 directive.generator = NULL; 1094 } else { 1095 directive.generator = generator_info->generator; 1096 } 1097 1098 // Split value at ':' to separate the generator parameter from the 1099 // filename. However, avoid doing this if the colon is part of a valid 1100 // Windows-style absolute path. 1101 string::size_type colon_pos = value.find_first_of(':'); 1102 if (colon_pos == string::npos || IsWindowsAbsolutePath(value)) { 1103 directive.output_location = value; 1104 } else { 1105 directive.parameter = value.substr(0, colon_pos); 1106 directive.output_location = value.substr(colon_pos + 1); 1107 } 1108 1109 output_directives_.push_back(directive); 1110 } 1111 } 1112 1113 return PARSE_ARGUMENT_DONE_AND_CONTINUE; 1114} 1115 1116void CommandLineInterface::PrintHelpText() { 1117 // Sorry for indentation here; line wrapping would be uglier. 1118 cerr << 1119"Usage: " << executable_name_ << " [OPTION] PROTO_FILES\n" 1120"Parse PROTO_FILES and generate output based on the options given:\n" 1121" -IPATH, --proto_path=PATH Specify the directory in which to search for\n" 1122" imports. May be specified multiple times;\n" 1123" directories will be searched in order. If not\n" 1124" given, the current working directory is used.\n" 1125" --version Show version info and exit.\n" 1126" -h, --help Show this text and exit.\n" 1127" --encode=MESSAGE_TYPE Read a text-format message of the given type\n" 1128" from standard input and write it in binary\n" 1129" to standard output. The message type must\n" 1130" be defined in PROTO_FILES or their imports.\n" 1131" --decode=MESSAGE_TYPE Read a binary message of the given type from\n" 1132" standard input and write it in text format\n" 1133" to standard output. The message type must\n" 1134" be defined in PROTO_FILES or their imports.\n" 1135" --decode_raw Read an arbitrary protocol message from\n" 1136" standard input and write the raw tag/value\n" 1137" pairs in text format to standard output. No\n" 1138" PROTO_FILES should be given when using this\n" 1139" flag.\n" 1140" -oFILE, Writes a FileDescriptorSet (a protocol buffer,\n" 1141" --descriptor_set_out=FILE defined in descriptor.proto) containing all of\n" 1142" the input files to FILE.\n" 1143" --include_imports When using --descriptor_set_out, also include\n" 1144" all dependencies of the input files in the\n" 1145" set, so that the set is self-contained.\n" 1146" --include_source_info When using --descriptor_set_out, do not strip\n" 1147" SourceCodeInfo from the FileDescriptorProto.\n" 1148" This results in vastly larger descriptors that\n" 1149" include information about the original\n" 1150" location of each decl in the source file as\n" 1151" well as surrounding comments.\n" 1152" --error_format=FORMAT Set the format in which to print errors.\n" 1153" FORMAT may be 'gcc' (the default) or 'msvs'\n" 1154" (Microsoft Visual Studio format)." << endl; 1155 if (!plugin_prefix_.empty()) { 1156 cerr << 1157" --plugin=EXECUTABLE Specifies a plugin executable to use.\n" 1158" Normally, protoc searches the PATH for\n" 1159" plugins, but you may specify additional\n" 1160" executables not in the path using this flag.\n" 1161" Additionally, EXECUTABLE may be of the form\n" 1162" NAME=PATH, in which case the given plugin name\n" 1163" is mapped to the given executable even if\n" 1164" the executable's own name differs." << endl; 1165 } 1166 1167 for (GeneratorMap::iterator iter = generators_by_flag_name_.begin(); 1168 iter != generators_by_flag_name_.end(); ++iter) { 1169 // FIXME(kenton): If the text is long enough it will wrap, which is ugly, 1170 // but fixing this nicely (e.g. splitting on spaces) is probably more 1171 // trouble than it's worth. 1172 cerr << " " << iter->first << "=OUT_DIR " 1173 << string(19 - iter->first.size(), ' ') // Spaces for alignment. 1174 << iter->second.help_text << endl; 1175 } 1176} 1177 1178bool CommandLineInterface::GenerateOutput( 1179 const vector<const FileDescriptor*>& parsed_files, 1180 const OutputDirective& output_directive, 1181 GeneratorContext* generator_context) { 1182 // Call the generator. 1183 string error; 1184 if (output_directive.generator == NULL) { 1185 // This is a plugin. 1186 GOOGLE_CHECK(HasPrefixString(output_directive.name, "--") && 1187 HasSuffixString(output_directive.name, "_out")) 1188 << "Bad name for plugin generator: " << output_directive.name; 1189 1190 // Strip the "--" and "_out" and add the plugin prefix. 1191 string plugin_name = plugin_prefix_ + "gen-" + 1192 output_directive.name.substr(2, output_directive.name.size() - 6); 1193 1194 if (!GeneratePluginOutput(parsed_files, plugin_name, 1195 output_directive.parameter, 1196 generator_context, &error)) { 1197 cerr << output_directive.name << ": " << error << endl; 1198 return false; 1199 } 1200 } else { 1201 // Regular generator. 1202 string parameters = output_directive.parameter; 1203 if (!generator_parameters_[output_directive.name].empty()) { 1204 if (!parameters.empty()) { 1205 parameters.append(","); 1206 } 1207 parameters.append(generator_parameters_[output_directive.name]); 1208 } 1209 for (int i = 0; i < parsed_files.size(); i++) { 1210 if (!output_directive.generator->Generate(parsed_files[i], parameters, 1211 generator_context, &error)) { 1212 // Generator returned an error. 1213 cerr << output_directive.name << ": " << parsed_files[i]->name() << ": " 1214 << error << endl; 1215 return false; 1216 } 1217 } 1218 } 1219 1220 return true; 1221} 1222 1223bool CommandLineInterface::GeneratePluginOutput( 1224 const vector<const FileDescriptor*>& parsed_files, 1225 const string& plugin_name, 1226 const string& parameter, 1227 GeneratorContext* generator_context, 1228 string* error) { 1229 CodeGeneratorRequest request; 1230 CodeGeneratorResponse response; 1231 1232 // Build the request. 1233 if (!parameter.empty()) { 1234 request.set_parameter(parameter); 1235 } 1236 1237 set<const FileDescriptor*> already_seen; 1238 for (int i = 0; i < parsed_files.size(); i++) { 1239 request.add_file_to_generate(parsed_files[i]->name()); 1240 GetTransitiveDependencies(parsed_files[i], 1241 true, // Include source code info. 1242 &already_seen, request.mutable_proto_file()); 1243 } 1244 1245 // Invoke the plugin. 1246 Subprocess subprocess; 1247 1248 if (plugins_.count(plugin_name) > 0) { 1249 subprocess.Start(plugins_[plugin_name], Subprocess::EXACT_NAME); 1250 } else { 1251 subprocess.Start(plugin_name, Subprocess::SEARCH_PATH); 1252 } 1253 1254 string communicate_error; 1255 if (!subprocess.Communicate(request, &response, &communicate_error)) { 1256 *error = strings::Substitute("$0: $1", plugin_name, communicate_error); 1257 return false; 1258 } 1259 1260 // Write the files. We do this even if there was a generator error in order 1261 // to match the behavior of a compiled-in generator. 1262 scoped_ptr<io::ZeroCopyOutputStream> current_output; 1263 for (int i = 0; i < response.file_size(); i++) { 1264 const CodeGeneratorResponse::File& output_file = response.file(i); 1265 1266 if (!output_file.insertion_point().empty()) { 1267 // Open a file for insert. 1268 // We reset current_output to NULL first so that the old file is closed 1269 // before the new one is opened. 1270 current_output.reset(); 1271 current_output.reset(generator_context->OpenForInsert( 1272 output_file.name(), output_file.insertion_point())); 1273 } else if (!output_file.name().empty()) { 1274 // Starting a new file. Open it. 1275 // We reset current_output to NULL first so that the old file is closed 1276 // before the new one is opened. 1277 current_output.reset(); 1278 current_output.reset(generator_context->Open(output_file.name())); 1279 } else if (current_output == NULL) { 1280 *error = strings::Substitute( 1281 "$0: First file chunk returned by plugin did not specify a file name.", 1282 plugin_name); 1283 return false; 1284 } 1285 1286 // Use CodedOutputStream for convenience; otherwise we'd need to provide 1287 // our own buffer-copying loop. 1288 io::CodedOutputStream writer(current_output.get()); 1289 writer.WriteString(output_file.content()); 1290 } 1291 1292 // Check for errors. 1293 if (!response.error().empty()) { 1294 // Generator returned an error. 1295 *error = response.error(); 1296 return false; 1297 } 1298 1299 return true; 1300} 1301 1302bool CommandLineInterface::EncodeOrDecode(const DescriptorPool* pool) { 1303 // Look up the type. 1304 const Descriptor* type = pool->FindMessageTypeByName(codec_type_); 1305 if (type == NULL) { 1306 cerr << "Type not defined: " << codec_type_ << endl; 1307 return false; 1308 } 1309 1310 DynamicMessageFactory dynamic_factory(pool); 1311 scoped_ptr<Message> message(dynamic_factory.GetPrototype(type)->New()); 1312 1313 if (mode_ == MODE_ENCODE) { 1314 SetFdToTextMode(STDIN_FILENO); 1315 SetFdToBinaryMode(STDOUT_FILENO); 1316 } else { 1317 SetFdToBinaryMode(STDIN_FILENO); 1318 SetFdToTextMode(STDOUT_FILENO); 1319 } 1320 1321 io::FileInputStream in(STDIN_FILENO); 1322 io::FileOutputStream out(STDOUT_FILENO); 1323 1324 if (mode_ == MODE_ENCODE) { 1325 // Input is text. 1326 ErrorPrinter error_collector(error_format_); 1327 TextFormat::Parser parser; 1328 parser.RecordErrorsTo(&error_collector); 1329 parser.AllowPartialMessage(true); 1330 1331 if (!parser.Parse(&in, message.get())) { 1332 cerr << "Failed to parse input." << endl; 1333 return false; 1334 } 1335 } else { 1336 // Input is binary. 1337 if (!message->ParsePartialFromZeroCopyStream(&in)) { 1338 cerr << "Failed to parse input." << endl; 1339 return false; 1340 } 1341 } 1342 1343 if (!message->IsInitialized()) { 1344 cerr << "warning: Input message is missing required fields: " 1345 << message->InitializationErrorString() << endl; 1346 } 1347 1348 if (mode_ == MODE_ENCODE) { 1349 // Output is binary. 1350 if (!message->SerializePartialToZeroCopyStream(&out)) { 1351 cerr << "output: I/O error." << endl; 1352 return false; 1353 } 1354 } else { 1355 // Output is text. 1356 if (!TextFormat::Print(*message, &out)) { 1357 cerr << "output: I/O error." << endl; 1358 return false; 1359 } 1360 } 1361 1362 return true; 1363} 1364 1365bool CommandLineInterface::WriteDescriptorSet( 1366 const vector<const FileDescriptor*> parsed_files) { 1367 FileDescriptorSet file_set; 1368 1369 if (imports_in_descriptor_set_) { 1370 set<const FileDescriptor*> already_seen; 1371 for (int i = 0; i < parsed_files.size(); i++) { 1372 GetTransitiveDependencies(parsed_files[i], 1373 source_info_in_descriptor_set_, 1374 &already_seen, file_set.mutable_file()); 1375 } 1376 } else { 1377 for (int i = 0; i < parsed_files.size(); i++) { 1378 FileDescriptorProto* file_proto = file_set.add_file(); 1379 parsed_files[i]->CopyTo(file_proto); 1380 if (source_info_in_descriptor_set_) { 1381 parsed_files[i]->CopySourceCodeInfoTo(file_proto); 1382 } 1383 } 1384 } 1385 1386 int fd; 1387 do { 1388 fd = open(descriptor_set_name_.c_str(), 1389 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); 1390 } while (fd < 0 && errno == EINTR); 1391 1392 if (fd < 0) { 1393 perror(descriptor_set_name_.c_str()); 1394 return false; 1395 } 1396 1397 io::FileOutputStream out(fd); 1398 if (!file_set.SerializeToZeroCopyStream(&out)) { 1399 cerr << descriptor_set_name_ << ": " << strerror(out.GetErrno()) << endl; 1400 out.Close(); 1401 return false; 1402 } 1403 if (!out.Close()) { 1404 cerr << descriptor_set_name_ << ": " << strerror(out.GetErrno()) << endl; 1405 return false; 1406 } 1407 1408 return true; 1409} 1410 1411void CommandLineInterface::GetTransitiveDependencies( 1412 const FileDescriptor* file, bool include_source_code_info, 1413 set<const FileDescriptor*>* already_seen, 1414 RepeatedPtrField<FileDescriptorProto>* output) { 1415 if (!already_seen->insert(file).second) { 1416 // Already saw this file. Skip. 1417 return; 1418 } 1419 1420 // Add all dependencies. 1421 for (int i = 0; i < file->dependency_count(); i++) { 1422 GetTransitiveDependencies(file->dependency(i), include_source_code_info, 1423 already_seen, output); 1424 } 1425 1426 // Add this file. 1427 FileDescriptorProto* new_descriptor = output->Add(); 1428 file->CopyTo(new_descriptor); 1429 if (include_source_code_info) { 1430 file->CopySourceCodeInfoTo(new_descriptor); 1431 } 1432} 1433 1434 1435} // namespace compiler 1436} // namespace protobuf 1437} // namespace google 1438