Skip to content

Commit

Permalink
Add new generation option for using proto sources from other frameworks.
Browse files Browse the repository at this point in the history
- Better docs in the generator for the different options that can be passed
  during an invoke of protoc.
- Add named_framework_to_proto_path_mappings_path to pass the path to a file
  containing mappings of frameworks for different proto files.
- Update the generation to use the mapping to change the #import directives
  it creates.

Note: the changes in helpers is mostly moving code within the fine, and then
a small change to expose the parsing so a passed on class can consume the line.

Fixes protocolbuffers#1457
  • Loading branch information
thomasvl authored Jun 17, 2016
1 parent 98bd6d7 commit 8c20e55
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 127 deletions.
23 changes: 23 additions & 0 deletions objectivec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,29 @@ supported keys are:
of being plain `#import "some/path/file.pbobjc.h"` lines, they will be
framework based, i.e. - `#import <VALUE/file.pbobjc.h>`.

_NOTE:_ If this is used with `named_framework_to_proto_path_mappings_path`,
then this is effectively the _default_ to use for everything that wasn't
mapped by the other.

* `named_framework_to_proto_path_mappings_path`: The `value` used for this key
is a path to a file containing the listing of framework names and proto
files. The generator uses this to decide if another proto file referenced
should use a framework style import vs. a user level import
(`#import <FRAMEWORK/file.pbobjc.h>` vs `#import "dir/file.pbobjc.h"`).

The format of the file is:
* An entry is a line of `frameworkName: file.proto, dir/file2.proto`.
* Comments start with `#`.
* A comment can go on a line after an entry.
(i.e. - `frameworkName: file.proto # comment`)

Any number of files can be listed for a framework, just separate them with
commas.

There can be multiple lines listing the same frameworkName incase it has a
lot of proto files included in it; and having multiple lines makes things
easier to read.

Contributing
------------

Expand Down
114 changes: 109 additions & 5 deletions src/google/protobuf/compiler/objectivec/objectivec_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/stubs/stl_util.h>
#include <google/protobuf/stubs/strutil.h>
#include <iostream>
#include <sstream>

// NOTE: src/google/protobuf/compiler/plugin.cc makes use of cerr for some
// error cases, so it seems to be ok to use as a back door for errors.

