Skip to content

Commit

Permalink
Merge pull request protocolbuffers#193 from TeBoring/sb
Browse files Browse the repository at this point in the history
Implement a feature to generate a dependency file
  • Loading branch information
TeBoring committed Mar 8, 2015
2 parents 88eda4d + eb2ce02 commit a5f7bb8
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 2 deletions.
114 changes: 112 additions & 2 deletions src/google/protobuf/compiler/command_line_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
#include <iostream>
#include <ctype.h>

#include <google/protobuf/stubs/hash.h>
#include <memory>
#ifndef _SHARED_PTR_H
#include <google/protobuf/stubs/shared_ptr.h>
Expand Down Expand Up @@ -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<string>* output_filenames);

// implements GeneratorContext --------------------------------------
io::ZeroCopyOutputStream* Open(const string& filename);
io::ZeroCopyOutputStream* OpenForAppend(const string& filename);
Expand Down Expand Up @@ -442,6 +444,14 @@ void CommandLineInterface::GeneratorContextImpl::AddJarManifest() {
}
}

void CommandLineInterface::GeneratorContextImpl::GetOutputFilenames(
vector<string>* output_filenames) {
for (map<string, string*>::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);
Expand Down Expand Up @@ -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<string, GeneratorContextImpl*> GeneratorContextMap;
GeneratorContextMap output_directories;

// Generate output.
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -1301,6 +1341,76 @@ bool CommandLineInterface::GenerateOutput(
return true;
}

bool CommandLineInterface::GenerateDependencyManifestFile(
const vector<const FileDescriptor*>& parsed_files,
const GeneratorContextMap& output_directories,
DiskSourceTree* source_tree) {
FileDescriptorSet file_set;

set<const FileDescriptor*> already_seen;
for (int i = 0; i < parsed_files.size(); i++) {
GetTransitiveDependencies(parsed_files[i],
false,
&already_seen,
file_set.mutable_file());
}

vector<string> 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<string> 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<const FileDescriptor*>& parsed_files,
const string& plugin_name,
Expand Down
12 changes: 12 additions & 0 deletions src/google/protobuf/compiler/command_line_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#define GOOGLE_PROTOBUF_COMPILER_COMMAND_LINE_INTERFACE_H__

#include <google/protobuf/stubs/common.h>
#include <google/protobuf/stubs/hash.h>
#include <string>
#include <vector>
#include <map>
Expand Down Expand Up @@ -190,6 +191,7 @@ class LIBPROTOC_EXPORT CommandLineInterface {
class ErrorPrinter;
class GeneratorContextImpl;
class MemoryOutputStream;
typedef hash_map<string, GeneratorContextImpl*> GeneratorContextMap;

// Clear state from previous Run().
void Clear();
Expand Down Expand Up @@ -247,6 +249,12 @@ class LIBPROTOC_EXPORT CommandLineInterface {
// Implements the --descriptor_set_out option.
bool WriteDescriptorSet(const vector<const FileDescriptor*> parsed_files);

// Implements the --dependency_out option
bool GenerateDependencyManifestFile(
const vector<const FileDescriptor*>& 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
Expand Down Expand Up @@ -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.
Expand Down
84 changes: 84 additions & 0 deletions src/google/protobuf/compiler/command_line_interface_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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_;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/google/protobuf/testing/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions src/google/protobuf/testing/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit a5f7bb8

Please sign in to comment.