Jelajahi Sumber

Merge branch 'diff-report-missing-api' into diff-report-pb-storage-async-progress

Mou 2 tahun lalu
induk
melakukan
720de3158d
51 mengubah file dengan 508 tambahan dan 118 penghapusan
  1. 2 0
      .github/workflows/api_diff_report.yml
  2. 11 0
      CMakeLists.txt
  3. 1 1
      Crashlytics/CHANGELOG.md
  4. 1 1
      FirebaseAnalytics.podspec
  5. 8 0
      FirebaseAppCheck/Interop/FIRAppCheckInterop.h
  6. 16 22
      FirebaseAppCheck/Sources/Core/FIRAppCheck.m
  7. 3 0
      FirebaseCore/CHANGELOG.md
  8. 1 1
      FirebaseCore/Internal/Sources/HeartbeatLogging/Heartbeat.swift
  9. 1 1
      FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift
  10. 31 0
      FirebaseCore/Sources/FIRApp.m
  11. 1 1
      FirebaseDatabase/Sources/Api/FIRDatabaseComponent.m
  12. 1 1
      FirebaseFunctions.podspec
  13. 2 0
      FirebaseFunctions/CHANGELOG.md
  14. 123 20
      FirebaseFunctions/Sources/Functions.swift
  15. 8 2
      FirebaseFunctions/Sources/HTTPSCallable.swift
  16. 28 0
      FirebaseFunctions/Sources/HTTPSCallableOptions.swift
  17. 24 8
      FirebaseFunctions/Sources/Internal/FunctionsContext.swift
  18. 1 0
      FirebaseFunctions/Tests/CombineUnit/HTTPSCallableTests.swift
  19. 6 0
      FirebaseFunctions/Tests/ObjCIntegration/ObjCAPITests.m
  20. 6 0
      FirebaseFunctions/Tests/ObjCIntegration/ObjCPPAPITests.mm
  21. 163 30
      FirebaseFunctions/Tests/Unit/FunctionsTests.swift
  22. 1 1
      FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.h
  23. 5 0
      Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm
  24. 1 1
      GoogleAppMeasurement.podspec
  25. 1 1
      GoogleAppMeasurementOnDeviceConversion.podspec
  26. 4 4
      Package.swift
  27. 1 0
      ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json
  28. 1 0
      ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json
  29. 1 0
      ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json
  30. 1 0
      ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json
  31. 1 0
      ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json
  32. 1 0
      ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json
  33. 1 0
      ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json
  34. 1 0
      ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json
  35. 1 0
      ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json
  36. 1 0
      ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json
  37. 1 0
      ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json
  38. 1 0
      ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json
  39. 1 0
      ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json
  40. 1 0
      ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json
  41. 1 0
      ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json
  42. 1 0
      ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json
  43. 1 0
      ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json
  44. 1 0
      ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json
  45. 1 0
      ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json
  46. 3 0
      SharedTestUtilities/AppCheckFake/FIRAppCheckFake.h
  47. 9 0
      SharedTestUtilities/AppCheckFake/FIRAppCheckFake.m
  48. 11 11
      cmake/external/leveldb-1.22_windows_paths.patch
  49. 5 1
      cmake/external/leveldb_patch.py
  50. 3 3
      scripts/api_diff_report/api_diff_report.py
  51. 8 8
      scripts/api_diff_report/api_info.py

+ 2 - 0
.github/workflows/api_diff_report.yml

@@ -16,6 +16,8 @@ env:
 jobs:
   diff_report:
     runs-on: macos-latest
+    env:
+      FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1
 
     steps:
       - name: Checkout PR branch

+ 11 - 0
CMakeLists.txt

@@ -218,6 +218,17 @@ if(CXX_CLANG)
   )
 endif()
 
+# Fix up targets included by boringssl (ver: b9232f9e27e5668bc0414879dcdedb2a59ea75f2)
+# We might be able to remove this with newer versions.
+if(CXX_CLANG)
+  if(TARGET crypto)
+    target_compile_options(
+      crypto PRIVATE
+      -Wno-unused-but-set-variable
+    )
+  endif()
+endif()
+
 if(MSVC)
   # Disable warnings about unsafe use of std::copy
   target_compile_definitions(

+ 1 - 1
Crashlytics/CHANGELOG.md

@@ -1,4 +1,4 @@
-# Unreleased
+# 10.10.0
 - [changed] Removed references to deprecated CTCarrier API in FirebaseSessions. (#11144)
 - [fixed] Fix Xcode 14.3 Analyzer issue. (#11228)
 

+ 1 - 1
FirebaseAnalytics.podspec

@@ -13,7 +13,7 @@ Pod::Spec.new do |s|
     s.authors          = 'Google, Inc.'
 
     s.source           = {
-        :http => 'https://dl.google.com/firebase/ios/analytics/d3a0626b9fa4b4f3/FirebaseAnalytics-10.9.0.tar.gz'
+        :http => 'https://dl.google.com/firebase/ios/analytics/cc35bd8c714a19cf/FirebaseAnalytics-10.10.0.tar.gz'
     }
 
     s.cocoapods_version = '>= 1.10.0'

+ 8 - 0
FirebaseAppCheck/Interop/FIRAppCheckInterop.h

@@ -43,6 +43,14 @@ NS_SWIFT_NAME(AppCheckInterop) @protocol FIRAppCheckInterop
 /// `tokenDidChangeNotificationName`.
 - (NSString *)notificationAppNameKey;
 
+// MARK: - Optional API
+
+@optional
+
+/// Retrieve a new limited-use Firebase App Check token
+- (void)getLimitedUseTokenWithCompletion:(FIRAppCheckTokenHandlerInterop)handler
+    NS_SWIFT_NAME(getLimitedUseToken(completion:));
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 16 - 22
FirebaseAppCheck/Sources/Core/FIRAppCheck.m

@@ -72,7 +72,6 @@ static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1Iif
 @property(nonatomic, readonly, nullable) id<FIRAppCheckTokenRefresherProtocol> tokenRefresher;
 
 @property(nonatomic, nullable) FBLPromise<FIRAppCheckToken *> *ongoingRetrieveOrRefreshTokenPromise;
-@property(nonatomic, nullable) FBLPromise<FIRAppCheckToken *> *ongoingLimitedUseTokenPromise;
 @end
 
 @implementation FIRAppCheck
@@ -180,7 +179,7 @@ static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1Iif
 
 - (void)limitedUseTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable token,
                                                 NSError *_Nullable error))handler {
-  [self retrieveLimitedUseToken]
+  [self limitedUseToken]
       .then(^id _Nullable(FIRAppCheckToken *token) {
         handler(token, nil);
         return token;
@@ -234,6 +233,21 @@ static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1Iif
       });
 }
 
+- (void)getLimitedUseTokenWithCompletion:(FIRAppCheckTokenHandlerInterop)handler {
+  [self limitedUseToken]
+      .then(^id _Nullable(FIRAppCheckToken *token) {
+        FIRAppCheckTokenResult *result = [[FIRAppCheckTokenResult alloc] initWithToken:token.token
+                                                                                 error:nil];
+        handler(result);
+        return result;
+      })
+      .catch(^(NSError *_Nonnull error) {
+        FIRAppCheckTokenResult *result =
+            [[FIRAppCheckTokenResult alloc] initWithToken:kDummyFACTokenValue error:error];
+        handler(result);
+      });
+}
+
 - (nonnull NSString *)tokenDidChangeNotificationName {
   return FIRAppCheckAppCheckTokenDidChangeNotification;
 }
@@ -299,26 +313,6 @@ static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1Iif
   });
 }
 
