diff options
55 files changed, 1590 insertions, 213 deletions
diff --git a/.travis.yml b/.travis.yml index 18b04e2..a2719b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,37 @@ before_install: - gem install xcpretty - bundle exec pod install --project-directory=Example --repo-update - bundle exec pod install --project-directory=Firestore/Example --no-repo-update + - brew install clang-format + - echo "$TRAVIS_COMMIT_RANGE" + - echo "$TRAVIS_PULL_REQUEST" + - | + if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then + SKIP_FIREBASE=0 + SKIP_FIRESTORE=0 + else + git diff --name-only $TRAVIS_COMMIT_RANGE | grep -Eq '^(Firebase|Example)' + SKIP_FIREBASE="$?" + git diff --name-only $TRAVIS_COMMIT_RANGE | grep -q Firestore + SKIP_FIRESTORE="$?" + fi script: - "! git grep -I ' $'" # Fail on trailing whitespace in non-binary files - - ./test.sh + - ./scripts/style.sh test-only # Validate clang-format compliance + - | + if [ $SKIP_FIREBASE != 1 ]; then + ./test.sh + fi + - | + if [ $SKIP_FIRESTORE != 1 ]; then + ./Firestore/test.sh + fi # TODO fix os_log deprecation warning in FIRLogger to remove --allow-warnings - - bundle exec pod lib lint FirebaseCore.podspec --allow-warnings + - | + if [ $SKIP_FIREBASE != 1 ]; then + bundle exec pod lib lint FirebaseCore.podspec --allow-warnings + fi # TODO - Uncomment subsequent lines once FirebaseCore source repo is in public Specs repo # - bundle exec pod lib lint FirebaseAuth.podspec diff --git a/CMakeLists.txt b/CMakeLists.txt index edcd611..f58a980 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ set(FIREBASE_INSTALL_DIR ${PROJECT_BINARY_DIR}) enable_testing() include(external/FirebaseCore) - include(external/googletest) include(external/leveldb) +include(external/grpc) include(external/firestore) diff --git a/Example/Core/Tests/FIROptionsTest.m b/Example/Core/Tests/FIROptionsTest.m index d27a0e1..8170405 100644 --- a/Example/Core/Tests/FIROptionsTest.m +++ b/Example/Core/Tests/FIROptionsTest.m @@ -25,7 +25,8 @@ extern NSString *const kFIRLibraryVersionID; @interface FIROptions (Test) -@property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary; +- (nullable NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary: + (nullable NSDictionary *)infoDictionary; @end @@ -263,22 +264,20 @@ extern NSString *const kFIRLibraryVersionID; } - (void)testAnalyticsOptions { - id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); - // No keys anywhere. NSDictionary *optionsDictionary = nil; FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary]; NSDictionary *mainDictionary = nil; - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); NSDictionary *expectedAnalyticsOptions = @{}; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + NSDictionary *analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:nil]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); optionsDictionary = @{}; options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary]; mainDictionary = @{}; - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); expectedAnalyticsOptions = @{}; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); // Main has no keys. optionsDictionary = @{ @@ -288,9 +287,9 @@ extern NSString *const kFIRLibraryVersionID; }; options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary]; mainDictionary = @{}; - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); expectedAnalyticsOptions = optionsDictionary; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); // Main overrides all the keys. optionsDictionary = @{ @@ -304,9 +303,9 @@ extern NSString *const kFIRLibraryVersionID; kFIRIsAnalyticsCollectionEnabled : @NO, kFIRIsMeasurementEnabled : @NO }; - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); expectedAnalyticsOptions = mainDictionary; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); // Keys exist only in main. optionsDictionary = @{}; @@ -316,9 +315,9 @@ extern NSString *const kFIRLibraryVersionID; kFIRIsAnalyticsCollectionEnabled : @YES, kFIRIsMeasurementEnabled : @YES }; - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); expectedAnalyticsOptions = mainDictionary; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); // Main overrides single keys. optionsDictionary = @{ @@ -328,13 +327,13 @@ extern NSString *const kFIRLibraryVersionID; }; options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary]; mainDictionary = @{ kFIRIsAnalyticsCollectionDeactivated : @NO }; - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); expectedAnalyticsOptions = @{ kFIRIsAnalyticsCollectionDeactivated : @NO, // override kFIRIsAnalyticsCollectionEnabled : @YES, kFIRIsMeasurementEnabled : @YES }; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); optionsDictionary = @{ kFIRIsAnalyticsCollectionDeactivated : @YES, @@ -343,13 +342,13 @@ extern NSString *const kFIRLibraryVersionID; }; options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary]; mainDictionary = @{ kFIRIsAnalyticsCollectionEnabled : @NO }; - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); expectedAnalyticsOptions = @{ kFIRIsAnalyticsCollectionDeactivated : @YES, kFIRIsAnalyticsCollectionEnabled : @NO, // override kFIRIsMeasurementEnabled : @YES }; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); optionsDictionary = @{ kFIRIsAnalyticsCollectionDeactivated : @YES, @@ -358,18 +357,18 @@ extern NSString *const kFIRLibraryVersionID; }; options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary]; mainDictionary = @{ kFIRIsMeasurementEnabled : @NO }; - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); expectedAnalyticsOptions = @{ kFIRIsAnalyticsCollectionDeactivated : @YES, kFIRIsAnalyticsCollectionEnabled : @YES, kFIRIsMeasurementEnabled : @NO // override }; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + analyticsOptions = [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); } - (void)testAnalyticsOptions_combinatorial { // Complete combinatorial test. - id mainBundleMock = OCMPartialMock([NSBundle mainBundle]); + // Possible values for the flags in the plist, where NSNull means the flag is not present. NSArray *values = @[ [NSNull null], @NO, @YES ]; @@ -398,6 +397,7 @@ extern NSString *const kFIRLibraryVersionID; if (![optionsMeasurementEnabled isEqual:[NSNull null]]) { optionsDictionary[kFIRIsMeasurementEnabled] = optionsMeasurementEnabled; } + FIROptions *options = [[FIROptions alloc] initInternalWithOptionsDictionary:optionsDictionary]; if (![uniqueOptionsCombinations containsObject:optionsDictionary]) { @@ -415,7 +415,8 @@ extern NSString *const kFIRLibraryVersionID; if (![mainMeasurementEnabled isEqual:[NSNull null]]) { mainDictionary[kFIRIsMeasurementEnabled] = mainMeasurementEnabled; } - OCMExpect([mainBundleMock infoDictionary]).andReturn(mainDictionary); + + // Add mainDictionary to uniqueMainCombinations if it isn't included yet. if (![uniqueMainCombinations containsObject:mainDictionary]) { [uniqueMainCombinations addObject:mainDictionary]; } @@ -427,7 +428,10 @@ extern NSString *const kFIRLibraryVersionID; NSMutableDictionary *expectedAnalyticsOptions = [[NSMutableDictionary alloc] initWithDictionary:optionsDictionary]; [expectedAnalyticsOptions addEntriesFromDictionary:mainDictionary]; - XCTAssertEqualObjects(options.analyticsOptionsDictionary, expectedAnalyticsOptions); + + NSDictionary *analyticsOptions = + [options analyticsOptionsDictionaryWithInfoDictionary:mainDictionary]; + XCTAssertEqualObjects(analyticsOptions, expectedAnalyticsOptions); combinationCount++; } diff --git a/Firebase/Core/FIROptions.m b/Firebase/Core/FIROptions.m index aecb95d..fc92a55 100644 --- a/Firebase/Core/FIROptions.m +++ b/Firebase/Core/FIROptions.m @@ -62,12 +62,19 @@ NSString *const kFIRExceptionBadModification = @property(nonatomic, readwrite) NSMutableDictionary *optionsDictionary; /** - * Combination of analytics options from both the main plist and the GoogleService-Info.plist. + * Calls `analyticsOptionsDictionaryWithInfoDictionary:` using [NSBundle mainBundle].infoDictionary. + * It combines analytics options from both the infoDictionary and the GoogleService-Info.plist. * Values which are present in the main plist override values from the GoogleService-Info.plist. */ @property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary; /** + * Combination of analytics options from both the infoDictionary and the GoogleService-Info.plist. + * Values which are present in the infoDictionary override values from the GoogleService-Info.plist. + */ +- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary; + +/** * Throw exception if editing is locked when attempting to modify an option. */ - (void)checkEditingLocked; @@ -346,16 +353,15 @@ static NSDictionary *sDefaultOptionsDictionary = nil; #pragma mark - Internal instance methods -- (NSDictionary *)analyticsOptionsDictionary { +- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary { dispatch_once(&_createAnalyticsOptionsDictionaryOnce, ^{ NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init]; - NSDictionary *mainInfoDictionary = [NSBundle mainBundle].infoDictionary; NSArray *measurementKeys = @[ kFIRIsMeasurementEnabled, kFIRIsAnalyticsCollectionEnabled, kFIRIsAnalyticsCollectionDeactivated ]; for (NSString *key in measurementKeys) { - id value = mainInfoDictionary[key] ?: self.optionsDictionary[key] ?: nil; + id value = infoDictionary[key] ?: self.optionsDictionary[key] ?: nil; if (!value) { continue; } @@ -366,6 +372,10 @@ static NSDictionary *sDefaultOptionsDictionary = nil; return _analyticsOptionsDictionary; } +- (NSDictionary *)analyticsOptionsDictionary { + return [self analyticsOptionsDictionaryWithInfoDictionary:[NSBundle mainBundle].infoDictionary]; +} + /** * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in * GoogleService-Info.plist. This uses the old plist flag IS_MEASUREMENT_ENABLED, which should still diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 476f196..b6b2966 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -46,6 +46,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, 'Firestore/third_party/Immutable/Tests/**', # Exclude alternate implementations for other platforms + 'Firestore/core/src/firebase/firestore/util/assert_stdio.cc', 'Firestore/core/src/firebase/firestore/util/log_stdio.cc' ] s.public_header_files = 'Firestore/Source/Public/*.h' diff --git a/Firestore/CMakeLists.txt b/Firestore/CMakeLists.txt index 9b90815..499e06c 100644 --- a/Firestore/CMakeLists.txt +++ b/Firestore/CMakeLists.txt @@ -13,9 +13,9 @@ # limitations under the License. cmake_minimum_required(VERSION 2.8.11) -project(firestore) +project(firestore C CXX) -set(FIREBASE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/..") +set(FIREBASE_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) # CMAKE_INSTALL_PREFIX should be passed in to this build so that it can find # outputs of the superbuild. This is handled automatically if run via the @@ -23,39 +23,41 @@ set(FIREBASE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/..") # # If you want to use this project directly in e.g. CLion, make sure you # configure this. -set(FIREBASE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}") +set(FIREBASE_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}) list(INSERT CMAKE_MODULE_PATH 0 ${FIREBASE_SOURCE_DIR}/cmake) include(utils) -find_package(GTest REQUIRED) +# Include GoogleTest directly in the build. +set(gtest_dir ${FIREBASE_INSTALL_DIR}/external/googletest) +add_subdirectory( + ${gtest_dir}/src/googletest + ${gtest_dir}/src/googletest-build + EXCLUDE_FROM_ALL +) + +# Set up aliases with the same names as available via FindGTest. +add_library( + GTest::GTest ALIAS gtest +) + +add_library( + GTest::Main ALIAS gtest_main +) + find_package(LevelDB REQUIRED) +find_package(GRPC REQUIRED) if(APPLE) find_package(FirebaseCore REQUIRED) endif() -# We use C++11 -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +enable_testing() +add_subdirectory(third_party/abseil-cpp) + +include(CompilerSetup) # Fully qualified imports, project wide -include_directories("${FIREBASE_SOURCE_DIR}") - -if(APPLE) - # CMake has no special support for Objective-C as a distinct language but enabling modules and - # other clang extensions would apply even to regular C++ sources which is nonportable. Keep these - # flags separate to avoid misuse. - set( - OBJC_FLAGS - -fobjc-arc - -fmodules - -fno-autolink - -F${FIREBASE_INSTALL_DIR}/Frameworks - ) -endif(APPLE) +include_directories(${FIREBASE_SOURCE_DIR}) -enable_testing() -add_subdirectory(third_party/abseil-cpp EXCLUDE_FROM_ALL) add_subdirectory(core) diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 06f790c..538758d 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 54740A581FC914F000713A1A /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; }; 54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAA1FAA0C320085E60A /* string_util_test.cc */; }; 54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; + 548DB927200D590300E00ABC /* assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB926200D590300E00ABC /* assert_test.cc */; }; 5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; 5491BC731FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; 54C2294F1FECABAE007D065B /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; }; @@ -63,6 +64,7 @@ 6ED54761B845349D43DB6B78 /* Pods_Firestore_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75A6FE51C1A02DF38F62FAAD /* Pods_Firestore_Example.framework */; }; 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; + AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; AB382F7C1FE02A1F007CA955 /* FIRDocumentReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB382F7B1FE02A1F007CA955 /* FIRDocumentReferenceTests.m */; }; AB382F7E1FE03059007CA955 /* FIRFieldPathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB382F7D1FE03059007CA955 /* FIRFieldPathTests.m */; }; AB9945261FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB9945251FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m */; }; @@ -201,6 +203,7 @@ 54740A531FC913E500713A1A /* secure_random_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = secure_random_test.cc; path = ../../core/test/firebase/firestore/util/secure_random_test.cc; sourceTree = "<group>"; }; 54764FAA1FAA0C320085E60A /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_util_test.cc; path = ../../Port/string_util_test.cc; sourceTree = "<group>"; }; 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FSTGoogleTestTests.mm; path = GoogleTest/FSTGoogleTestTests.mm; sourceTree = "<group>"; }; + 548DB926200D590300E00ABC /* assert_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = assert_test.cc; path = ../../core/test/firebase/firestore/util/assert_test.cc; sourceTree = "<group>"; }; 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTIntegrationTestCase.mm; sourceTree = "<group>"; }; 54C2294E1FECABAE007D065B /* log_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = log_test.cc; path = ../../core/test/firebase/firestore/util/log_test.cc; sourceTree = "<group>"; }; 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = collection_spec_test.json; sourceTree = "<group>"; }; @@ -243,6 +246,7 @@ 8E002F4AD5D9B6197C940847 /* Firestore.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Firestore.podspec; path = ../Firestore.podspec; sourceTree = "<group>"; }; 9D52E67EE96AA7E5D6F69748 /* Pods-Firestore_IntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests/Pods-Firestore_IntegrationTests.debug.xcconfig"; sourceTree = "<group>"; }; 9EF477AD4B2B643FD320867A /* Pods-Firestore_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example/Pods-Firestore_Example.debug.xcconfig"; sourceTree = "<group>"; }; + AB356EF6200EA5EB0089B766 /* field_value_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_test.cc; sourceTree = "<group>"; }; AB382F7B1FE02A1F007CA955 /* FIRDocumentReferenceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRDocumentReferenceTests.m; sourceTree = "<group>"; }; AB382F7D1FE03059007CA955 /* FIRFieldPathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRFieldPathTests.m; sourceTree = "<group>"; }; AB9945251FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRCollectionReferenceTests.m; sourceTree = "<group>"; }; @@ -394,6 +398,7 @@ 54740A561FC913EB00713A1A /* util */ = { isa = PBXGroup; children = ( + 548DB926200D590300E00ABC /* assert_test.cc */, 54740A521FC913E500713A1A /* autoid_test.cc */, 54C2294E1FECABAE007D065B /* log_test.cc */, 54740A531FC913E500713A1A /* secure_random_test.cc */, @@ -405,6 +410,7 @@ 54764FAC1FAA0C390085E60A /* GoogleTests */ = { isa = PBXGroup; children = ( + AB356EF5200E9D1A0089B766 /* model */, 54764FAD1FAA0C650085E60A /* Port */, 54740A561FC913EB00713A1A /* util */, 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */, @@ -537,6 +543,15 @@ name = Pods; sourceTree = "<group>"; }; + AB356EF5200E9D1A0089B766 /* model */ = { + isa = PBXGroup; + children = ( + AB356EF6200EA5EB0089B766 /* field_value_test.cc */, + ); + name = model; + path = ../../core/test/firebase/firestore/model; + sourceTree = "<group>"; + }; DE0761E51F2FE611003233AF /* SwiftBuildTest */ = { isa = PBXGroup; children = ( @@ -1217,6 +1232,7 @@ DE51B1D41F0D48CD0013853F /* FSTViewTests.m in Sources */, 54740A581FC914F000713A1A /* autoid_test.cc in Sources */, DE51B1F41F0D491B0013853F /* FSTRemoteEventTests.m in Sources */, + 548DB927200D590300E00ABC /* assert_test.cc in Sources */, 54E928241F33953300C1953E /* FSTEventAccumulator.m in Sources */, DE51B1D11F0D48CD0013853F /* FSTTargetIDGeneratorTests.m in Sources */, 5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */, @@ -1232,6 +1248,7 @@ DE51B1F11F0D49140013853F /* FSTMutationTests.m in Sources */, DE51B1FB1F0D492C0013853F /* FSTMemorySpecTests.m in Sources */, DE51B1DB1F0D490D0013853F /* FSTLevelDBQueryCacheTests.m in Sources */, + AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */, 54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */, 54E9282C1F339CAD00C1953E /* XCTestCase+Await.m in Sources */, AB99452E1FE30AC800DFC1E6 /* FIRFieldValueTests.m in Sources */, diff --git a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m index 27c3dc3..95b9b11 100644 --- a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m +++ b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m @@ -157,6 +157,7 @@ NS_ASSUME_NONNULL_BEGIN FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query targetID:targetID + listenSequenceNumber:10 purpose:FSTQueryPurposeListen snapshotVersion:version resumeToken:resumeToken]; @@ -166,6 +167,7 @@ NS_ASSUME_NONNULL_BEGIN FSTPBTarget *expected = [FSTPBTarget message]; expected.targetId = targetID; + expected.lastListenSequenceNumber = 10; expected.snapshotVersion.nanos = 1039000; expected.resumeToken = [resumeToken copy]; expected.query.parent = queryTarget.parent; diff --git a/Firestore/Example/Tests/Local/FSTQueryCacheTests.m b/Firestore/Example/Tests/Local/FSTQueryCacheTests.m index 0b80bd9..0c6a2a4 100644 --- a/Firestore/Example/Tests/Local/FSTQueryCacheTests.m +++ b/Firestore/Example/Tests/Local/FSTQueryCacheTests.m @@ -31,12 +31,18 @@ NS_ASSUME_NONNULL_BEGIN @implementation FSTQueryCacheTests { FSTQuery *_queryRooms; + FSTListenSequenceNumber _previousSequenceNumber; + FSTTargetID _previousTargetID; + FSTTestSnapshotVersion _previousSnapshotVersion; } - (void)setUp { [super setUp]; _queryRooms = FSTTestQuery(@"rooms"); + _previousSequenceNumber = 1000; + _previousTargetID = 500; + _previousSnapshotVersion = 100; } /** @@ -56,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)testSetAndReadAQuery { if ([self isTestBaseClass]) return; - FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms targetID:1 version:1]; + FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms]; [self addQueryData:queryData]; FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms]; @@ -74,14 +80,14 @@ NS_ASSUME_NONNULL_BEGIN FSTQuery *q2 = [FSTTestQuery(@"a") queryByAddingFilter:FSTTestFilter(@"foo", @"==", @"1")]; XCTAssertEqualObjects(q1.canonicalID, q2.canonicalID); - FSTQueryData *data1 = [self queryDataWithQuery:q1 targetID:1 version:1]; + FSTQueryData *data1 = [self queryDataWithQuery:q1]; [self addQueryData:data1]; // Using the other query should not return the query cache entry despite equal canonicalIDs. XCTAssertNil([self.queryCache queryDataForQuery:q2]); XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1); - FSTQueryData *data2 = [self queryDataWithQuery:q2 targetID:2 version:1]; + FSTQueryData *data2 = [self queryDataWithQuery:q2]; [self addQueryData:data2]; XCTAssertEqualObjects([self.queryCache queryDataForQuery:q1], data1); @@ -99,10 +105,12 @@ NS_ASSUME_NONNULL_BEGIN - (void)testSetQueryToNewValue { if ([self isTestBaseClass]) return; - FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms targetID:1 version:1]; + FSTQueryData *queryData1 = + [self queryDataWithQuery:_queryRooms targetID:1 listenSequenceNumber:10 version:1]; [self addQueryData:queryData1]; - FSTQueryData *queryData2 = [self queryDataWithQuery:_queryRooms targetID:1 version:2]; + FSTQueryData *queryData2 = + [self queryDataWithQuery:_queryRooms targetID:1 listenSequenceNumber:10 version:2]; [self addQueryData:queryData2]; FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms]; @@ -115,7 +123,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)testRemoveQuery { if ([self isTestBaseClass]) return; - FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms targetID:1 version:1]; + FSTQueryData *queryData1 = [self queryDataWithQuery:_queryRooms]; [self addQueryData:queryData1]; [self removeQueryData:queryData1]; @@ -127,7 +135,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)testRemoveNonExistentQuery { if ([self isTestBaseClass]) return; - FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms targetID:1 version:1]; + FSTQueryData *queryData = [self queryDataWithQuery:_queryRooms]; // no-op, but make sure it doesn't throw. XCTAssertNoThrow([self removeQueryData:queryData]); @@ -136,7 +144,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)testRemoveQueryRemovesMatchingKeysToo { if ([self isTestBaseClass]) return; - FSTQueryData *rooms = [self queryDataWithQuery:_queryRooms targetID:1 version:1]; + FSTQueryData *rooms = [self queryDataWithQuery:_queryRooms]; [self addQueryData:rooms]; FSTDocumentKey *key1 = FSTTestDocKey(@"rooms/foo"); @@ -204,14 +212,14 @@ NS_ASSUME_NONNULL_BEGIN [garbageCollector addGarbageSource:self.queryCache]; FSTAssertEqualSets([garbageCollector collectGarbage], @[]); - FSTQueryData *rooms = [self queryDataWithQuery:FSTTestQuery(@"rooms") targetID:1 version:1]; + FSTQueryData *rooms = [self queryDataWithQuery:FSTTestQuery(@"rooms")]; FSTDocumentKey *room1 = FSTTestDocKey(@"rooms/bar"); FSTDocumentKey *room2 = FSTTestDocKey(@"rooms/foo"); [self addQueryData:rooms]; [self addMatchingKey:room1 forTargetID:rooms.targetID]; [self addMatchingKey:room2 forTargetID:rooms.targetID]; - FSTQueryData *halls = [self queryDataWithQuery:FSTTestQuery(@"halls") targetID:2 version:1]; + FSTQueryData *halls = [self queryDataWithQuery:FSTTestQuery(@"halls")]; FSTDocumentKey *hall1 = FSTTestDocKey(@"halls/bar"); FSTDocumentKey *hall2 = FSTTestDocKey(@"halls/foo"); [self addQueryData:halls]; @@ -249,6 +257,46 @@ NS_ASSUME_NONNULL_BEGIN FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:2], (@[ key1, key3 ])); } +- (void)testHighestListenSequenceNumber { + if ([self isTestBaseClass]) return; + + FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"rooms") + targetID:1 + listenSequenceNumber:10 + purpose:FSTQueryPurposeListen]; + [self addQueryData:query1]; + FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"halls") + targetID:2 + listenSequenceNumber:20 + purpose:FSTQueryPurposeListen]; + [self addQueryData:query2]; + XCTAssertEqual([self.queryCache highestListenSequenceNumber], 20); + + // TargetIDs never come down. + [self removeQueryData:query2]; + XCTAssertEqual([self.queryCache highestListenSequenceNumber], 20); + + // A query with an empty result set still counts. + FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"garages") + targetID:42 + listenSequenceNumber:100 + purpose:FSTQueryPurposeListen]; + [self addQueryData:query3]; + XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100); + + [self removeQueryData:query1]; + XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100); + + [self removeQueryData:query3]; + XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100); + + // Verify that the highestTargetID even survives restarts. + [self.queryCache shutdown]; + self.queryCache = [self.persistence queryCache]; + [self.queryCache start]; + XCTAssertEqual([self.queryCache highestListenSequenceNumber], 100); +} + - (void)testHighestTargetID { if ([self isTestBaseClass]) return; @@ -256,6 +304,7 @@ NS_ASSUME_NONNULL_BEGIN FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"rooms") targetID:1 + listenSequenceNumber:10 purpose:FSTQueryPurposeListen]; FSTDocumentKey *key1 = FSTTestDocKey(@"rooms/bar"); FSTDocumentKey *key2 = FSTTestDocKey(@"rooms/foo"); @@ -265,6 +314,7 @@ NS_ASSUME_NONNULL_BEGIN FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"halls") targetID:2 + listenSequenceNumber:20 purpose:FSTQueryPurposeListen]; FSTDocumentKey *key3 = FSTTestDocKey(@"halls/foo"); [self addQueryData:query2]; @@ -278,6 +328,7 @@ NS_ASSUME_NONNULL_BEGIN // A query with an empty result set still counts. FSTQueryData *query3 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"garages") targetID:42 + listenSequenceNumber:100 purpose:FSTQueryPurposeListen]; [self addQueryData:query3]; XCTAssertEqual([self.queryCache highestTargetID], 42); @@ -319,12 +370,21 @@ NS_ASSUME_NONNULL_BEGIN * Creates a new FSTQueryData object from the given parameters, synthesizing a resume token from * the snapshot version. */ +- (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query { + return [self queryDataWithQuery:query + targetID:++_previousTargetID + listenSequenceNumber:++_previousSequenceNumber + version:++_previousSnapshotVersion]; +} + - (FSTQueryData *)queryDataWithQuery:(FSTQuery *)query targetID:(FSTTargetID)targetID + listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber version:(FSTTestSnapshotVersion)version { NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(version); return [[FSTQueryData alloc] initWithQuery:query targetID:targetID + listenSequenceNumber:sequenceNumber purpose:FSTQueryPurposeListen snapshotVersion:FSTTestVersion(version) resumeToken:resumeToken]; diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m index 61847b0..3357078 100644 --- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m +++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m @@ -396,20 +396,25 @@ NS_ASSUME_NONNULL_BEGIN - (void)testEncodesListenRequestLabels { FSTQuery *query = FSTTestQuery(@"collection/key"); - FSTQueryData *queryData = - [[FSTQueryData alloc] initWithQuery:query targetID:2 purpose:FSTQueryPurposeListen]; + FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query + targetID:2 + listenSequenceNumber:3 + purpose:FSTQueryPurposeListen]; NSDictionary<NSString *, NSString *> *result = [self.serializer encodedListenRequestLabelsForQueryData:queryData]; XCTAssertNil(result); - queryData = - [[FSTQueryData alloc] initWithQuery:query targetID:2 purpose:FSTQueryPurposeLimboResolution]; + queryData = [[FSTQueryData alloc] initWithQuery:query + targetID:2 + listenSequenceNumber:3 + purpose:FSTQueryPurposeLimboResolution]; result = [self.serializer encodedListenRequestLabelsForQueryData:queryData]; XCTAssertEqualObjects(result, @{@"goog-listen-tags" : @"limbo-document"}); queryData = [[FSTQueryData alloc] initWithQuery:query targetID:2 + listenSequenceNumber:3 purpose:FSTQueryPurposeExistenceFilterMismatch]; result = [self.serializer encodedListenRequestLabelsForQueryData:queryData]; XCTAssertEqualObjects(result, @{@"goog-listen-tags" : @"existence-filter-mismatch"}); @@ -627,6 +632,7 @@ NS_ASSUME_NONNULL_BEGIN FSTQuery *q = FSTTestQuery(@"docs"); FSTQueryData *model = [[FSTQueryData alloc] initWithQuery:q targetID:1 + listenSequenceNumber:0 purpose:FSTQueryPurposeListen snapshotVersion:[FSTSnapshotVersion noVersion] resumeToken:FSTTestData(1, 2, 3, -1)]; @@ -647,6 +653,7 @@ NS_ASSUME_NONNULL_BEGIN - (FSTQueryData *)queryDataForQuery:(FSTQuery *)query { return [[FSTQueryData alloc] initWithQuery:query targetID:1 + listenSequenceNumber:0 purpose:FSTQueryPurposeListen snapshotVersion:[FSTSnapshotVersion noVersion] resumeToken:[NSData data]]; diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.m b/Firestore/Example/Tests/SpecTests/FSTSpecTests.m index 3abcb48..7fed64a 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.m +++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.m @@ -503,6 +503,7 @@ static NSString *const kNoIOSTag = @"no-ios"; expectedActiveTargets[@(targetID)] = [[FSTQueryData alloc] initWithQuery:query targetID:targetID + listenSequenceNumber:0 purpose:FSTQueryPurposeListen snapshotVersion:[FSTSnapshotVersion noVersion] resumeToken:resumeToken]; diff --git a/Firestore/Source/Core/FSTListenSequence.h b/Firestore/Source/Core/FSTListenSequence.h new file mode 100644 index 0000000..56d0e78 --- /dev/null +++ b/Firestore/Source/Core/FSTListenSequence.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import <Foundation/Foundation.h> + +#import "FSTTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * FSTListenSequence is a monotonic sequence. It is initialized with a minimum value to + * exceed. All subsequent calls to next will return increasing values. + */ +@interface FSTListenSequence : NSObject + +- (instancetype)initStartingAfter:(FSTListenSequenceNumber)after NS_DESIGNATED_INITIALIZER; + +- (id)init NS_UNAVAILABLE; + +- (FSTListenSequenceNumber)next; + +@end + +NS_ASSUME_NONNULL_END
\ No newline at end of file diff --git a/Firestore/Source/Core/FSTListenSequence.m b/Firestore/Source/Core/FSTListenSequence.m new file mode 100644 index 0000000..27ade7c --- /dev/null +++ b/Firestore/Source/Core/FSTListenSequence.m @@ -0,0 +1,34 @@ +#import "FSTListenSequence.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - FSTListenSequence + +@interface FSTListenSequence () { + FSTListenSequenceNumber _previousSequenceNumber; +} + +@end + +@implementation FSTListenSequence + +#pragma mark - Constructors + +- (instancetype)initStartingAfter:(FSTListenSequenceNumber)after { + self = [super init]; + if (self) { + _previousSequenceNumber = after; + } + return self; +} + +#pragma mark - Public methods + +- (FSTListenSequenceNumber)next { + _previousSequenceNumber++; + return _previousSequenceNumber; +} + +@end + +NS_ASSUME_NONNULL_END
\ No newline at end of file diff --git a/Firestore/Source/Core/FSTSyncEngine.m b/Firestore/Source/Core/FSTSyncEngine.m index 27ab73e..f90c5dd 100644 --- a/Firestore/Source/Core/FSTSyncEngine.m +++ b/Firestore/Source/Core/FSTSyncEngine.m @@ -43,6 +43,10 @@ NS_ASSUME_NONNULL_BEGIN +// Limbo documents don't use persistence, and are eagerly GC'd. So, listens for them don't need +// real sequence numbers. +static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1; + #pragma mark - FSTQueryView /** @@ -490,6 +494,7 @@ NS_ASSUME_NONNULL_BEGIN FSTQuery *query = [FSTQuery queryWithPath:key.path]; FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query targetID:limboTargetID + listenSequenceNumber:kIrrelevantSequenceNumber purpose:FSTQueryPurposeLimboResolution]; self.limboKeysByTarget[@(limboTargetID)] = key; [self.remoteStore listenToTargetWithQueryData:queryData]; diff --git a/Firestore/Source/Core/FSTTypes.h b/Firestore/Source/Core/FSTTypes.h index b47bd0b..877ec94 100644 --- a/Firestore/Source/Core/FSTTypes.h +++ b/Firestore/Source/Core/FSTTypes.h @@ -26,6 +26,8 @@ typedef int32_t FSTBatchID; typedef int32_t FSTTargetID; +typedef int64_t FSTListenSequenceNumber; + typedef NSNumber FSTBoxedTargetID; /** diff --git a/Firestore/Source/Local/FSTLevelDBQueryCache.mm b/Firestore/Source/Local/FSTLevelDBQueryCache.mm index 13d15ee..8388b96 100644 --- a/Firestore/Source/Local/FSTLevelDBQueryCache.mm +++ b/Firestore/Source/Local/FSTLevelDBQueryCache.mm @@ -100,6 +100,10 @@ static ReadOptions GetStandardReadOptions() { return self.metadata.highestTargetId; } +- (FSTListenSequenceNumber)highestListenSequenceNumber { + return self.metadata.highestListenSequenceNumber; +} + - (FSTSnapshotVersion *)lastRemoteSnapshotVersion { return _lastRemoteSnapshotVersion; } @@ -116,7 +120,6 @@ static ReadOptions GetStandardReadOptions() { } - (void)addQueryData:(FSTQueryData *)queryData group:(FSTWriteGroup *)group { - // TODO(mcg): actually populate listen sequence number FSTTargetID targetID = queryData.targetID; std::string key = [FSTLevelDBTargetKey keyWithTargetID:targetID]; [group setMessage:[self.serializer encodedQueryData:queryData] forKey:key]; @@ -127,9 +130,19 @@ static ReadOptions GetStandardReadOptions() { std::string emptyBuffer; [group setData:emptyBuffer forKey:indexKey]; + BOOL saveMetadata = NO; FSTPBTargetGlobal *metadata = self.metadata; if (targetID > metadata.highestTargetId) { metadata.highestTargetId = targetID; + saveMetadata = YES; + } + + if (queryData.sequenceNumber > metadata.highestListenSequenceNumber) { + metadata.highestListenSequenceNumber = queryData.sequenceNumber; + saveMetadata = YES; + } + + if (saveMetadata) { [group setMessage:metadata forKey:[FSTLevelDBTargetGlobalKey key]]; } } diff --git a/Firestore/Source/Local/FSTLocalSerializer.m b/Firestore/Source/Local/FSTLocalSerializer.m index c71e9dd..82aec4d 100644 --- a/Firestore/Source/Local/FSTLocalSerializer.m +++ b/Firestore/Source/Local/FSTLocalSerializer.m @@ -156,6 +156,7 @@ FSTPBTarget *proto = [FSTPBTarget message]; proto.targetId = queryData.targetID; + proto.lastListenSequenceNumber = queryData.sequenceNumber; proto.snapshotVersion = [remoteSerializer encodedVersion:queryData.snapshotVersion]; proto.resumeToken = queryData.resumeToken; @@ -173,6 +174,7 @@ FSTSerializerBeta *remoteSerializer = self.remoteSerializer; FSTTargetID targetID = target.targetId; + FSTListenSequenceNumber sequenceNumber = target.lastListenSequenceNumber; FSTSnapshotVersion *version = [remoteSerializer decodedVersion:target.snapshotVersion]; NSData *resumeToken = target.resumeToken; @@ -192,6 +194,7 @@ return [[FSTQueryData alloc] initWithQuery:query targetID:targetID + listenSequenceNumber:sequenceNumber purpose:FSTQueryPurposeListen snapshotVersion:version resumeToken:resumeToken]; diff --git a/Firestore/Source/Local/FSTLocalStore.m b/Firestore/Source/Local/FSTLocalStore.m index cde7104..3a5b0b4 100644 --- a/Firestore/Source/Local/FSTLocalStore.m +++ b/Firestore/Source/Local/FSTLocalStore.m @@ -17,6 +17,7 @@ #import "Firestore/Source/Local/FSTLocalStore.h" #import "Firestore/Source/Auth/FSTUser.h" +#import "Firestore/Source/Core/FSTListenSequence.h" #import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Core/FSTSnapshotVersion.h" #import "Firestore/Source/Core/FSTTargetIDGenerator.h" @@ -76,6 +77,8 @@ NS_ASSUME_NONNULL_BEGIN /** Used to generate targetIDs for queries tracked locally. */ @property(nonatomic, strong) FSTTargetIDGenerator *targetIDGenerator; +@property(nonatomic, strong) FSTListenSequence *listenSequence; + /** * A heldBatchResult is a mutation batch result (from a write acknowledgement) that arrived before * the watch stream got notified of a snapshot that includes the write.  So we "hold" it until @@ -148,6 +151,8 @@ NS_ASSUME_NONNULL_BEGIN FSTTargetID targetID = [self.queryCache highestTargetID]; self.targetIDGenerator = [FSTTargetIDGenerator generatorForLocalStoreStartingAfterID:targetID]; + FSTListenSequenceNumber sequenceNumber = [self.queryCache highestListenSequenceNumber]; + self.listenSequence = [[FSTListenSequence alloc] initStartingAfter:sequenceNumber]; } - (void)shutdown { @@ -380,6 +385,7 @@ NS_ASSUME_NONNULL_BEGIN - (FSTQueryData *)allocateQuery:(FSTQuery *)query { FSTQueryData *cached = [self.queryCache queryDataForQuery:query]; FSTTargetID targetID; + FSTListenSequenceNumber sequenceNumber = [self.listenSequence next]; if (cached) { // This query has been listened to previously, so reuse the previous targetID. // TODO(mcg): freshen last accessed date? @@ -388,8 +394,10 @@ NS_ASSUME_NONNULL_BEGIN FSTWriteGroup *group = [self.persistence startGroupWithAction:@"Allocate query"]; targetID = [self.targetIDGenerator nextID]; - cached = - [[FSTQueryData alloc] initWithQuery:query targetID:targetID purpose:FSTQueryPurposeListen]; + cached = [[FSTQueryData alloc] initWithQuery:query + targetID:targetID + listenSequenceNumber:sequenceNumber + purpose:FSTQueryPurposeListen]; [self.queryCache addQueryData:cached group:group]; [self.persistence commitGroup:group]; diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.m b/Firestore/Source/Local/FSTMemoryQueryCache.m index 8d37bcb..bcab174 100644 --- a/Firestore/Source/Local/FSTMemoryQueryCache.m +++ b/Firestore/Source/Local/FSTMemoryQueryCache.m @@ -34,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN /** The highest numbered target ID encountered. */ @property(nonatomic, assign) FSTTargetID highestTargetID; +@property(nonatomic, assign) FSTListenSequenceNumber highestListenSequenceNumber; + @end @implementation FSTMemoryQueryCache { @@ -65,6 +67,10 @@ NS_ASSUME_NONNULL_BEGIN return _highestTargetID; } +- (FSTListenSequenceNumber)highestListenSequenceNumber { + return _highestListenSequenceNumber; +} + - (FSTSnapshotVersion *)lastRemoteSnapshotVersion { return _lastRemoteSnapshotVersion; } @@ -79,6 +85,9 @@ NS_ASSUME_NONNULL_BEGIN if (queryData.targetID > self.highestTargetID) { self.highestTargetID = queryData.targetID; } + if (queryData.sequenceNumber > self.highestListenSequenceNumber) { + self.highestListenSequenceNumber = queryData.sequenceNumber; + } } - (void)removeQueryData:(FSTQueryData *)queryData group:(__unused FSTWriteGroup *)group { diff --git a/Firestore/Source/Local/FSTQueryCache.h b/Firestore/Source/Local/FSTQueryCache.h index e0cf4c8..88c9df9 100644 --- a/Firestore/Source/Local/FSTQueryCache.h +++ b/Firestore/Source/Local/FSTQueryCache.h @@ -53,6 +53,11 @@ NS_ASSUME_NONNULL_BEGIN - (FSTTargetID)highestTargetID; /** + * Returns the highest listen sequence number of any query seen by the cache. + */ +- (FSTListenSequenceNumber)highestListenSequenceNumber; + +/** * A global snapshot version representing the last consistent snapshot we received from the * backend. This is monotonically increasing and any snapshots received from the backend prior to * this version (e.g. for targets resumed with a resume_token) should be suppressed (buffered) diff --git a/Firestore/Source/Local/FSTQueryData.h b/Firestore/Source/Local/FSTQueryData.h index 048bfad..5db2de6 100644 --- a/Firestore/Source/Local/FSTQueryData.h +++ b/Firestore/Source/Local/FSTQueryData.h @@ -40,6 +40,7 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) { - (instancetype)initWithQuery:(FSTQuery *)query targetID:(FSTTargetID)targetID + listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber purpose:(FSTQueryPurpose)purpose snapshotVersion:(FSTSnapshotVersion *)snapshotVersion resumeToken:(NSData *)resumeToken NS_DESIGNATED_INITIALIZER; @@ -47,6 +48,7 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) { /** Convenience initializer for use when creating an FSTQueryData for the first time. */ - (instancetype)initWithQuery:(FSTQuery *)query targetID:(FSTTargetID)targetID + listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber purpose:(FSTQueryPurpose)purpose; - (instancetype)init NS_UNAVAILABLE; @@ -64,6 +66,8 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) { */ @property(nonatomic, assign, readonly) FSTTargetID targetID; +@property(nonatomic, assign, readonly) FSTListenSequenceNumber sequenceNumber; + /** The purpose of the query. */ @property(nonatomic, assign, readonly) FSTQueryPurpose purpose; diff --git a/Firestore/Source/Local/FSTQueryData.m b/Firestore/Source/Local/FSTQueryData.m index 080f136..6bb716a 100644 --- a/Firestore/Source/Local/FSTQueryData.m +++ b/Firestore/Source/Local/FSTQueryData.m @@ -25,6 +25,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithQuery:(FSTQuery *)query targetID:(FSTTargetID)targetID + listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber purpose:(FSTQueryPurpose)purpose snapshotVersion:(FSTSnapshotVersion *)snapshotVersion resumeToken:(NSData *)resumeToken { @@ -32,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN if (self) { _query = query; _targetID = targetID; + _sequenceNumber = sequenceNumber; _purpose = purpose; _snapshotVersion = snapshotVersion; _resumeToken = [resumeToken copy]; @@ -41,9 +43,11 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithQuery:(FSTQuery *)query targetID:(FSTTargetID)targetID + listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber purpose:(FSTQueryPurpose)purpose { return [self initWithQuery:query targetID:targetID + listenSequenceNumber:sequenceNumber purpose:purpose snapshotVersion:[FSTSnapshotVersion noVersion] resumeToken:[NSData data]]; @@ -83,6 +87,7 @@ NS_ASSUME_NONNULL_BEGIN resumeToken:(NSData *)resumeToken { return [[FSTQueryData alloc] initWithQuery:self.query targetID:self.targetID + listenSequenceNumber:self.sequenceNumber purpose:self.purpose snapshotVersion:snapshotVersion resumeToken:resumeToken]; diff --git a/Firestore/Source/Remote/FSTRemoteStore.m b/Firestore/Source/Remote/FSTRemoteStore.m index a0c5059..1201049 100644 --- a/Firestore/Source/Remote/FSTRemoteStore.m +++ b/Firestore/Source/Remote/FSTRemoteStore.m @@ -468,8 +468,10 @@ static const int kOnlineAttemptsBeforeFailure = 2; [remoteEvent handleExistenceFilterMismatchForTargetID:target]; // Clear the resume token for the query, since we're in a known mismatch state. - queryData = - [[FSTQueryData alloc] initWithQuery:query targetID:targetID purpose:queryData.purpose]; + queryData = [[FSTQueryData alloc] initWithQuery:query + targetID:targetID + listenSequenceNumber:queryData.sequenceNumber + purpose:queryData.purpose]; self.listenTargets[target] = queryData; // Cause a hard reset by unwatching and rewatching immediately, but deliberately don't @@ -483,6 +485,7 @@ static const int kOnlineAttemptsBeforeFailure = 2; FSTQueryData *requestQueryData = [[FSTQueryData alloc] initWithQuery:query targetID:targetID + listenSequenceNumber:queryData.sequenceNumber purpose:FSTQueryPurposeExistenceFilterMismatch]; [self sendWatchRequestWithQueryData:requestQueryData]; } diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index c49b6db..da08dfc 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -12,5 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +add_subdirectory(src/firebase/firestore/model) +add_subdirectory(src/firebase/firestore/remote) add_subdirectory(src/firebase/firestore/util) + +add_subdirectory(test/firebase/firestore/model) +add_subdirectory(test/firebase/firestore/remote) add_subdirectory(test/firebase/firestore/util) diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt new file mode 100644 index 0000000..ae80de3 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + firebase_firestore_model + SOURCES + field_value.cc + DEPENDS + firebase_firestore_util +) diff --git a/Firestore/core/src/firebase/firestore/model/field_value.cc b/Firestore/core/src/firebase/firestore/model/field_value.cc new file mode 100644 index 0000000..45886bf --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/field_value.cc @@ -0,0 +1,177 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/field_value.h" + +#include <algorithm> +#include <memory> +#include <utility> +#include <vector> + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { +namespace model { + +using Type = FieldValue::Type; + +namespace { +/** + * This deviates from the other platforms that define TypeOrder. Since + * we already define Type for union types, we use it together with this + * function to achive the equivalent order of types i.e. + * i) if two types are comparable, then they are of equal order; + * ii) otherwise, their order is the same as the order of their Type. + */ +bool Comparable(Type lhs, Type rhs) { + switch (lhs) { + case Type::Long: + case Type::Double: + return rhs == Type::Long || rhs == Type::Double; + case Type::Timestamp: + case Type::ServerTimestamp: + return rhs == Type::Timestamp || rhs == Type::ServerTimestamp; + default: + return lhs == rhs; + } +} + +} // namespace + +FieldValue::FieldValue(const FieldValue& value) { + *this = value; +} + +FieldValue::FieldValue(FieldValue&& value) { + *this = std::move(value); +} + +FieldValue::~FieldValue() { + SwitchTo(Type::Null); +} + +FieldValue& FieldValue::operator=(const FieldValue& value) { + SwitchTo(value.tag_); + switch (tag_) { + case Type::Null: + break; + case Type::Boolean: + boolean_value_ = value.boolean_value_; + break; + case Type::Array: { + // copy-and-swap + std::vector<const FieldValue> tmp = value.array_value_; + std::swap(array_value_, tmp); + break; + } + default: + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + false, lhs.type(), "Unsupported type %d", value.type()); + } + return *this; +} + +FieldValue& FieldValue::operator=(FieldValue&& value) { + switch (value.tag_) { + case Type::Array: + SwitchTo(Type::Array); + std::swap(array_value_, value.array_value_); + return *this; + default: + // We just copy over POD union types. + return *this = value; + } +} + +const FieldValue& FieldValue::NullValue() { + static const FieldValue kNullInstance; + return kNullInstance; +} + +const FieldValue& FieldValue::TrueValue() { + static const FieldValue kTrueInstance(true); + return kTrueInstance; +} + +const FieldValue& FieldValue::FalseValue() { + static const FieldValue kFalseInstance(false); + return kFalseInstance; +} + +const FieldValue& FieldValue::BooleanValue(bool value) { + return value ? TrueValue() : FalseValue(); +} + +FieldValue FieldValue::ArrayValue(const std::vector<const FieldValue>& value) { + std::vector<const FieldValue> copy(value); + return ArrayValue(std::move(copy)); +} + +FieldValue FieldValue::ArrayValue(std::vector<const FieldValue>&& value) { + FieldValue result; + result.SwitchTo(Type::Array); + std::swap(result.array_value_, value); + return result; +} + +bool operator<(const FieldValue& lhs, const FieldValue& rhs) { + if (!Comparable(lhs.type(), rhs.type())) { + return lhs.type() < rhs.type(); + } + + switch (lhs.type()) { + case Type::Null: + return false; + case Type::Boolean: + // lhs < rhs iff lhs == false and rhs == true. + return !lhs.boolean_value_ && rhs.boolean_value_; + case Type::Array: + return lhs.array_value_ < rhs.array_value_; + default: + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + false, lhs.type(), "Unsupported type %d", lhs.type()); + // return false if assertion does not abort the program. We will say + // each unsupported type takes only one value thus everything is equal. + return false; + } +} + +void FieldValue::SwitchTo(const Type type) { + if (tag_ == type) { + return; + } + // Not same type. Destruct old type first and then initialize new type. + // Must call destructor explicitly for any non-POD type. + switch (tag_) { + case Type::Array: + array_value_.~vector(); + break; + default: {} // The other types where there is nothing to worry about. + } + tag_ = type; + // Must call constructor explicitly for any non-POD type to initialize. + switch (tag_) { + case Type::Array: + new (&array_value_) std::vector<const FieldValue>(); + break; + default: {} // The other types where there is nothing to worry about. + } +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/field_value.h b/Firestore/core/src/firebase/firestore/model/field_value.h new file mode 100644 index 0000000..781e34f --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/field_value.h @@ -0,0 +1,130 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_ + +#include <memory> +#include <vector> + +namespace firebase { +namespace firestore { +namespace model { + +/** + * tagged-union class representing an immutable data value as stored in + * Firestore. FieldValue represents all the different kinds of values + * that can be stored in fields in a document. + */ +class FieldValue { + public: + /** + * All the different kinds of values that can be stored in fields in + * a document. The types of the same comparison order should be defined + * together as a group. The order of each group is defined by the Firestore + * backend and is available at: + * https://firebase.google.com/docs/firestore/manage-data/data-types + */ + enum class Type { + Null, // Null + Boolean, // Boolean + Long, // Number type starts here + Double, + Timestamp, // Timestamp type starts here + ServerTimestamp, + String, // String + Blob, // Blob + Reference, // Reference + GeoPoint, // GeoPoint + Array, // Array + Object, // Object + // New enum should not always been added at the tail. Add it to the correct + // position instead, see the doc comment above. + }; + + FieldValue() : tag_(Type::Null) { + } + + // Do not inline these ctor/dtor below, which contain call to non-trivial + // operator=. + FieldValue(const FieldValue& value); + FieldValue(FieldValue&& value); + + ~FieldValue(); + + FieldValue& operator=(const FieldValue& value); + FieldValue& operator=(FieldValue&& value); + + /** Returns the true type for this value. */ + Type type() const { + return tag_; + } + + /** factory methods. */ + static const FieldValue& NullValue(); + static const FieldValue& TrueValue(); + static const FieldValue& FalseValue(); + static const FieldValue& BooleanValue(bool value); + static FieldValue ArrayValue(const std::vector<const FieldValue>& value); + static FieldValue ArrayValue(std::vector<const FieldValue>&& value); + + friend bool operator<(const FieldValue& lhs, const FieldValue& rhs); + + private: + explicit FieldValue(bool value) : tag_(Type::Boolean), boolean_value_(value) { + } + + /** + * Switch to the specified type, if different from the current type. + */ + void SwitchTo(const Type type); + + Type tag_; + union { + // There is no null type as tag_ alone is enough for Null FieldValue. + bool boolean_value_; + std::vector<const FieldValue> array_value_; + }; +}; + +/** Compares against another FieldValue. */ +bool operator<(const FieldValue& lhs, const FieldValue& rhs); + +inline bool operator>(const FieldValue& lhs, const FieldValue& rhs) { + return rhs < lhs; +} + +inline bool operator>=(const FieldValue& lhs, const FieldValue& rhs) { + return !(lhs < rhs); +} + +inline bool operator<=(const FieldValue& lhs, const FieldValue& rhs) { + return !(lhs > rhs); +} + +inline bool operator!=(const FieldValue& lhs, const FieldValue& rhs) { + return lhs < rhs || lhs > rhs; +} + +inline bool operator==(const FieldValue& lhs, const FieldValue& rhs) { + return !(lhs != rhs); +} + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_VALUE_H_ diff --git a/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt b/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt new file mode 100644 index 0000000..43320ce --- /dev/null +++ b/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + firebase_firestore_remote + SOURCES + datastore.h + datastore.cc + DEPENDS + grpc::grpc +) diff --git a/Firestore/core/src/firebase/firestore/remote/datastore.cc b/Firestore/core/src/firebase/firestore/remote/datastore.cc new file mode 100644 index 0000000..f8a8988 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/remote/datastore.cc @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/remote/datastore.h" + +namespace firebase { +namespace firestore { +namespace remote { + +Datastore::Datastore() { +} + +Datastore::~Datastore() { +} + +} // namespace remote +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/remote/datastore.h b/Firestore/core/src/firebase/firestore/remote/datastore.h new file mode 100644 index 0000000..807b75f --- /dev/null +++ b/Firestore/core/src/firebase/firestore/remote/datastore.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_DATASTORE_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_DATASTORE_H_ + +namespace firebase { +namespace firestore { +namespace remote { + +class Datastore { + public: + Datastore(); + ~Datastore(); + + Datastore(const Datastore& other) = delete; + Datastore(Datastore&& other) = delete; + + Datastore& operator=(const Datastore& other) = delete; + Datastore& operator=(Datastore&& other) = delete; +}; + +} // namespace remote +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_DATASTORE_H_ diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt index 3028a95..7283942 100644 --- a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt @@ -16,69 +16,61 @@ # libraries in here are an implementation detail of making this a # mutli-platform build. -add_library( +cc_library( firebase_firestore_util_base - secure_random_arc4random.cc - string_printf.cc -) -target_link_libraries( - firebase_firestore_util_base - PUBLIC - absl_base + SOURCES + secure_random.h + secure_random_arc4random.cc + string_printf.cc + string_printf.h + DEPENDS + absl_base ) -# stdio-dependent bits can be built and tested everywhere -add_library( - firebase_firestore_util_stdio - assert_stdio.cc - log_stdio.cc -) -target_link_libraries( +## assert and log + +cc_library( firebase_firestore_util_stdio - PUBLIC - firebase_firestore_util_base + SOURCES + assert_stdio.cc + log_stdio.cc + DEPENDS + firebase_firestore_util_base + absl_base + EXCLUDE_FROM_ALL ) -# apple-dependent bits can only built and tested on apple plaforms -if(APPLE) - add_library( - firebase_firestore_util_apple +cc_library( + firebase_firestore_util_apple + SOURCES assert_apple.mm log_apple.mm - ) - target_compile_options( - firebase_firestore_util_apple - PRIVATE - ${OBJC_FLAGS} - ) - target_link_libraries( - firebase_firestore_util_apple - PUBLIC + string_apple.h + DEPENDS FirebaseCore - ) -endif(APPLE) - -add_library( - firebase_firestore_util - autoid.cc + EXCLUDE_FROM_ALL ) # Export a dependency on the correct logging library for this platform. All # buildable libraries are built and tested but only the best fit is exported. if(APPLE) - target_link_libraries( - firebase_firestore_util - PUBLIC - firebase_firestore_util_apple - firebase_firestore_util_base - ) + list(APPEND UTIL_DEPENDS firebase_firestore_util_apple) +else() + list(APPEND UTIL_DEPENDS firebase_firestore_util_stdio) +endif() -else(NOT APPLE) - target_link_libraries( - firebase_firestore_util - PUBLIC - firebase_firestore_util_stdio - firebase_firestore_util_base - ) -endif(APPLE) +## main library + +cc_library( + firebase_firestore_util + SOURCES + autoid.cc + autoid.h + firebase_assert.h + log.h + DEPENDS + ${UTIL_DEPENDS} + firebase_firestore_util_base + absl_base +) diff --git a/Firestore/core/src/firebase/firestore/util/assert_stdio.cc b/Firestore/core/src/firebase/firestore/util/assert_stdio.cc index 5476e65..f0d5251 100644 --- a/Firestore/core/src/firebase/firestore/util/assert_stdio.cc +++ b/Firestore/core/src/firebase/firestore/util/assert_stdio.cc @@ -14,16 +14,14 @@ * limitations under the License. */ -#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" - #include <stdarg.h> #include <exception> #include <string> -#include <absl/base/config.h> - +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" #include "Firestore/core/src/firebase/firestore/util/string_printf.h" +#include "absl/base/config.h" namespace firebase { namespace firestore { diff --git a/Firestore/core/src/firebase/firestore/util/firebase_assert.h b/Firestore/core/src/firebase/firestore/util/firebase_assert.h index da01864..cff550a 100644 --- a/Firestore/core/src/firebase/firestore/util/firebase_assert.h +++ b/Firestore/core/src/firebase/firestore/util/firebase_assert.h @@ -52,7 +52,7 @@ #else #define FIREBASE_DEV_ASSERT_WITH_EXPRESSION(condition, expression) \ FIREBASE_ASSERT_WITH_EXPRESSION(condition, expression) -#endif // !defined(NDEBUG) +#endif // defined(NDEBUG) // Custom assert() implementation that is not compiled out in release builds. #define FIREBASE_ASSERT(expression) \ @@ -85,7 +85,7 @@ #define FIREBASE_DEV_ASSERT_MESSAGE_WITH_EXPRESSION(condition, expression, \ ...) \ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(condition, expression, __VA_ARGS__) -#endif // !defined(NDEBUG) +#endif // defined(NDEBUG) namespace firebase { namespace firestore { diff --git a/Firestore/core/src/firebase/firestore/util/string_printf.cc b/Firestore/core/src/firebase/firestore/util/string_printf.cc index 60cc564..9c4e31c 100644 --- a/Firestore/core/src/firebase/firestore/util/string_printf.cc +++ b/Firestore/core/src/firebase/firestore/util/string_printf.cc @@ -38,7 +38,7 @@ void StringAppendV(std::string* dst, const char* format, va_list ap) { if (result < kSpaceLength) { if (result >= 0) { // Normal case -- everything fit. - dst->append(space, result); + dst->append(space, static_cast<size_t>(result)); return; } @@ -49,28 +49,29 @@ void StringAppendV(std::string* dst, const char* format, va_list ap) { result = vsnprintf(nullptr, 0, format, backup_ap); va_end(backup_ap); #endif + } - if (result < 0) { - // Just an error. - return; - } + if (result < 0) { + // Just an error. + return; } + size_t result_size = static_cast<size_t>(result); // Increase the buffer size to the size requested by vsnprintf, // plus one for the closing \0. size_t initial_size = dst->size(); - size_t target_size = initial_size + result; + size_t target_size = initial_size + result_size; dst->resize(target_size + 1); char* buf = &(*dst)[initial_size]; - int buf_remain = result + 1; + size_t buf_remain = result_size + 1; // Restore the va_list before we use it again va_copy(backup_ap, ap); result = vsnprintf(buf, buf_remain, format, backup_ap); va_end(backup_ap); - if (result >= 0 && result < buf_remain) { + if (result >= 0 && static_cast<size_t>(result) < buf_remain) { // It fit and vsnprintf copied in directly. Resize down one to // remove the trailing \0. dst->resize(target_size); diff --git a/Firestore/core/src/firebase/firestore/util/string_printf.h b/Firestore/core/src/firebase/firestore/util/string_printf.h index d15296e..10dfae9 100644 --- a/Firestore/core/src/firebase/firestore/util/string_printf.h +++ b/Firestore/core/src/firebase/firestore/util/string_printf.h @@ -21,7 +21,7 @@ #include <string> -#include <absl/base/attributes.h> +#include "absl/base/attributes.h" namespace firebase { namespace firestore { @@ -44,4 +44,4 @@ void StringAppendV(std::string* dst, const char* format, va_list ap); } // namespace firestore } // namespace firebase -#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_FORMAT_H_ +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_STRING_PRINTF_H_ diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt new file mode 100644 index 0000000..31fe040 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_test( + firebase_firestore_model_test + SOURCES + field_value_test.cc + DEPENDS + firebase_firestore_model +) diff --git a/Firestore/core/test/firebase/firestore/model/field_value_test.cc b/Firestore/core/test/firebase/firestore/model/field_value_test.cc new file mode 100644 index 0000000..1194e63 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/model/field_value_test.cc @@ -0,0 +1,164 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/field_value.h" + +#include <vector> + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { + +using Type = FieldValue::Type; + +TEST(FieldValue, NullType) { + const FieldValue value = FieldValue::NullValue(); + EXPECT_EQ(Type::Null, value.type()); + EXPECT_FALSE(value < value); +} + +TEST(FieldValue, BooleanType) { + const FieldValue true_value = FieldValue::BooleanValue(true); + const FieldValue false_value = FieldValue::BooleanValue(false); + EXPECT_EQ(Type::Boolean, true_value.type()); + EXPECT_FALSE(true_value < true_value); + EXPECT_FALSE(true_value < false_value); + EXPECT_FALSE(false_value < false_value); + EXPECT_TRUE(false_value < true_value); +} + +TEST(FieldValue, ArrayType) { + const FieldValue empty = + FieldValue::ArrayValue(std::vector<const FieldValue>{}); + std::vector<const FieldValue> array{FieldValue::NullValue(), + FieldValue::BooleanValue(true), + FieldValue::BooleanValue(false)}; + // copy the array + const FieldValue small = FieldValue::ArrayValue(array); + std::vector<const FieldValue> another_array{FieldValue::BooleanValue(true), + FieldValue::BooleanValue(false)}; + // move the array + const FieldValue large = FieldValue::ArrayValue(std::move(another_array)); + EXPECT_EQ(Type::Array, empty.type()); + EXPECT_EQ(Type::Array, small.type()); + EXPECT_EQ(Type::Array, large.type()); + EXPECT_TRUE(empty < small); + EXPECT_FALSE(small < empty); + EXPECT_FALSE(small < small); + EXPECT_TRUE(small < large); + EXPECT_FALSE(large < small); +} + +TEST(FieldValue, Copy) { + FieldValue clone = FieldValue::TrueValue(); + const FieldValue null_value = FieldValue::NullValue(); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + EXPECT_EQ(FieldValue::NullValue(), null_value); + clone = clone; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue true_value = FieldValue::TrueValue(); + clone = true_value; + EXPECT_EQ(FieldValue::TrueValue(), clone); + EXPECT_EQ(FieldValue::TrueValue(), true_value); + clone = clone; + EXPECT_EQ(FieldValue::TrueValue(), clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); + + const FieldValue array_value = + FieldValue::ArrayValue(std::vector<const FieldValue>{ + FieldValue::TrueValue(), FieldValue::FalseValue()}); + clone = array_value; + EXPECT_EQ(FieldValue::ArrayValue(std::vector<const FieldValue>{ + FieldValue::TrueValue(), FieldValue::FalseValue()}), + clone); + EXPECT_EQ(FieldValue::ArrayValue(std::vector<const FieldValue>{ + FieldValue::TrueValue(), FieldValue::FalseValue()}), + array_value); + clone = clone; + EXPECT_EQ(FieldValue::ArrayValue(std::vector<const FieldValue>{ + FieldValue::TrueValue(), FieldValue::FalseValue()}), + clone); + clone = null_value; + EXPECT_EQ(FieldValue::NullValue(), clone); +} + +TEST(FieldValue, Move) { + FieldValue clone = FieldValue::TrueValue(); + + FieldValue null_value = FieldValue::NullValue(); + clone = std::move(null_value); + EXPECT_EQ(FieldValue::NullValue(), clone); + + FieldValue true_value = FieldValue::TrueValue(); + clone = std::move(true_value); + EXPECT_EQ(FieldValue::TrueValue(), clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); + + FieldValue array_value = FieldValue::ArrayValue(std::vector<const FieldValue>{ + FieldValue::TrueValue(), FieldValue::FalseValue()}); + clone = std::move(array_value); + EXPECT_EQ(FieldValue::ArrayValue(std::vector<const FieldValue>{ + FieldValue::TrueValue(), FieldValue::FalseValue()}), + clone); + clone = FieldValue::NullValue(); + EXPECT_EQ(FieldValue::NullValue(), clone); +} + +TEST(FieldValue, CompareMixedType) { + const FieldValue null_value = FieldValue::NullValue(); + const FieldValue true_value = FieldValue::TrueValue(); + const FieldValue array_value = + FieldValue::ArrayValue(std::vector<const FieldValue>()); + EXPECT_TRUE(null_value < true_value); + EXPECT_TRUE(true_value < array_value); +} + +TEST(FieldValue, CompareWithOperator) { + const FieldValue small = FieldValue::NullValue(); + const FieldValue large = FieldValue::TrueValue(); + + EXPECT_TRUE(small < large); + EXPECT_FALSE(small < small); + EXPECT_FALSE(large < small); + + EXPECT_TRUE(large > small); + EXPECT_FALSE(small > small); + EXPECT_FALSE(small > large); + + EXPECT_TRUE(large >= small); + EXPECT_TRUE(small >= small); + EXPECT_FALSE(small >= large); + + EXPECT_TRUE(small <= large); + EXPECT_TRUE(small <= small); + EXPECT_FALSE(large <= small); + + EXPECT_TRUE(small != large); + EXPECT_FALSE(small != small); + + EXPECT_TRUE(small == small); + EXPECT_FALSE(small == large); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/remote/CMakeLists.txt b/Firestore/core/test/firebase/firestore/remote/CMakeLists.txt new file mode 100644 index 0000000..7d99e6f --- /dev/null +++ b/Firestore/core/test/firebase/firestore/remote/CMakeLists.txt @@ -0,0 +1,21 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_test( + firebase_firestore_remote_test + SOURCES + datastore_test.cc + DEPENDS + firebase_firestore_remote +) diff --git a/Firestore/core/test/firebase/firestore/remote/datastore_test.cc b/Firestore/core/test/firebase/firestore/remote/datastore_test.cc new file mode 100644 index 0000000..46a19cc --- /dev/null +++ b/Firestore/core/test/firebase/firestore/remote/datastore_test.cc @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/remote/datastore.h" + +#include <grpc/grpc.h> +#include <gtest/gtest.h> + +TEST(Datastore, CanLinkToGrpc) { + // This test doesn't actually do anything interesting as far as actually + // using gRPC is concerned but that it can run at all is proof that all the + // libraries required for gRPC to work are actually linked correctly into the + // test. + grpc_init(); +} diff --git a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt index e51bb51..7f0539c 100644 --- a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt @@ -14,33 +14,30 @@ cc_test( firebase_firestore_util_test - autoid_test.cc - secure_random_test.cc - string_printf_test.cc -) -target_link_libraries( - firebase_firestore_util_test - firebase_firestore_util + SOURCES + autoid_test.cc + secure_random_test.cc + string_printf_test.cc + DEPENDS + firebase_firestore_util ) if(APPLE) cc_test( firebase_firestore_util_apple_test - assert_test.cc - log_test.cc - ) - target_link_libraries( - firebase_firestore_util_apple_test - firebase_firestore_util_apple + SOURCES + assert_test.cc + log_test.cc + DEPENDS + firebase_firestore_util_apple ) endif(APPLE) cc_test( firebase_firestore_util_stdio_test - assert_test.cc - log_test.cc -) -target_link_libraries( - firebase_firestore_util_stdio_test - firebase_firestore_util_stdio + SOURCES + assert_test.cc + log_test.cc + DEPENDS + firebase_firestore_util_stdio ) diff --git a/Firestore/core/test/firebase/firestore/util/assert_test.cc b/Firestore/core/test/firebase/firestore/util/assert_test.cc index 7c49462..fb15e61 100644 --- a/Firestore/core/test/firebase/firestore/util/assert_test.cc +++ b/Firestore/core/test/firebase/firestore/util/assert_test.cc @@ -14,10 +14,9 @@ * limitations under the License. */ -#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" - #include <exception> +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" #include "gtest/gtest.h" namespace firebase { diff --git a/Firestore/core/test/firebase/firestore/util/autoid_test.cc b/Firestore/core/test/firebase/firestore/util/autoid_test.cc index e55a8e9..079b990 100644 --- a/Firestore/core/test/firebase/firestore/util/autoid_test.cc +++ b/Firestore/core/test/firebase/firestore/util/autoid_test.cc @@ -25,8 +25,8 @@ using firebase::firestore::util::CreateAutoId; TEST(AutoId, IsSane) { for (int i = 0; i < 50; i++) { std::string auto_id = CreateAutoId(); - EXPECT_EQ(20, auto_id.length()); - for (int pos = 0; pos < 20; pos++) { + EXPECT_EQ(20u, auto_id.length()); + for (size_t pos = 0; pos < 20; pos++) { char c = auto_id[pos]; EXPECT_TRUE(isalpha(c) || isdigit(c)) << "Should be printable ascii character: '" << c << "' in \"" diff --git a/Firestore/core/test/firebase/firestore/util/string_printf_test.cc b/Firestore/core/test/firebase/firestore/util/string_printf_test.cc index 76f7cde..085be84 100644 --- a/Firestore/core/test/firebase/firestore/util/string_printf_test.cc +++ b/Firestore/core/test/firebase/firestore/util/string_printf_test.cc @@ -64,7 +64,7 @@ TEST(StringPrintf, DontOverwriteErrno) { TEST(StringPrintf, LargeBuf) { // Check that the large buffer is handled correctly. - int n = 2048; + size_t n = 2048; char* buf = new char[n + 1]; memset(buf, ' ', n); buf[n] = 0; diff --git a/cmake/CompilerSetup.cmake b/cmake/CompilerSetup.cmake new file mode 100644 index 0000000..8bcfa76 --- /dev/null +++ b/cmake/CompilerSetup.cmake @@ -0,0 +1,92 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# C++ Compiler setup + +# We use C++11 +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CLANG ON) +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(GNU ON) +endif() + +if(CLANG OR GNU) + set( + common_flags + -Wall -Wextra -Wconversion -Werror + + # Be super pedantic about format strings + -Wformat + + # Avoid use of uninitialized values + -Wuninitialized + -fno-common + + # Delete unused things + -Wunused-function -Wunused-value -Wunused-variable + + # Cut down on symbol clutter + # TODO(wilhuff) try -fvisibility=hidden + -fvisibility-inlines-hidden + ) + + set( + c_flags + -Wstrict-prototypes + ) + + if(CLANG) + list( + APPEND common_flags + -Wconditional-uninitialized -Werror=return-type -Winfinite-recursion -Wmove + -Wrange-loop-analysis -Wunreachable-code + ) + endif() + + foreach(flag ${common_flags} ${c_flags}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}") + endforeach() + + foreach(flag ${common_flags}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}") + endforeach() +endif() + +if(APPLE) + # CMake has no special support for Objective-C as a distinct language but + # enabling modules and other clang extensions would apply even to regular C++ + # sources which is nonportable. Keep these flags separate to avoid misuse. + set( + OBJC_FLAGS + -Werror=deprecated-objc-isa-usage + -Werror=non-modular-include-in-framework-module + -Werror=objc-root-class + + -Wblock-capture-autoreleasing + -Wimplicit-atomic-properties + -Wnon-modular-include-in-framework-module + + -fobjc-arc + -fmodules + -fno-autolink + + -F${FIREBASE_INSTALL_DIR}/Frameworks + ) +endif(APPLE) diff --git a/cmake/ExternalProjectFlags.cmake b/cmake/ExternalProjectFlags.cmake new file mode 100644 index 0000000..ed4db2c --- /dev/null +++ b/cmake/ExternalProjectFlags.cmake @@ -0,0 +1,71 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(CMakeParseArguments) + +# Assemble the git-related arguments to an external project making use of the +# latest features where available but avoiding them when run under CMake +# versions that don't support them. +# +# The complete set of git-related arguments are stored as a list in the +# variable named by RESULT_VAR in the calling scope. +# +# Currently this handles: +# * GIT_SUBMODULES -- added on CMake 3.0 or later. Earlier CMakes will +# check out all submodules. +# * GIT_SHALLOW -- added by default on CMake 3.6 or later. Disable by passing +# GIT_SHALLOW OFF +# * GIT_PROGRESS -- added by default on CMake 3.8 or later. Disable by +# passing GIT_PROGRESS OFF +function(ExternalProject_GitSource RESULT_VAR) + # Parse arguments + set(options "") + set(single_value GIT_REPOSITORY GIT_TAG GIT_PROGRESS GIT_SHALLOW) + set(multi_value GIT_SUBMODULES) + cmake_parse_arguments(EP "${options}" "${single_value}" "${multi_value}" ${ARGN}) + + set( + result + GIT_REPOSITORY ${EP_GIT_REPOSITORY} + GIT_TAG ${EP_GIT_TAG} + ${EP_UNPARSED_ARGUMENTS} + ) + + # CMake 3.0 added support for constraining the set of submodules to clone + if(NOT (CMAKE_VERSION VERSION_LESS "3.0") AND EP_GIT_SUBMODULES) + list(APPEND result GIT_SUBMODULES ${EP_GIT_SUBMODULES}) + endif() + + # CMake 3.6 added support for shallow git clones. Use a shallow clone if + # available + if(NOT (CMAKE_VERSION VERSION_LESS "3.6")) + if(NOT EP_GIT_SHALLOW) + set(EP_GIT_SHALLOW ON) + endif() + + list(APPEND result GIT_SHALLOW ${EP_GIT_SHALLOW}) + endif() + + # CMake 3.8 added support for showing progress for large downloads + if(NOT (CMAKE_VERSION VERSION_LESS "3.8")) + if(NOT EP_GIT_PROGRESS) + set(EP_GIT_PROGRESS ON) + endif() + + list(APPEND result GIT_PROGRESS ${EP_GIT_PROGRESS}) + endif() + + set(${RESULT_VAR} ${result} PARENT_SCOPE) + +endfunction() diff --git a/cmake/FindGRPC.cmake b/cmake/FindGRPC.cmake new file mode 100644 index 0000000..f594b9e --- /dev/null +++ b/cmake/FindGRPC.cmake @@ -0,0 +1,142 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(FindPackageHandleStandardArgs) +include(FindZLIB) + +set(BINARY_DIR ${FIREBASE_INSTALL_DIR}/external/grpc) + +## ZLIB + +# the grpc ExternalProject already figures out if zlib should be built or +# referenced from its installed location. If it elected to allow grpc to build +# zlib then it will be available at this location. +find_library( + ZLIB_LIBRARY + NAMES z + HINTS ${BINARY_DIR}/src/grpc-build/third_party/zlib +) + +# If found above, the standard package will honor the ZLIB_LIBRARY variable. +find_package(ZLIB REQUIRED) + + +## BoringSSL/OpenSSL + +find_path( + OPENSSL_INCLUDE_DIR openssl/ssl.h + HINTS ${BINARY_DIR}/src/grpc/third_party/boringssl/include +) + +find_library( + OPENSSL_SSL_LIBRARY + NAMES ssl + HINTS ${BINARY_DIR}/src/grpc-build/third_party/boringssl/ssl +) + +find_library( + OPENSSL_CRYPTO_LIBRARY + NAMES crypto + HINTS ${BINARY_DIR}/src/grpc-build/third_party/boringssl/crypto +) + +find_package(OpenSSL REQUIRED) + + +## C-Ares + +find_library( + CARES_LIBRARY + NAMES cares + HINTS ${BINARY_DIR}/src/grpc-build/third_party/cares/cares/lib +) +if(NOT (CARES_LIBRARY STREQUAL "CARES_LIBRARY-NOTFOUND")) + if (NOT TARGET c-ares::ares) + add_library(c-ares::ares UNKNOWN IMPORTED) + set_target_properties( + c-ares::ares PROPERTIES + IMPORTED_LOCATION ${CARES_LIBRARY} + ) + endif() +endif() + + +## GRPC + +find_path( + GRPC_INCLUDE_DIR grpc/grpc.h + HINTS + $ENV{GRPC_ROOT}/include + ${GRPC_ROOT}/include + ${BINARY_DIR}/src/grpc/include +) + +find_library( + GPR_LIBRARY + NAMES gpr + HINTS + $ENV{GRPC_ROOT}/lib + ${GRPC_ROOT}/lib + ${BINARY_DIR}/src/grpc-build +) + +find_library( + GRPC_LIBRARY + NAMES grpc + HINTS + $ENV{GRPC_ROOT}/lib + ${GRPC_ROOT}/lib + ${BINARY_DIR}/src/grpc-build +) + +find_package_handle_standard_args( + gRPC + DEFAULT_MSG + GRPC_INCLUDE_DIR + GRPC_LIBRARY + GPR_LIBRARY +) + +if(GRPC_FOUND) + set(GRPC_INCLUDE_DIRS ${GRPC_INCLUDE_DIR}) + set(GRPC_LIBRARIES ${GRPC_LIBRARY}) + + if (NOT TARGET grpc::gpr) + add_library(grpc::gpr UNKNOWN IMPORTED) + set_target_properties( + grpc::gpr PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} + IMPORTED_LOCATION ${GPR_LIBRARY} + ) + endif() + + if (NOT TARGET grpc::grpc) + set( + GRPC_LINK_LIBRARIES + c-ares::ares + grpc::gpr + OpenSSL::SSL + OpenSSL::Crypto + ZLIB::ZLIB + ) + + add_library(grpc::grpc UNKNOWN IMPORTED) + set_target_properties( + grpc::grpc PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR} + INTERFACE_LINK_LIBRARIES "${GRPC_LINK_LIBRARIES}" + IMPORTED_LOCATION ${GRPC_LIBRARY} + ) + endif() +endif(GRPC_FOUND) diff --git a/cmake/FindLevelDB.cmake b/cmake/FindLevelDB.cmake index 386a298..b664fa8 100644 --- a/cmake/FindLevelDB.cmake +++ b/cmake/FindLevelDB.cmake @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -set(binary_dir ${FIREBASE_INSTALL_DIR}/third_party/leveldb/src/leveldb) +set(binary_dir ${FIREBASE_INSTALL_DIR}/external/leveldb/src/leveldb) find_path( LEVELDB_INCLUDE_DIR leveldb/db.h diff --git a/cmake/external/firestore.cmake b/cmake/external/firestore.cmake index 1abb629..1a89435 100644 --- a/cmake/external/firestore.cmake +++ b/cmake/external/firestore.cmake @@ -14,27 +14,26 @@ include(ExternalProject) -set(source_dir ${PROJECT_SOURCE_DIR}/Firestore) -set(binary_dir ${PROJECT_BINARY_DIR}/Firestore) - ExternalProject_Add( Firestore - DEPENDS FirebaseCore googletest leveldb + DEPENDS + FirebaseCore + googletest + leveldb + grpc # Lay the binary directory out as if this were a subproject. This makes it # possible to build and test in it directly. - PREFIX ${binary_dir} - SOURCE_DIR ${source_dir} - BINARY_DIR ${binary_dir} + PREFIX ${PROJECT_BINARY_DIR}/external/Firestore + SOURCE_DIR ${PROJECT_SOURCE_DIR}/Firestore + BINARY_DIR ${PROJECT_BINARY_DIR}/Firestore + + CMAKE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX:PATH=${FIREBASE_INSTALL_DIR} + BUILD_ALWAYS ON - # Even though this isn't installed, set up the INSTALL_DIR so that - # find_package can find dependencies built from source. - INSTALL_DIR ${FIREBASE_INSTALL_DIR} INSTALL_COMMAND "" TEST_BEFORE_INSTALL ON - - CMAKE_ARGS - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} - -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ) diff --git a/cmake/external/googletest.cmake b/cmake/external/googletest.cmake index 128f849..56a5f13 100644 --- a/cmake/external/googletest.cmake +++ b/cmake/external/googletest.cmake @@ -13,21 +13,26 @@ # limitations under the License. include(ExternalProject) +include(ExternalProjectFlags) -ExternalProject_Add( - googletest - +ExternalProject_GitSource( + GOOGLETEST_GIT GIT_REPOSITORY "https://github.com/google/googletest.git" GIT_TAG "release-1.8.0" +) - PREFIX ${PROJECT_BINARY_DIR}/third_party/googletest +ExternalProject_Add( + googletest + DEPENDS + FirebaseCore # for sequencing - INSTALL_DIR ${FIREBASE_INSTALL_DIR} + ${GOOGLETEST_GIT} - TEST_COMMAND "" + PREFIX ${PROJECT_BINARY_DIR}/external/googletest - CMAKE_ARGS - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} - -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> - -DBUILD_SHARED_LIBS:BOOL=OFF + # Just download the sources without building. + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" ) diff --git a/cmake/external/grpc.cmake b/cmake/external/grpc.cmake new file mode 100644 index 0000000..fb54960 --- /dev/null +++ b/cmake/external/grpc.cmake @@ -0,0 +1,83 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(ExternalProject) +include(ExternalProjectFlags) +include(FindZLIB) + +if(GRPC_ROOT) + # If the user has supplied a GRPC_ROOT then just use it. Add an empty custom + # target so that the superbuild dependencies still work. + add_custom_target(grpc) + +else() + set( + GIT_SUBMODULES + third_party/boringssl + third_party/cares/cares + ) + + set( + CMAKE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DgRPC_BUILD_TESTS:BOOL=OFF + -DBUILD_SHARED_LIBS:BOOL=OFF + ) + + # zlib can be built by grpc but we can avoid it on platforms that provide it + # by default. + find_package(ZLIB) + if(ZLIB_FOUND) + list( + APPEND CMAKE_ARGS + -DgRPC_ZLIB_PROVIDER:STRING=package + -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} + -DZLIB_LIBRARY=${ZLIB_LIBRARY} + ) + + else() + list( + APPEND GIT_SUBMODULES + third_party/zlib + ) + + endif(ZLIB_FOUND) + + ExternalProject_GitSource( + GRPC_GIT + GIT_REPOSITORY "https://github.com/grpc/grpc.git" + GIT_TAG "v1.8.3" + GIT_SUBMODULES ${GIT_SUBMODULES} + ) + + ExternalProject_Add( + grpc + DEPENDS + leveldb # for sequencing + + ${GRPC_GIT} + + PREFIX ${PROJECT_BINARY_DIR}/external/grpc + + CMAKE_ARGS ${CMAKE_ARGS} + + BUILD_COMMAND + ${CMAKE_COMMAND} --build . --target grpc + + TEST_COMMAND "" + INSTALL_COMMAND "" + ) + +endif(GRPC_ROOT) + diff --git a/cmake/external/leveldb.cmake b/cmake/external/leveldb.cmake index b3b3fe1..5b2068a 100644 --- a/cmake/external/leveldb.cmake +++ b/cmake/external/leveldb.cmake @@ -13,6 +13,7 @@ # limitations under the License. include(ExternalProject) +include(ExternalProjectFlags) if(WIN32 OR LEVELDB_ROOT) # If the user has supplied a LEVELDB_ROOT then just use it. Add an empty @@ -41,23 +42,38 @@ else() $<$<CONFIG:Release>:${CMAKE_CXX_FLAGS_RELEASE}>" ) + ExternalProject_GitSource( + LEVELDB_GIT + GIT_REPOSITORY "https://github.com/google/leveldb.git" + GIT_TAG "v1.20" + ) + ExternalProject_Add( leveldb + DEPENDS + googletest # for sequencing - GIT_REPOSITORY "https://github.com/google/leveldb.git" - GIT_TAG "v1.20" + ${LEVELDB_GIT} - PREFIX ${PROJECT_BINARY_DIR}/third_party/leveldb + PREFIX ${PROJECT_BINARY_DIR}/external/leveldb + # LevelDB's configuration is done in the Makefile CONFIGURE_COMMAND "" - BUILD_ALWAYS ON + + # The Makefile-based build of leveldb does not support building + # out-of-source. BUILD_IN_SOURCE ON + + # Only build the leveldb library skipping the tools and in-memory + # implementation we don't use. BUILD_COMMAND - env CXXFLAGS=${LEVELDB_CXX_FLAGS} OPT=${LEVELDB_OPT} make -j all + env CXXFLAGS=${LEVELDB_CXX_FLAGS} OPT=${LEVELDB_OPT} + make -j out-static/libleveldb.a INSTALL_DIR ${FIREBASE_INSTALL_DIR} INSTALL_COMMAND "" TEST_COMMAND "" ) + endif(WIN32 OR LEVELDB_ROOT) diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 54044d6..1c3cbd6 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -12,16 +12,80 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Defines a new test executable and does all the things we want done with -# tests: +include(CMakeParseArguments) + +# cc_library( +# target +# SOURCES sources... +# DEPENDS libraries... +# ) +# +# Defines a new library target with the given target name, sources, and dependencies. +function(cc_library name) + set(flag EXCLUDE_FROM_ALL) + set(multi DEPENDS SOURCES) + cmake_parse_arguments(ccl "${flag}" "" "${multi}" ${ARGN}) + + add_library( + ${name} + ${ccl_SOURCES} + ) + add_objc_flags(${name} ccl) + target_link_libraries( + ${name} + PUBLIC + ${ccl_DEPENDS} + ) + + if(ccl_EXCLUDE_FROM_ALL) + set_property( + TARGET ${name} + PROPERTY EXCLUDE_FROM_ALL ON + ) + endif() + +endfunction() + +# cc_test( +# target +# SOURCES sources... +# DEPENDS libraries... +# ) # -# * add_executable (with the given arguments) -# * add_Test - defines a test with the same name -# * declares that the test links against gtest -# * adds the executable as a dependency of the `check` target. +# Defines a new test executable target with the given target name, sources, and +# dependencies. Implicitly adds DEPENDS on GTest::GTest and GTest::Main. function(cc_test name) - add_executable(${name} ${ARGN}) + set(multi DEPENDS SOURCES) + cmake_parse_arguments(cct "" "" "${multi}" ${ARGN}) + + list(APPEND cct_DEPENDS GTest::GTest GTest::Main) + + add_executable(${name} ${cct_SOURCES}) + add_objc_flags(${name} cct) add_test(${name} ${name}) - target_link_libraries(${name} GTest::GTest GTest::Main) + target_link_libraries(${name} ${cct_DEPENDS}) +endfunction() + +# add_objc_flags(target sources...) +# +# Adds OBJC_FLAGS to the compile options of the given target if any of the +# sources have filenames that indicate they are are Objective-C. +function(add_objc_flags target) + set(_has_objc OFF) + + foreach(source ${ARGN}) + get_filename_component(ext ${source} EXT) + if((ext STREQUAL ".m") OR (ext STREQUAL ".mm")) + set(_has_objc ON) + endif() + endforeach() + + if(_has_objc) + target_compile_options( + ${target} + PRIVATE + ${OBJC_FLAGS} + ) + endif() endfunction() diff --git a/cmake/xcodebuild.cmake b/cmake/xcodebuild.cmake index 8312f6d..01a2961 100644 --- a/cmake/xcodebuild.cmake +++ b/cmake/xcodebuild.cmake @@ -33,7 +33,7 @@ function(xcodebuild framework) set(options "") set(single_value SCHEME WORKSPACE) set(multi_value DEPENDS) - cmake_parse_arguments(xcb "${options}" "${single_value}" "${multi_value}") + cmake_parse_arguments(xcb "${options}" "${single_value}" "${multi_value}" ${ARGN}) if(NOT xcb_WORKSPACE) set(xcb_WORKSPACE ${PROJECT_SOURCE_DIR}/Example/Firebase.xcworkspace) @@ -46,8 +46,6 @@ function(xcodebuild framework) set(destination "platform=macOS,arch=x86_64") set(scheme "${framework}-${platform}") - set(binary_dir ${PROJECT_BINARY_DIR}/${scheme}) - # CMake has a variety of release types, but Xcode has just one by default. if(CMAKE_BUILD_TYPE STREQUAL Debug) set(configuration Debug) @@ -65,11 +63,11 @@ function(xcodebuild framework) ${framework} DEPENDS ${xcb_DEPENDS} - PREFIX ${binary_dir} + PREFIX ${PROJECT_BINARY_DIR}/external/${framework} # The source directory doesn't actually matter SOURCE_DIR ${PROJECT_SOURCE_DIR} - BINARY_DIR ${binary_dir} + BINARY_DIR ${PROJECT_BINARY_DIR}/Frameworks CONFIGURE_COMMAND "" @@ -79,7 +77,7 @@ function(xcodebuild framework) -scheme ${scheme} -configuration ${configuration} -destination ${destination} - CONFIGURATION_BUILD_DIR=${FIREBASE_INSTALL_DIR}/Frameworks + CONFIGURATION_BUILD_DIR=<BINARY_DIR> build ${pipe_xcpretty} BUILD_ALWAYS ${BUILD_PODS} @@ -87,4 +85,5 @@ function(xcodebuild framework) INSTALL_COMMAND "" TEST_COMMAND "" ) + endfunction() diff --git a/scripts/style.sh b/scripts/style.sh index 72f7520..96255f9 100755 --- a/scripts/style.sh +++ b/scripts/style.sh @@ -30,16 +30,17 @@ fi if [[ $# -gt 0 && "$1" = "test-only" ]]; then test_only=true options="-output-replacements-xml" + shift else test_only=false options="-i" fi ( - if [[ "$test_only" = false && $# -gt 0 ]]; then + if [[ $# -gt 0 ]]; then if git rev-parse "$1" -- >& /dev/null; then # Argument was a branch name show files changed since that branch - git diff --name-only --relative "$1" + git diff --name-only --relative --diff-filter=ACMR "$1" else # Otherwise assume the passed things are files or directories find "$@" -type f @@ -57,6 +58,9 @@ fi \%/third_party/% d \%/Firestore/Port/% d +# Sources pulled in by travis bundler +\%/vendor/bundle/% d + # Sources within the tree that are not subject to formatting \%^./(Example|Firebase)/(Auth|AuthSamples|Database|Messaging)/% d @@ -65,9 +69,11 @@ fi # Format C-ish sources only \%\.(h|m|mm|cc)$% p -' | xargs clang-format -style=file $options | grep "<replacement " > /dev/null +' | xargs clang-format -style=file $options \ + | grep "<replacement " > /dev/null if [[ "$test_only" = true && $? -ne 1 ]]; then - echo "Proposed commit is not style compliant. Run scripts/style.sh and git add the result." + echo "Proposed commit is not style compliant." + echo "Run scripts/style.sh and git add the result." exit 1 fi @@ -70,6 +70,3 @@ if [ $RESULT != 0 ]; then exit $RESULT; fi test_tvOS; RESULT=$? if [ $RESULT != 0 ]; then exit $RESULT; fi - -# Also test Firestore -Firestore/test.sh |