namespace google {
namespace protobuf {

Expand All @@ -54,13 +58,31 @@ namespace {

class ImportWriter {
public:
ImportWriter(const Options& options) : options_(options) {}
ImportWriter(const Options& options)
: options_(options),
need_to_parse_mapping_file_(true) {}

void AddFile(const FileGenerator* file);
void Print(io::Printer *printer) const;

private:
class ProtoFrameworkCollector : public LineConsumer {
public:
ProtoFrameworkCollector(map<string, string>* inout_proto_file_to_framework_name)
: map_(inout_proto_file_to_framework_name) {}

virtual bool ConsumeLine(const StringPiece& line, string* out_error);

private:
map<string, string>* map_;
};

void ParseFrameworkMappings();

const Options options_;
map<string, string> proto_file_to_framework_name_;
bool need_to_parse_mapping_file_;

vector<string> protobuf_framework_imports_;
vector<string> protobuf_non_framework_imports_;
vector<string> other_framework_imports_;
Expand All @@ -70,20 +92,39 @@ class ImportWriter {
void ImportWriter::AddFile(const FileGenerator* file) {
const FileDescriptor* file_descriptor = file->Descriptor();
const string extension(".pbobjc.h");

if (IsProtobufLibraryBundledProtoFile(file_descriptor)) {
protobuf_framework_imports_.push_back(
FilePathBasename(file_descriptor) + extension);
protobuf_non_framework_imports_.push_back(file->Path() + extension);
} else if (!options_.generate_for_named_framework.empty()) {
return;
}

// Lazy parse any mappings.
if (need_to_parse_mapping_file_) {
ParseFrameworkMappings();
}

map<string, string>::iterator proto_lookup =
proto_file_to_framework_name_.find(file_descriptor->name());
if (proto_lookup != proto_file_to_framework_name_.end()) {
other_framework_imports_.push_back(
proto_lookup->second + "/" +
FilePathBasename(file_descriptor) + extension);
return;
}

if (!options_.generate_for_named_framework.empty()) {
other_framework_imports_.push_back(
options_.generate_for_named_framework + "/" +
FilePathBasename(file_descriptor) + extension);
} else {
other_imports_.push_back(file->Path() + extension);
return;
}

other_imports_.push_back(file->Path() + extension);
}

void ImportWriter::Print(io::Printer *printer) const {
void ImportWriter::Print(io::Printer* printer) const {
assert(protobuf_non_framework_imports_.size() ==
protobuf_framework_imports_.size());

Expand Down Expand Up @@ -146,6 +187,69 @@ void ImportWriter::Print(io::Printer *printer) const {
}
}

void ImportWriter::ParseFrameworkMappings() {
need_to_parse_mapping_file_ = false;
if (options_.named_framework_to_proto_path_mappings_path.empty()) {
return; // Nothing to do.
}

ProtoFrameworkCollector collector(&proto_file_to_framework_name_);
string parse_error;
if (!ParseSimpleFile(options_.named_framework_to_proto_path_mappings_path,
&collector, &parse_error)) {
cerr << "error parsing " << options_.named_framework_to_proto_path_mappings_path
<< " : " << parse_error << endl;
cerr.flush();
}
}

bool ImportWriter::ProtoFrameworkCollector::ConsumeLine(
const StringPiece& line, string* out_error) {
int offset = line.find(':');
if (offset == StringPiece::npos) {
*out_error =
string("Framework/proto file mapping line without colon sign: '") +
line.ToString() + "'.";
return false;
}
StringPiece framework_name(line, 0, offset);
StringPiece proto_file_list(line, offset + 1, line.length() - offset - 1);
StringPieceTrimWhitespace(&framework_name);

int start = 0;
while (start < proto_file_list.length()) {
offset = proto_file_list.find(',', start);
if (offset == StringPiece::npos) {
offset = proto_file_list.length();
}

StringPiece proto_file(proto_file_list, start, offset);
StringPieceTrimWhitespace(&proto_file);
if (proto_file.size() != 0) {
map<string, string>::iterator existing_entry =
map_->find(proto_file.ToString());
if (existing_entry != map_->end()) {
cerr << "warning: duplicate proto file reference, replacing framework entry for '"
<< proto_file.ToString() << "' with '" << framework_name.ToString()
<< "' (was '" << existing_entry->second << "')." << endl;
cerr.flush();
}

if (proto_file.find(' ') != StringPiece::npos) {
cerr << "note: framework mapping file had a proto file with a space in, hopefully that isn't a missing comma: '"
<< proto_file.ToString() << "'" << endl;
cerr.flush();
}

(*map_)[proto_file.ToString()] = framework_name.ToString();
}

start = offset + 1;
}

return true;
}

} // namespace


Expand Down
41 changes: 40 additions & 1 deletion src/google/protobuf/compiler/objectivec/objectivec_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,48 @@ bool ObjectiveCGenerator::Generate(const FileDescriptor* file,
ParseGeneratorParameter(parameter, &options);
for (int i = 0; i < options.size(); i++) {
if (options[i].first == "expected_prefixes_path") {
// Path to find a file containing the expected prefixes
// (objc_class_prefix "PREFIX") for proto packages (package NAME). The
// generator will then issue warnings/errors if in the proto files being
// generated the option is not listed/wrong/etc in the file.
//
// The format of the file is:
// - An entry is a line of "package=prefix".
// - Comments start with "#".
// - A comment can go on a line after a expected package/prefix pair.
// (i.e. - "package=prefix # comment")
//
// There is no validation that the prefixes are good prefixes, it is
// assume they are when you create the file.
generation_options.expected_prefixes_path = options[i].second;
} else if (options[i].first == "generate_for_named_framework") {
generation_options.generate_for_named_framework = options[i].second;
// The name of the framework that protos are being generated for. This
// will cause the #import statements to be framework based using this
// name (i.e. - "#import <NAME/proto.pbobjc.h>).
//
// NOTE: If this option is used with
// named_framework_to_proto_path_mappings_path, then this is effectively
// the "default" to use for everything that wasn't mapped by the other.
generation_options.named_framework_to_proto_path_mappings_path = options[i].second;
} else if (options[i].first == "named_framework_to_proto_path_mappings_path") {
// Path to find a file containing the listing of framework names and
// proto files. The generator uses this to decide if another proto file
// referenced should use a framework style import vs. a user level import
// (#import <FRAMEWORK/file.pbobjc.h> vs #import "dir/file.pbobjc.h").
//
// The format of the file is:
// - An entry is a line of "frameworkName: file.proto, dir/file2.proto".
// - Comments start with "#".
// - A comment can go on a line after a expected package/prefix pair.
// (i.e. - "frameworkName: file.proto # comment")
//
// Any number of files can be listed for a framework, just separate them
// with commas.
//
// There can be multiple lines listing the same frameworkName incase it
// has a lot of proto files included in it; and having multiple lines
// makes things easier to read.
generation_options.named_framework_to_proto_path_mappings_path = options[i].second;
} else {
*error = "error: Unknown generator option: " + options[i].first;
return false;
Expand Down
Loading

0 comments on commit 8c20e55

Please sign in to comment.