Skip to content

Commit

Permalink
Add prefix_to_proto_package_mappings_path ObjC option.
Browse files Browse the repository at this point in the history
  • Loading branch information
dnkoutso committed Feb 23, 2022
1 parent d0c06bc commit 7f747ff
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 17 deletions.
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:
// - 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 {
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) {
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

0 comments on commit 7f747ff

Please sign in to comment.