aboutsummaryrefslogtreecommitdiff
path: root/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/google/protobuf/compiler/objectivec/objectivec_helpers.cc')
-rw-r--r--src/google/protobuf/compiler/objectivec/objectivec_helpers.cc882
1 files changed, 666 insertions, 216 deletions
diff --git a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
index 8527b74b..df71c8bb 100644
--- a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
+++ b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc
@@ -28,9 +28,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#ifdef _MSC_VER
-#include <io.h>
-#else
+#ifndef _MSC_VER
#include <unistd.h>
#endif
#include <climits>
@@ -44,12 +42,15 @@
#include <google/protobuf/stubs/hash.h>
#include <google/protobuf/compiler/objectivec/objectivec_helpers.h>
+#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/printer.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
-#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/io_win32.h>
#include <google/protobuf/stubs/strutil.h>
+
// 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.
@@ -58,6 +59,24 @@ namespace protobuf {
namespace compiler {
namespace objectivec {
+// <io.h> is transitively included in this file. Import the functions explicitly
+// in this port namespace to avoid ambiguous definition.
+namespace posix {
+#ifdef _WIN32
+using ::google::protobuf::internal::win32::open;
+#else
+using ::open;
+#endif
+} // namespace port
+
+Options::Options() {
+ // Default is the value of the env for the package prefixes.
+ const char* file_path = getenv("GPB_OBJC_EXPECTED_PACKAGE_PREFIXES");
+ if (file_path) {
+ expected_prefixes_path = file_path;
+ }
+}
+
namespace {
hash_set<string> MakeWordsMap(const char* const words[], size_t num_words) {
@@ -73,11 +92,15 @@ const char* const kUpperSegmentsList[] = {"url", "http", "https"};
hash_set<string> kUpperSegments =
MakeWordsMap(kUpperSegmentsList, GOOGLE_ARRAYSIZE(kUpperSegmentsList));
+bool ascii_isnewline(char c) {
+ return c == '\n' || c == '\r';
+}
+
// Internal helper for name handing.
// Do not expose this outside of helpers, stick to having functions for specific
// cases (ClassName(), FieldName()), so there is always consistent suffix rules.
string UnderscoresToCamelCase(const string& input, bool first_capitalized) {
- vector<string> values;
+ std::vector<string> values;
string current;
bool last_char_was_number = false;
@@ -116,9 +139,14 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) {
}
values.push_back(current);
- for (vector<string>::iterator i = values.begin(); i != values.end(); ++i) {
+ string result;
+ bool first_segment_forces_upper = false;
+ for (std::vector<string>::iterator i = values.begin(); i != values.end(); ++i) {
string value = *i;
bool all_upper = (kUpperSegments.count(value) > 0);
+ if (all_upper && (result.length() == 0)) {
+ first_segment_forces_upper = true;
+ }
for (int j = 0; j < value.length(); j++) {
if (j == 0 || all_upper) {
value[j] = ascii_toupper(value[j]);
@@ -126,13 +154,11 @@ string UnderscoresToCamelCase(const string& input, bool first_capitalized) {
// Nothing, already in lower.
}
}
- *i = value;
- }
- string result;
- for (vector<string>::iterator i = values.begin(); i != values.end(); ++i) {
- result += *i;
+ result += value;
}
- if ((result.length() != 0) && !first_capitalized) {
+ if ((result.length() != 0) &&
+ !first_capitalized &&
+ !first_segment_forces_upper) {
result[0] = ascii_tolower(result[0]);
}
return result;
@@ -180,7 +206,7 @@ const char* const kReservedWordList[] = {
// method declared in protos. The main cases are methods
// that take no arguments, or setFoo:/hasFoo: type methods.
"clear", "data", "delimitedData", "descriptor", "extensionRegistry",
- "extensionsCurrentlySet", "isInitialized", "serializedSize",
+ "extensionsCurrentlySet", "initialized", "isInitialized", "serializedSize",
"sortedExtensionsInUse", "unknownFields",
// MacTypes.h names
@@ -194,10 +220,14 @@ const char* const kReservedWordList[] = {
hash_set<string> kReservedWords =
MakeWordsMap(kReservedWordList, GOOGLE_ARRAYSIZE(kReservedWordList));
-string SanitizeNameForObjC(const string& input, const string& extension) {
+string SanitizeNameForObjC(const string& input,
+ const string& extension,
+ string* out_suffix_added) {
if (kReservedWords.count(input) > 0) {
+ if (out_suffix_added) *out_suffix_added = extension;
return input + extension;
}
+ if (out_suffix_added) out_suffix_added->clear();
return input;
}
@@ -209,11 +239,6 @@ string NameFromFieldDescriptor(const FieldDescriptor* field) {
}
}
-// Escape C++ trigraphs by escaping question marks to \?
-string EscapeTrigraphs(const string& to_escape) {
- return StringReplace(to_escape, "?", "\\?", true);
-}
-
void PathSplit(const string& path, string* directory, string* basename) {
string::size_type last_slash = path.rfind('/');
if (last_slash == string::npos) {
@@ -251,8 +276,41 @@ bool IsSpecialName(const string& name, const string* special_names,
return false;
}
+string GetZeroEnumNameForFlagType(const FlagType flag_type) {
+ switch(flag_type) {
+ case FLAGTYPE_DESCRIPTOR_INITIALIZATION:
+ return "GPBDescriptorInitializationFlag_None";
+ case FLAGTYPE_EXTENSION:
+ return "GPBExtensionNone";
+ case FLAGTYPE_FIELD:
+ return "GPBFieldNone";
+ default:
+ GOOGLE_LOG(FATAL) << "Can't get here.";
+ return "0";
+ }
+}
+
+string GetEnumNameForFlagType(const FlagType flag_type) {
+ switch(flag_type) {
+ case FLAGTYPE_DESCRIPTOR_INITIALIZATION:
+ return "GPBDescriptorInitializationFlags";
+ case FLAGTYPE_EXTENSION:
+ return "GPBExtensionOptions";
+ case FLAGTYPE_FIELD:
+ return "GPBFieldFlags";
+ default:
+ GOOGLE_LOG(FATAL) << "Can't get here.";
+ return string();
+ }
+}
+
} // namespace
+// Escape C++ trigraphs by escaping question marks to \?
+string EscapeTrigraphs(const string& to_escape) {
+ return StringReplace(to_escape, "?", "\\?", true);
+}
+
string StripProto(const string& filename) {
if (HasSuffixString(filename, ".protodevel")) {
return StripSuffixString(filename, ".protodevel");
@@ -261,6 +319,16 @@ string StripProto(const string& filename) {
}
}
+void StringPieceTrimWhitespace(StringPiece* input) {
+ while (!input->empty() && ascii_isspace(*input->data())) {
+ input->remove_prefix(1);
+ }
+ while (!input->empty() && ascii_isspace((*input)[input->length() - 1])) {
+ input->remove_suffix(1);
+ }
+}
+
+
bool IsRetainedName(const string& name) {
// List of prefixes from
// http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html
@@ -282,11 +350,10 @@ string BaseFileName(const FileDescriptor* file) {
return basename;
}
-string FileName(const FileDescriptor* file) {
- string path = FilePath(file);
- string basename;
- PathSplit(path, NULL, &basename);
- return basename;
+string FileClassPrefix(const FileDescriptor* file) {
+ // Default is empty string, no need to check has_objc_class_prefix.
+ string result = file->options().objc_class_prefix();
+ return result;
}
string FilePath(const FileDescriptor* file) {
@@ -306,10 +373,17 @@ string FilePath(const FileDescriptor* file) {
return output;
}
-string FileClassPrefix(const FileDescriptor* file) {
- // Default is empty string, no need to check has_objc_class_prefix.
- string result = file->options().objc_class_prefix();
- return result;
+string FilePathBasename(const FileDescriptor* file) {
+ string output;
+ string basename;
+ string directory;
+ PathSplit(file->name(), &directory, &basename);
+ basename = StripProto(basename);
+
+ // CamelCase to be more ObjC friendly.
+ output = UnderscoresToCamelCase(basename, true);
+
+ return output;
}
string FileClassName(const FileDescriptor* file) {
@@ -318,7 +392,7 @@ string FileClassName(const FileDescriptor* file) {
name += "Root";
// There aren't really any reserved words that end in "Root", but playing
// it safe and checking.
- return SanitizeNameForObjC(name, "_RootClass");
+ return SanitizeNameForObjC(name, "_RootClass", NULL);
}
string ClassNameWorker(const Descriptor* descriptor) {
@@ -340,11 +414,15 @@ string ClassNameWorker(const EnumDescriptor* descriptor) {
}
string ClassName(const Descriptor* descriptor) {
+ return ClassName(descriptor, NULL);
+}
+
+string ClassName(const Descriptor* descriptor, string* out_suffix_added) {
// 1. Message names are used as is (style calls for CamelCase, trust it).
// 2. Check for reserved word at the very end and then suffix things.
string prefix = FileClassPrefix(descriptor->file());
string name = ClassNameWorker(descriptor);
- return SanitizeNameForObjC(prefix + name, "_Class");
+ return SanitizeNameForObjC(prefix + name, "_Class", out_suffix_added);
}
string EnumName(const EnumDescriptor* descriptor) {
@@ -358,7 +436,7 @@ string EnumName(const EnumDescriptor* descriptor) {
// yields Fixed_Class, Fixed_Size.
string name = FileClassPrefix(descriptor->file());
name += ClassNameWorker(descriptor);
- return SanitizeNameForObjC(name, "_Enum");
+ return SanitizeNameForObjC(name, "_Enum", NULL);
}
string EnumValueName(const EnumValueDescriptor* descriptor) {
@@ -373,7 +451,7 @@ string EnumValueName(const EnumValueDescriptor* descriptor) {
const string& name = class_name + "_" + value_str;
// There aren't really any reserved words with an underscore and a leading
// capital letter, but playing it safe and checking.
- return SanitizeNameForObjC(name, "_Value");
+ return SanitizeNameForObjC(name, "_Value", NULL);
}
string EnumValueShortName(const EnumValueDescriptor* descriptor) {
@@ -410,7 +488,7 @@ string UnCamelCaseEnumShortName(const string& name) {
string ExtensionMethodName(const FieldDescriptor* descriptor) {
const string& name = NameFromFieldDescriptor(descriptor);
const string& result = UnderscoresToCamelCase(name, false);
- return SanitizeNameForObjC(result, "_Extension");
+ return SanitizeNameForObjC(result, "_Extension", NULL);
}
string FieldName(const FieldDescriptor* field) {
@@ -425,7 +503,7 @@ string FieldName(const FieldDescriptor* field) {
result += "_p";
}
}
- return SanitizeNameForObjC(result, "_p");
+ return SanitizeNameForObjC(result, "_p", NULL);
}
string FieldNameCapitalized(const FieldDescriptor* field) {
@@ -723,7 +801,7 @@ string DefaultValue(const FieldDescriptor* field) {
uint32 length = ghtonl(default_string.length());
string bytes((const char*)&length, sizeof(length));
bytes.append(default_string);
- return "(NSData*)\"" + CEscape(bytes) + "\"";
+ return "(NSData*)\"" + EscapeTrigraphs(CEscape(bytes)) + "\"";
} else {
return "@\"" + EscapeTrigraphs(CEscape(default_string)) + "\"";
}
@@ -740,102 +818,155 @@ string DefaultValue(const FieldDescriptor* field) {
return NULL;
}
-string BuildFlagsString(const vector<string>& strings) {
+bool HasNonZeroDefaultValue(const FieldDescriptor* field) {
+ // Repeated fields don't have defaults.
+ if (field->is_repeated()) {
+ return false;
+ }
+
+ // As much as checking field->has_default_value() seems useful, it isn't
+ // because of enums. proto2 syntax allows the first item in an enum (the
+ // default) to be non zero. So checking field->has_default_value() would
+ // result in missing this non zero default. See MessageWithOneBasedEnum in
+ // objectivec/Tests/unittest_objc.proto for a test Message to confirm this.
+
+ // Some proto file set the default to the zero value, so make sure the value
+ // isn't the zero case.
+ switch (field->cpp_type()) {
+ case FieldDescriptor::CPPTYPE_INT32:
+ return field->default_value_int32() != 0;
+ case FieldDescriptor::CPPTYPE_UINT32:
+ return field->default_value_uint32() != 0U;
+ case FieldDescriptor::CPPTYPE_INT64:
+ return field->default_value_int64() != 0LL;
+ case FieldDescriptor::CPPTYPE_UINT64:
+ return field->default_value_uint64() != 0ULL;
+ case FieldDescriptor::CPPTYPE_DOUBLE:
+ return field->default_value_double() != 0.0;
+ case FieldDescriptor::CPPTYPE_FLOAT:
+ return field->default_value_float() != 0.0f;
+ case FieldDescriptor::CPPTYPE_BOOL:
+ return field->default_value_bool();
+ case FieldDescriptor::CPPTYPE_STRING: {
+ const string& default_string = field->default_value_string();
+ return default_string.length() != 0;
+ }
+ case FieldDescriptor::CPPTYPE_ENUM:
+ return field->default_value_enum()->number() != 0;
+ case FieldDescriptor::CPPTYPE_MESSAGE:
+ return false;
+ }
+
+ // Some compilers report reaching end of function even though all cases of
+ // the enum are handed in the switch.
+ GOOGLE_LOG(FATAL) << "Can't get here.";
+ return false;
+}
+
+string BuildFlagsString(const FlagType flag_type,
+ const std::vector<string>& strings) {
if (strings.size() == 0) {
- return "0";
+ return GetZeroEnumNameForFlagType(flag_type);
+ } else if (strings.size() == 1) {
+ return strings[0];
}
- string string;
+ string string("(" + GetEnumNameForFlagType(flag_type) + ")(");
for (size_t i = 0; i != strings.size(); ++i) {
if (i > 0) {
string.append(" | ");
}
string.append(strings[i]);
}
+ string.append(")");
return string;
}
-string BuildCommentsString(const SourceLocation& location) {
+string BuildCommentsString(const SourceLocation& location,
+ bool prefer_single_line) {
const string& comments = location.leading_comments.empty()
? location.trailing_comments
: location.leading_comments;
- vector<string> lines;
+ std::vector<string> lines;
SplitStringAllowEmpty(comments, "\n", &lines);
while (!lines.empty() && lines.back().empty()) {
lines.pop_back();
}
- string prefix("//");
- string suffix("\n");
- string final_comments;
- for (int i = 0; i < lines.size(); i++) {
- // We use $ for delimiters, so replace comments with dollars with
- // html escaped version.
- // None of the other compilers handle this (as of this writing) but we
- // ran into it once, so just to be safe.
- final_comments +=
- prefix + StringReplace(lines[i], "$", "&#36;", true) + suffix;
+ // If there are no comments, just return an empty string.
+ if (lines.size() == 0) {
+ return "";
}
- return final_comments;
-}
-namespace {
-
-// Internal helper class that parses the expected package to prefix mappings
-// file.
-class Parser {
- public:
- Parser(map<string, string>* inout_package_to_prefix_map)
- : prefix_map_(inout_package_to_prefix_map), line_(0) {}
-
- // Parses a check of input, returning success/failure.
- bool ParseChunk(StringPiece chunk);
-
- // Should be called to finish parsing (after all input has been provided via
- // ParseChunk()). Returns success/failure.
- bool Finish();
-
- int last_line() const { return line_; }
- string error_str() const { return error_str_; }
-
- private:
- bool ParseLoop();
+ string prefix;
+ string suffix;
+ string final_comments;
+ string epilogue;
- map<string, string>* prefix_map_;
- int line_;
- string error_str_;
- StringPiece p_;
- string leftover_;
-};
+ bool add_leading_space = false;
-bool Parser::ParseChunk(StringPiece chunk) {
- if (!leftover_.empty()) {
- chunk.AppendToString(&leftover_);
- p_ = StringPiece(leftover_);
- } else {
- p_ = chunk;
- }
- bool result = ParseLoop();
- if (p_.empty()) {
- leftover_.clear();
+ if (prefer_single_line && lines.size() == 1) {
+ prefix = "/** ";
+ suffix = " */\n";
} else {
- leftover_ = p_.ToString();
+ prefix = "* ";
+ suffix = "\n";
+ final_comments += "/**\n";
+ epilogue = " **/\n";
+ add_leading_space = true;
}
+
+ for (int i = 0; i < lines.size(); i++) {
+ string line = StripPrefixString(lines[i], " ");
+ // HeaderDoc and appledoc use '\' and '@' for markers; escape them.
+ line = StringReplace(line, "\\", "\\\\", true);
+ line = StringReplace(line, "@", "\\@", true);
+ // Decouple / from * to not have inline comments inside comments.
+ line = StringReplace(line, "/*", "/\\*", true);
+ line = StringReplace(line, "*/", "*\\/", true);
+ line = prefix + line;
+ StripWhitespace(&line);
+ // If not a one line, need to add the first space before *, as
+ // StripWhitespace would have removed it.
+ line = (add_leading_space ? " " : "") + line;
+ final_comments += line + suffix;
+ }
+ final_comments += epilogue;
+ return final_comments;
+}
+
+// Making these a generator option for folks that don't use CocoaPods, but do
+// want to put the library in a framework is an interesting question. The
+// problem is it means changing sources shipped with the library to actually
+// use a different value; so it isn't as simple as a option.
+const char* const ProtobufLibraryFrameworkName = "Protobuf";
+
+string ProtobufFrameworkImportSymbol(const string& framework_name) {
+ // GPB_USE_[framework_name]_FRAMEWORK_IMPORTS
+ string result = string("GPB_USE_");
+ result += ToUpper(framework_name);
+ result += "_FRAMEWORK_IMPORTS";
return result;
}
-bool Parser::Finish() {
- if (leftover_.empty()) {
+bool IsProtobufLibraryBundledProtoFile(const FileDescriptor* file) {
+ // We don't check the name prefix or proto package because some files
+ // (descriptor.proto), aren't shipped generated by the library, so this
+ // seems to be the safest way to only catch the ones shipped.
+ const string name = file->name();
+ if (name == "google/protobuf/any.proto" ||
+ name == "google/protobuf/api.proto" ||
+ name == "google/protobuf/duration.proto" ||
+ name == "google/protobuf/empty.proto" ||
+ name == "google/protobuf/field_mask.proto" ||
+ name == "google/protobuf/source_context.proto" ||
+ name == "google/protobuf/struct.proto" ||
+ name == "google/protobuf/timestamp.proto" ||
+ name == "google/protobuf/type.proto" ||
+ name == "google/protobuf/wrappers.proto") {
return true;
}
- // Force a newline onto the end to finish parsing.
- p_ = StringPiece(leftover_ + "\n");
- if (!ParseLoop()) {
- return false;
- }
- return p_.empty(); // Everything used?
+ return false;
}
-static bool ascii_isnewline(char c) { return c == '\n' || c == '\r'; }
-
bool ReadLine(StringPiece* input, StringPiece* line) {
for (int len = 0; len < input->size(); ++len) {
if (ascii_isnewline((*input)[len])) {
@@ -848,15 +979,6 @@ bool ReadLine(StringPiece* input, StringPiece* line) {
return false; // Ran out of input with no newline.
}
-void TrimWhitespace(StringPiece* input) {
- while (!input->empty() && ascii_isspace(*input->data())) {
- input->remove_prefix(1);
- }
- while (!input->empty() && ascii_isspace((*input)[input->length() - 1])) {
- input->remove_suffix(1);
- }
-}
-
void RemoveComment(StringPiece* input) {
int offset = input->find('#');
if (offset != StringPiece::npos) {
@@ -864,91 +986,64 @@ void RemoveComment(StringPiece* input) {
}
}
-bool Parser::ParseLoop() {
- StringPiece line;
- while (ReadLine(&p_, &line)) {
- ++line_;
- RemoveComment(&line);
- TrimWhitespace(&line);
- if (line.size() == 0) {
- continue; // Blank line.
- }
- int offset = line.find('=');
- if (offset == StringPiece::npos) {
- error_str_ =
- string("Line without equal sign: '") + line.ToString() + "'.";
- return false;
- }
- StringPiece package(line, 0, offset);
- StringPiece prefix(line, offset + 1, line.length() - offset - 1);
- TrimWhitespace(&package);
- TrimWhitespace(&prefix);
- // Don't really worry about error checking the package/prefix for
- // being valid. Assume the file is validated when it is created/edited.
- (*prefix_map_)[package.ToString()] = prefix.ToString();
+namespace {
+
+class ExpectedPrefixesCollector : public LineConsumer {
+ public:
+ ExpectedPrefixesCollector(std::map<string, string>* inout_package_to_prefix_map)
+ : prefix_map_(inout_package_to_prefix_map) {}
+
+ virtual bool ConsumeLine(const StringPiece& line, string* out_error);
+
+ private:
+ std::map<string, string>* prefix_map_;
+};
+
+bool ExpectedPrefixesCollector::ConsumeLine(
+ const StringPiece& line, string* out_error) {
+ int offset = line.find('=');
+ if (offset == StringPiece::npos) {
+ *out_error =
+ string("Expected prefixes file line without equal sign: '") +
+ line.ToString() + "'.";
+ return false;
}
+ StringPiece package(line, 0, offset);
+ StringPiece prefix(line, offset + 1, line.length() - offset - 1);
+ StringPieceTrimWhitespace(&package);
+ StringPieceTrimWhitespace(&prefix);
+ // Don't really worry about error checking the package/prefix for
+ // being valid. Assume the file is validated when it is created/edited.
+ (*prefix_map_)[package.ToString()] = prefix.ToString();
return true;
}
-bool LoadExpectedPackagePrefixes(map<string, string>* prefix_map,
- string* out_expect_file_path,
+bool LoadExpectedPackagePrefixes(const Options &generation_options,
+ std::map<string, string>* prefix_map,
string* out_error) {
- const char* file_path = getenv("GPB_OBJC_EXPECTED_PACKAGE_PREFIXES");
- if (file_path == NULL) {
+ if (generation_options.expected_prefixes_path.empty()) {
return true;
}
- int fd;
- do {
- fd = open(file_path, O_RDONLY);
- } while (fd < 0 && errno == EINTR);
- if (fd < 0) {
- *out_error =
- string(file_path) + ":0:0: error: Unable to open." + strerror(errno);
- return false;
- }
- io::FileInputStream file_stream(fd);
- file_stream.SetCloseOnDelete(true);
- *out_expect_file_path = file_path;
-
- Parser parser(prefix_map);
- const void* buf;
- int buf_len;
- while (file_stream.Next(&buf, &buf_len)) {
- if (buf_len == 0) {
- continue;
- }
-
- if (!parser.ParseChunk(StringPiece(static_cast<const char*>(buf), buf_len))) {
- *out_error = string(file_path) + ":" + SimpleItoa(parser.last_line()) +
- ":0: error: " + parser.error_str();
- return false;
- }
- }
- return parser.Finish();
+ ExpectedPrefixesCollector collector(prefix_map);
+ return ParseSimpleFile(
+ generation_options.expected_prefixes_path, &collector, out_error);
}
-} // namespace
-
-bool ValidateObjCClassPrefix(const FileDescriptor* file, string* out_error) {
+bool ValidateObjCClassPrefix(
+ const FileDescriptor* file,
+ const string& expected_prefixes_path,
+ const std::map<string, string>& expected_package_prefixes,
+ string* out_error) {
const string prefix = file->options().objc_class_prefix();
const string package = file->package();
// 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 warnings.
- // Load any expected package prefixes to validate against those.
- map<string, string> expected_package_prefixes;
- string expect_file_path;
- if (!LoadExpectedPackagePrefixes(&expected_package_prefixes,
- &expect_file_path, out_error)) {
- // Any error, clear the entries that were read.
- expected_package_prefixes.clear();
- }
-
// Check: Error - See if there was an expected prefix for the package and
// report if it doesn't match (wrong or missing).
- map<string, string>::iterator package_match =
+ std::map<string, string>::const_iterator package_match =
expected_package_prefixes.find(package);
if (package_match != expected_package_prefixes.end()) {
// There was an entry, and...
@@ -957,8 +1052,9 @@ bool ValidateObjCClassPrefix(const FileDescriptor* file, string* out_error) {
return true;
} else {
// ...it didn't match!
- *out_error = "protoc:0: error: Expected 'option objc_class_prefix = \"" +
- package_match->second + "\";' in '" + file->name() + "'";
+ *out_error = "error: Expected 'option objc_class_prefix = \"" +
+ package_match->second + "\";' for package '" + package +
+ "' in '" + file->name() + "'";
if (prefix.length()) {
*out_error += "; but found '" + prefix + "' instead";
}
@@ -968,72 +1064,136 @@ bool ValidateObjCClassPrefix(const FileDescriptor* file, string* out_error) {
}
// If there was no prefix option, we're done at this point.
- if (prefix.length() == 0) {
+ if (prefix.empty()) {
// No prefix, nothing left to check.
return true;
}
- // Check: Error - Make sure the prefix wasn't expected for a different
- // package (overlap is allowed, but it has to be listed as an expected
- // overlap).
- for (map<string, string>::iterator i = expected_package_prefixes.begin();
- i != expected_package_prefixes.end(); ++i) {
- if (i->second == prefix) {
- *out_error =
- "protoc:0: error: Found 'option objc_class_prefix = \"" + prefix +
- "\";' in '" + file->name() +
- "'; that prefix is already used for 'package " + i->first +
- ";'. It can only be reused by listing it in the expected file (" +
- expect_file_path + ").";
- return false; // Only report first usage of the prefix.
- }
- }
-
// Check: Warning - Make sure the prefix is is a reasonable value according
// to Apple's rules (the checks above implicitly whitelist anything that
// doesn't meet these rules).
if (!ascii_isupper(prefix[0])) {
- cerr << endl
+ std::cerr << std::endl
<< "protoc:0: warning: Invalid 'option objc_class_prefix = \""
<< prefix << "\";' in '" << file->name() << "';"
- << " it should start with a capital letter." << endl;
- cerr.flush();
+ << " it should start with a capital letter." << std::endl;
+ std::cerr.flush();
}
if (prefix.length() < 3) {
// Apple reserves 2 character prefixes for themselves. They do use some
// 3 character prefixes, but they haven't updated the rules/docs.
- cerr << endl
+ std::cerr << std::endl
<< "protoc:0: warning: Invalid 'option objc_class_prefix = \""
<< prefix << "\";' in '" << file->name() << "';"
<< " Apple recommends they should be at least 3 characters long."
- << endl;
- cerr.flush();
+ << std::endl;
+ std::cerr.flush();
+ }
+
+ // Look for any other package that uses the same prefix.
+ string other_package_for_prefix;
+ for (std::map<string, string>::const_iterator i = expected_package_prefixes.begin();
+ i != expected_package_prefixes.end(); ++i) {
+ if (i->second == prefix) {
+ other_package_for_prefix = i->first;
+ break;
+ }
+ }
+
+ // Check: Warning - If the file does not have a package, check whether
+ // the prefix declared is being used by another package or not.
+ if (package.empty()) {
+ // The file does not have a package and ...
+ if (other_package_for_prefix.empty()) {
+ // ... no other package has declared that prefix.
+ std::cerr << std::endl
+ << "protoc:0: warning: File '" << file->name() << "' has no "
+ << "package. Consider adding a new package to the proto and adding '"
+ << "new.package = " << prefix << "' to the expected prefixes file ("
+ << expected_prefixes_path << ")." << std::endl;
+ std::cerr.flush();
+ } else {
+ // ... another package has declared the same prefix.
+ std::cerr << std::endl
+ << "protoc:0: warning: File '" << file->name() << "' has no package "
+ << "and package '" << other_package_for_prefix << "' already uses '"
+ << prefix << "' as its prefix. Consider either adding a new package "
+ << "to the proto, or reusing one of the packages already using this "
+ << "prefix in the expected prefixes file ("
+ << expected_prefixes_path << ")." << std::endl;
+ std::cerr.flush();
+ }
+ return true;
+ }
+
+ // Check: Error - Make sure the prefix wasn't expected for a different
+ // package (overlap is allowed, but it has to be listed as an expected
+ // overlap).
+ if (!other_package_for_prefix.empty()) {
+ *out_error =
+ "error: Found 'option objc_class_prefix = \"" + prefix +
+ "\";' in '" + file->name() +
+ "'; that prefix is already used for 'package " +
+ other_package_for_prefix + ";'. It can only be reused by listing " +
+ "it in the expected file (" +
+ expected_prefixes_path + ").";
+ return false; // Only report first usage of the prefix.
}
// Check: Warning - If the given package/prefix pair wasn't expected, issue a
// warning issue a warning suggesting it gets added to the file.
if (!expected_package_prefixes.empty()) {
- cerr << endl
+ std::cerr << std::endl
<< "protoc:0: warning: Found unexpected 'option objc_class_prefix = \""
<< prefix << "\";' in '" << file->name() << "';"
<< " consider adding it to the expected prefixes file ("
- << expect_file_path << ")." << endl;
- cerr.flush();
+ << expected_prefixes_path << ")." << std::endl;
+ std::cerr.flush();
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool ValidateObjCClassPrefixes(const std::vector<const FileDescriptor*>& files,
+ const Options& generation_options,
+ string* out_error) {
+ // Load the expected package prefixes, if available, to validate against.
+ std::map<string, string> expected_package_prefixes;
+ if (!LoadExpectedPackagePrefixes(generation_options,
+ &expected_package_prefixes,
+ out_error)) {
+ return false;
}
+ for (int i = 0; i < files.size(); i++) {
+ bool is_valid =
+ ValidateObjCClassPrefix(files[i],
+ generation_options.expected_prefixes_path,
+ expected_package_prefixes,
+ out_error);
+ if (!is_valid) {
+ return false;
+ }
+ }
return true;
}
+TextFormatDecodeData::TextFormatDecodeData() { }
+
+TextFormatDecodeData::~TextFormatDecodeData() { }
+
void TextFormatDecodeData::AddString(int32 key,
const string& input_for_decode,
const string& desired_output) {
- for (vector<DataEntry>::const_iterator i = entries_.begin();
+ for (std::vector<DataEntry>::const_iterator i = entries_.begin();
i != entries_.end(); ++i) {
if (i->first == key) {
- cerr << "error: duplicate key (" << key
+ std::cerr << "error: duplicate key (" << key
<< ") making TextFormat data, input: \"" << input_for_decode
- << "\", desired: \"" << desired_output << "\"." << endl;
- cerr.flush();
+ << "\", desired: \"" << desired_output << "\"." << std::endl;
+ std::cerr.flush();
abort();
}
}
@@ -1044,14 +1204,14 @@ void TextFormatDecodeData::AddString(int32 key,
}
string TextFormatDecodeData::Data() const {
- ostringstream data_stringstream;
+ std::ostringstream data_stringstream;
if (num_entries() > 0) {
io::OstreamOutputStream data_outputstream(&data_stringstream);
io::CodedOutputStream output_stream(&data_outputstream);
output_stream.WriteVarint32(num_entries());
- for (vector<DataEntry>::const_iterator i = entries_.begin();
+ for (std::vector<DataEntry>::const_iterator i = entries_.begin();
i != entries_.end(); ++i) {
output_stream.WriteVarint32(i->first);
output_stream.WriteString(i->second);
@@ -1185,18 +1345,18 @@ string DirectDecodeString(const string& str) {
string TextFormatDecodeData::DecodeDataForString(const string& input_for_decode,
const string& desired_output) {
if ((input_for_decode.size() == 0) || (desired_output.size() == 0)) {
- cerr << "error: got empty string for making TextFormat data, input: \""
+ std::cerr << "error: got empty string for making TextFormat data, input: \""
<< input_for_decode << "\", desired: \"" << desired_output << "\"."
- << endl;
- cerr.flush();
+ << std::endl;
+ std::cerr.flush();
abort();
}
if ((input_for_decode.find('\0') != string::npos) ||
(desired_output.find('\0') != string::npos)) {
- cerr << "error: got a null char in a string for making TextFormat data,"
+ std::cerr << "error: got a null char in a string for making TextFormat data,"
<< " input: \"" << CEscape(input_for_decode) << "\", desired: \""
- << CEscape(desired_output) << "\"." << endl;
- cerr.flush();
+ << CEscape(desired_output) << "\"." << std::endl;
+ std::cerr.flush();
abort();
}
@@ -1232,6 +1392,296 @@ string TextFormatDecodeData::DecodeDataForString(const string& input_for_decode,
return builder.Finish() + (char)'\0';
}
+namespace {
+
+class Parser {
+ public:
+ Parser(LineConsumer* line_consumer)
+ : line_consumer_(line_consumer), line_(0) {}
+
+ // Parses a check of input, returning success/failure.
+ bool ParseChunk(StringPiece chunk);
+
+ // Should be called to finish parsing (after all input has been provided via
+ // ParseChunk()). Returns success/failure.
+ bool Finish();
+
+ int last_line() const { return line_; }
+ string error_str() const { return error_str_; }
+
+ private:
+ bool ParseLoop();
+
+ LineConsumer* line_consumer_;
+ int line_;
+ string error_str_;
+ StringPiece p_;
+ string leftover_;
+};
+
+bool Parser::ParseChunk(StringPiece chunk) {
+ if (!leftover_.empty()) {
+ chunk.AppendToString(&leftover_);
+ p_ = StringPiece(leftover_);
+ } else {
+ p_ = chunk;
+ }
+ bool result = ParseLoop();
+ if (p_.empty()) {
+ leftover_.clear();
+ } else {
+ leftover_ = p_.ToString();
+ }
+ return result;
+}
+
+bool Parser::Finish() {
+ if (leftover_.empty()) {
+ return true;
+ }
+ // Force a newline onto the end to finish parsing.
+ leftover_ += "\n";
+ p_ = StringPiece(leftover_);
+ if (!ParseLoop()) {
+ return false;
+ }
+ return p_.empty(); // Everything used?
+}
+
+bool Parser::ParseLoop() {
+ StringPiece line;
+ while (ReadLine(&p_, &line)) {
+ ++line_;
+ RemoveComment(&line);
+ StringPieceTrimWhitespace(&line);
+ if (line.size() == 0) {
+ continue; // Blank line.
+ }
+ if (!line_consumer_->ConsumeLine(line, &error_str_)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+LineConsumer::LineConsumer() {}
+
+LineConsumer::~LineConsumer() {}
+
+bool ParseSimpleFile(
+ const string& path, LineConsumer* line_consumer, string* out_error) {
+ int fd;
+ do {
+ fd = posix::open(path.c_str(), O_RDONLY);
+ } while (fd < 0 && errno == EINTR);
+ if (fd < 0) {
+ *out_error =
+ string("error: Unable to open \"") + path + "\", " + strerror(errno);
+ return false;
+ }
+ io::FileInputStream file_stream(fd);
+ file_stream.SetCloseOnDelete(true);
+
+ Parser parser(line_consumer);
+ const void* buf;
+ int buf_len;
+ while (file_stream.Next(&buf, &buf_len)) {
+ if (buf_len == 0) {
+ continue;
+ }
+
+ if (!parser.ParseChunk(StringPiece(static_cast<const char*>(buf), buf_len))) {
+ *out_error =
+ string("error: ") + path +
+ " Line " + SimpleItoa(parser.last_line()) + ", " + parser.error_str();
+ return false;
+ }
+ }
+ return parser.Finish();
+}
+
+ImportWriter::ImportWriter(
+ const string& generate_for_named_framework,
+ const string& named_framework_to_proto_path_mappings_path,
+ bool include_wkt_imports)
+ : generate_for_named_framework_(generate_for_named_framework),
+ named_framework_to_proto_path_mappings_path_(
+ named_framework_to_proto_path_mappings_path),
+ include_wkt_imports_(include_wkt_imports),
+ need_to_parse_mapping_file_(true) {
+}
+
+ImportWriter::~ImportWriter() {}
+
+void ImportWriter::AddFile(const FileDescriptor* file,
+ const string& header_extension) {
+ const string file_path(FilePath(file));
+
+ if (IsProtobufLibraryBundledProtoFile(file)) {
+ // The imports of the WKTs are only needed within the library itself,
+ // in other cases, they get skipped because the generated code already
+ // import GPBProtocolBuffers.h and hence proves them.
+ if (include_wkt_imports_) {
+ protobuf_framework_imports_.push_back(
+ FilePathBasename(file) + header_extension);
+ protobuf_non_framework_imports_.push_back(file_path + header_extension);
+ }
+ return;
+ }
+
+ // Lazy parse any mappings.
+ if (need_to_parse_mapping_file_) {
+ ParseFrameworkMappings();
+ }
+
+ std::map<string, string>::iterator proto_lookup =
+ proto_file_to_framework_name_.find(file->name());
+ if (proto_lookup != proto_file_to_framework_name_.end()) {
+ other_framework_imports_.push_back(
+ proto_lookup->second + "/" +
+ FilePathBasename(file) + header_extension);
+ return;
+ }
+
+ if (!generate_for_named_framework_.empty()) {
+ other_framework_imports_.push_back(
+ generate_for_named_framework_ + "/" +
+ FilePathBasename(file) + header_extension);
+ return;
+ }
+
+ other_imports_.push_back(file_path + header_extension);
+}
+
+void ImportWriter::Print(io::Printer* printer) const {
+ assert(protobuf_non_framework_imports_.size() ==
+ protobuf_framework_imports_.size());
+
+ bool add_blank_line = false;
+
+ if (protobuf_framework_imports_.size() > 0) {
+ const string framework_name(ProtobufLibraryFrameworkName);
+ const string cpp_symbol(ProtobufFrameworkImportSymbol(framework_name));
+
+ printer->Print(
+ "#if $cpp_symbol$\n",
+ "cpp_symbol", cpp_symbol);
+ for (std::vector<string>::const_iterator iter = protobuf_framework_imports_.begin();
+ iter != protobuf_framework_imports_.end(); ++iter) {
+ printer->Print(
+ " #import <$framework_name$/$header$>\n",
+ "framework_name", framework_name,
+ "header", *iter);
+ }
+ printer->Print(
+ "#else\n");
+ for (std::vector<string>::const_iterator iter = protobuf_non_framework_imports_.begin();
+ iter != protobuf_non_framework_imports_.end(); ++iter) {
+ printer->Print(
+ " #import \"$header$\"\n",
+ "header", *iter);
+ }
+ printer->Print(
+ "#endif\n");
+
+ add_blank_line = true;
+ }
+
+ if (other_framework_imports_.size() > 0) {
+ if (add_blank_line) {
+ printer->Print("\n");
+ }
+
+ for (std::vector<string>::const_iterator iter = other_framework_imports_.begin();
+ iter != other_framework_imports_.end(); ++iter) {
+ printer->Print(
+ "#import <$header$>\n",
+ "header", *iter);
+ }
+
+ add_blank_line = true;
+ }
+
+ if (other_imports_.size() > 0) {
+ if (add_blank_line) {
+ printer->Print("\n");
+ }
+
+ for (std::vector<string>::const_iterator iter = other_imports_.begin();
+ iter != other_imports_.end(); ++iter) {
+ printer->Print(
+ "#import \"$header$\"\n",
+ "header", *iter);
+ }
+ }
+}
+
+void ImportWriter::ParseFrameworkMappings() {
+ need_to_parse_mapping_file_ = false;
+ if (named_framework_to_proto_path_mappings_path_.empty()) {
+ return; // Nothing to do.
+ }
+
+ ProtoFrameworkCollector collector(&proto_file_to_framework_name_);
+ string parse_error;
+ if (!ParseSimpleFile(named_framework_to_proto_path_mappings_path_,
+ &collector, &parse_error)) {
+ std::cerr << "error parsing " << named_framework_to_proto_path_mappings_path_
+ << " : " << parse_error << std::endl;
+ std::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 - start);
+ StringPieceTrimWhitespace(&proto_file);
+ if (proto_file.size() != 0) {
+ std::map<string, string>::iterator existing_entry =
+ map_->find(proto_file.ToString());
+ if (existing_entry != map_->end()) {
+ std::cerr << "warning: duplicate proto file reference, replacing framework entry for '"
+ << proto_file.ToString() << "' with '" << framework_name.ToString()
+ << "' (was '" << existing_entry->second << "')." << std::endl;
+ std::cerr.flush();
+ }
+
+ if (proto_file.find(' ') != StringPiece::npos) {
+ std::cerr << "note: framework mapping file had a proto file with a space in, hopefully that isn't a missing comma: '"
+ << proto_file.ToString() << "'" << std::endl;
+ std::cerr.flush();
+ }
+
+ (*map_)[proto_file.ToString()] = framework_name.ToString();
+ }
+
+ start = offset + 1;
+ }
+
+ return true;
+}
+
+
} // namespace objectivec
} // namespace compiler
} // namespace protobuf