diff options
-rw-r--r-- | .travis.yml | 3 | ||||
-rw-r--r-- | cmake/CMakeLists.txt | 4 | ||||
-rw-r--r-- | cmake/examples.cmake | 3 | ||||
-rw-r--r-- | cmake/protobuf-config-version.cmake.in | 28 | ||||
-rw-r--r-- | cmake/protobuf-module.cmake.in | 33 | ||||
-rw-r--r-- | js/gulpfile.js | 37 | ||||
-rw-r--r-- | objectivec/GPBDescriptor.m | 5 | ||||
-rw-r--r-- | objectivec/README.md | 40 | ||||
-rwxr-xr-x | objectivec/Tests/CocoaPods/run_tests.sh | 11 | ||||
-rw-r--r-- | src/README.md | 2 | ||||
-rw-r--r-- | src/google/protobuf/compiler/command_line_interface.cc | 9 | ||||
-rw-r--r-- | src/google/protobuf/compiler/command_line_interface_unittest.cc | 15 | ||||
-rwxr-xr-x | src/google/protobuf/compiler/js/js_generator.cc | 14 | ||||
-rw-r--r-- | src/google/protobuf/compiler/objectivec/objectivec_file.cc | 152 | ||||
-rw-r--r-- | src/google/protobuf/compiler/objectivec/objectivec_file.h | 2 | ||||
-rw-r--r-- | src/google/protobuf/compiler/objectivec/objectivec_generator.cc | 41 | ||||
-rw-r--r-- | src/google/protobuf/compiler/objectivec/objectivec_helpers.cc | 279 | ||||
-rw-r--r-- | src/google/protobuf/compiler/objectivec/objectivec_helpers.h | 19 | ||||
-rw-r--r-- | src/google/protobuf/map.h | 11 | ||||
-rwxr-xr-x | tests.sh | 12 |
20 files changed, 537 insertions, 183 deletions
diff --git a/.travis.yml b/.travis.yml index 86451edd..46abbb9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ env: - CONFIG=objectivec_ios_debug - CONFIG=objectivec_ios_release - CONFIG=objectivec_osx + - CONFIG=objectivec_cocoapods_integration - CONFIG=python - CONFIG=python_cpp - CONFIG=ruby19 @@ -67,6 +68,8 @@ matrix: env: CONFIG=objectivec_ios_release - os: linux env: CONFIG=objectivec_osx + - os: linux + env: CONFIG=objectivec_cocoapods_integration allow_failures: # These currently do not work on OS X but are being worked on by @haberman. - os: osx diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 7e1b385e..07b176d9 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -20,7 +20,9 @@ else (BUILD_SHARED_LIBS) set(protobuf_BUILD_SHARED_LIBS_DEFAULT OFF) endif (BUILD_SHARED_LIBS) option(protobuf_BUILD_SHARED_LIBS "Build Shared Libraries" ${protobuf_BUILD_SHARED_LIBS_DEFAULT}) -option(protobuf_MSVC_STATIC_RUNTIME "Link static runtime libraries" ON) +include(CMakeDependentOption) +cmake_dependent_option(protobuf_MSVC_STATIC_RUNTIME "Link static runtime libraries" ON + "NOT protobuf_BUILD_SHARED_LIBS" OFF) if (MSVC) set(protobuf_WITH_ZLIB_DEFAULT OFF) else (MSVC) diff --git a/cmake/examples.cmake b/cmake/examples.cmake index 0e33cd5a..83d0e988 100644 --- a/cmake/examples.cmake +++ b/cmake/examples.cmake @@ -19,8 +19,7 @@ function(add_examples_build NAME) STAMP_DIR ${NAME}/logs
INSTALL_COMMAND "" #Skip
LOG_CONFIGURE 1
- CMAKE_CACHE_ARGS "-Dprotobuf_MSVC_STATIC_RUNTIME:BOOL=${protobuf_MSVC_STATIC_RUNTIME}"
- "-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}"
+ CMAKE_CACHE_ARGS "-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}"
"-Dprotobuf_VERBOSE:BOOL=${protobuf_VERBOSE}"
${ARGN}
)
diff --git a/cmake/protobuf-config-version.cmake.in b/cmake/protobuf-config-version.cmake.in index 2a6feee1..3b8ced2d 100644 --- a/cmake/protobuf-config-version.cmake.in +++ b/cmake/protobuf-config-version.cmake.in @@ -34,15 +34,25 @@ elseif(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION) endif() endif() -# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: -if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "@CMAKE_SIZEOF_VOID_P@" STREQUAL "") - return() -endif() +# Check and save build options used to create this package +macro(_check_and_save_build_option OPTION VALUE) + if(DEFINED ${PACKAGE_FIND_NAME}_${OPTION} AND + NOT ${PACKAGE_FIND_NAME}_${OPTION} EQUAL VALUE) + set(PACKAGE_VERSION_UNSUITABLE TRUE) + endif() + set(${PACKAGE_FIND_NAME}_${OPTION} ${VALUE}) +endmacro() +_check_and_save_build_option(WITH_ZLIB @protobuf_WITH_ZLIB@) +_check_and_save_build_option(MSVC_STATIC_RUNTIME @protobuf_MSVC_STATIC_RUNTIME@) +_check_and_save_build_option(BUILD_SHARED_LIBS @protobuf_BUILD_SHARED_LIBS@) -# check that the installed version has the same 32/64bit-ness as the one which is currently searching: -if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "@CMAKE_SIZEOF_VOID_P@") - math(EXPR installedBits "@CMAKE_SIZEOF_VOID_P@ * 8") - set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") - set(PACKAGE_VERSION_UNSUITABLE TRUE) +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if(NOT "${CMAKE_SIZEOF_VOID_P}" STREQUAL "" AND NOT "@CMAKE_SIZEOF_VOID_P@" STREQUAL "") + # check that the installed version has the same 32/64bit-ness as the one which is currently searching: + if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "@CMAKE_SIZEOF_VOID_P@") + math(EXPR installedBits "@CMAKE_SIZEOF_VOID_P@ * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) + endif() endif() diff --git a/cmake/protobuf-module.cmake.in b/cmake/protobuf-module.cmake.in index 7ced934a..6e0bcf90 100644 --- a/cmake/protobuf-module.cmake.in +++ b/cmake/protobuf-module.cmake.in @@ -143,15 +143,10 @@ function(_protobuf_find_libraries name filename) # Honor cache entry used by CMake 3.5 and lower. set(${name}_LIBRARIES "${${name}_LIBRARY}" PARENT_SCOPE) else() - get_target_property(_aliased protobuf::lib${filename} ALIASED_TARGET) - if(_aliased) - set(${name}_LIBRARY_RELEASE $<TARGET_FILE:protobuf::lib${filename}>) - set(${name}_LIBRARY_DEBUG $<TARGET_FILE:protobuf::lib${filename}>) - else() - get_target_property(${name}_LIBRARY_RELEASE protobuf::lib${filename} - LOCATION_RELEASE) - get_target_property(${name}_LIBRARY_DEBUG protobuf::lib${filename} - LOCATION_DEBUG) + get_target_property(${name}_LIBRARY_RELEASE protobuf::lib${filename} + LOCATION_RELEASE) + get_target_property(${name}_LIBRARY_DEBUG protobuf::lib${filename} + LOCATION_DEBUG) endif() select_library_configurations(${name}) @@ -198,21 +193,11 @@ get_target_property(Protobuf_INCLUDE_DIRS protobuf::libprotobuf INTERFACE_INCLUDE_DIRECTORIES) # Set the protoc Executable -get_target_property(_aliased protobuf::protoc ALIASED_TARGET) -if(_aliased) - if(POLICY CMP0026) - set(Protobuf_PROTOC_EXECUTABLE $<TARGET_FILE:protobuf::protoc>) - else() - get_target_property(Protobuf_PROTOC_EXECUTABLE protobuf::protoc - LOCATION) - endif() -else() +get_target_property(Protobuf_PROTOC_EXECUTABLE protobuf::protoc + IMPORTED_LOCATION_RELEASE) +if(NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}") get_target_property(Protobuf_PROTOC_EXECUTABLE protobuf::protoc - IMPORTED_LOCATION_RELEASE) - if(NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}") - get_target_property(Protobuf_PROTOC_EXECUTABLE protobuf::protoc - IMPORTED_LOCATION_DEBUG) - endif() + IMPORTED_LOCATION_DEBUG) endif() # Version info variable @@ -220,7 +205,7 @@ set(Protobuf_VERSION "@protobuf_VERSION@") include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Protobuf - REQUIRED_VARS Protobuf_LIBRARIES Protobuf_INCLUDE_DIRS + REQUIRED_VARS Protobuf_PROTOC_EXECUTABLE Protobuf_LIBRARIES Protobuf_INCLUDE_DIRS VERSION_VAR Protobuf_VERSION ) diff --git a/js/gulpfile.js b/js/gulpfile.js index c5220153..cca99131 100644 --- a/js/gulpfile.js +++ b/js/gulpfile.js @@ -8,6 +8,21 @@ function exec(command, cb) { var protoc = process.env.PROTOC || '../src/protoc'; +var wellKnownTypes = [ + '../src/google/protobuf/any.proto', + '../src/google/protobuf/api.proto', + '../src/google/protobuf/compiler/plugin.proto', + '../src/google/protobuf/descriptor.proto', + '../src/google/protobuf/duration.proto', + '../src/google/protobuf/empty.proto', + '../src/google/protobuf/field_mask.proto', + '../src/google/protobuf/source_context.proto', + '../src/google/protobuf/struct.proto', + '../src/google/protobuf/timestamp.proto', + '../src/google/protobuf/type.proto', + '../src/google/protobuf/wrappers.proto', +]; + gulp.task('genproto_closure', function (cb) { exec(protoc + ' --js_out=library=testproto_libs,binary:. -I ../src -I . *.proto ../src/google/protobuf/descriptor.proto', function (err, stdout, stderr) { @@ -26,7 +41,25 @@ gulp.task('genproto_commonjs', function (cb) { }); }); -gulp.task('dist', function (cb) { +gulp.task('genproto_commonjs_wellknowntypes', function (cb) { + exec('mkdir -p commonjs_out/node_modules/google-protobuf && ' + protoc + ' --js_out=import_style=commonjs,binary:commonjs_out/node_modules/google-protobuf -I ../src ../src/google/protobuf/descriptor.proto', + function (err, stdout, stderr) { + console.log(stdout); + console.log(stderr); + cb(err); + }); +}); + +gulp.task('genproto_wellknowntypes', function (cb) { + exec(protoc + ' --js_out=import_style=commonjs,binary:. -I ../src ' + wellKnownTypes.join(' '), + function (err, stdout, stderr) { + console.log(stdout); + console.log(stderr); + cb(err); + }); +}); + +gulp.task('dist', ['genproto_wellknowntypes'], function (cb) { // TODO(haberman): minify this more aggressively. // Will require proper externs/exports. exec('./node_modules/google-closure-library/closure/bin/calcdeps.py -i message.js -i binary/reader.js -i binary/writer.js -i commonjs/export.js -p . -p node_modules/google-closure-library/closure -o compiled --compiler_jar node_modules/google-closure-compiler/compiler.jar > google-protobuf.js', @@ -55,7 +88,7 @@ gulp.task('commonjs_testdeps', function (cb) { }); }); -gulp.task('make_commonjs_out', ['dist', 'genproto_commonjs', 'commonjs_asserts', 'commonjs_testdeps'], function (cb) { +gulp.task('make_commonjs_out', ['dist', 'genproto_commonjs', 'genproto_commonjs_wellknowntypes', 'commonjs_asserts', 'commonjs_testdeps'], function (cb) { // TODO(haberman): minify this more aggressively. // Will require proper externs/exports. var cmd = "mkdir -p commonjs_out/binary && mkdir -p commonjs_out/test_node_modules && "; diff --git a/objectivec/GPBDescriptor.m b/objectivec/GPBDescriptor.m index 898f231d..d27d6892 100644 --- a/objectivec/GPBDescriptor.m +++ b/objectivec/GPBDescriptor.m @@ -271,6 +271,11 @@ static NSArray *NewFieldsArrayForHasIndex(int hasIndex, return self; } +- (void)dealloc { + [package_ release]; + [super dealloc]; +} + @end @implementation GPBOneofDescriptor diff --git a/objectivec/README.md b/objectivec/README.md index c7313e4f..25355e05 100644 --- a/objectivec/README.md +++ b/objectivec/README.md @@ -123,8 +123,8 @@ never included when the message is encoded. The Objective C classes/enums can be used from Swift code. -Objective C Generator Options ------------------------------ +Objective C Generator Proto File Options +---------------------------------------- **objc_class_prefix=\<prefix\>** (no default) @@ -133,6 +133,42 @@ be collisions. This option provides a prefix that will be added to the Enums and Objects (for messages) generated from the proto. Convention is to base the prefix on the package the proto is in. +Objective C Generator `protoc` Options +-------------------------------------- + +When generating Objective C code, `protoc` supports a `--objc_opt` argument; the +argument is comma-delimited name/value pairs (_key=value,key2=value2_). The +_keys_ are used to change the behavior during generation. The currently +supported keys are: + + * `generate_for_framework_named`: The `value` used for this key will be used + when generating the `#import` statements in the generated code. Instead + 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 ------------ diff --git a/objectivec/Tests/CocoaPods/run_tests.sh b/objectivec/Tests/CocoaPods/run_tests.sh index edf0fe81..749f413c 100755 --- a/objectivec/Tests/CocoaPods/run_tests.sh +++ b/objectivec/Tests/CocoaPods/run_tests.sh @@ -119,9 +119,18 @@ do_test() { # Put the right Podfile in place. cp -f "Podfile-${TEST_MODE}" "Podfile" + xcodebuild_args=( "-workspace" "${TEST_NAME}.xcworkspace" "-scheme" "${TEST_NAME}" ) + + # For iOS, if the SDK is not provided it tries to use iphoneos, and the test + # fail on Travis since those machines don't have a Code Signing identity. + if [[ "${TEST_NAME}" == iOS* ]] ; then + xcodebuild_args+=( "-sdk" "iphonesimulator" "ONLY_ACTIVE_ARCH=NO" ) + fi + # Do the work! pod install --verbose - xcodebuild -workspace "${TEST_NAME}.xcworkspace" -scheme "${TEST_NAME}" build + + xcodebuild "${xcodebuild_args[@]}" build # Clear the hook and manually run cleanup. trap - EXIT diff --git a/src/README.md b/src/README.md index 63d6e24c..c9362ee2 100644 --- a/src/README.md +++ b/src/README.md @@ -22,7 +22,7 @@ To build protobuf from source, the following tools are needed: On Ubuntu, you can install them with: - $ sudo apt-get install autoconf automake libtool curl make g++ unzip + $ sudo apt-get install autoconf automake libtool curl make g++ unzip On other platforms, please use the corresponding package managing tool to install them before proceeding. diff --git a/src/google/protobuf/compiler/command_line_interface.cc b/src/google/protobuf/compiler/command_line_interface.cc index fcad6b61..c1a6c15d 100644 --- a/src/google/protobuf/compiler/command_line_interface.cc +++ b/src/google/protobuf/compiler/command_line_interface.cc @@ -1136,8 +1136,13 @@ CommandLineInterface::InterpretArgument(const string& name, // Make sure disk path exists, warn otherwise. if (access(disk_path.c_str(), F_OK) < 0) { - std::cerr << disk_path << ": warning: directory does not exist." - << std::endl; + // Try the original path; it may have just happed to have a '=' in it. + if (access(parts[i].c_str(), F_OK) < 0) { + cerr << disk_path << ": warning: directory does not exist." << endl; + } else { + virtual_path = ""; + disk_path = parts[i]; + } } // Don't use make_pair as the old/default standard library on Solaris diff --git a/src/google/protobuf/compiler/command_line_interface_unittest.cc b/src/google/protobuf/compiler/command_line_interface_unittest.cc index 9b504d25..0ebf9b6a 100644 --- a/src/google/protobuf/compiler/command_line_interface_unittest.cc +++ b/src/google/protobuf/compiler/command_line_interface_unittest.cc @@ -786,6 +786,21 @@ TEST_F(CommandLineInterfaceTest, NonRootMapping) { ExpectGenerated("test_generator", "", "bar/foo.proto", "Foo"); } +TEST_F(CommandLineInterfaceTest, PathWithEqualsSign) { + // Test setting up a search path which happens to have '=' in it. + + CreateTempDir("with=sign"); + CreateTempFile("with=sign/foo.proto", + "syntax = \"proto2\";\n" + "message Foo {}\n"); + + Run("protocol_compiler --test_out=$tmpdir " + "--proto_path=$tmpdir/with=sign foo.proto"); + + ExpectNoErrors(); + ExpectGenerated("test_generator", "", "foo.proto", "Foo"); +} + TEST_F(CommandLineInterfaceTest, MultipleGenerators) { // Test that we can have multiple generators and use both in one invocation, // each with a different output directory. diff --git a/src/google/protobuf/compiler/js/js_generator.cc b/src/google/protobuf/compiler/js/js_generator.cc index a72cf6a3..8fb24bed 100755 --- a/src/google/protobuf/compiler/js/js_generator.cc +++ b/src/google/protobuf/compiler/js/js_generator.cc @@ -159,8 +159,16 @@ string GetJSFilename(const string& filename) { // Given a filename like foo/bar/baz.proto, returns the root directory // path ../../ -string GetRootPath(const string& filename) { - size_t slashes = std::count(filename.begin(), filename.end(), '/'); +string GetRootPath(const string& from_filename, const string& to_filename) { + if (to_filename.find("google/protobuf") == 0) { + // Well-known types (.proto files in the google/protobuf directory) are + // assumed to come from the 'google-protobuf' npm package. We may want to + // generalize this exception later by letting others put generated code in + // their own npm packages. + return "google-protobuf/"; + } + + size_t slashes = std::count(from_filename.begin(), from_filename.end(), '/'); if (slashes == 0) { return "./"; } @@ -2838,7 +2846,7 @@ void Generator::GenerateFile(const GeneratorOptions& options, printer->Print( "var $alias$ = require('$file$');\n", "alias", ModuleAlias(name), - "file", GetRootPath(file->name()) + GetJSFilename(name)); + "file", GetRootPath(file->name(), name) + GetJSFilename(name)); } } diff --git a/src/google/protobuf/compiler/objectivec/objectivec_file.cc b/src/google/protobuf/compiler/objectivec/objectivec_file.cc index 7774058e..cf8c34e1 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_file.cc +++ b/src/google/protobuf/compiler/objectivec/objectivec_file.cc @@ -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 { @@ -54,33 +58,78 @@ namespace { class ImportWriter { public: - ImportWriter() {} + 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_; vector<string> other_imports_; }; 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 { - other_imports_.push_back(file->Path() + extension); + 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); + 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()); + bool add_blank_line = false; + if (protobuf_framework_imports_.size() > 0) { const string framework_name(ProtobufLibraryFrameworkName); const string cpp_symbol(ProtobufFrameworkImportSymbol(framework_name)); @@ -106,12 +155,29 @@ void ImportWriter::Print(io::Printer *printer) const { printer->Print( "#endif\n"); - if (other_imports_.size() > 0) { + add_blank_line = true; + } + + if (other_framework_imports_.size() > 0) { + if (add_blank_line) { printer->Print("\n"); } + + for (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 (vector<string>::const_iterator iter = other_imports_.begin(); iter != other_imports_.end(); ++iter) { printer->Print( @@ -121,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 @@ -156,7 +285,7 @@ FileGenerator::~FileGenerator() { } void FileGenerator::GenerateHeader(io::Printer *printer) { - PrintFilePreamble(printer, "GPBProtocolBuffers.h"); + PrintFileRuntimePreamble(printer, "GPBProtocolBuffers.h"); // Add some verification that the generated code matches the source the // code is being compiled with. @@ -170,7 +299,7 @@ void FileGenerator::GenerateHeader(io::Printer *printer) { // #import any headers for "public imports" in the proto file. { - ImportWriter import_writer; + ImportWriter import_writer(options_); const vector<FileGenerator *> &dependency_generators = DependencyGenerators(); for (vector<FileGenerator *>::const_iterator iter = dependency_generators.begin(); @@ -273,10 +402,10 @@ void FileGenerator::GenerateHeader(io::Printer *printer) { void FileGenerator::GenerateSource(io::Printer *printer) { // #import the runtime support. - PrintFilePreamble(printer, "GPBProtocolBuffers_RuntimeSupport.h"); + PrintFileRuntimePreamble(printer, "GPBProtocolBuffers_RuntimeSupport.h"); { - ImportWriter import_writer; + ImportWriter import_writer(options_); // #import the header for this proto file. import_writer.AddFile(this); @@ -471,7 +600,10 @@ const vector<FileGenerator *> &FileGenerator::DependencyGenerators() { return dependency_generators_; } -void FileGenerator::PrintFilePreamble( +// Helper to print the import of the runtime support at the top of generated +// files. This currently only supports the runtime coming from a framework +// as defined by the official CocoaPod. +void FileGenerator::PrintFileRuntimePreamble( io::Printer* printer, const string& header_to_import) const { printer->Print( "// Generated by the protocol buffer compiler. DO NOT EDIT!\n" diff --git a/src/google/protobuf/compiler/objectivec/objectivec_file.h b/src/google/protobuf/compiler/objectivec/objectivec_file.h index 7326c901..8e4388d8 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_file.h +++ b/src/google/protobuf/compiler/objectivec/objectivec_file.h @@ -88,7 +88,7 @@ class FileGenerator { const Options options_; const vector<FileGenerator*>& DependencyGenerators(); - void PrintFilePreamble( + void PrintFileRuntimePreamble( io::Printer* printer, const string& header_to_import) const; GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FileGenerator); diff --git a/src/google/protobuf/compiler/objectivec/objectivec_generator.cc b/src/google/protobuf/compiler/objectivec/objectivec_generator.cc index 72e295de..29a8765c 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_generator.cc +++ b/src/google/protobuf/compiler/objectivec/objectivec_generator.cc @@ -58,7 +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") { + // 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; diff --git a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc index 65bf8348..311bf35d 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc +++ b/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc @@ -81,6 +81,10 @@ 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. @@ -272,6 +276,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 @@ -871,65 +885,6 @@ bool IsProtobufLibraryBundledProtoFile(const FileDescriptor* file) { return false; } -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(); - - map<string, string>* prefix_map_; - 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. - p_ = StringPiece(leftover_ + "\n"); - if (!ParseLoop()) { - return false; - } - return p_.empty(); // Everything used? -} - -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])) { @@ -942,15 +897,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) { @@ -958,29 +904,35 @@ 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(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: + 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; } @@ -991,36 +943,9 @@ bool LoadExpectedPackagePrefixes(const Options &generation_options, return true; } - int fd; - do { - fd = open(generation_options.expected_prefixes_path.c_str(), O_RDONLY); - } while (fd < 0 && errno == EINTR); - if (fd < 0) { - *out_error = - string("error: Unable to open \"") + - generation_options.expected_prefixes_path + - "\", " + strerror(errno); - return false; - } - io::FileInputStream file_stream(fd); - file_stream.SetCloseOnDelete(true); - - 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("error: ") + generation_options.expected_prefixes_path + - " Line " + SimpleItoa(parser.last_line()) + ", " + parser.error_str(); - return false; - } - } - return parser.Finish(); + ExpectedPrefixesCollector collector(prefix_map); + return ParseSimpleFile( + generation_options.expected_prefixes_path, &collector, out_error); } } // namespace @@ -1121,6 +1046,10 @@ bool ValidateObjCClassPrefix(const FileDescriptor* file, return true; } +TextFormatDecodeData::TextFormatDecodeData() { } + +TextFormatDecodeData::~TextFormatDecodeData() { } + void TextFormatDecodeData::AddString(int32 key, const string& input_for_decode, const string& desired_output) { @@ -1329,6 +1258,116 @@ 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. + p_ = StringPiece(leftover_ + "\n"); + 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 = 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(); +} + + } // namespace objectivec } // namespace compiler } // namespace protobuf diff --git a/src/google/protobuf/compiler/objectivec/objectivec_helpers.h b/src/google/protobuf/compiler/objectivec/objectivec_helpers.h index 5898d692..be20beee 100644 --- a/src/google/protobuf/compiler/objectivec/objectivec_helpers.h +++ b/src/google/protobuf/compiler/objectivec/objectivec_helpers.h @@ -46,6 +46,8 @@ namespace objectivec { struct Options { Options(); string expected_prefixes_path; + string generate_for_named_framework; + string named_framework_to_proto_path_mappings_path; }; // Escape C++ trigraphs by escaping question marks to "\?". @@ -54,6 +56,9 @@ string EscapeTrigraphs(const string& to_escape); // Strips ".proto" or ".protodevel" from the end of a filename. string StripProto(const string& filename); +// Remove white space from either end of a StringPiece. +void StringPieceTrimWhitespace(StringPiece* input); + // Returns true if the name requires a ns_returns_not_retained attribute applied // to it. bool IsRetainedName(const string& name); @@ -189,7 +194,8 @@ bool ValidateObjCClassPrefix(const FileDescriptor* file, // the input into the expected output. class LIBPROTOC_EXPORT TextFormatDecodeData { public: - TextFormatDecodeData() {} + TextFormatDecodeData(); + ~TextFormatDecodeData(); void AddString(int32 key, const string& input_for_decode, const string& desired_output); @@ -206,6 +212,17 @@ class LIBPROTOC_EXPORT TextFormatDecodeData { vector<DataEntry> entries_; }; +// Helper for parsing simple files. +class LIBPROTOC_EXPORT LineConsumer { + public: + LineConsumer(); + virtual ~LineConsumer(); + virtual bool ConsumeLine(const StringPiece& line, string* out_error) = 0; +}; + +bool ParseSimpleFile( + const string& path, LineConsumer* line_consumer, string* out_error); + } // namespace objectivec } // namespace compiler } // namespace protobuf diff --git a/src/google/protobuf/map.h b/src/google/protobuf/map.h index 6f1a71e4..31593c1a 100644 --- a/src/google/protobuf/map.h +++ b/src/google/protobuf/map.h @@ -587,7 +587,7 @@ class Map { explicit MapAllocator(Arena* arena) : arena_(arena) {} template <typename X> MapAllocator(const MapAllocator<X>& allocator) - : arena_(allocator.arena_) {} + : arena_(allocator.arena()) {} pointer allocate(size_type n, const_pointer hint = 0) { // If arena is not given, malloc needs to be called which doesn't @@ -650,12 +650,15 @@ class Map { return std::numeric_limits<size_type>::max(); } + // To support gcc-4.4, which does not properly + // support templated friend classes + Arena* arena() const { + return arena_; + } + private: typedef void DestructorSkippable_; Arena* const arena_; - - template <typename X> - friend class MapAllocator; }; // InnerMap's key type is Key and its value type is value_type*. We use a @@ -223,6 +223,17 @@ build_objectivec_osx() { --core-only --skip-xcode-ios } +build_objectivec_cocoapods_integration() { + # First, load the RVM environment in bash, needed to update ruby. + source ~/.rvm/scripts/rvm + # Update ruby to 2.2.3 as the default one crashes with segmentation faults + # when using pod. + rvm use 2.2.3 --install --binary --fuzzy + # Update pod to the latest version. + gem install cocoapods --no-ri --no-rdoc + objectivec/Tests/CocoaPods/run_tests.sh +} + build_python() { internal_build_cpp internal_install_python_deps @@ -304,6 +315,7 @@ Usage: $0 { cpp | objectivec_ios_debug | objectivec_ios_release | objectivec_osx | + objectivec_cocoapods_integration | python | python_cpp | ruby19 | |