Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefix_to_proto_package_mappings_path ObjC option. #9498

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 24 additions & 6 deletions objectivec/README.md
Expand Up @@ -133,12 +133,12 @@ This options allow you to provide a custom prefix for all the symbols generated
from a proto file (classes (from message), enums, the Root for extension
support).

If not set, the generation option `use_package_as_prefix` (documented below)
controls what is used instead. Since Objective C uses a global namespace for all
of its classes, there can be collisions. `use_package_as_prefix=yes` should
avoid collisions since proto package are used to scope/name things in other
languages, but this option can be used to get shorter names instead. Convention
is to base the explicit prefix on the proto package.
If not set, the generation options `prefix_to_proto_package_mappings_path` and
`use_package_as_prefix` (documented below) controls what is used instead. Since
Objective C uses a global namespace for all of its classes, there can be collisions.
`use_package_as_prefix=yes` should avoid collisions since proto package are used to
scope/name things in other languages, but this option can be used to get shorter
names instead. Convention is to base the explicit prefix on the proto package.

Objective C Generator `protoc` Options
--------------------------------------
Expand Down Expand Up @@ -182,6 +182,24 @@ supported keys are:
having to add the runtime directory to the header search path since the
generate `#import` will be more complete.

* `prefix_to_proto_package_mappings_path`: The `value` used for
this key is a path to a file containing a list of prefixes and proto packages.
The generator will use this to locate which ObjC class prefix to use when
generating sources _unless_ the `objc_class_prefix` file option is set.
This option can be useful if multiple apps consume a common set of
proto files but wish to use a different prefix for the generated sources
between them. This option takes precedent over the `use_package_as_prefix`
option.

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")
* For files that do NOT have a proto package (not recommended), an
entry can be made as "no_package:PATH=prefix", where PATH is the
path for the .proto file.