-- (FBLPromise<FIRAppCheckToken *> *)retrieveLimitedUseToken {
-  return [FBLPromise do:^id _Nullable {
-    if (self.ongoingLimitedUseTokenPromise == nil) {
-      // Kick off a new operation only when there is not an ongoing one.
-      self.ongoingLimitedUseTokenPromise =
-          [self limitedUseToken]
-              // Release the ongoing operation promise on completion.
-              .then(^FIRAppCheckToken *(FIRAppCheckToken *token) {
-                self.ongoingLimitedUseTokenPromise = nil;
-                return token;
-              })
-              .recover(^NSError *(NSError *error) {
-                self.ongoingLimitedUseTokenPromise = nil;
-                return error;
-              });
-    }
-    return self.ongoingLimitedUseTokenPromise;
-  }];
-}
-
 - (FBLPromise<FIRAppCheckToken *> *)refreshToken {
   return [FBLPromise
              wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {

+ 3 - 0
FirebaseCore/CHANGELOG.md

@@ -1,3 +1,6 @@
+# Unreleased
+- [changed] Improved error reporting for misnamed configuration plist files (#11317).
+
 # Firebase 10.10.0
 - [changed] Firebase now requires at least Xcode 14.1.
 

+ 1 - 1
FirebaseCore/Internal/Sources/HeartbeatLogging/Heartbeat.swift

@@ -48,7 +48,7 @@ struct Heartbeat: Codable, Equatable {
   /// a moving average.
   let timePeriods: [TimePeriod]
 
-  /// Designated intializer.
+  /// Designated initializer.
   /// - Parameters:
   ///   - agent: An anonymous string of information to associate the heartbeat with.
   ///   - date: The date when the heartbeat was recorded.

+ 1 - 1
FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatController.swift

@@ -36,7 +36,7 @@ public final class HeartbeatController {
     self.init(id: id, dateProvider: Date.init)
   }
 
-  /// Convenience initializer. Mirrors the semantics of the public intializer with the added benefit of
+  /// Convenience initializer. Mirrors the semantics of the public initializer with the added benefit of
   /// injecting a custom date provider for improved testability.
   /// - Parameters:
   ///   - id: The id to associate this controller's heartbeat storage with.

+ 31 - 0
FirebaseCore/Sources/FIRApp.m

@@ -112,6 +112,9 @@ static FIRApp *sDefaultApp;
 + (void)configure {
   FIROptions *options = [FIROptions defaultOptions];
   if (!options) {
+#if DEBUG
+    [self findMisnamedGoogleServiceInfoPlist];
+#endif  // DEBUG
     [NSException raise:kFirebaseCoreErrorDomain
                 format:@"`FirebaseApp.configure()` could not find "
                        @"a valid GoogleService-Info.plist in your project. Please download one "
@@ -883,4 +886,32 @@ static FIRApp *sDefaultApp;
   }
 }
 
+#if DEBUG
++ (void)findMisnamedGoogleServiceInfoPlist {
+  for (NSBundle *bundle in [NSBundle allBundles]) {
+    // Not recursive, but we're looking for misnames, not people accidentally
+    // hiding their config file in a subdirectory of their bundle.
+    NSArray *plistPaths = [bundle pathsForResourcesOfType:@"plist" inDirectory:nil];
+    for (NSString *path in plistPaths) {
+      @autoreleasepool {
+        NSDictionary<NSString *, id> *contents = [NSDictionary dictionaryWithContentsOfFile:path];
+        if (contents == nil) {
+          continue;
+        }
+
+        NSString *projectID = contents[@"PROJECT_ID"];
+        if (projectID != nil) {
+          [NSException raise:kFirebaseCoreErrorDomain
+                      format:@"`FirebaseApp.configure()` could not find the default "
+                             @"configuration plist in your project, but did find one at "
+                             @"%@. Please rename this file to GoogleService-Info.plist to "
+                             @"use it as the default configuration.",
+                             path];
+        }
+      }
+    }
+  }
+}
+#endif  // DEBUG
+
 @end

+ 1 - 1
FirebaseDatabase/Sources/Api/FIRDatabaseComponent.m

@@ -32,7 +32,7 @@ typedef NSMutableDictionary<NSString *, FIRDatabase *> FIRDatabaseDictionary;
 
 @interface FIRDatabaseComponent () <FIRComponentLifecycleMaintainer, FIRLibrary>
 @property(nonatomic) FIRDatabaseDictionary *instances;
-/// Internal intializer.
+/// Internal initializer.
 - (instancetype)initWithApp:(FIRApp *)app;
 @end
 

+ 1 - 1
FirebaseFunctions.podspec

@@ -39,7 +39,7 @@ Cloud Functions for Firebase.
 
   s.dependency 'FirebaseCore', '~> 10.0'
   s.dependency 'FirebaseCoreExtension', '~> 10.0'
-  s.dependency 'FirebaseAppCheckInterop', '~> 10.0'
+  s.dependency 'FirebaseAppCheckInterop', '~> 10.10'
   s.dependency 'FirebaseAuthInterop', '~> 10.0'
   s.dependency 'FirebaseMessagingInterop', '~> 10.0'
   s.dependency 'FirebaseSharedSwift', '~> 10.0'

+ 2 - 0
FirebaseFunctions/CHANGELOG.md

@@ -1,5 +1,7 @@
 # 10.10.0
 - [fixed] Fixed potential memory leak of Functions instances. (#11248)
+- [added] Callable functions can now opt in to using limited-use App Check
+  tokens. (#11270)
 
 # 10.0.0
 - [fixed] Remove unnecessary and unused `encoder` and `decoder` parameters from

+ 123 - 20
FirebaseFunctions/Sources/Functions.swift

@@ -135,25 +135,50 @@ internal enum FunctionsConstants {
     return functions(app: app, region: FunctionsConstants.defaultRegion, customDomain: customDomain)
   }
 
-  /**
-   * Creates a reference to the Callable HTTPS trigger with the given name.
-   * - Parameter name The name of the Callable HTTPS trigger.
-   */
+  /// Creates a reference to the Callable HTTPS trigger with the given name.
+  /// - Parameter name: The name of the Callable HTTPS trigger.
+  /// - Returns: A reference to a Callable HTTPS trigger.
   @objc(HTTPSCallableWithName:) open func httpsCallable(_ name: String) -> HTTPSCallable {
     return HTTPSCallable(functions: self, name: name)
   }
 
+  /// Creates a reference to the Callable HTTPS trigger with the given name and configuration options.
+  /// - Parameters:
+  ///   - name: The name of the Callable HTTPS trigger.
+  ///   - options: The options with which to customize the Callable HTTPS trigger.
+  /// - Returns: A reference to a Callable HTTPS trigger.
+  @objc(HTTPSCallableWithName:options:) public func httpsCallable(_ name: String,
+                                                                  options: HTTPSCallableOptions)
+    -> HTTPSCallable {
+    return HTTPSCallable(functions: self, name: name, options: options)
+  }
+
+  /// Creates a reference to the Callable HTTPS trigger with the given name.
+  /// - Parameter url: The URL of the Callable HTTPS trigger.
+  /// - Returns: A reference to a Callable HTTPS trigger.
   @objc(HTTPSCallableWithURL:) open func httpsCallable(_ url: URL) -> HTTPSCallable {
     return HTTPSCallable(functions: self, url: url)
   }
 
+  /// Creates a reference to the Callable HTTPS trigger with the given name and configuration options.
+  /// - Parameters:
+  ///   - url: The URL of the Callable HTTPS trigger.
+  ///   - options: The options with which to customize the Callable HTTPS trigger.
+  /// - Returns: A reference to a Callable HTTPS trigger.
+  @objc(HTTPSCallableWithURL:options:) public func httpsCallable(_ url: URL,
+                                                                 options: HTTPSCallableOptions)
+    -> HTTPSCallable {
+    return HTTPSCallable(functions: self, url: url, options: options)
+  }
+
   /// Creates a reference to the Callable HTTPS trigger with the given name, the type of an `Encodable`
   /// request and the type of a `Decodable` response.
-  /// - Parameter name: The name of the Callable HTTPS trigger
-  /// - Parameter requestAs: The type of the `Encodable` entity to use for requests to this `Callable`
-  /// - Parameter responseAs: The type of the `Decodable` entity to use for responses from this `Callable`
-  /// - Parameter encoder: The encoder instance to use to run the encoding.
-  /// - Parameter decoder: The decoder instance to use to run the decoding.
+  /// - Parameters:
+  ///   - name: The name of the Callable HTTPS trigger
+  ///   - requestAs: The type of the `Encodable` entity to use for requests to this `Callable`
+  ///   - responseAs: The type of the `Decodable` entity to use for responses from this `Callable`
+  ///   - encoder: The encoder instance to use to perform encoding.
+  ///   - decoder: The decoder instance to use to perform decoding.
   /// - Returns: A reference to an HTTPS-callable Cloud Function that can be used to make Cloud Functions invocations.
   open func httpsCallable<Request: Encodable,
     Response: Decodable>(_ name: String,
@@ -164,16 +189,48 @@ internal enum FunctionsConstants {
                          decoder: FirebaseDataDecoder = FirebaseDataDecoder(
                          ))
     -> Callable<Request, Response> {
-    return Callable(callable: httpsCallable(name), encoder: encoder, decoder: decoder)
+    return Callable(
+      callable: httpsCallable(name),
+      encoder: encoder,
+      decoder: decoder
+    )
   }
 
   /// Creates a reference to the Callable HTTPS trigger with the given name, the type of an `Encodable`
   /// request and the type of a `Decodable` response.
-  /// - Parameter url: The url of the Callable HTTPS trigger
-  /// - Parameter requestAs: The type of the `Encodable` entity to use for requests to this `Callable`
-  /// - Parameter responseAs: The type of the `Decodable` entity to use for responses from this `Callable`
-  /// - Parameter encoder: The encoder instance to use to run the encoding.
-  /// - Parameter decoder: The decoder instance to use to run the decoding.
+  /// - Parameters:
+  ///   - name: The name of the Callable HTTPS trigger
+  ///   - options: The options with which to customize the Callable HTTPS trigger.
+  ///   - requestAs: The type of the `Encodable` entity to use for requests to this `Callable`
+  ///   - responseAs: The type of the `Decodable` entity to use for responses from this `Callable`
+  ///   - encoder: The encoder instance to use to perform encoding.
+  ///   - decoder: The decoder instance to use to perform decoding.
+  /// - Returns: A reference to an HTTPS-callable Cloud Function that can be used to make Cloud Functions invocations.
+  open func httpsCallable<Request: Encodable,
+    Response: Decodable>(_ name: String,
+                         options: HTTPSCallableOptions,
+                         requestAs: Request.Type = Request.self,
+                         responseAs: Response.Type = Response.self,
+                         encoder: FirebaseDataEncoder = FirebaseDataEncoder(
+                         ),
+                         decoder: FirebaseDataDecoder = FirebaseDataDecoder(
+                         ))
+    -> Callable<Request, Response> {
+    return Callable(
+      callable: httpsCallable(name, options: options),
+      encoder: encoder,
+      decoder: decoder
+    )
+  }
+
+  /// Creates a reference to the Callable HTTPS trigger with the given name, the type of an `Encodable`
+  /// request and the type of a `Decodable` response.
+  /// - Parameters:
+  ///   - url: The url of the Callable HTTPS trigger
+  ///   - requestAs: The type of the `Encodable` entity to use for requests to this `Callable`
+  ///   - responseAs: The type of the `Decodable` entity to use for responses from this `Callable`
+  ///   - encoder: The encoder instance to use to perform encoding.
+  ///   - decoder: The decoder instance to use to perform decoding.
   /// - Returns: A reference to an HTTPS-callable Cloud Function that can be used to make Cloud Functions invocations.
   open func httpsCallable<Request: Encodable,
     Response: Decodable>(_ url: URL,
@@ -184,7 +241,38 @@ internal enum FunctionsConstants {
                          decoder: FirebaseDataDecoder = FirebaseDataDecoder(
                          ))
     -> Callable<Request, Response> {
-    return Callable(callable: httpsCallable(url), encoder: encoder, decoder: decoder)
+    return Callable(
+      callable: httpsCallable(url),
+      encoder: encoder,
+      decoder: decoder
+    )
+  }
+
+  /// Creates a reference to the Callable HTTPS trigger with the given name, the type of an `Encodable`
+  /// request and the type of a `Decodable` response.
+  /// - Parameters:
+  ///   - url: The url of the Callable HTTPS trigger
+  ///   - options: The options with which to customize the Callable HTTPS trigger.
+  ///   - requestAs: The type of the `Encodable` entity to use for requests to this `Callable`
+  ///   - responseAs: The type of the `Decodable` entity to use for responses from this `Callable`
+  ///   - encoder: The encoder instance to use to perform encoding.
+  ///   - decoder: The decoder instance to use to perform decoding.
+  /// - Returns: A reference to an HTTPS-callable Cloud Function that can be used to make Cloud Functions invocations.
+  open func httpsCallable<Request: Encodable,
+    Response: Decodable>(_ url: URL,
+                         options: HTTPSCallableOptions,
+                         requestAs: Request.Type = Request.self,
+                         responseAs: Response.Type = Response.self,
+                         encoder: FirebaseDataEncoder = FirebaseDataEncoder(
+                         ),
+                         decoder: FirebaseDataDecoder = FirebaseDataDecoder(
+                         ))
+    -> Callable<Request, Response> {
+    return Callable(
+      callable: httpsCallable(url, options: options),
+      encoder: encoder,
+      decoder: decoder
+    )
   }
 
   /**
@@ -273,10 +361,11 @@ internal enum FunctionsConstants {
 
   internal func callFunction(name: String,
                              withObject data: Any?,
+                             options: HTTPSCallableOptions?,
                              timeout: TimeInterval,
                              completion: @escaping ((Result<HTTPSCallableResult, Error>) -> Void)) {
     // Get context first.
-    contextProvider.getContext { context, error in
+    contextProvider.getContext(options: options) { context, error in
       // Note: context is always non-nil since some checks could succeed, we're only failing if
       // there's an error.
       if let error = error {
@@ -285,6 +374,7 @@ internal enum FunctionsConstants {
         let url = self.urlWithName(name)
         self.callFunction(url: URL(string: url)!,
                           withObject: data,
+                          options: options,
                           timeout: timeout,
                           context: context,
                           completion: completion)
@@ -294,10 +384,11 @@ internal enum FunctionsConstants {
 
   internal func callFunction(url: URL,
                              withObject data: Any?,
+                             options: HTTPSCallableOptions?,
                              timeout: TimeInterval,
                              completion: @escaping ((Result<HTTPSCallableResult, Error>) -> Void)) {
     // Get context first.
-    contextProvider.getContext { context, error in
+    contextProvider.getContext(options: options) { context, error in
       // Note: context is always non-nil since some checks could succeed, we're only failing if
       // there's an error.
       if let error = error {
@@ -305,6 +396,7 @@ internal enum FunctionsConstants {
       } else {
         self.callFunction(url: url,
                           withObject: data,
+                          options: options,
                           timeout: timeout,
                           context: context,
                           completion: completion)
@@ -314,6 +406,7 @@ internal enum FunctionsConstants {
 
   private func callFunction(url: URL,
                             withObject data: Any?,
+                            options: HTTPSCallableOptions?,
                             timeout: TimeInterval,
                             context: FunctionsContext,
                             completion: @escaping ((Result<HTTPSCallableResult, Error>) -> Void)) {
@@ -353,8 +446,18 @@ internal enum FunctionsConstants {
       fetcher.setRequestValue(fcmToken, forHTTPHeaderField: Constants.fcmTokenHeader)
     }
 
-    if let appCheckToken = context.appCheckToken {
-      fetcher.setRequestValue(appCheckToken, forHTTPHeaderField: Constants.appCheckTokenHeader)
+    if options?.requireLimitedUseAppCheckTokens == true {
+      if let appCheckToken = context.limitedUseAppCheckToken {
+        fetcher.setRequestValue(
+          appCheckToken,
+          forHTTPHeaderField: Constants.appCheckTokenHeader
+        )
+      }
+    } else if let appCheckToken = context.appCheckToken {
+      fetcher.setRequestValue(
+        appCheckToken,
+        forHTTPHeaderField: Constants.appCheckTokenHeader
+      )
     }
 
     // Override normal security rules if this is a local test.

+ 8 - 2
FirebaseFunctions/Sources/HTTPSCallable.swift

@@ -50,6 +50,8 @@ open class HTTPSCallable: NSObject {
 
   private let endpoint: EndpointType
 
+  private let options: HTTPSCallableOptions?
+
   // MARK: - Public Properties
 
   /**
@@ -57,13 +59,15 @@ open class HTTPSCallable: NSObject {
    */
   @objc open var timeoutInterval: TimeInterval = 70
 
-  internal init(functions: Functions, name: String) {
+  internal init(functions: Functions, name: String, options: HTTPSCallableOptions? = nil) {
     self.functions = functions
+    self.options = options
     endpoint = .name(name)
   }
 
-  internal init(functions: Functions, url: URL) {
+  internal init(functions: Functions, url: URL, options: HTTPSCallableOptions? = nil) {
     self.functions = functions
+    self.options = options
     endpoint = .url(url)
   }
 
@@ -105,11 +109,13 @@ open class HTTPSCallable: NSObject {
     case let .name(name):
       functions.callFunction(name: name,
                              withObject: data,
+                             options: options,
                              timeout: timeoutInterval,
                              completion: callback)
     case let .url(url):
       functions.callFunction(url: url,
                              withObject: data,
+                             options: options,
                              timeout: timeoutInterval,
                              completion: callback)
     }

+ 28 - 0
FirebaseFunctions/Sources/HTTPSCallableOptions.swift

@@ -0,0 +1,28 @@
+// Copyright 2023 Google LLC
+//
+// 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
+
+/// Configuration options for a ``HTTPSCallable`` instance.
+@objc(FIRHTTPSCallableOptions) public class HTTPSCallableOptions: NSObject {
+  /// Whether or not to protect the callable function with a limited-use App Check token.
+  @objc public let requireLimitedUseAppCheckTokens: Bool
+
+  /// Designated initializer.
+  /// - Parameter requireLimitedUseAppCheckTokens: A boolean used to decide whether or not to
+  /// protect the callable function with a limited use App Check token.
+  @objc public init(requireLimitedUseAppCheckTokens: Bool) {
+    self.requireLimitedUseAppCheckTokens = requireLimitedUseAppCheckTokens
+  }
+}

+ 24 - 8
FirebaseFunctions/Sources/Internal/FunctionsContext.swift

@@ -22,11 +22,14 @@ internal class FunctionsContext: NSObject {
   let authToken: String?
   let fcmToken: String?
   let appCheckToken: String?
+  let limitedUseAppCheckToken: String?
 
-  init(authToken: String?, fcmToken: String?, appCheckToken: String?) {
+  init(authToken: String?, fcmToken: String?, appCheckToken: String?,
+       limitedUseAppCheckToken: String?) {
     self.authToken = authToken
     self.fcmToken = fcmToken
     self.appCheckToken = appCheckToken
+    self.limitedUseAppCheckToken = limitedUseAppCheckToken
   }
 }
 
@@ -48,12 +51,14 @@ internal class FunctionsContextProvider: NSObject {
 //
 //  }
 
-  internal func getContext(_ completion: @escaping ((FunctionsContext, Error?) -> Void)) {
+  internal func getContext(options: HTTPSCallableOptions? = nil,
+                           _ completion: @escaping ((FunctionsContext, Error?) -> Void)) {
     let dispatchGroup = DispatchGroup()
 
     var authToken: String?
     var appCheckToken: String?
     var error: Error?
+    var limitedUseAppCheckToken: String?
 
     if let auth = auth {
       dispatchGroup.enter()
@@ -68,19 +73,30 @@ internal class FunctionsContextProvider: NSObject {
     if let appCheck = appCheck {
       dispatchGroup.enter()
 
-      appCheck.getToken(forcingRefresh: false) { tokenResult in
-        // Send only valid token to functions.
-        if tokenResult.error == nil {
-          appCheckToken = tokenResult.token
+      if options?.requireLimitedUseAppCheckTokens == true {
+        appCheck.getLimitedUseToken? { tokenResult in
+          // Send only valid token to functions.
+          if tokenResult.error == nil {
+            limitedUseAppCheckToken = tokenResult.token
+          }
+          dispatchGroup.leave()
+        }
+      } else {
+        appCheck.getToken(forcingRefresh: false) { tokenResult in
+          // Send only valid token to functions.
+          if tokenResult.error == nil {
+            appCheckToken = tokenResult.token
+          }
+          dispatchGroup.leave()
         }
-        dispatchGroup.leave()
       }
     }
 
     dispatchGroup.notify(queue: .main) {
       let context = FunctionsContext(authToken: authToken,
                                      fcmToken: self.messaging?.fcmToken,
-                                     appCheckToken: appCheckToken)
+                                     appCheckToken: appCheckToken,
+                                     limitedUseAppCheckToken: limitedUseAppCheckToken)
       completion(context, error)
     }
   }

+ 1 - 0
FirebaseFunctions/Tests/CombineUnit/HTTPSCallableTests.swift

@@ -33,6 +33,7 @@ class MockFunctions: Functions {
   var verifyParameters: ((_ name: String, _ data: Any?, _ timeout: TimeInterval) throws -> Void)?
   override func callFunction(name: String,
                              withObject data: Any?,
+                             options: HTTPSCallableOptions?,
                              timeout: TimeInterval,
                              completion: @escaping ((Result<HTTPSCallableResult, Error>) -> Void)) {
     do {

+ 6 - 0
FirebaseFunctions/Tests/ObjCIntegration/ObjCAPITests.m

@@ -37,6 +37,12 @@
   FIRHTTPSCallable *callable = [func HTTPSCallableWithURL:url];
   callable = [func HTTPSCallableWithName:@"name"];
 
+  FIRHTTPSCallableOptions *options =
+      [[FIRHTTPSCallableOptions alloc] initWithRequireLimitedUseAppCheckTokens:YES];
+  __unused BOOL requireLimitedUseAppCheckTokens = options.requireLimitedUseAppCheckTokens;
+  callable = [func HTTPSCallableWithURL:url options:options];
+  callable = [func HTTPSCallableWithName:@"name" options:options];
+
   [func useEmulatorWithHost:@"host" port:123];
 
 #pragma mark - HTTPSCallable and HTTPSCallableResult

+ 6 - 0
FirebaseFunctions/Tests/ObjCIntegration/ObjCPPAPITests.mm

@@ -37,6 +37,12 @@
   NSURL *url = [NSURL URLWithString:@"http://host:123/project/location/name"];
   callable = [func HTTPSCallableWithURL:url];
 
+  FIRHTTPSCallableOptions *options =
+      [[FIRHTTPSCallableOptions alloc] initWithRequireLimitedUseAppCheckTokens:YES];
+  __unused BOOL requireLimitedUseAppCheckTokens = options.requireLimitedUseAppCheckTokens;
+  callable = [func HTTPSCallableWithURL:url options:options];
+  callable = [func HTTPSCallableWithName:@"name" options:options];
+
   [func useEmulatorWithHost:@"host" port:123];
 
 #pragma mark - HTTPSCallable and HTTPSCallableResult

+ 163 - 30
FirebaseFunctions/Tests/Unit/FunctionsTests.swift

@@ -106,10 +106,153 @@ class FunctionsTests: XCTestCase {
     XCTAssertEqual("http://localhost:1000", functions?.emulatorOrigin)
   }
 
-  // MARK: - App Check integration
+  /// Test that Functions instances get deallocated.
+  func testFunctionsLifecycle() throws {
+    weak var weakApp: FirebaseApp?
+    weak var weakFunctions: Functions?
+    try autoreleasepool {
+      let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
+                                    gcmSenderID: "00000000000000000-00000000000-000000000")
+      options.projectID = "myProjectID"
+      let app1 = FirebaseApp(instanceWithName: "transitory app", options: options)
+      weakApp = try XCTUnwrap(app1)
+      let functions = Functions(app: app1, region: "transitory-region", customDomain: nil)
+      weakFunctions = functions
+      XCTAssertNotNil(weakFunctions)
+    }
+    XCTAssertNil(weakApp)
+    XCTAssertNil(weakFunctions)
+  }
+
+  // MARK: - App Check Integration
+
+  func testCallFunctionWhenUsingLimitedUseAppCheckTokenThenTokenSuccess() {
+    // Given
+    // Stub returns of two different kinds of App Check tokens. Only the
+    // limited use token should be present in Functions's request header.
+    appCheckFake.tokenResult = FIRAppCheckTokenResultFake(token: "shared_valid_token", error: nil)
+    appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
+      token: "limited_use_valid_token",
+      error: nil
+    )
+
+    let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
+    fetcherService.testBlock = { fetcherToTest, testResponse in
+      let appCheckTokenHeader = fetcherToTest.request?
+        .value(forHTTPHeaderField: "X-Firebase-AppCheck")
+      // Assert that header contains limited use token.
+      XCTAssertEqual(appCheckTokenHeader, "limited_use_valid_token")
+      testResponse(nil, "{\"data\":\"May the force be with you!\"}".data(using: .utf8), nil)
+      httpRequestExpectation.fulfill()
+    }
+
+    // When
+    let options = HTTPSCallableOptions(requireLimitedUseAppCheckTokens: true)
+
+    // Then
+    let completionExpectation = expectation(description: "completionExpectation")
+    functions?
+      .httpsCallable("fake_func", options: options)
+      .call { result, error in
+        guard let result = result else {
+          return XCTFail("Unexpected error: \(error!).")
+        }
+
+        XCTAssertEqual(result.data as! String, "May the force be with you!")
+
+        completionExpectation.fulfill()
+      }
+
+    waitForExpectations(timeout: 1.5)
+  }
+
+  func testCallFunctionWhenLimitedUseAppCheckTokenDisabledThenCallWithoutToken() {
+    // Given
+    let limitedUseDummyToken = "limited use dummy token"
+    appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
+      token: limitedUseDummyToken,
+      error: NSError(domain: #function, code: -1)
+    )
+
+    let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
+    fetcherService.testBlock = { fetcherToTest, testResponse in
+      // Assert that header does not contain an AppCheck token.
+      fetcherToTest.request?.allHTTPHeaderFields?.forEach { key, value in
+        if key == "X-Firebase-AppCheck" {
+          XCTAssertNotEqual(value, limitedUseDummyToken)
+        }
+      }
+
+      testResponse(nil, "{\"data\":\"May the force be with you!\"}".data(using: .utf8), nil)
+      httpRequestExpectation.fulfill()
+    }
+
+    // When
+    let options = HTTPSCallableOptions(requireLimitedUseAppCheckTokens: false)
+
+    // Then
+    let completionExpectation = expectation(description: "completionExpectation")
+    functions?
+      .httpsCallable("fake_func", options: options)
+      .call { result, error in
+        guard let result = result else {
+          return XCTFail("Unexpected error: \(error!).")
+        }
+
+        XCTAssertEqual(result.data as! String, "May the force be with you!")
+
+        completionExpectation.fulfill()
+      }
+
+    waitForExpectations(timeout: 1.5)
+  }
+
+  func testCallFunctionWhenLimitedUseAppCheckTokenCannotBeGeneratedThenCallWithoutToken() {
+    // Given
+    appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
+      token: "dummy token",
+      error: NSError(domain: #function, code: -1)
+    )
+
+    let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
+    fetcherService.testBlock = { fetcherToTest, testResponse in
+      // Assert that header does not contain an AppCheck token.
+      fetcherToTest.request?.allHTTPHeaderFields?.forEach { key, _ in
+        XCTAssertNotEqual(key, "X-Firebase-AppCheck")
+      }
+
+      testResponse(nil, "{\"data\":\"May the force be with you!\"}".data(using: .utf8), nil)
+      httpRequestExpectation.fulfill()
+    }
+
+    // When
+    let options = HTTPSCallableOptions(requireLimitedUseAppCheckTokens: true)
+
+    // Then
+    let completionExpectation = expectation(description: "completionExpectation")
+    functions?
+      .httpsCallable("fake_func", options: options)
+      .call { result, error in
+        guard let result = result else {
+          return XCTFail("Unexpected error: \(error!).")
+        }
+
+        XCTAssertEqual(result.data as! String, "May the force be with you!")
+
+        completionExpectation.fulfill()
+      }
+
+    waitForExpectations(timeout: 1.5)
+  }
 
   func testCallFunctionWhenAppCheckIsInstalledAndFACTokenSuccess() {
-    appCheckFake.tokenResult = FIRAppCheckTokenResultFake(token: "valid_token", error: nil)
+    // Stub returns of two different kinds of App Check tokens. Only the
+    // shared use token should be present in Functions's request header.
+    appCheckFake.tokenResult = FIRAppCheckTokenResultFake(token: "shared_valid_token", error: nil)
+    appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
+      token: "limited_use_valid_token",
+      error: nil
+    )
 
     let networkError = NSError(
       domain: "testCallFunctionWhenAppCheckIsInstalled",
@@ -121,21 +264,24 @@ class FunctionsTests: XCTestCase {
     fetcherService.testBlock = { fetcherToTest, testResponse in
       let appCheckTokenHeader = fetcherToTest.request?
         .value(forHTTPHeaderField: "X-Firebase-AppCheck")
-      XCTAssertEqual(appCheckTokenHeader, "valid_token")
+      XCTAssertEqual(appCheckTokenHeader, "shared_valid_token")
       testResponse(nil, nil, networkError)
       httpRequestExpectation.fulfill()
     }
 
     let completionExpectation = expectation(description: "completionExpectation")
-    functions?.callFunction(name: "fake_func", withObject: nil, timeout: 10) { result in
-      switch result {
-      case .success:
-        XCTFail("Unexpected success from functions?.callFunction")
-      case let .failure(error as NSError):
-        XCTAssertEqual(error, networkError)
+    functions?
+      .httpsCallable("fake_func")
+      .call { result, error in
+        guard let error = error else {
+          return XCTFail("Unexpected success: \(result!).")
+        }
+
+        XCTAssertEqual(error as NSError, networkError)
+
+        completionExpectation.fulfill()
       }
-      completionExpectation.fulfill()
-    }
+
     waitForExpectations(timeout: 1.5)
   }
 
@@ -156,7 +302,12 @@ class FunctionsTests: XCTestCase {
     }
 
     let completionExpectation = expectation(description: "completionExpectation")
-    functionsCustomDomain?.callFunction(name: "fake_func", withObject: nil, timeout: 10) { result in
+    functionsCustomDomain?.callFunction(
+      name: "fake_func",
+      withObject: nil,
+      options: nil,
+      timeout: 10
+    ) { result in
       switch result {
       case .success:
         XCTFail("Unexpected success from functions?.callFunction")
@@ -167,22 +318,4 @@ class FunctionsTests: XCTestCase {
     }
     waitForExpectations(timeout: 1.5)
   }
-
-  /// Test that Functions instances get deallocated.
-  func testFunctionsLifecycle() throws {
-    weak var weakApp: FirebaseApp?
-    weak var weakFunctions: Functions?
-    try autoreleasepool {
-      let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
-                                    gcmSenderID: "00000000000000000-00000000000-000000000")
-      options.projectID = "myProjectID"
-      let app1 = FirebaseApp(instanceWithName: "transitory app", options: options)
-      weakApp = try XCTUnwrap(app1)
-      let functions = Functions(app: app1, region: "transitory-region", customDomain: nil)
-      weakFunctions = functions
-      XCTAssertNotNil(weakFunctions)
-    }
-    XCTAssertNil(weakApp)
-    XCTAssertNil(weakFunctions)
-  }
 }

+ 1 - 1
FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.h

@@ -48,7 +48,7 @@ typedef NS_OPTIONS(NSUInteger, FIRMessagingInvalidTokenReason) {
 - (instancetype)init NS_UNAVAILABLE;
 
 /**
- *  Designated intializer.
+ *  Designated initializer.
  *
  *  @param heartbeatLogger The heartbeat logger that is injected into token operations.
  */

+ 5 - 0
Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm

@@ -24,6 +24,8 @@
 #import "Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h"
 #import "Firestore/Source/API/FIRQuery+Internal.h"
 
+#include "Firestore/core/src/util/sanitizers.h"
+
 #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
 
 @interface FIRAggregateTests : FSTIntegrationTestCase
@@ -306,6 +308,8 @@
   XCTAssertTrue([[result localizedDescription] containsString:@"Aggregations can not be empty"]);
 }
 
+// (TODO:b/283101111): Try thread sanitizer to see if timeout on Github Actions is gone.
+#if !defined(THREAD_SANITIZER)
 - (void)testAggregateFieldQuerySnapshotEquality {
   // TODO(sum/avg) remove the check below when sum and avg are supported in production
   XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
@@ -381,6 +385,7 @@
   XCTAssertNotEqual([snapshot1 hash], [snapshot4 hash]);
   XCTAssertNotEqual([snapshot3 hash], [snapshot4 hash]);
 }
+#endif  // #if !defined(THREAD_SANITIZER)
 
 - (void)testAllowsAliasesLongerThan1500Bytes {
   // TODO(sum/avg) remove the check below when sum and avg are supported in production

+ 1 - 1
GoogleAppMeasurement.podspec

@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
     s.authors          = 'Google, Inc.'
 
     s.source           = {
-        :http => 'https://dl.google.com/firebase/ios/analytics/785f9e166c7ac0d4/GoogleAppMeasurement-10.9.0.tar.gz'
+        :http => 'https://dl.google.com/firebase/ios/analytics/dd11315e6d615190/GoogleAppMeasurement-10.10.0.tar.gz'
     }
 
     s.cocoapods_version = '>= 1.10.2'

+ 1 - 1
GoogleAppMeasurementOnDeviceConversion.podspec

@@ -17,7 +17,7 @@ Pod::Spec.new do |s|
     s.authors          = 'Google, Inc.'
 
     s.source           = {
-        :http => 'https://dl.google.com/firebase/ios/analytics/ca4b7cccbf4bea2f/GoogleAppMeasurementOnDeviceConversion-10.4.0.tar.gz'
+        :http => 'https://dl.google.com/firebase/ios/analytics/69bb50195b3d6c40/GoogleAppMeasurementOnDeviceConversion-10.10.0.tar.gz'
     }
 
     s.cocoapods_version = '>= 1.10.2'

+ 4 - 4
Package.swift

@@ -316,8 +316,8 @@ let package = Package(
     ),
     .binaryTarget(
       name: "FirebaseAnalytics",
-      url: "https://dl.google.com/firebase/ios/swiftpm/10.9.0/FirebaseAnalytics.zip",
-      checksum: "b0a16eef8caf30eadc496ab24fef5216798c8ec360addb6af53806957950c300"
+      url: "https://dl.google.com/firebase/ios/swiftpm/10.10.0/FirebaseAnalytics.zip",
+      checksum: "335d26f7cc88f2bb9196826344ff772f762ce0cc8a1918dfaa79282424bbab5c"
     ),
     .target(
       name: "FirebaseAnalyticsSwiftTarget",
@@ -674,7 +674,7 @@ let package = Package(
     .binaryTarget(
       name: "FirebaseFirestore",
       url: "https://dl.google.com/firebase/ios/bin/firestore/10.10.0/FirebaseFirestore.zip",
-      checksum: "bb362e131fda776d6911e553516439fe7d3744a213c6720ee9d58ebc5b6de789"
+      checksum: "4a0070c4bf7e5ab59359dd8a0e68f402f3ec6c1e189fc39cc44ca88418f26ac4"
     ),
 
     .target(
@@ -1349,5 +1349,5 @@ func googleAppMeasurementDependency() -> Package.Dependency {
     return .package(url: appMeasurementURL, branch: "main")
   }
 
-  return .package(url: appMeasurementURL, exact: "10.9.0")
+  return .package(url: appMeasurementURL, exact: "10.10.0")
 }

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseABTesting-7e99c47d786adc1b.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseABTesting-51e22666b1949041.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseABTesting-a496877e09766ffd.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseABTesting-3c0e5c9ddc52bccd.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseABTesting-e87c686cee02758a.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseABTesting-6a65ab8b888172af.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/Google-Mobile-Ads-SDK-d588c632d268acbb.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/Google-Mobile-Ads-SDK-6856ec2c0d236d2b.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/Google-Mobile-Ads-SDK-b5a530fd44233f7d.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/Google-Mobile-Ads-SDK-c8bc252ed3323212.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/Google-Mobile-Ads-SDK-8b0d1ce3d1162b67.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/Google-Mobile-Ads-SDK-046511c3fd0189eb.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseAnalytics-02b2758dd4a5b9b7.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseAnalytics-681bcc7575c820f9.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseAnalytics-9184b9234c446c27.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAnalytics-6f8b70c8ee2efc85.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAnalytics-95669fcf109f74a2.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAnalytics-c0db6cb0e858e397.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseAnalyticsOnDeviceConversion-676ea1e23b8cec47.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseAnalyticsOnDeviceConversion-fa5d0eeb77cbfdcd.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseAnalyticsOnDeviceConversion-6824008b7b8206f0.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAnalyticsOnDeviceConversion-37cf6277991d7d75.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAnalyticsOnDeviceConversion-091f5252d693a9f9.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAnalyticsOnDeviceConversion-7bbb73d46383a042.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseAppCheck-1573377f9ee5662f.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseAppCheck-6b3d194d9650cf1a.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseAppCheck-e90037cbb5f37d9e.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAppCheck-b0ead84a126d24d4.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAppCheck-d19e46a728b1ac4f.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAppCheck-8339fde989fe8f24.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseAppDistribution-497310e37f4c709b.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseAppDistribution-078ae8953529e0a8.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseAppDistribution-00f73c94a027a21f.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAppDistribution-45b5c85bba08a85b.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAppDistribution-cefc3327ddfceda6.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAppDistribution-7931e42d39575534.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseAuth-c1309119bfccd5d7.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseAuth-f2dddb776209b8b5.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseAuth-6b65afe0ec7a68b1.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAuth-2165e27f89d4959e.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAuth-e43e66353617f093.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAuth-8a9591e6daa7e207.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseCrashlytics-59b1fffe41d52a08.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseCrashlytics-15efab8a4392b221.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseCrashlytics-bd6959f4d666a5d9.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseCrashlytics-054718c61ef054f9.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseCrashlytics-d29d3285a7d9fa1d.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseCrashlytics-165beb64483b4278.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseDatabase-31f2e4a5b6161aa6.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseDatabase-20809c274dc7412d.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseDatabase-7620a67e855e754a.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseDatabase-8b7048f7890bb665.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseDatabase-5b22f689cb66d83a.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseDatabase-e1a9d1f0c4222cf7.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseDynamicLinks-3a8c79fd8550e514.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseDynamicLinks-ea6ec159d0d48ff7.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseDynamicLinks-76eac37d8efb10de.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseDynamicLinks-bfdce6ac5d591ab3.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseDynamicLinks-7cf4ae5e96882ca8.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseDynamicLinks-c3bdeb37651a5d5d.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseFirestore-07afb0388fbd6796.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseFirestore-2e32666a88605d1e.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseFirestore-2821defd38b13951.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseFirestore-4c3d1568e379a98c.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseFirestore-73ba0700b1aa6d6a.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseFirestore-02eb8da05f81fca5.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseFunctions-5e24694e2487d78b.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseFunctions-c01eb0ea942aff82.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseFunctions-4286994831189479.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseFunctions-b949cfeca4e7a80f.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseFunctions-47189f2c99cdf806.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseFunctions-17c4b760141e38ad.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/GoogleSignIn-52ebdb62b0759928.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/GoogleSignIn-a25c47bfeba32c5f.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/GoogleSignIn-196084297b5a61f9.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/GoogleSignIn-c887dbc6bd07c787.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/GoogleSignIn-a5b49807be66100b.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/GoogleSignIn-0d2e746eb3ff9f92.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseInAppMessaging-cdf310c038b162ba.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseInAppMessaging-e27fb2a1620a8623.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseInAppMessaging-4d023471298d5ac0.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseInAppMessaging-f29d7b7839cda915.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseInAppMessaging-91e5426eade46bca.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseInAppMessaging-10801bd111df59de.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseMLModelDownloader-1d05e75ca6ace6e2.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseMLModelDownloader-7d05cd03ee5e2426.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseMLModelDownloader-3bbaf04586b17070.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseMLModelDownloader-ee2af587027e74d3.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseMLModelDownloader-559cb113c0cfd8f2.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseMLModelDownloader-9c909894999c92e4.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseMessaging-31a5d25e099280b3.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseMessaging-90c34382a1b9958b.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseMessaging-4483e7eb0a3e5db6.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseMessaging-289a04c85f7e771d.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseMessaging-59ef1cc63c660712.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseMessaging-76c02a69e3fe1008.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebasePerformance-c842a05ab2e3a37f.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebasePerformance-5a6e015d91228ace.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebasePerformance-386ab14104423a32.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebasePerformance-7a7398acc615dbb6.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebasePerformance-36ac6dfb99caa11b.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebasePerformance-f9f5be8ffad5cbb0.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseRemoteConfig-8b8836adfb7eec2e.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseRemoteConfig-b71a3f5bc20825f7.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseRemoteConfig-7266f96e3df8c3fa.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseRemoteConfig-45a7f541a654884c.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseRemoteConfig-edd1b427b8bbe782.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseRemoteConfig-10b62ee5663aaab3.zip",

+ 1 - 0
ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json

@@ -1,6 +1,7 @@
 {
   "10.0.0": "https://dl.google.com/dl/firebase/ios/carthage/10.0.0/FirebaseStorage-691ea7b0344883ca.zip",
   "10.1.0": "https://dl.google.com/dl/firebase/ios/carthage/10.1.0/FirebaseStorage-b53678fed326a955.zip",
+  "10.10.0": "https://dl.google.com/dl/firebase/ios/carthage/10.10.0/FirebaseStorage-13cbbbbaccd67201.zip",
   "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseStorage-347e6a3402706596.zip",
   "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseStorage-ac463d14593d10a8.zip",
   "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseStorage-fdf8479115660ce6.zip",

+ 3 - 0
SharedTestUtilities/AppCheckFake/FIRAppCheckFake.h

@@ -25,6 +25,9 @@ NS_ASSUME_NONNULL_BEGIN
 /** The tokenResult to be passed to `-[getTokenWithCompletion:]` completion handler. */
 @property(nonatomic, nonnull) id<FIRAppCheckTokenResultInterop> tokenResult;
 
+/** The token result to be passed to `-[getLimitedUseTokenWithCompletion:]` completion handler. */
+@property(nonatomic, nonnull) id<FIRAppCheckTokenResultInterop> limitedUseTokenResult;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 9 - 0
SharedTestUtilities/AppCheckFake/FIRAppCheckFake.m

@@ -24,6 +24,9 @@
   self = [super init];
   if (self) {
     _tokenResult = [[FIRAppCheckTokenResultFake alloc] initWithToken:@"fake_valid_token" error:nil];
+    _limitedUseTokenResult =
+        [[FIRAppCheckTokenResultFake alloc] initWithToken:@"fake_limited_use_valid_token"
+                                                    error:nil];
   }
   return self;
 }
@@ -35,6 +38,12 @@
   });
 }
 
+- (void)getLimitedUseTokenWithCompletion:(FIRAppCheckTokenHandlerInterop)handler {
+  dispatch_async(dispatch_get_main_queue(), ^{
+    handler(self.limitedUseTokenResult);
+  });
+}
+
 - (nonnull NSString *)notificationAppNameKey {
   return @"FakeAppCheckTokenDidChangeNotification";
 }

+ 11 - 11
cmake/external/leveldb-1.22_windows_paths.patch

@@ -1,5 +1,5 @@
 diff --git a/util/env_windows.cc b/util/env_windows.cc
-index 09e3df6..23d60f1 100644
+index 09e3df6..5d9b2f2 100644
 --- a/util/env_windows.cc
 +++ b/util/env_windows.cc
 @@ -362,9 +362,11 @@ class WindowsEnv : public Env {
@@ -118,7 +118,7 @@ index 09e3df6..23d60f1 100644
      DWORD last_error = ::GetLastError();
      ::FindClose(dir_handle);
      if (last_error != ERROR_NO_MORE_FILES) {
-@@ -482,13 +496,16 @@ class WindowsEnv : public Env {
+@@ -482,21 +496,24 @@ class WindowsEnv : public Env {
    }
  
    Status DeleteFile(const std::string& fname) override {
@@ -131,12 +131,12 @@ index 09e3df6..23d60f1 100644
    }
  
    Status CreateDir(const std::string& name) override {
+-    if (!::CreateDirectoryA(name.c_str(), nullptr)) {
 +    auto wDirname = toUtf16(name);
 +    if (!::CreateDirectoryW(wDirname.c_str(), nullptr)) {
-     if (!::CreateDirectoryA(name.c_str(), nullptr)) {
        return WindowsError(name, ::GetLastError());
      }
-@@ -496,7 +513,8 @@ class WindowsEnv : public Env {
+     return Status::OK();
    }
  
    Status DeleteDir(const std::string& name) override {
@@ -146,7 +146,7 @@ index 09e3df6..23d60f1 100644
        return WindowsError(name, ::GetLastError());
      }
      return Status::OK();
-@@ -504,7 +522,9 @@ class WindowsEnv : public Env {
+@@ -504,7 +521,9 @@ class WindowsEnv : public Env {
  
    Status GetFileSize(const std::string& fname, uint64_t* size) override {
      WIN32_FILE_ATTRIBUTE_DATA attrs;
@@ -157,7 +157,7 @@ index 09e3df6..23d60f1 100644
        return WindowsError(fname, ::GetLastError());
      }
      ULARGE_INTEGER file_size;
-@@ -518,7 +538,9 @@ class WindowsEnv : public Env {
+@@ -518,7 +537,9 @@ class WindowsEnv : public Env {
                      const std::string& target) override {
      // Try a simple move first.  It will only succeed when |to_path| doesn't
      // already exist.
@@ -168,7 +168,7 @@ index 09e3df6..23d60f1 100644
        return Status::OK();
      }
      DWORD move_error = ::GetLastError();
-@@ -527,7 +549,7 @@ class WindowsEnv : public Env {
+@@ -527,7 +548,7 @@ class WindowsEnv : public Env {
      // succeed when |to_path| does exist. When writing to a network share, we
      // may not be able to change the ACLs. Ignore ACL errors then
      // (REPLACEFILE_IGNORE_MERGE_ERRORS).
@@ -177,7 +177,7 @@ index 09e3df6..23d60f1 100644
                         REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr)) {
        return Status::OK();
      }
-@@ -546,8 +568,9 @@ class WindowsEnv : public Env {
+@@ -546,8 +567,9 @@ class WindowsEnv : public Env {
    Status LockFile(const std::string& fname, FileLock** lock) override {
      *lock = nullptr;
      Status result;
@@ -189,7 +189,7 @@ index 09e3df6..23d60f1 100644
          /*lpSecurityAttributes=*/nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
          nullptr);
      if (!handle.is_valid()) {
-@@ -584,10 +607,11 @@ class WindowsEnv : public Env {
+@@ -584,10 +606,11 @@ class WindowsEnv : public Env {
        return Status::OK();
      }
  
@@ -203,7 +203,7 @@ index 09e3df6..23d60f1 100644
      std::stringstream ss;
      ss << tmp_path << "leveldbtest-" << std::this_thread::get_id();
      *result = ss.str();
-@@ -598,7 +622,8 @@ class WindowsEnv : public Env {
+@@ -598,7 +621,8 @@ class WindowsEnv : public Env {
    }
  
    Status NewLogger(const std::string& filename, Logger** result) override {
@@ -213,7 +213,7 @@ index 09e3df6..23d60f1 100644
      if (fp == nullptr) {
        *result = nullptr;
        return WindowsError("NewLogger", ::GetLastError());
-@@ -640,6 +665,31 @@ class WindowsEnv : public Env {
+@@ -640,6 +664,31 @@ class WindowsEnv : public Env {
    bool started_bgthread_;
    std::deque<BGItem> queue_;
    Limiter mmap_limiter_;

+ 5 - 1
cmake/external/leveldb_patch.py

@@ -53,9 +53,13 @@ def main() -> None:
   with cmakelists_txt_file.open("wt", encoding="utf8") as f:
     f.writelines(patched_lines)
 
-  if additional_patch_file:
+  additional_patch_stamp = 'leveldb_additional_patch_stamp'
+  if additional_patch_file and not os.path.exists(additional_patch_stamp):
+    print("Applying patch %s" % additional_patch_file)
     subprocess.run(['git', 'apply', '-v', additional_patch_file],
                    check=True)
+    # Create a stamp file so the patch isn't applied twice.
+    open(additional_patch_stamp, 'a').close()
 
 
 @dataclasses.dataclass(frozen=True)

+ 3 - 3
scripts/api_diff_report/api_diff_report.py

@@ -177,9 +177,9 @@ def generate_markdown_report(diff, level=0):
       else:
         current_status = value.get('status')
         if current_status:
-          # Module level: Always print out module name as title
-          if level == 0:
-            report += f'{header_str} {key} [{current_status}]\n'
+          # Module level: Always print out module name and class name as title
+          if level in [0, 2]:
+            report += f'{header_str} [{current_status}] {key}\n'
           if current_status != STATUS_ERROR:  # ADDED,REMOVED,MODIFIED
             report += '<details>\n<summary>\n'
             report += f'[{current_status}] {key}\n'

+ 8 - 8
scripts/api_diff_report/api_info.py

@@ -198,14 +198,14 @@ def parse_sub_api(api_link, sub_api_data_container):
 
   # Parse the HTML content
   soup = BeautifulSoup(html_content, 'html.parser')
-  for s_api in soup.find('div', class_='task-group').find_all('li',
-                                                              class_='item'):
-    api_name = s_api.find('a', class_='token').text
-    sub_api_data_container[api_name] = {'declaration': []}
-    for api_declaration in s_api.find_all('div', class_='language'):
-      api_declaration_text = ' '.join(api_declaration.stripped_strings)
-      sub_api_data_container[api_name]['declaration'].append(
-          api_declaration_text)
+  for s_api_group in soup.find_all('div', class_='task-group'):
+    for s_api in s_api_group.find_all('li', class_='item'):
+      api_name = s_api.find('a', class_='token').text
+      sub_api_data_container[api_name] = {'declaration': []}
+      for api_declaration in s_api.find_all('div', class_='language'):
+        api_declaration_text = ' '.join(api_declaration.stripped_strings)
+        sub_api_data_container[api_name]['declaration'].append(
+            api_declaration_text)
 
 
 def parse_cmdline_args():