From a112c4ab9642b13b648b19d3a416cfcf9994f3fd Mon Sep 17 00:00:00 2001 From: Dimitris Koutsogiorgas Date: Fri, 11 Feb 2022 11:05:29 -0800 Subject: [PATCH] Add `prefix_to_proto_package_mappings_path` ObjC option. --- objectivec/README.md | 30 +++++-- .../objectivec/objectivec_generator.cc | 15 ++++ .../compiler/objectivec/objectivec_helpers.cc | 84 ++++++++++++++++--- .../compiler/objectivec/objectivec_helpers.h | 4 + 4 files changed, 116 insertions(+), 17 deletions(-) diff --git a/objectivec/README.md b/objectivec/README.md index 9aedb219f5..4d0ea95316 100644 --- a/objectivec/README.md +++ b/objectivec/README.md @@ -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 -------------------------------------- @@ -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 diff --git a/src/google/protobuf/compiler/objectivec/objectivec_generator.cc b/src/google/protobuf/compiler/objectivec/objectivec_generator.cc index 713f93ef1b..6eb537f844 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_generator.cc +++ b/src/google/protobuf/compiler/objectivec/objectivec_generator.cc @@ -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 diff --git a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc index cf926071fc..4b33165781 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc +++ b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc @@ -95,10 +95,29 @@ class SimpleLineCollector : public LineConsumer { std::unordered_set* set_; }; +class ExpectedPrefixesCollector : public LineConsumer { + public: + ExpectedPrefixesCollector(std::map* 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* 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; } @@ -116,6 +135,8 @@ class PrefixModeStorage { private: bool use_package_name_; + std::map prefix_to_proto_package_map_; + std::string prefix_to_proto_package_mappings_path_; std::string exception_path_; std::string forced_prefix_; std::unordered_set exceptions_; @@ -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::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; @@ -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(); } @@ -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 ""; @@ -1205,17 +1278,6 @@ void RemoveComment(StringPiece* input) { namespace { -class ExpectedPrefixesCollector : public LineConsumer { - public: - ExpectedPrefixesCollector(std::map* 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* prefix_map_; -}; - bool ExpectedPrefixesCollector::ConsumeLine( const StringPiece& line, std::string* out_error) { int offset = line.find('='); diff --git a/src/google/protobuf/compiler/objectivec/objectivec_helpers.h b/src/google/protobuf/compiler/objectivec/objectivec_helpers.h index f4b71ced8e..2a37e6374a 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_helpers.h +++ b/src/google/protobuf/compiler/objectivec/objectivec_helpers.h @@ -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