* `use_package_as_prefix` and `proto_package_prefix_exceptions_path`: The
`value` for `use_package_as_prefix` can be `yes` or `no`, and indicates
if a prefix should be derived from the proto package for all the symbols
Expand Down
15 changes: 15 additions & 0 deletions src/google/protobuf/compiler/objectivec/objectivec_generator.cc
Expand Up @@ -190,6 +190,21 @@ bool ObjectiveCGenerator::GenerateAll(
// header search path since the generate #import will be more complete.
generation_options.runtime_import_prefix =
StripSuffixString(options[i].second, "/");
} else if (options[i].first == "prefix_to_proto_package_mappings_path") {
// Path to use for when loading the objc class prefix mappings to use.
// The `objc_class_prefix` file option is always honored first if one is present.
// This option also has precedent over the use_package_as_prefix option.
//
// The format of the file is:
thomasvl marked this conversation as resolved.
Show resolved Hide resolved
// - 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")
// - For files that do NOT have a proto package (not recommended), an
// entry can be made as "no_package:PATH=prefix", where PATH is the
// path for the .proto file.
//
SetPrefixToProtoPackageMappingsPath(options[i].second);
} else if (options[i].first == "use_package_as_prefix") {
// Controls how the symbols should be prefixed to avoid symbols
// collisions. The objc_class_prefix file option is always honored, this
Expand Down
84 changes: 73 additions & 11 deletions src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
Expand Up @@ -95,10 +95,29 @@ class SimpleLineCollector : public LineConsumer {
std::unordered_set<std::string>* set_;
};

class ExpectedPrefixesCollector : public LineConsumer {
thomasvl marked this conversation as resolved.
Show resolved Hide resolved
public:
ExpectedPrefixesCollector(std::map<std::string, std::string>* inout_package_to_prefix_map)
: prefix_map_(inout_package_to_prefix_map) {}

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

private:
std::map<std::string, std::string>* prefix_map_;
};

class PrefixModeStorage {
public:
PrefixModeStorage();

const std::string prefix_to_proto_package_mappings_path() const { return prefix_to_proto_package_mappings_path_; }
void set_prefix_to_proto_package_mappings_path(const std::string& path) {
prefix_to_proto_package_mappings_path_ = path;
prefix_to_proto_package_map_.clear();
}

std::string prefix_from_proto_package_mappings(const FileDescriptor* file);

bool use_package_name() const { return use_package_name_; }
void set_use_package_name(bool on_or_off) { use_package_name_ = on_or_off; }

Expand All @@ -116,6 +135,8 @@ class PrefixModeStorage {

private:
bool use_package_name_;
std::map<std::string, std::string> prefix_to_proto_package_map_;
std::string prefix_to_proto_package_mappings_path_;
std::string exception_path_;
std::string forced_prefix_;
std::unordered_set<std::string> exceptions_;
Expand All @@ -140,6 +161,44 @@ PrefixModeStorage::PrefixModeStorage() {
}
}

std::string PrefixModeStorage::prefix_from_proto_package_mappings(const FileDescriptor* file) {
if (!file) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually do need the file here as we perform some logic below to figure out the package to use.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I had commented not about the passing of file, but the passing of it by reference so it can't be nullptr.

return "";
}

if (prefix_to_proto_package_map_.empty() && !prefix_to_proto_package_mappings_path_.empty()) {
std::string error_str;
// Re use the same collector as we use for expected_prefixes_path since the file
// format is the same.
ExpectedPrefixesCollector collector(&prefix_to_proto_package_map_);
if (!ParseSimpleFile(prefix_to_proto_package_mappings_path_, &collector, &error_str)) {
if (error_str.empty()) {
error_str = std::string("protoc:0: warning: Failed to parse")
+ std::string(" prefix to proto package mappings file: ")
+ prefix_to_proto_package_mappings_path_;
}
std::cerr << error_str << std::endl;
std::cerr.flush();
prefix_to_proto_package_map_.clear();
}
}

const std::string package = file->package();
// For files without packages, the can be registered as "no_package:PATH",
// allowing the expected prefixes file.
static const std::string no_package_prefix("no_package:");
const std::string lookup_key = package.empty() ? no_package_prefix + file->name() : package;

std::map<std::string, std::string>::const_iterator prefix_lookup =
prefix_to_proto_package_map_.find(lookup_key);

if (prefix_lookup != prefix_to_proto_package_map_.end()) {
return prefix_lookup->second;
}

return "";
}

bool PrefixModeStorage::is_package_exempted(const std::string& package) {
if (exceptions_.empty() && !exception_path_.empty()) {
std::string error_str;
Expand Down Expand Up @@ -169,6 +228,14 @@ PrefixModeStorage g_prefix_mode;

} // namespace

std::string GetPrefixToProtoPackageMappingsPath() {
return g_prefix_mode.prefix_to_proto_package_mappings_path();
}

void SetPrefixToProtoPackageMappingsPath(const std::string& file_path) {
g_prefix_mode.set_prefix_to_proto_package_mappings_path(file_path);
}

bool UseProtoPackageAsDefaultPrefix() {
return g_prefix_mode.use_package_name();
}
Expand Down Expand Up @@ -531,6 +598,12 @@ std::string FileClassPrefix(const FileDescriptor* file) {
return file->options().objc_class_prefix();
}

// If package prefix is specified in an prefix to proto mappings file then use that.
std::string objc_class_prefix = g_prefix_mode.prefix_from_proto_package_mappings(file);
if (!objc_class_prefix.empty()) {
return objc_class_prefix;
}

// If package prefix isn't enabled, done.
if (!g_prefix_mode.use_package_name()) {
return "";
Expand Down Expand Up @@ -1205,17 +1278,6 @@ void RemoveComment(StringPiece* input) {

namespace {

class ExpectedPrefixesCollector : public LineConsumer {
public:
ExpectedPrefixesCollector(std::map<std::string, std::string>* inout_package_to_prefix_map)
: prefix_map_(inout_package_to_prefix_map) {}

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

private:
std::map<std::string, std::string>* prefix_map_;
};

bool ExpectedPrefixesCollector::ConsumeLine(
const StringPiece& line, std::string* out_error) {
int offset = line.find('=');
Expand Down
4 changes: 4 additions & 0 deletions src/google/protobuf/compiler/objectivec/objectivec_helpers.h
Expand Up @@ -47,6 +47,10 @@ namespace protobuf {
namespace compiler {
namespace objectivec {

// Get/Set the path to a file to load for objc class prefix lookups.
std::string PROTOC_EXPORT GetPrefixToProtoPackageMappingsPath();
void PROTOC_EXPORT SetPrefixToProtoPackageMappingsPath(
const std::string& file_path);
// Get/Set if the proto package should be used to make the default prefix for
// symbols. This will then impact most of the type naming apis below. It is done
// as a global to not break any other generator reusing the methods since they
Expand Down