Quellcode durchsuchen

Stop using built-in CMake Framework support (#5316)

* Fix previously unknown warnings

These become visible once flags are handled uniformly across all
Firestore targets in an upcoming build change.

* Stop using built-in CMake Framework support

CMake has built-in support for building Framework bundles (briefly:
bundles of libraries, associated headers, and resources) but this
doesn't work well for our needs.

1. In CMake 3.5.1 (our earliest supported CMake version) it doesn't copy
headers properly at build time.

2. Even up to CMake 3.15.5, CMake doesn't handle link-line multiplicity
correctly. CMake passes the static library location within the framework
and this seems to cause the linker to mishandle Objective-C metaclass
information resulting in duplicate symbols. Building with `-framework`
flags works correctly.

Previously we partially worked around the latter problem by building
some frameworks as shared instead of static frameworks, but this is
problematic because this mode of operation isn't actually intended and
is unsupported as far as the rest of Firebase is concerned. This turned
out to be fragile though and while refactoring libraries this workaround
broke.

Rather than piling on additional workarounds, this change converts the
frameworks to simple library targets and emulates framework imports
through include paths.

Additionally, this starts refactoring our cmake helper functions to make
them more like built-in functions. This makes it possible to freely use
different CMake features that were made difficult by trying to wrap
everything up in a single CMake command.

* style.sh generated changes

* Review feedback

* More review feedback
Gil vor 6 Jahren
Ursprung
Commit
77f92cd2c6

+ 20 - 19
FirebaseCore/CMakeLists.txt

@@ -16,28 +16,29 @@ if(NOT APPLE)
   return()
 endif()
 
-file(GLOB sources Sources/*.m)
 file(GLOB headers Sources/Private/*.h Sources/Public/*.h)
+file(GLOB sources Sources/*.m)
 
 podspec_version(version ${PROJECT_SOURCE_DIR}/FirebaseCore.podspec)
-firebase_version(firebase_ios_version ${PROJECT_SOURCE_DIR}/FirebaseCore.podspec)
-
-firebase_ios_objc_framework(
-  FirebaseCore
-  SOURCES ${sources}
-  HEADERS ${headers}
-  VERSION ${version}
-  DEFINES
-    FIRCore_VERSION=${version}
-    Firebase_VERSION=${firebase_ios_version}
-  INCLUDES
-    ${PROJECT_SOURCE_DIR}
-  DEPENDS
-    FirebaseCoreDiagnosticsInterop
-    GoogleUtilities
-    "-framework Foundation"
-  DISABLE_STRICT_WARNINGS
-  EXCLUDE_FROM_ALL
+firebase_version(firebase_version ${PROJECT_SOURCE_DIR}/FirebaseCore.podspec)
+
+firebase_ios_add_framework(
+  FirebaseCore DISABLE_STRICT_WARNINGS EXCLUDE_FROM_ALL ${headers} ${sources}
+)
+
+firebase_ios_framework_public_headers(FirebaseCore ${headers})
+
+target_compile_definitions(
+  FirebaseCore PRIVATE
+  FIRCore_VERSION=${version}
+  Firebase_VERSION=${firebase_version}
+)
+
+target_link_libraries(
+  FirebaseCore PRIVATE
+  "-framework Foundation"
+  FirebaseCoreDiagnosticsInterop
+  GoogleUtilities
 )
 
 if(IOS)

+ 2 - 2
Firestore/Example/Benchmarks/remote_document_cache_benchmark.mm

@@ -89,7 +89,7 @@ FIRQuerySnapshot* GetDocumentsFromServer(FIRQuery* query) {
 
 void WaitForPendingWrites(FIRFirestore* db) {
   dispatch_semaphore_t done = dispatch_semaphore_create(0);
-  [db waitForPendingWritesWithCompletion:^(NSError* error) {
+  [db waitForPendingWritesWithCompletion:^(NSError*) {
     dispatch_semaphore_signal(done);
   }];
   dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);
@@ -107,7 +107,7 @@ void WriteDocs(FIRCollectionReference* collection, int64_t count, bool match) {
 
 void Shutdown(FIRFirestore* db) {
   dispatch_semaphore_t done = dispatch_semaphore_create(0);
-  [db terminateWithCompletion:^(NSError* error) {
+  [db terminateWithCompletion:^(NSError*) {
     dispatch_semaphore_signal(done);
   }];
   dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);

+ 13 - 14
Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.mm

@@ -31,14 +31,14 @@
   FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
 
   __block int callbacks = 0;
-  id<FIRListenerRegistration> one = [collectionRef
-      addSnapshotListener:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) {
+  id<FIRListenerRegistration> one =
+      [collectionRef addSnapshotListener:^(FIRQuerySnapshot *, NSError *_Nullable error) {
         XCTAssertNil(error);
         callbacks++;
       }];
 
-  id<FIRListenerRegistration> two = [collectionRef
-      addSnapshotListener:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) {
+  id<FIRListenerRegistration> two =
+      [collectionRef addSnapshotListener:^(FIRQuerySnapshot *, NSError *_Nullable error) {
         XCTAssertNil(error);
         callbacks++;
       }];
@@ -68,12 +68,11 @@
   FIRCollectionReference *collectionRef = [self collectionRef];
   FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
 
-  id<FIRListenerRegistration> one = [collectionRef
-      addSnapshotListener:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error){
-      }];
-  id<FIRListenerRegistration> two = [docRef
-      addSnapshotListener:^(FIRDocumentSnapshot *_Nullable snapshot, NSError *_Nullable error){
+  id<FIRListenerRegistration> one =
+      [collectionRef addSnapshotListener:^(FIRQuerySnapshot *, NSError *){
       }];
+  id<FIRListenerRegistration> two = [docRef addSnapshotListener:^(FIRDocumentSnapshot *, NSError *){
+  }];
 
   [one remove];
   [one remove];
@@ -88,14 +87,14 @@
 
   __block int callbacksOne = 0;
   __block int callbacksTwo = 0;
-  id<FIRListenerRegistration> one = [collectionRef
-      addSnapshotListener:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) {
+  id<FIRListenerRegistration> one =
+      [collectionRef addSnapshotListener:^(FIRQuerySnapshot *, NSError *_Nullable error) {
         XCTAssertNil(error);
         callbacksOne++;
       }];
 
-  id<FIRListenerRegistration> two = [collectionRef
-      addSnapshotListener:^(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error) {
+  id<FIRListenerRegistration> two =
+      [collectionRef addSnapshotListener:^(FIRQuerySnapshot *, NSError *_Nullable error) {
         XCTAssertNil(error);
         callbacksTwo++;
       }];
@@ -137,7 +136,7 @@
   @autoreleasepool {
     FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
     documentID = docRef.documentID;
-    registration = [docRef addSnapshotListener:^(FIRDocumentSnapshot *snapshot, NSError *error) {
+    registration = [docRef addSnapshotListener:^(FIRDocumentSnapshot *snapshot, NSError *) {
       if (snapshot.exists) {
         [seen fulfill];
       }

+ 4 - 4
Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm

@@ -137,11 +137,11 @@
 - (void)runTransactionBlock:(void (^)(FIRTransaction *transaction))transactionBlock {
   XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
   [_docRef.firestore
-      runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
+      runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **) {
         transactionBlock(transaction);
         return nil;
       }
-      completion:^(id result, NSError *error) {
+      completion:^(id, NSError *error) {
         XCTAssertNil(error);
         [expectation fulfill];
       }];
@@ -298,11 +298,11 @@
 - (void)testServerTimestampsFailViaTransactionUpdateOnNonexistentDocument {
   XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
   [_docRef.firestore
-      runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
+      runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **) {
         [transaction updateData:self->_updateData forDocument:self->_docRef];
         return nil;
       }
-      completion:^(id result, NSError *error) {
+      completion:^(id, NSError *error) {
         XCTAssertNotNil(error);
         XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
         XCTAssertEqual(error.code, FIRFirestoreErrorCodeNotFound);

+ 2 - 2
Firestore/Example/Tests/Integration/FSTSmokeTests.mm

@@ -42,7 +42,7 @@
 }
 
 - (void)testObservesExistingDocument {
-  [self readerAndWriterOnDocumentRef:^(NSString *path, FIRDocumentReference *readerRef,
+  [self readerAndWriterOnDocumentRef:^(NSString *, FIRDocumentReference *readerRef,
                                        FIRDocumentReference *writerRef) {
     NSDictionary<NSString *, id> *data = [self chatMessage];
     [self writeDocumentRef:writerRef data:data];
@@ -59,7 +59,7 @@
 }
 
 - (void)testObservesNewDocument {
-  [self readerAndWriterOnDocumentRef:^(NSString *path, FIRDocumentReference *readerRef,
+  [self readerAndWriterOnDocumentRef:^(NSString *, FIRDocumentReference *readerRef,
                                        FIRDocumentReference *writerRef) {
     id<FIRListenerRegistration> listenerRegistration =
         [readerRef addSnapshotListener:self.eventAccumulator.valueEventHandler];

+ 1 - 1
Firestore/Example/Tests/Util/FSTEventAccumulator.mm

@@ -97,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN
 }
 
 - (void (^)(id _Nullable, NSError *_Nullable))valueEventHandler {
-  return ^void(id _Nullable value, NSError *_Nullable error) {
+  return ^void(id _Nullable value, NSError *) {
     // We can't store nil in the _events array, but these are still interesting to tests so store
     // NSNull instead.
     id event = value ? value : [NSNull null];

+ 1 - 1
Firestore/Example/Tests/Util/FSTHelpers.mm

@@ -142,7 +142,7 @@ PatchMutation FSTTestPatchMutation(const absl::string_view path,
 
   __block ObjectValue objectValue = ObjectValue::Empty();
   __block std::set<FieldPath> fieldMaskPaths;
-  [values enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
+  [values enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *) {
     const FieldPath path = testutil::Field(util::MakeString(key));
     fieldMaskPaths.insert(path);
     if (![value isEqual:kDeleteSentinel]) {

+ 2 - 0
Firestore/Source/API/FIRCollectionReference.mm

@@ -54,6 +54,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 // Override the designated initializer from the super class.
 - (instancetype)initWithQuery:(api::Query &&)query {
+  (void)query;
+
   HARD_FAIL("Use FIRCollectionReference initWithPath: initializer.");
 }
 

+ 1 - 0
Firestore/Source/API/FIRFieldPath.mm

@@ -74,6 +74,7 @@ NS_ASSUME_NONNULL_BEGIN
 }
 
 - (id)copyWithZone:(NSZone *__nullable)zone {
+  (void)zone;
   return [[[self class] alloc] initPrivate:_internalValue];
 }
 

+ 1 - 1
Firestore/Source/API/FIRFirestore.mm

@@ -350,7 +350,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 - (id<FIRListenerRegistration>)addSnapshotsInSyncListener:(void (^)(void))listener {
   std::unique_ptr<core::EventListener<Empty>> eventListener =
-      core::EventListener<Empty>::Create([listener](const StatusOr<Empty> &v) { listener(); });
+      core::EventListener<Empty>::Create([listener](const StatusOr<Empty> &) { listener(); });
   std::unique_ptr<ListenerRegistration> result =
       _firestore->AddSnapshotsInSyncListener(std::move(eventListener));
   return [[FSTListenerRegistration alloc] initWithRegistration:std::move(result)];

+ 2 - 0
Firestore/Source/API/FIRFirestoreSettings.mm

@@ -79,6 +79,8 @@ ABSL_CONST_INIT extern "C" const int64_t kFIRFirestoreCacheSizeUnlimited =
 }
 
 - (id)copyWithZone:(nullable NSZone *)zone {
+  (void)zone;
+
   FIRFirestoreSettings *copy = [[FIRFirestoreSettings alloc] init];
   copy.host = _host;
   copy.sslEnabled = _sslEnabled;

+ 1 - 0
Firestore/Source/API/FIRGeoPoint.mm

@@ -72,6 +72,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 /** Implements NSCopying without actually copying because geopoints are immutable. */
 - (id)copyWithZone:(NSZone *_Nullable)zone {
+  (void)zone;
   return self;
 }
 

+ 3 - 4
Firestore/Source/API/FIRQuery.mm

@@ -358,10 +358,9 @@ int32_t SaturatedLimitValue(NSInteger limit) {
     return [self queryFilteredUsingComparisonPredicate:predicate];
   } else if ([predicate isKindOfClass:[NSCompoundPredicate class]]) {
     return [self queryFilteredUsingCompoundPredicate:predicate];
-  } else if ([predicate isKindOfClass:[[NSPredicate
-                                          predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
-                                            return true;
-                                          }] class]]) {
+  } else if ([predicate isKindOfClass:[[NSPredicate predicateWithBlock:^BOOL(id, NSDictionary *) {
+                          return true;
+                        }] class]]) {
     ThrowInvalidArgument("Invalid query. Block-based predicates are not supported. Please use "
                          "predicateWithFormat to create predicates instead.");
   } else {

+ 1 - 0
Firestore/Source/API/FIRTimestamp.m

@@ -116,6 +116,7 @@ static const int kNanosPerSecond = 1000000000;
 
 /** Implements NSCopying without actually copying because timestamps are immutable. */
 - (id)copyWithZone:(NSZone *_Nullable)zone {
+  (void)zone;
   return self;
 }
 

+ 2 - 0
Firestore/Source/API/FSTFirestoreComponent.mm

@@ -129,6 +129,8 @@ NS_ASSUME_NONNULL_BEGIN
 #pragma mark - FIRComponentLifecycleMaintainer
 
 - (void)appWillBeDeleted:(FIRApp *)app {
+  (void)app;
+
   NSDictionary<NSString *, FIRFirestore *> *instances;
   @synchronized(_instances) {
     instances = [_instances copy];

+ 3 - 3
Firestore/Source/API/FSTUserDataConverter.mm

@@ -196,7 +196,7 @@ NS_ASSUME_NONNULL_BEGIN
   __block ParseContext context = accumulator.RootContext();
   __block ObjectValue updateData = ObjectValue::Empty();
 
-  [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
+  [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *) {
     FieldPath path;
 
     if ([key isKindOfClass:[NSString class]]) {
@@ -295,7 +295,7 @@ NS_ASSUME_NONNULL_BEGIN
   } else {
     __block ObjectValue result = ObjectValue::Empty();
 
-    [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
+    [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *) {
       absl::optional<FieldValue> parsedValue =
           [self parseData:value context:context.ChildContext(util::MakeString(key))];
       if (parsedValue) {
@@ -312,7 +312,7 @@ NS_ASSUME_NONNULL_BEGIN
   __block FieldValue::Array result;
   result.reserve(array.count);
 
-  [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *stop) {
+  [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *) {
     absl::optional<FieldValue> parsedEntry = [self parseData:entry
                                                      context:context.ChildContext(idx)];
     if (!parsedEntry) {

+ 19 - 82
Firestore/Source/CMakeLists.txt

@@ -12,86 +12,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-if(APPLE)
-  podspec_version(
-    firestore_version
-    ${PROJECT_SOURCE_DIR}/FirebaseFirestore.podspec
-  )
-
-  firebase_ios_objc_framework(
-    FirebaseFirestore
-    SHARED
-    HEADERS
-      Public/FirebaseFirestore.h
-      Public/FIRCollectionReference.h
-      Public/FIRDocumentChange.h
-      Public/FIRDocumentReference.h
-      Public/FIRDocumentSnapshot.h
-      Public/FIRFieldPath.h
-      Public/FIRFieldValue.h
-      Public/FIRFirestore.h
-      Public/FIRFirestoreErrors.h
-      Public/FIRFirestoreSettings.h
-      Public/FIRFirestoreSource.h
-      Public/FIRGeoPoint.h
-      Public/FIRListenerRegistration.h
-      Public/FIRQuery.h
-      Public/FIRQuerySnapshot.h
-      Public/FIRSnapshotMetadata.h
-      Public/FIRTimestamp.h
-      Public/FIRTransaction.h
-      Public/FIRWriteBatch.h
-    INCLUDES
-      Public
-    SOURCES
-      API/FIRCollectionReference+Internal.h
-      API/FIRCollectionReference.mm
-      API/FIRDocumentChange+Internal.h
-      API/FIRDocumentChange.mm
-      API/FIRDocumentReference+Internal.h
-      API/FIRDocumentReference.mm
-      API/FIRDocumentSnapshot+Internal.h
-      API/FIRDocumentSnapshot.mm
-      API/FIRFieldPath+Internal.h
-      API/FIRFieldPath.mm
-      API/FIRFieldValue+Internal.h
-      API/FIRFieldValue.mm
-      API/FIRFirestore+Internal.h
-      API/FIRFirestore.mm
-      API/FIRFirestoreSettings+Internal.h
-      API/FIRFirestoreSettings.mm
-      API/FIRFirestoreSource+Internal.h
-      API/FIRFirestoreSource.mm
-      API/FIRFirestoreVersion.h
-      API/FIRFirestoreVersion.mm
-      API/FIRGeoPoint+Internal.h
-      API/FIRGeoPoint.mm
-      API/FIRListenerRegistration+Internal.h
-      API/FIRListenerRegistration.mm
-      API/FIRQuery+Internal.h
-      API/FIRQuery.mm
-      API/FIRQuerySnapshot+Internal.h
-      API/FIRQuerySnapshot.mm
-      API/FIRSnapshotMetadata+Internal.h
-      API/FIRSnapshotMetadata.mm
-      API/FIRTimestamp.m
-      API/FIRTimestamp+Internal.h
-      API/FIRTransaction+Internal.h
-      API/FIRTransaction.mm
-      API/FIRWriteBatch+Internal.h
-      API/FIRWriteBatch.mm
-      API/FSTFirestoreComponent.h
-      API/FSTFirestoreComponent.mm
-      API/FSTUserDataConverter.h
-      API/FSTUserDataConverter.mm
-      API/converters.h
-      API/converters.mm
-    DEPENDS
-      absl_any
-      absl_strings
-      firebase_firestore_api
-      firebase_firestore_core_transaction
-      firebase_firestore_remote_datastore
-      firebase_firestore_version
-  )
+if(NOT APPLE)
+  return()
 endif()
+
+file(GLOB headers Public/*.h)
+file(GLOB sources API/*.h API/*.m API/*.mm)
+
+firebase_ios_add_framework(FirebaseFirestore ${headers} ${sources})
+firebase_ios_framework_public_headers(FirebaseFirestore ${headers})
+
+target_link_libraries(
+  FirebaseFirestore PRIVATE
+  FirebaseAuthInterop
+  FirebaseCore
+  absl_strings
+  firebase_firestore_api
+  firebase_firestore_core_transaction
+  firebase_firestore_remote_datastore
+  firebase_firestore_version
+)

+ 9 - 14
GoogleUtilities/CMakeLists.txt

@@ -31,19 +31,14 @@ file(
   Logger/Public/*.h
 )
 
-podspec_version(version ${PROJECT_SOURCE_DIR}/GoogleUtilities.podspec)
+firebase_ios_add_framework(
+  GoogleUtilities DISABLE_STRICT_WARNINGS EXCLUDE_FROM_ALL
+  ${headers} ${sources}
+)
+
+firebase_ios_framework_public_headers(GoogleUtilities ${headers})
 
-firebase_ios_objc_framework(
-  GoogleUtilities
-  SOURCES ${sources}
-  HEADERS ${headers}
-  VERSION ${version}
-  INCLUDES
-    Private
-    Public
-    ${PROJECT_SOURCE_DIR}
-  DEPENDS
-    "-framework Foundation"
-  DISABLE_STRICT_WARNINGS
-  EXCLUDE_FROM_ALL
+target_link_libraries(
+  GoogleUtilities PRIVATE
+  "-framework Foundation"
 )

+ 6 - 8
Interop/Auth/CMakeLists.txt

@@ -17,13 +17,11 @@ if(NOT APPLE)
 endif()
 
 file(GLOB headers Public/*.h)
+firebase_ios_generate_dummy_source(FirebaseAuthInterop sources)
 
-podspec_version(version ${PROJECT_SOURCE_DIR}/FirebaseAuthInterop.podspec)
-
-firebase_ios_objc_framework(
-  FirebaseAuthInterop
-  HEADERS ${headers}
-  VERSION ${version}
-  DISABLE_STRICT_WARNINGS
-  EXCLUDE_FROM_ALL
+firebase_ios_add_framework(
+  FirebaseAuthInterop DISABLE_STRICT_WARNINGS EXCLUDE_FROM_ALL
+  ${headers} ${sources}
 )
+
+firebase_ios_framework_public_headers(FirebaseAuthInterop ${headers})

+ 5 - 11
Interop/CoreDiagnostics/CMakeLists.txt

@@ -17,17 +17,11 @@ if(NOT APPLE)
 endif()
 
 file(GLOB headers Public/*.h)
+firebase_ios_generate_dummy_source(FirebaseCoreDiagnosticsInterop sources)
 
-podspec_version(
-  version
-  ${PROJECT_SOURCE_DIR}/FirebaseCoreDiagnosticsInterop.podspec
+firebase_ios_add_framework(
+  FirebaseCoreDiagnosticsInterop DISABLE_STRICT_WARNINGS EXCLUDE_FROM_ALL
+  ${headers} ${sources}
 )
 
-firebase_ios_objc_framework(
-  FirebaseCoreDiagnosticsInterop
-  HEADERS ${headers}
-  VERSION ${version}
-  INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
-  DISABLE_STRICT_WARNINGS
-  EXCLUDE_FROM_ALL
-)
+firebase_ios_framework_public_headers(FirebaseCoreDiagnosticsInterop ${headers})

+ 124 - 91
cmake/cc_rules.cmake

@@ -15,6 +15,60 @@
 include(CMakeParseArguments)
 include(FindASANDylib)
 
+
+# firebase_ios_add_framework(
+#   target
+#   sources...
+# )
+#
+# Wraps a call to add_library (including arguments like EXCLUDE_FROM_ALL) that
+# additionally sets common options that apply to all frameworks in this repo.
+#
+# Note that this does not build an actual framework bundle--even recent versions
+# of CMake (3.15.5 as of this writing) produce problematic output when
+# frameworks are enabled. However `firebase_ios_add_framework` along with
+# `firebase_ios_framework_public_headers` produces a library target that
+# can be compiled against as if it *were* a framework. Notably, imports of the
+# form #import <Framework/Framework.h> and public headers that refer to each
+# other unqualified will work.
+function(firebase_ios_add_framework target)
+  firebase_ios_split_target_options(flag ${ARGN})
+
+  add_library(${target} ${flag_REST})
+  firebase_ios_set_common_target_options(${target} ${flag_OPTIONS})
+
+  target_link_libraries(${target} INTERFACE -ObjC)
+endfunction()
+
+# firebase_ios_framework_public_headers(
+#   target
+#   headers...
+# )
+#
+# Prepares the given headers for use as public headers of the given target.
+# This emulates CocoaPods behavior of flattening public headers by creating
+# a separate directory tree in the build output, consisting of symbolic links to
+# the actual header files. This makes it possible to use imports of these forms:
+#
+#   * #import <Framework/Header.h>
+#   * #import "Header.h"
+function(firebase_ios_framework_public_headers target)
+  target_sources(${target} PRIVATE ${ARGN})
+
+  podspec_prep_headers(${target} ${ARGN})
+  target_include_directories(
+    ${target}
+
+    # ${target} is a subdirectory, making <Framework/Header.h> work.
+    PUBLIC ${PROJECT_BINARY_DIR}/Headers
+
+    # Make unqualified imports work too, often needed for peer imports from
+    # one public header to another.
+    PUBLIC ${PROJECT_BINARY_DIR}/Headers/${target}
+  )
+endfunction()
+
+
 # firebase_ios_cc_library(
 #   target
 #   SOURCES sources...
@@ -300,92 +354,6 @@ function(firebase_ios_add_alias ALIAS_TARGET ACTUAL_TARGET)
   endif()
 endfunction()
 
-# firebase_ios_objc_framework(
-#   target
-#   HEADERS headers...
-#   SOURCES sources...
-#   INCLUDES include_directories...
-#   DEFINES macros...
-#   DEPENDS libraries...
-#   [EXCLUDE_FROM_ALL]
-# )
-#
-# Defines a new framework target with the given target name and parameters.
-#
-# If SOURCES is not included, a dummy file will be generated.
-function(firebase_ios_objc_framework target)
-  if(APPLE)
-    set(flag DISABLE_STRICT_WARNINGS EXCLUDE_FROM_ALL SHARED)
-    set(single VERSION)
-    set(multi DEPENDS DEFINES HEADERS INCLUDES SOURCES)
-    cmake_parse_arguments(of "${flag}" "${single}" "${multi}" ${ARGN})
-
-    if (NOT of_SOURCES)
-      firebase_ios_generate_dummy_source(${target} of_SOURCES)
-    endif()
-
-    set(shared_flag "")
-    if(of_SHARED)
-      set(shared_flag SHARED)
-    endif()
-    add_library(
-      ${target}
-      ${shared_flag}
-      ${of_HEADERS}
-      ${of_SOURCES}
-    )
-
-    set(warnings_flag "")
-    if(of_DISABLE_STRICT_WARNINGS)
-      set(warnings_flag DISABLE_STRICT_WARNINGS)
-    endif()
-    firebase_ios_add_compile_options(${target} ${warnings_flag} ${of_SOURCES})
-
-    set_property(TARGET ${target} PROPERTY PUBLIC_HEADER ${of_HEADERS})
-    set_property(TARGET ${target} PROPERTY FRAMEWORK ON)
-    set_property(TARGET ${target} PROPERTY VERSION ${of_VERSION})
-
-    if(of_EXCLUDE_FROM_ALL)
-      set_property(TARGET ${target} PROPERTY EXCLUDE_FROM_ALL ON)
-    endif()
-
-    target_compile_definitions(${target} PUBLIC ${of_DEFINES})
-    target_compile_options(${target} INTERFACE -F${CMAKE_CURRENT_BINARY_DIR})
-
-    # Include directories are carefully crafted to support the following forms
-    # of import, both before and after the framework is built.
-    #   * #import <Framework/Header.h>
-    #   * #import "Header.h"
-    #
-    # Do not use #import "Firestore/Source/Public/Header.h".
-    podspec_prep_headers(${target} ${of_HEADERS})
-    target_include_directories(
-      ${target}
-      # Before the framework is built, Framework.framework/Headers isn't
-      # available yet, so use podspec_prep_headers to create symbolic links
-      # fitting the <Framework/Header.h> pattern.
-      PRIVATE ${PROJECT_BINARY_DIR}/Headers
-
-      # Also support unqualified imports of public headers to work, fitting the
-      # "Header.h" pattern.
-      PRIVATE ${PROJECT_BINARY_DIR}/Headers/${target}
-
-      # Building the framework copies public headers into it. Unfortunately
-      # these copies defeat Clang's #import deduplication mechanism, so the
-      # podspec_prep_headers versions (and any original locations) must not be
-      # made available to clients of the framework. Clients get the qualified
-      # form through the public header support in Clang's module system, and
-      # unqualified names through this additional entry.
-      INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/${target}.framework/Headers
-
-      PRIVATE ${of_INCLUDES}
-    )
-
-    target_link_options(${target} PRIVATE -ObjC)
-    target_link_libraries(${target} PUBLIC ${of_DEPENDS})
-  endif()
-endfunction()
-
 function(firebase_ios_objc_test target)
   if(NOT APPLE OR NOT FIREBASE_IOS_BUILD_TESTS)
     return()
@@ -440,24 +408,89 @@ function(firebase_ios_objc_test target)
   endif()
 endfunction()
 
-# firebase_ios_generate_dummy_source(name, sources_list)
+# firebase_ios_generate_dummy_source(target, sources_list)
 #
 # Generates a dummy source file containing a single symbol, suitable for use as
 # a source file in when defining a header-only library.
 #
-# Appends the generated source file name to the list named by sources_list.
-macro(firebase_ios_generate_dummy_source name sources_list)
-  set(__empty_header_only_file "${CMAKE_CURRENT_BINARY_DIR}/${name}_header_only_empty.cc")
+# Appends the generated source file target to the list named by sources_list.
+macro(firebase_ios_generate_dummy_source target sources_list)
+  set(
+    __empty_header_only_file
+    "${CMAKE_CURRENT_BINARY_DIR}/${target}_header_only_empty.cc"
+  )
 
   if(NOT EXISTS ${__empty_header_only_file})
     file(WRITE ${__empty_header_only_file}
       "// Generated file that keeps header-only CMake libraries happy.
 
       // single meaningless symbol
-      void ${name}_header_only_fakesym() {}
+      void ${target}_header_only_fakesym() {}
       "
     )
   endif()
 
   list(APPEND ${sources_list} ${__empty_header_only_file})
 endmacro()
+
+# Splits the given arguments into two lists: those suitable for passing to
+# `firebase_ios_set_common_target_options` and everything else, usually passed
+# to `add_library`, `add_executable`, or similar.
+#
+# The resulting lists are set in the parent scope as `${prefix}_OPTIONS` for use
+# when calling `firebase_ios_set_common_target_options`, and `${prefix}_REST`
+# that contains any arguments that were unrecognized.
+#
+# See `firebase_ios_set_common_target_options` for details on which arguments
+# are split out.
+function(firebase_ios_split_target_options prefix)
+  set(option_names DISABLE_STRICT_WARNINGS)
+
+  set(${prefix}_OPTIONS)
+  set(${prefix}_REST)
+
+  foreach(arg ${ARGN})
+    list(FIND option_names ${arg} option_index)
+    if(NOT option_index EQUAL -1)
+      list(APPEND ${prefix}_OPTIONS ${arg})
+    else()
+      list(APPEND ${prefix}_REST ${arg})
+    endif()
+  endforeach()
+
+  set(${prefix}_OPTIONS ${${prefix}_OPTIONS} PARENT_SCOPE)
+  set(${prefix}_REST ${${prefix}_REST} PARENT_SCOPE)
+endfunction()
+
+# Sets common options for a given target. This includes:
+#
+#   * `DISABLE_STRICT_WARNINGS`: disables stricter warnings usually applied to
+#     targets in this repo. Use this to compile code that's local but doesn't
+#     hold itself to Firestore's warning strictness.
+#
+# All other arguments are ignored.
+function(firebase_ios_set_common_target_options target)
+  set(options DISABLE_STRICT_WARNINGS)
+  cmake_parse_arguments(flag "${options}" "" "" ${ARGN})
+
+  if(flag_DISABLE_STRICT_WARNINGS)
+    set(cxx_flags ${FIREBASE_IOS_CXX_FLAGS})
+    set(objc_flags ${FIREBASE_IOS_OBJC_FLAGS})
+  else()
+    set(cxx_flags ${FIREBASE_IOS_CXX_FLAGS_STRICT})
+    set(objc_flags ${FIREBASE_IOS_OBJC_FLAGS_STRICT})
+  endif()
+
+  target_compile_options(${target} PRIVATE ${cxx_flags})
+  if(APPLE)
+    target_compile_options(${target} PRIVATE ${objc_flags})
+  endif()
+
+  target_include_directories(
+    ${target} PRIVATE
+    # Put the binary dir first so that the generated config.h trumps any one
+    # generated statically by a Cocoapods-based build in the same source tree.
+    ${PROJECT_BINARY_DIR}
+    ${PROJECT_SOURCE_DIR}
+  )
+endfunction()