Commit 5f12164f authored by kenton@google.com's avatar kenton@google.com

Refactor the way output is handled in CommandLineInterface -- now it will be…

Refactor the way output is handled in CommandLineInterface -- now it will be stored in-memory until all code generators have completed, then dumped to disk all at once.  While this means that protoc uses more memory, the code is much simpler, and handles insertions much faster.  Also, this made it easier to implement a useful feature:  insertions will be indented to match the insertion point line.  Therefore, when inserting into Python code, you don't have to figure out how much to indent your inserted code.  The refactoring should also make it easier to implement output-to-jar at some point.
parent 46ed74e8
...@@ -61,6 +61,8 @@ ...@@ -61,6 +61,8 @@
#include <google/protobuf/stubs/strutil.h> #include <google/protobuf/stubs/strutil.h>
#include <google/protobuf/stubs/substitute.h> #include <google/protobuf/stubs/substitute.h>
#include <google/protobuf/stubs/map-util.h> #include <google/protobuf/stubs/map-util.h>
#include <google/protobuf/stubs/stl_util-inl.h>
#include <google/protobuf/stubs/hash.h>
namespace google { namespace google {
...@@ -132,6 +134,45 @@ void SetFdToBinaryMode(int fd) { ...@@ -132,6 +134,45 @@ void SetFdToBinaryMode(int fd) {
// (Text and binary are the same on non-Windows platforms.) // (Text and binary are the same on non-Windows platforms.)
} }
void AddTrailingSlash(string* path) {
if (!path->empty() && path->at(path->size() - 1) != '/') {
path->push_back('/');
}
}
bool VerifyDirectoryExists(const string& path) {
if (path.empty()) return true;
if (access(path.c_str(), W_OK) == -1) {
cerr << path << ": " << strerror(errno) << endl;
return false;
} else {
return true;
}
}
// Try to create the parent directory of the given file, creating the parent's
// parent if necessary, and so on. The full file name is actually
// (prefix + filename), but we assume |prefix| already exists and only create
// directories listed in |filename|.
void TryCreateParentDirectory(const string& prefix, const string& filename) {
// Recursively create parent directories to the output file.
vector<string> parts;
SplitStringUsing(filename, "/", &parts);
string path_so_far = prefix;
for (int i = 0; i < parts.size() - 1; i++) {
path_so_far += parts[i];
if (mkdir(path_so_far.c_str(), 0777) != 0) {
if (errno != EEXIST) {
cerr << filename << ": while trying to create directory "
<< path_so_far << ": " << strerror(errno) << endl;
return;
}
}
path_so_far += '/';
}
}
} // namespace } // namespace
// A MultiFileErrorCollector that prints errors to stderr. // A MultiFileErrorCollector that prints errors to stderr.
...@@ -176,15 +217,12 @@ class CommandLineInterface::ErrorPrinter : public MultiFileErrorCollector, ...@@ -176,15 +217,12 @@ class CommandLineInterface::ErrorPrinter : public MultiFileErrorCollector,
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// An OutputDirectory implementation that writes to disk. // An OutputDirectory implementation that writes to disk.
class CommandLineInterface::DiskOutputDirectory : public OutputDirectory { class CommandLineInterface::MemoryOutputDirectory : public OutputDirectory {
public: public:
DiskOutputDirectory(const string& root); MemoryOutputDirectory();
~DiskOutputDirectory(); ~MemoryOutputDirectory();
bool VerifyExistence(); bool WriteAllToDisk();
inline bool had_error() { return had_error_; }
inline void set_had_error(bool value) { had_error_ = value; }
// implements OutputDirectory -------------------------------------- // implements OutputDirectory --------------------------------------
io::ZeroCopyOutputStream* Open(const string& filename); io::ZeroCopyOutputStream* Open(const string& filename);
...@@ -192,362 +230,264 @@ class CommandLineInterface::DiskOutputDirectory : public OutputDirectory { ...@@ -192,362 +230,264 @@ class CommandLineInterface::DiskOutputDirectory : public OutputDirectory {
const string& filename, const string& insertion_point); const string& filename, const string& insertion_point);
private: private:
string root_; friend class MemoryOutputStream;
hash_map<string, string*> files_;
bool had_error_; bool had_error_;
}; };
// A FileOutputStream that checks for errors in the destructor and reports // OutputDirectory that just adds some prefix to every file name.
// them. We extend FileOutputStream via wrapping rather than inheritance class CommandLineInterface::SubOutputDirectory : public OutputDirectory {
// for two reasons:
// 1) Implementation inheritance is evil.
// 2) We need to close the file descriptor *after* the FileOutputStream's
// destructor is run to make sure it flushes the file contents.
class CommandLineInterface::ErrorReportingFileOutput
: public io::ZeroCopyOutputStream {
public: public:
ErrorReportingFileOutput(int file_descriptor, SubOutputDirectory(OutputDirectory* parent, const string& prefix)
const string& filename, : parent_(parent), prefix_(prefix) {}
DiskOutputDirectory* directory); ~SubOutputDirectory() {}
~ErrorReportingFileOutput();
// implements ZeroCopyOutputStream --------------------------------- // implements OutputDirectory --------------------------------------
bool Next(void** data, int* size) { return file_stream_->Next(data, size); } io::ZeroCopyOutputStream* Open(const string& filename) {
void BackUp(int count) { file_stream_->BackUp(count); } // TODO(kenton): This is not the cleanest place to deal with creation of
int64 ByteCount() const { return file_stream_->ByteCount(); } // parent directories, but it does the right thing given the way this
// class is used, and this class is private to this file anyway, so it's
// probably not worth fixing for now.
TryCreateParentDirectory(prefix_, filename);
return parent_->Open(prefix_ + filename);
}
io::ZeroCopyOutputStream* OpenForInsert(
const string& filename, const string& insertion_point) {
return parent_->OpenForInsert(prefix_ + filename, insertion_point);
}
private: private:
scoped_ptr<io::FileOutputStream> file_stream_; OutputDirectory* parent_;
string filename_; string prefix_;
DiskOutputDirectory* directory_;
}; };
// Kind of like ErrorReportingFileOutput, but used when inserting class CommandLineInterface::MemoryOutputStream
// (OutputDirectory::OpenForInsert()). In this case, we are writing to a
// temporary file, since we must copy data from the original. We copy the
// data up to the insertion point in the constructor, and the remainder in the
// destructor. We then replace the original file with the temporary, also in
// the destructor.
class CommandLineInterface::InsertionOutputStream
: public io::ZeroCopyOutputStream { : public io::ZeroCopyOutputStream {
public: public:
InsertionOutputStream( MemoryOutputStream(MemoryOutputDirectory* directory, const string& filename);
const string& filename, MemoryOutputStream(MemoryOutputDirectory* directory, const string& filename,
const string& temp_filename, const string& insertion_point);
const string& insertion_point, virtual ~MemoryOutputStream();
int original_file_descriptor, // Takes ownership.
int temp_file_descriptor, // Takes ownership.
DiskOutputDirectory* directory); // Does not take ownership.
~InsertionOutputStream();
// implements ZeroCopyOutputStream --------------------------------- // implements ZeroCopyOutputStream ---------------------------------
bool Next(void** data, int* size) { return temp_file_->Next(data, size); } virtual bool Next(void** data, int* size) { return inner_->Next(data, size); }
void BackUp(int count) { temp_file_->BackUp(count); } virtual void BackUp(int count) { inner_->BackUp(count); }
int64 ByteCount() const { return temp_file_->ByteCount(); } virtual int64 ByteCount() const { return inner_->ByteCount(); }
private: private:
scoped_ptr<io::FileInputStream> original_file_; // Where to insert the string when it's done.
scoped_ptr<io::FileOutputStream> temp_file_; MemoryOutputDirectory* directory_;
string filename_; string filename_;
string temp_filename_; string insertion_point_;
DiskOutputDirectory* directory_;
// The contents of the line containing the insertion point. // The string we're building.
string magic_line_; string data_;
// StringOutputStream writing to data_.
scoped_ptr<io::StringOutputStream> inner_;
}; };
// ------------------------------------------------------------------- // -------------------------------------------------------------------
CommandLineInterface::DiskOutputDirectory::DiskOutputDirectory( CommandLineInterface::MemoryOutputDirectory::MemoryOutputDirectory()
const string& root) : had_error_(false) {}
: root_(root), had_error_(false) {
// Add a '/' to the end if it doesn't already have one. But don't add a
// '/' to an empty string since this probably means the current directory.
if (!root_.empty() && root[root_.size() - 1] != '/') {
root_ += '/';
}
}
CommandLineInterface::DiskOutputDirectory::~DiskOutputDirectory() { CommandLineInterface::MemoryOutputDirectory::~MemoryOutputDirectory() {
STLDeleteValues(&files_);
} }
bool CommandLineInterface::DiskOutputDirectory::VerifyExistence() { bool CommandLineInterface::MemoryOutputDirectory::WriteAllToDisk() {
if (!root_.empty()) { if (had_error_) {
// Make sure the directory exists. If it isn't a directory, this will fail return false;
// because we added a '/' to the end of the name in the constructor.
if (access(root_.c_str(), W_OK) == -1) {
cerr << root_ << ": " << strerror(errno) << endl;
return false;
}
} }
return true; for (hash_map<string, string*>::const_iterator iter = files_.begin();
} iter != files_.end(); ++iter) {
const string& filename = iter->first;
// ------------------------------------------------------------------- const char* data = iter->second->data();
int size = iter->second->size();
// Create the output file.
int file_descriptor;
do {
file_descriptor =
open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
} while (file_descriptor < 0 && errno == EINTR);
if (file_descriptor < 0) {
int error = errno;
cerr << filename << ": " << strerror(error);
return false;
}
io::ZeroCopyOutputStream* CommandLineInterface::DiskOutputDirectory::Open( // Write the file.
const string& filename) { while (size > 0) {
// Recursively create parent directories to the output file. int write_result;
vector<string> parts; do {
SplitStringUsing(filename, "/", &parts); write_result = write(file_descriptor, data, size);
string path_so_far = root_; } while (write_result < 0 && errno == EINTR);
for (int i = 0; i < parts.size() - 1; i++) {
path_so_far += parts[i]; if (write_result <= 0) {
if (mkdir(path_so_far.c_str(), 0777) != 0) { // Write error.
if (errno != EEXIST) {
cerr << filename << ": while trying to create directory " // FIXME(kenton): According to the man page, if write() returns zero,
<< path_so_far << ": " << strerror(errno) << endl; // there was no error; write() simply did not write anything. It's
had_error_ = true; // unclear under what circumstances this might happen, but presumably
// Return a dummy stream. // errno won't be set in this case. I am confused as to how such an
return new io::ArrayOutputStream(NULL, 0); // event should be handled. For now I'm treating it as an error,
// since retrying seems like it could lead to an infinite loop. I
// suspect this never actually happens anyway.
if (write_result < 0) {
int error = errno;
cerr << filename << ": write: " << strerror(error);
} else {
cerr << filename << ": write() returned zero?" << endl;
}
return false;
} }
data += write_result;
size -= write_result;
} }
path_so_far += '/';
}
// Create the output file. if (close(file_descriptor) != 0) {
int file_descriptor; int error = errno;
do { cerr << filename << ": close: " << strerror(error);
file_descriptor = return false;
open((root_ + filename).c_str(), }
O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
} while (file_descriptor < 0 && errno == EINTR);
if (file_descriptor < 0) {
// Failed to open.
cerr << filename << ": " << strerror(errno) << endl;
had_error_ = true;
// Return a dummy stream.
return new io::ArrayOutputStream(NULL, 0);
} }
return new ErrorReportingFileOutput(file_descriptor, filename, this); return true;
} }
CommandLineInterface::ErrorReportingFileOutput::ErrorReportingFileOutput( io::ZeroCopyOutputStream* CommandLineInterface::MemoryOutputDirectory::Open(
int file_descriptor, const string& filename) {
const string& filename, return new MemoryOutputStream(this, filename);
DiskOutputDirectory* directory)
: file_stream_(new io::FileOutputStream(file_descriptor)),
filename_(filename),
directory_(directory) {}
CommandLineInterface::ErrorReportingFileOutput::~ErrorReportingFileOutput() {
// Check if we had any errors while writing.
if (file_stream_->GetErrno() != 0) {
cerr << filename_ << ": " << strerror(file_stream_->GetErrno()) << endl;
directory_->set_had_error(true);
}
// Close the file stream.
if (!file_stream_->Close()) {
cerr << filename_ << ": " << strerror(file_stream_->GetErrno()) << endl;
directory_->set_had_error(true);
}
} }
// -------------------------------------------------------------------
io::ZeroCopyOutputStream* io::ZeroCopyOutputStream*
CommandLineInterface::DiskOutputDirectory::OpenForInsert( CommandLineInterface::MemoryOutputDirectory::OpenForInsert(
const string& filename, const string& insertion_point) { const string& filename, const string& insertion_point) {
string path = root_ + filename; return new MemoryOutputStream(this, filename, insertion_point);
}
// Put the temp file in the same directory so that we can simply rename() it
// into place later.
string temp_path = path + ".protoc_temp";
// Open the original file.
int original_file;
do {
original_file = open(path.c_str(), O_RDONLY | O_BINARY);
} while (original_file < 0 && errno == EINTR);
if (original_file < 0) { // -------------------------------------------------------------------
// Failed to open.
cerr << path << ": " << strerror(errno) << endl;
had_error_ = true;
// Return a dummy stream.
return new io::ArrayOutputStream(NULL, 0);
}
// Create the temp file. CommandLineInterface::MemoryOutputStream::MemoryOutputStream(
int temp_file; MemoryOutputDirectory* directory, const string& filename)
do { : directory_(directory),
temp_file = filename_(filename),
open(temp_path.c_str(), inner_(new io::StringOutputStream(&data_)) {
O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); }
} while (temp_file < 0 && errno == EINTR);
if (temp_file < 0) {
// Failed to open.
cerr << temp_path << ": " << strerror(errno) << endl;
had_error_ = true;
close(original_file);
// Return a dummy stream.
return new io::ArrayOutputStream(NULL, 0);
}
return new InsertionOutputStream( CommandLineInterface::MemoryOutputStream::MemoryOutputStream(
path, temp_path, insertion_point, original_file, temp_file, this); MemoryOutputDirectory* directory, const string& filename,
const string& insertion_point)
: directory_(directory),
filename_(filename),
insertion_point_(insertion_point),
inner_(new io::StringOutputStream(&data_)) {
} }
namespace { CommandLineInterface::MemoryOutputStream::~MemoryOutputStream() {
// Make sure all data has been written.
inner_.reset();
// Helper for reading lines from a ZeroCopyInputStream. // Insert into the directory.
// TODO(kenton): Put somewhere reusable? string** map_slot = &directory_->files_[filename_];
class LineReader {
public:
LineReader(io::ZeroCopyInputStream* input)
: input_(input), buffer_(NULL), size_(0) {}
~LineReader() { if (insertion_point_.empty()) {
if (size_ > 0) { // This was just a regular Open().
input_->BackUp(size_); if (*map_slot != NULL) {
cerr << filename_ << ": Tried to write the same file twice." << endl;
directory_->had_error_ = true;
return;
} }
}
bool ReadLine(string* line) {
line->clear();
while (true) { *map_slot = new string;
for (int i = 0; i < size_; i++) { (*map_slot)->swap(data_);
if (buffer_[i] == '\n') { } else {
line->append(buffer_, i + 1); // This was an OpenForInsert().
buffer_ += i + 1;
size_ -= i + 1;
return true;
}
}
line->append(buffer_, size_);
const void* void_buffer;
if (!input_->Next(&void_buffer, &size_)) {
buffer_ = NULL;
size_ = 0;
return false;
}
buffer_ = reinterpret_cast<const char*>(void_buffer); // If the data doens't end with a clean line break, add one.
if (!data_.empty() && data_[data_.size() - 1] != '\n') {
data_.push_back('\n');
} }
}
private: // Find the file we are going to insert into.
io::ZeroCopyInputStream* input_; if (*map_slot == NULL) {
const char* buffer_; cerr << filename_ << ": Tried to insert into file that doesn't exist."
int size_; << endl;
}; directory_->had_error_ = true;
return;
} // namespace
CommandLineInterface::InsertionOutputStream::InsertionOutputStream(
const string& filename,
const string& temp_filename,
const string& insertion_point,
int original_file_descriptor,
int temp_file_descriptor,
DiskOutputDirectory* directory)
: original_file_(new io::FileInputStream(original_file_descriptor)),
temp_file_(new io::FileOutputStream(temp_file_descriptor)),
filename_(filename),
temp_filename_(temp_filename),
directory_(directory) {
string magic_string = strings::Substitute(
"@@protoc_insertion_point($0)", insertion_point);
LineReader reader(original_file_.get());
io::Printer writer(temp_file_.get(), '$');
string line;
while (true) {
if (!reader.ReadLine(&line)) {
int error = temp_file_->GetErrno();
if (error == 0) {
cerr << filename << ": Insertion point not found: "
<< insertion_point << endl;
} else {
cerr << filename << ": " << strerror(error) << endl;
}
original_file_->Close();
original_file_.reset();
// Will finish handling error in the destructor.
break;
} }
string* target = *map_slot;
if (line.find(magic_string) != string::npos) {
// Found the magic line. Since we want to insert before it, save it for // Find the insertion point.
// later. string magic_string = strings::Substitute(
magic_line_ = line; "@@protoc_insertion_point($0)", insertion_point_);
break; string::size_type pos = target->find(magic_string);
if (pos == string::npos) {
cerr << filename_ << ": insertion point \"" << insertion_point_
<< "\" not found." << endl;
directory_->had_error_ = true;
return;
} }
writer.PrintRaw(line); // Seek backwards to the beginning of the line, which is where we will
} // insert the data. Note that this has the effect of pushing the insertion
} // point down, so the data is inserted before it. This is intentional
// because it means that multiple insertions at the same point will end
CommandLineInterface::InsertionOutputStream::~InsertionOutputStream() { // up in the expected order in the final output.
// C-style error handling is teh best. pos = target->find_last_of('\n', pos);
bool had_error = false; if (pos == string::npos) {
// Insertion point is on the first line.
if (original_file_ == NULL) { pos = 0;
// We had an error in the constructor. } else {
had_error = true; // Advance to character after '\n'.
} else { ++pos;
// Use CodedOutputStream for convenience, so we don't have to deal with
// copying buffers ourselves.
io::CodedOutputStream out(temp_file_.get());
out.WriteRaw(magic_line_.data(), magic_line_.size());
// Write the rest of the original file.
const void* buffer;
int size;
while (original_file_->Next(&buffer, &size)) {
out.WriteRaw(buffer, size);
} }
// Close the original file. // Extract indent.
if (!original_file_->Close()) { string indent_(*target, pos, target->find_first_not_of(" \t", pos) - pos);
cerr << filename_ << ": " << strerror(original_file_->GetErrno()) << endl;
had_error = true;
}
}
// Check if we had any errors while writing. if (indent_.empty()) {
if (temp_file_->GetErrno() != 0) { // No indent. This makes things easier.
cerr << filename_ << ": " << strerror(temp_file_->GetErrno()) << endl; target->insert(pos, data_);
had_error = true; } else {
} // Calculate how much space we need.
int indent_size = 0;
for (int i = 0; i < data_.size(); i++) {
if (data_[i] == '\n') indent_size += indent_.size();
}
// Close the temp file. // Make a hole for it.
if (!temp_file_->Close()) { target->insert(pos, data_.size() + indent_size, '\0');
cerr << filename_ << ": " << strerror(temp_file_->GetErrno()) << endl;
had_error = true; // Now copy in the data.
} string::size_type data_pos = 0;
char* target_ptr = string_as_array(target) + pos;
while (data_pos < data_.size()) {
// Copy indent.
memcpy(target_ptr, indent_.data(), indent_.size());
target_ptr += indent_.size();
// Copy line from data_.
// We already guaranteed that data_ ends with a newline (above), so this
// search can't fail.
string::size_type line_length =
data_.find_first_of('\n', data_pos) + 1 - data_pos;
memcpy(target_ptr, data_.data() + data_pos, line_length);
target_ptr += line_length;
data_pos += line_length;
}
// If everything was successful, overwrite the original file with the temp GOOGLE_CHECK_EQ(target_ptr,
// file. string_as_array(target) + pos + data_.size() + indent_size);
if (!had_error) {
#ifdef _WIN32
// rename() on Windows fails if the file exists.
if (!MoveFileEx(temp_filename_.c_str(), filename_.c_str(),
MOVEFILE_REPLACE_EXISTING)) {
cerr << filename_ << ": MoveFileEx: "
<< Subprocess::Win32ErrorMessage(GetLastError()) << endl;
}
#else // _WIN32
if (rename(temp_filename_.c_str(), filename_.c_str()) < 0) {
cerr << filename_ << ": rename: " << strerror(errno) << endl;
had_error = true;
} }
#endif // !_WIN32
}
if (had_error) {
// We had some sort of error so let's try to delete the temp file.
remove(temp_filename_.c_str());
directory_->set_had_error(true);
} }
} }
...@@ -613,14 +553,20 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) { ...@@ -613,14 +553,20 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) {
} }
// Generate output. // Generate output.
MemoryOutputDirectory output_directory;
if (mode_ == MODE_COMPILE) { if (mode_ == MODE_COMPILE) {
for (int i = 0; i < output_directives_.size(); i++) { for (int i = 0; i < output_directives_.size(); i++) {
if (!GenerateOutput(parsed_files, output_directives_[i])) { if (!GenerateOutput(parsed_files, output_directives_[i],
&output_directory)) {
return 1; return 1;
} }
} }
} }
if (!output_directory.WriteAllToDisk()) {
return 1;
}
if (!descriptor_set_name_.empty()) { if (!descriptor_set_name_.empty()) {
if (!WriteDescriptorSet(parsed_files)) { if (!WriteDescriptorSet(parsed_files)) {
return 1; return 1;
...@@ -1062,14 +1008,15 @@ void CommandLineInterface::PrintHelpText() { ...@@ -1062,14 +1008,15 @@ void CommandLineInterface::PrintHelpText() {
bool CommandLineInterface::GenerateOutput( bool CommandLineInterface::GenerateOutput(
const vector<const FileDescriptor*>& parsed_files, const vector<const FileDescriptor*>& parsed_files,
const OutputDirective& output_directive) { const OutputDirective& output_directive,
// Create the output directory. OutputDirectory* parent_output_directory) {
DiskOutputDirectory output_directory(output_directive.output_location); // Set up the OutputDirectory.
if (!output_directory.VerifyExistence()) { string path = output_directive.output_location;
AddTrailingSlash(&path);
if (!VerifyDirectoryExists(path)) {
return false; return false;
} }
SubOutputDirectory output_directory(parent_output_directory, path);
// Opened successfully. Write it.
// Call the generator. // Call the generator.
string error; string error;
...@@ -1103,11 +1050,6 @@ bool CommandLineInterface::GenerateOutput( ...@@ -1103,11 +1050,6 @@ bool CommandLineInterface::GenerateOutput(
} }
} }
// Check for write errors.
if (output_directory.had_error()) {
return false;
}
return true; return true;
} }
......
...@@ -174,9 +174,9 @@ class LIBPROTOC_EXPORT CommandLineInterface { ...@@ -174,9 +174,9 @@ class LIBPROTOC_EXPORT CommandLineInterface {
// ----------------------------------------------------------------- // -----------------------------------------------------------------
class ErrorPrinter; class ErrorPrinter;
class DiskOutputDirectory; class MemoryOutputDirectory;
class ErrorReportingFileOutput; class SubOutputDirectory;
class InsertionOutputStream; class MemoryOutputStream;
// Clear state from previous Run(). // Clear state from previous Run().
void Clear(); void Clear();
...@@ -212,7 +212,8 @@ class LIBPROTOC_EXPORT CommandLineInterface { ...@@ -212,7 +212,8 @@ class LIBPROTOC_EXPORT CommandLineInterface {
// Generate the given output file from the given input. // Generate the given output file from the given input.
struct OutputDirective; // see below struct OutputDirective; // see below
bool GenerateOutput(const vector<const FileDescriptor*>& parsed_files, bool GenerateOutput(const vector<const FileDescriptor*>& parsed_files,
const OutputDirective& output_directive); const OutputDirective& output_directive,
OutputDirectory* parent_output_directory);
bool GeneratePluginOutput(const vector<const FileDescriptor*>& parsed_files, bool GeneratePluginOutput(const vector<const FileDescriptor*>& parsed_files,
const string& plugin_name, const string& plugin_name,
const string& parameter, const string& parameter,
......
...@@ -933,7 +933,10 @@ TEST_F(CommandLineInterfaceTest, OutputWriteError) { ...@@ -933,7 +933,10 @@ TEST_F(CommandLineInterfaceTest, OutputWriteError) {
Run("protocol_compiler --test_out=$tmpdir " Run("protocol_compiler --test_out=$tmpdir "
"--proto_path=$tmpdir foo.proto"); "--proto_path=$tmpdir foo.proto");
ExpectErrorSubstring("MockCodeGenerator detected write error."); // MockCodeGenerator no longer detects an error because we actually write to
// an in-memory location first, then dump to disk at the end. This is no
// big deal.
// ExpectErrorSubstring("MockCodeGenerator detected write error.");
#if defined(_WIN32) && !defined(__CYGWIN__) #if defined(_WIN32) && !defined(__CYGWIN__)
// Windows with MSVCRT.dll produces EPERM instead of EISDIR. // Windows with MSVCRT.dll produces EPERM instead of EISDIR.
......
...@@ -50,7 +50,7 @@ static const char* kSecondInsertionPointName = "second_mock_insertion_point"; ...@@ -50,7 +50,7 @@ static const char* kSecondInsertionPointName = "second_mock_insertion_point";
static const char* kFirstInsertionPoint = static const char* kFirstInsertionPoint =
"# @@protoc_insertion_point(first_mock_insertion_point) is here\n"; "# @@protoc_insertion_point(first_mock_insertion_point) is here\n";
static const char* kSecondInsertionPoint = static const char* kSecondInsertionPoint =
"# @@protoc_insertion_point(second_mock_insertion_point) is here\n"; " # @@protoc_insertion_point(second_mock_insertion_point) is here\n";
MockCodeGenerator::MockCodeGenerator(const string& name) MockCodeGenerator::MockCodeGenerator(const string& name)
: name_(name) {} : name_(name) {}
...@@ -94,8 +94,10 @@ void MockCodeGenerator::ExpectGenerated( ...@@ -94,8 +94,10 @@ void MockCodeGenerator::ExpectGenerated(
EXPECT_EQ(GetOutputFileContent(insertion_list[i], "first_insert", EXPECT_EQ(GetOutputFileContent(insertion_list[i], "first_insert",
file, first_message_name), file, first_message_name),
lines[1 + i]); lines[1 + i]);
EXPECT_EQ(GetOutputFileContent(insertion_list[i], "second_insert", // Second insertion point is indented, so the inserted text should
file, first_message_name), // automatically be indented too.
EXPECT_EQ(" " + GetOutputFileContent(insertion_list[i], "second_insert",
file, first_message_name),
lines[2 + insertion_list.size() + i]); lines[2 + insertion_list.size() + i]);
} }
} }
......
...@@ -123,6 +123,13 @@ message CodeGeneratorResponse { ...@@ -123,6 +123,13 @@ message CodeGeneratorResponse {
// insertion_point "package_level_decls" to generate additional classes or // insertion_point "package_level_decls" to generate additional classes or
// other declarations that should be placed in this scope. // other declarations that should be placed in this scope.
// //
// Note that if the line containing the insertion point begins with
// whitespace, the same whitespace will be added to every line of the
// inserted text. This is useful for languages like Python, where
// indentation matters. In these languages, the insertion point comment
// should be indented the same amount as any inserted code will need to be
// in order to work correctly in that context.
//
// If |insertion_point| is present, |name| must also be present. // If |insertion_point| is present, |name| must also be present.
optional string insertion_point = 2; optional string insertion_point = 2;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment