diff --git a/src/google/protobuf/compiler/command_line_interface.cc b/src/google/protobuf/compiler/command_line_interface.cc index 09106313..567238ae 100644 --- a/src/google/protobuf/compiler/command_line_interface.cc +++ b/src/google/protobuf/compiler/command_line_interface.cc @@ -48,7 +48,6 @@ #include #include -#include #include #ifndef _SHARED_PTR_H #include @@ -255,6 +254,9 @@ class CommandLineInterface::GeneratorContextImpl : public GeneratorContext { // format, unless one has already been written. void AddJarManifest(); + // Get name of all output files. + void GetOutputFilenames(vector* output_filenames); + // implements GeneratorContext -------------------------------------- io::ZeroCopyOutputStream* Open(const string& filename); io::ZeroCopyOutputStream* OpenForAppend(const string& filename); @@ -442,6 +444,14 @@ void CommandLineInterface::GeneratorContextImpl::AddJarManifest() { } } +void CommandLineInterface::GeneratorContextImpl::GetOutputFilenames( + vector* output_filenames) { + for (map::iterator iter = files_.begin(); + iter != files_.end(); ++iter) { + output_filenames->push_back(iter->first); + } +} + io::ZeroCopyOutputStream* CommandLineInterface::GeneratorContextImpl::Open( const string& filename) { return new MemoryOutputStream(this, filename, false); @@ -673,7 +683,6 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) { // We construct a separate GeneratorContext for each output location. Note // that two code generators may output to the same location, in which case // they should share a single GeneratorContext so that OpenForInsert() works. - typedef hash_map GeneratorContextMap; GeneratorContextMap output_directories; // Generate output. @@ -720,6 +729,13 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) { } } + if (!dependency_out_name_.empty()) { + if (!GenerateDependencyManifestFile(parsed_files, output_directories, + &source_tree)) { + return 1; + } + } + STLDeleteValues(&output_directories); if (!descriptor_set_name_.empty()) { @@ -778,6 +794,7 @@ void CommandLineInterface::Clear() { output_directives_.clear(); codec_type_.clear(); descriptor_set_name_.clear(); + dependency_out_name_.clear(); mode_ = MODE_COMPILE; print_mode_ = PRINT_NONE; @@ -880,6 +897,15 @@ CommandLineInterface::ParseArguments(int argc, const char* const argv[]) { std::cerr << "Missing output directives." << std::endl; return PARSE_ARGUMENT_FAIL; } + if (mode_ != MODE_COMPILE && !dependency_out_name_.empty()) { + cerr << "Can only use --dependency_out=FILE when generating code." << endl; + return PARSE_ARGUMENT_FAIL; + } + if (!dependency_out_name_.empty() && input_files_.size() > 1) { + cerr << "Can only process one input file when using --dependency_out=FILE." + << endl; + return PARSE_ARGUMENT_FAIL; + } if (imports_in_descriptor_set_ && descriptor_set_name_.empty()) { std::cerr << "--include_imports only makes sense when combined with " "--descriptor_set_out." << std::endl; @@ -1026,6 +1052,17 @@ CommandLineInterface::InterpretArgument(const string& name, } descriptor_set_name_ = value; + } else if (name == "--dependency_out") { + if (!dependency_out_name_.empty()) { + cerr << name << " may only be passed once." << endl; + return PARSE_ARGUMENT_FAIL; + } + if (value.empty()) { + cerr << name << " requires a non-empty value." << endl; + return PARSE_ARGUMENT_FAIL; + } + dependency_out_name_ = value; + } else if (name == "--include_imports") { if (imports_in_descriptor_set_) { std::cerr << name << " may only be passed once." << std::endl; @@ -1225,6 +1262,9 @@ void CommandLineInterface::PrintHelpText() { " include information about the original\n" " location of each decl in the source file as\n" " well as surrounding comments.\n" +" --dependency_out=FILE Write a dependency output file in the format\n" +" expected by make. This writes the transitive\n" +" set of input file paths to FILE\n" " --error_format=FORMAT Set the format in which to print errors.\n" " FORMAT may be 'gcc' (the default) or 'msvs'\n" " (Microsoft Visual Studio format).\n" @@ -1301,6 +1341,76 @@ bool CommandLineInterface::GenerateOutput( return true; } +bool CommandLineInterface::GenerateDependencyManifestFile( + const vector& parsed_files, + const GeneratorContextMap& output_directories, + DiskSourceTree* source_tree) { + FileDescriptorSet file_set; + + set already_seen; + for (int i = 0; i < parsed_files.size(); i++) { + GetTransitiveDependencies(parsed_files[i], + false, + &already_seen, + file_set.mutable_file()); + } + + vector output_filenames; + for (GeneratorContextMap::const_iterator iter = output_directories.begin(); + iter != output_directories.end(); ++iter) { + const string& location = iter->first; + GeneratorContextImpl* directory = iter->second; + vector relative_output_filenames; + directory->GetOutputFilenames(&relative_output_filenames); + for (int i = 0; i < relative_output_filenames.size(); i++) { + string output_filename = location + relative_output_filenames[i]; + if (output_filename.compare(0, 2, "./") == 0) { + output_filename = output_filename.substr(2); + } + output_filenames.push_back(output_filename); + } + } + + int fd; + do { + fd = open(dependency_out_name_.c_str(), + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); + } while (fd < 0 && errno == EINTR); + + if (fd < 0) { + perror(dependency_out_name_.c_str()); + return false; + } + + io::FileOutputStream out(fd); + io::Printer printer(&out, '$'); + + for (int i = 0; i < output_filenames.size(); i++) { + printer.Print(output_filenames[i].c_str()); + if (i == output_filenames.size() - 1) { + printer.Print(":"); + } else { + printer.Print(" \\\n"); + } + } + + for (int i = 0; i < file_set.file_size(); i++) { + const FileDescriptorProto& file = file_set.file(i); + const string& virtual_file = file.name(); + string disk_file; + if (source_tree && + source_tree->VirtualFileToDiskFile(virtual_file, &disk_file)) { + printer.Print(" $disk_file$", "disk_file", disk_file); + if (i < file_set.file_size() - 1) printer.Print("\\\n"); + } else { + cerr << "Unable to identify path for file " << virtual_file << endl; + return false; + } + } + + return true; +} + bool CommandLineInterface::GeneratePluginOutput( const vector& parsed_files, const string& plugin_name, diff --git a/src/google/protobuf/compiler/command_line_interface.h b/src/google/protobuf/compiler/command_line_interface.h index 74a0adb4..7e611c44 100644 --- a/src/google/protobuf/compiler/command_line_interface.h +++ b/src/google/protobuf/compiler/command_line_interface.h @@ -39,6 +39,7 @@ #define GOOGLE_PROTOBUF_COMPILER_COMMAND_LINE_INTERFACE_H__ #include +#include #include #include #include @@ -190,6 +191,7 @@ class LIBPROTOC_EXPORT CommandLineInterface { class ErrorPrinter; class GeneratorContextImpl; class MemoryOutputStream; + typedef hash_map GeneratorContextMap; // Clear state from previous Run(). void Clear(); @@ -247,6 +249,12 @@ class LIBPROTOC_EXPORT CommandLineInterface { // Implements the --descriptor_set_out option. bool WriteDescriptorSet(const vector parsed_files); + // Implements the --dependency_out option + bool GenerateDependencyManifestFile( + const vector& parsed_files, + const GeneratorContextMap& output_directories, + DiskSourceTree* source_tree); + // Get all transitive dependencies of the given file (including the file // itself), adding them to the given list of FileDescriptorProtos. The // protos will be ordered such that every file is listed before any file that @@ -353,6 +361,10 @@ class LIBPROTOC_EXPORT CommandLineInterface { // FileDescriptorSet should be written. Otherwise, empty. string descriptor_set_name_; + // If --dependency_out was given, this is the path to the file where the + // dependency file will be written. Otherwise, empty. + string dependency_out_name_; + // True if --include_imports was given, meaning that we should // write all transitive dependencies to the DescriptorSet. Otherwise, only // the .proto files listed on the command-line are added. diff --git a/src/google/protobuf/compiler/command_line_interface_unittest.cc b/src/google/protobuf/compiler/command_line_interface_unittest.cc index 3d27829e..2b26f3be 100644 --- a/src/google/protobuf/compiler/command_line_interface_unittest.cc +++ b/src/google/protobuf/compiler/command_line_interface_unittest.cc @@ -115,6 +115,11 @@ class CommandLineInterfaceTest : public testing::Test { // Create a subdirectory within temp_directory_. void CreateTempDir(const string& name); + // Change working directory to temp directory. + void SwitchToTempDirectory() { + File::ChangeWorkingDirectory(temp_directory_); + } + void SetInputsAreProtoPathRelative(bool enable) { cli_.SetInputsAreProtoPathRelative(enable); } @@ -179,6 +184,9 @@ class CommandLineInterfaceTest : public testing::Test { void ReadDescriptorSet(const string& filename, FileDescriptorSet* descriptor_set); + void ExpectFileContent(const string& filename, + const string& content); + private: // The object we are testing. CommandLineInterface cli_; @@ -459,6 +467,17 @@ void CommandLineInterfaceTest::ExpectCapturedStdout( EXPECT_EQ(expected_text, captured_stdout_); } + +void CommandLineInterfaceTest::ExpectFileContent( + const string& filename, const string& content) { + string path = temp_directory_ + "/" + filename; + string file_contents; + GOOGLE_CHECK_OK(File::GetContents(path, &file_contents, true)); + + EXPECT_EQ(StringReplace(content, "$tmpdir", temp_directory_, true), + file_contents); +} + // =================================================================== TEST_F(CommandLineInterfaceTest, BasicOutput) { @@ -943,6 +962,71 @@ TEST_F(CommandLineInterfaceTest, WriteTransitiveDescriptorSetWithSourceInfo) { EXPECT_TRUE(descriptor_set.file(1).has_source_code_info()); } +TEST_F(CommandLineInterfaceTest, WriteDependencyManifestFileGivenTwoInputs) { + CreateTempFile("foo.proto", + "syntax = \"proto2\";\n" + "message Foo {}\n"); + CreateTempFile("bar.proto", + "syntax = \"proto2\";\n" + "import \"foo.proto\";\n" + "message Bar {\n" + " optional Foo foo = 1;\n" + "}\n"); + + Run("protocol_compiler --dependency_out=$tmpdir/manifest " + "--test_out=$tmpdir --proto_path=$tmpdir bar.proto foo.proto"); + + ExpectErrorText( + "Can only process one input file when using --dependency_out=FILE.\n"); +} + +TEST_F(CommandLineInterfaceTest, WriteDependencyManifestFile) { + CreateTempFile("foo.proto", + "syntax = \"proto2\";\n" + "message Foo {}\n"); + CreateTempFile("bar.proto", + "syntax = \"proto2\";\n" + "import \"foo.proto\";\n" + "message Bar {\n" + " optional Foo foo = 1;\n" + "}\n"); + + string current_working_directory = get_current_dir_name(); + SwitchToTempDirectory(); + + Run("protocol_compiler --dependency_out=manifest --test_out=. " + "bar.proto"); + + ExpectNoErrors(); + + ExpectFileContent("manifest", + "bar.proto.MockCodeGenerator.test_generator: " + "foo.proto\\\n bar.proto"); + + File::ChangeWorkingDirectory(current_working_directory); +} + +TEST_F(CommandLineInterfaceTest, WriteDependencyManifestFileForAbsolutePath) { + CreateTempFile("foo.proto", + "syntax = \"proto2\";\n" + "message Foo {}\n"); + CreateTempFile("bar.proto", + "syntax = \"proto2\";\n" + "import \"foo.proto\";\n" + "message Bar {\n" + " optional Foo foo = 1;\n" + "}\n"); + + Run("protocol_compiler --dependency_out=$tmpdir/manifest " + "--test_out=$tmpdir --proto_path=$tmpdir bar.proto"); + + ExpectNoErrors(); + + ExpectFileContent("manifest", + "$tmpdir/bar.proto.MockCodeGenerator.test_generator: " + "$tmpdir/foo.proto\\\n $tmpdir/bar.proto"); +} + // ------------------------------------------------------------------- TEST_F(CommandLineInterfaceTest, ParseErrors) { diff --git a/src/google/protobuf/testing/file.cc b/src/google/protobuf/testing/file.cc index 5344ec15..3d07b127 100644 --- a/src/google/protobuf/testing/file.cc +++ b/src/google/protobuf/testing/file.cc @@ -192,5 +192,9 @@ void File::DeleteRecursively(const string& name, #endif } +bool File::ChangeWorkingDirectory(const string& new_working_directory) { + return chdir(new_working_directory.c_str()) == 0; +} + } // namespace protobuf } // namespace google diff --git a/src/google/protobuf/testing/file.h b/src/google/protobuf/testing/file.h index d2aeabf2..2f63f80e 100644 --- a/src/google/protobuf/testing/file.h +++ b/src/google/protobuf/testing/file.h @@ -77,6 +77,9 @@ class File { static void DeleteRecursively(const string& name, void* dummy1, void* dummy2); + // Change working directory to given directory. + static bool ChangeWorkingDirectory(const string& new_working_directory); + static bool GetContents( const string& name, string* output, bool /*is_default*/) { return ReadFileToString(name, output);