From dd4a403beca2dec836fa165ab6e3577384398c1a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Sun, 16 Mar 2025 15:55:15 -0400 Subject: [PATCH 01/21] [Infra] Add write permissions to notice_generation.yml and prerelease.yml (#14583) --- .github/workflows/notice_generation.yml | 3 +++ .github/workflows/prerelease.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/notice_generation.yml b/.github/workflows/notice_generation.yml index 58f9a4345b8..fd3dc5b3bd2 100644 --- a/.github/workflows/notice_generation.yml +++ b/.github/workflows/notice_generation.yml @@ -1,5 +1,8 @@ name: generate_notices +permissions: + pull-requests: write + on: pull_request: paths: diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 4513b92ed9e..123593c8597 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -1,5 +1,8 @@ name: prerelease +permissions: + contents: write + on: pull_request: # closed will be triggered when a pull request is merged. This is to keep https://github.com/firebase/SpecsTesting up to date. From 9ba8fc0f99a2d09a68057efd7b7f2598fe900def Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 17 Mar 2025 10:18:36 -0400 Subject: [PATCH 02/21] [Vertex AI] Add Developer API encoding `CountTokensRequest` (#14512) --- .../Sources/GenerateContentRequest.swift | 19 +++ .../Sources/GenerativeModel.swift | 24 +++- .../Requests}/CountTokensRequest.swift | 51 ++++++-- .../Tests/TestApp/Sources/Constants.swift | 1 + .../CountTokensIntegrationTests.swift | 122 ++++++++++++++++++ .../GenerateContentIntegrationTests.swift | 29 +---- ...AITestUtils.swift => InstanceConfig.swift} | 44 +++++++ .../VertexAITestApp.xcodeproj/project.pbxproj | 12 +- .../Requests/CountTokensRequestTests.swift | 111 ++++++++++++++++ 9 files changed, 368 insertions(+), 45 deletions(-) rename FirebaseVertexAI/Sources/{ => Types/Internal/Requests}/CountTokensRequest.swift (63%) create mode 100644 FirebaseVertexAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift rename FirebaseVertexAI/Tests/TestApp/Tests/Utilities/{VertexAITestUtils.swift => InstanceConfig.swift} (57%) create mode 100644 FirebaseVertexAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift diff --git a/FirebaseVertexAI/Sources/GenerateContentRequest.swift b/FirebaseVertexAI/Sources/GenerateContentRequest.swift index 97e7248fc7c..21acd502a75 100644 --- a/FirebaseVertexAI/Sources/GenerateContentRequest.swift +++ b/FirebaseVertexAI/Sources/GenerateContentRequest.swift @@ -18,12 +18,14 @@ import Foundation struct GenerateContentRequest: Sendable { /// Model name. let model: String + let contents: [ModelContent] let generationConfig: GenerationConfig? let safetySettings: [SafetySetting]? let tools: [Tool]? let toolConfig: ToolConfig? let systemInstruction: ModelContent? + let apiConfig: APIConfig let apiMethod: APIMethod let options: RequestOptions @@ -32,6 +34,7 @@ struct GenerateContentRequest: Sendable { @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GenerateContentRequest: Encodable { enum CodingKeys: String, CodingKey { + case model case contents case generationConfig case safetySettings @@ -39,6 +42,22 @@ extension GenerateContentRequest: Encodable { case toolConfig case systemInstruction } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + // The model name only needs to be encoded when this `GenerateContentRequest` instance is used + // in a `CountTokensRequest` (calling `countTokens`). When calling `generateContent` or + // `generateContentStream`, the `model` field is populated in the backend from the `url`. + if apiMethod == .countTokens { + try container.encode(model, forKey: .model) + } + try container.encode(contents, forKey: .contents) + try container.encodeIfPresent(generationConfig, forKey: .generationConfig) + try container.encodeIfPresent(safetySettings, forKey: .safetySettings) + try container.encodeIfPresent(tools, forKey: .tools) + try container.encodeIfPresent(toolConfig, forKey: .toolConfig) + try container.encodeIfPresent(systemInstruction, forKey: .systemInstruction) + } } @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index 3f57c3ed80d..3d37be52061 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -260,15 +260,31 @@ public final class GenerativeModel: Sendable { /// - Returns: The results of running the model's tokenizer on the input; contains /// ``CountTokensResponse/totalTokens``. public func countTokens(_ content: [ModelContent]) async throws -> CountTokensResponse { - let countTokensRequest = CountTokensRequest( + let requestContent = switch apiConfig.service { + case .vertexAI: + content + case .developer: + // The `role` defaults to "user" but is ignored in `countTokens`. However, it is erroneously + // erroneously counted towards the prompt and total token count when using the Developer API + // backend; set to `nil` to avoid token count discrepancies between `countTokens` and + // `generateContent` and the two backend APIs. + content.map { ModelContent(role: nil, parts: $0.parts) } + } + + let generateContentRequest = GenerateContentRequest( model: modelResourceName, - contents: content, - systemInstruction: systemInstruction, - tools: tools, + contents: requestContent, generationConfig: generationConfig, + safetySettings: safetySettings, + tools: tools, + toolConfig: toolConfig, + systemInstruction: systemInstruction, apiConfig: apiConfig, + apiMethod: .countTokens, options: requestOptions ) + let countTokensRequest = CountTokensRequest(generateContentRequest: generateContentRequest) + return try await generativeAIService.loadRequest(request: countTokensRequest) } diff --git a/FirebaseVertexAI/Sources/CountTokensRequest.swift b/FirebaseVertexAI/Sources/Types/Internal/Requests/CountTokensRequest.swift similarity index 63% rename from FirebaseVertexAI/Sources/CountTokensRequest.swift rename to FirebaseVertexAI/Sources/Types/Internal/Requests/CountTokensRequest.swift index 1a0866b2f35..8a49adcab3f 100644 --- a/FirebaseVertexAI/Sources/CountTokensRequest.swift +++ b/FirebaseVertexAI/Sources/Types/Internal/Requests/CountTokensRequest.swift @@ -16,24 +16,21 @@ import Foundation @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) struct CountTokensRequest { - let model: String - - let contents: [ModelContent] - let systemInstruction: ModelContent? - let tools: [Tool]? - let generationConfig: GenerationConfig? - - let apiConfig: APIConfig - let options: RequestOptions + let generateContentRequest: GenerateContentRequest } @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension CountTokensRequest: GenerativeAIRequest { typealias Response = CountTokensResponse + var options: RequestOptions { generateContentRequest.options } + + var apiConfig: APIConfig { generateContentRequest.apiConfig } + var url: URL { - URL(string: - "\(apiConfig.service.endpoint.rawValue)/\(apiConfig.version.rawValue)/\(model):countTokens")! + let version = apiConfig.version.rawValue + let endpoint = apiConfig.service.endpoint.rawValue + return URL(string: "\(endpoint)/\(version)/\(generateContentRequest.model):countTokens")! } } @@ -57,12 +54,42 @@ public struct CountTokensResponse { @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension CountTokensRequest: Encodable { - enum CodingKeys: CodingKey { + enum VertexCodingKeys: CodingKey { case contents case systemInstruction case tools case generationConfig } + + enum DeveloperCodingKeys: CodingKey { + case generateContentRequest + } + + func encode(to encoder: any Encoder) throws { + switch apiConfig.service { + case .vertexAI: + try encodeForVertexAI(to: encoder) + case .developer: + try encodeForDeveloper(to: encoder) + } + } + + private func encodeForVertexAI(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: VertexCodingKeys.self) + try container.encode(generateContentRequest.contents, forKey: .contents) + try container.encodeIfPresent( + generateContentRequest.systemInstruction, forKey: .systemInstruction + ) + try container.encodeIfPresent(generateContentRequest.tools, forKey: .tools) + try container.encodeIfPresent( + generateContentRequest.generationConfig, forKey: .generationConfig + ) + } + + private func encodeForDeveloper(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: DeveloperCodingKeys.self) + try container.encode(generateContentRequest, forKey: .generateContentRequest) + } } @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/Constants.swift b/FirebaseVertexAI/Tests/TestApp/Sources/Constants.swift index 7e2f450597a..f26fec45fb3 100644 --- a/FirebaseVertexAI/Tests/TestApp/Sources/Constants.swift +++ b/FirebaseVertexAI/Tests/TestApp/Sources/Constants.swift @@ -21,5 +21,6 @@ public enum FirebaseAppNames { } public enum ModelNames { + public static let gemini2Flash = "gemini-2.0-flash-001" public static let gemini2FlashLite = "gemini-2.0-flash-lite-001" } diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift new file mode 100644 index 00000000000..bd1a5b9d935 --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift @@ -0,0 +1,122 @@ +// Copyright 2025 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 FirebaseAuth +import FirebaseCore +import FirebaseStorage +import FirebaseVertexAI +import Testing +import VertexAITestApp + +@testable import struct FirebaseVertexAI.APIConfig + +@Suite(.serialized) +struct CountTokensIntegrationTests { + let generationConfig = GenerationConfig( + temperature: 1.2, + topP: 0.95, + topK: 32, + candidateCount: 1, + maxOutputTokens: 8192, + presencePenalty: 1.5, + frequencyPenalty: 1.75, + stopSequences: ["cat", "dog", "bird"] + ) + let safetySettings = [ + SafetySetting(harmCategory: .harassment, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .hateSpeech, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .sexuallyExplicit, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .dangerousContent, threshold: .blockLowAndAbove), + SafetySetting(harmCategory: .civicIntegrity, threshold: .blockLowAndAbove), + ] + let systemInstruction = ModelContent( + role: "system", + parts: "You are a friendly and helpful assistant." + ) + + @Test(arguments: InstanceConfig.allConfigs) + func countTokens_text(_ config: InstanceConfig) async throws { + let prompt = "Why is the sky blue?" + let model = VertexAI.componentInstance(config).generativeModel( + modelName: ModelNames.gemini2Flash, + generationConfig: generationConfig, + safetySettings: safetySettings + ) + + let response = try await model.countTokens(prompt) + + #expect(response.totalTokens == 6) + switch config.apiConfig.service { + case .vertexAI: + #expect(response.totalBillableCharacters == 16) + case .developer: + #expect(response.totalBillableCharacters == nil) + } + #expect(response.promptTokensDetails.count == 1) + let promptTokensDetails = try #require(response.promptTokensDetails.first) + #expect(promptTokensDetails.modality == .text) + #expect(promptTokensDetails.tokenCount == response.totalTokens) + } + + @Test(arguments: [ + InstanceConfig.vertexV1, + InstanceConfig.vertexV1Beta, + /* System instructions are not supported on the v1 Developer API. */ + InstanceConfig.developerV1Beta, + ]) + func countTokens_text_systemInstruction(_ config: InstanceConfig) async throws { + let model = VertexAI.componentInstance(config).generativeModel( + modelName: ModelNames.gemini2Flash, + generationConfig: generationConfig, + safetySettings: safetySettings, + systemInstruction: systemInstruction // Not supported on the v1 Developer API + ) + + let response = try await model.countTokens("What is your favourite colour?") + + #expect(response.totalTokens == 14) + switch config.apiConfig.service { + case .vertexAI: + #expect(response.totalBillableCharacters == 61) + case .developer: + #expect(response.totalBillableCharacters == nil) + } + #expect(response.promptTokensDetails.count == 1) + let promptTokensDetails = try #require(response.promptTokensDetails.first) + #expect(promptTokensDetails.modality == .text) + #expect(promptTokensDetails.tokenCount == response.totalTokens) + } + + @Test(arguments: [ + /* System instructions are not supported on the v1 Developer API. */ + InstanceConfig.developerV1, + ]) + func countTokens_text_systemInstruction_unsupported(_ config: InstanceConfig) async throws { + let model = VertexAI.componentInstance(config).generativeModel( + modelName: ModelNames.gemini2Flash, + systemInstruction: systemInstruction // Not supported on the v1 Developer API + ) + + try await #require( + throws: BackendError.self, + """ + If this test fails (i.e., `countTokens` succeeds), remove \(config) from this test and add it + to `countTokens_text_systemInstruction`. + """, + performing: { + try await model.countTokens("What is your favourite colour?") + } + ) + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index de0b2e76556..92ebe09d159 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift @@ -19,29 +19,8 @@ import FirebaseVertexAI import Testing import VertexAITestApp -@testable import struct FirebaseVertexAI.APIConfig - @Suite(.serialized) struct GenerateContentIntegrationTests { - static let vertexV1Config = - InstanceConfig(apiConfig: APIConfig(service: .vertexAI, version: .v1)) - static let vertexV1BetaConfig = - InstanceConfig(apiConfig: APIConfig(service: .vertexAI, version: .v1beta)) - static let developerV1Config = InstanceConfig( - appName: FirebaseAppNames.spark, - apiConfig: APIConfig( - service: .developer(endpoint: .generativeLanguage), version: .v1 - ) - ) - static let developerV1BetaConfig = InstanceConfig( - appName: FirebaseAppNames.spark, - apiConfig: APIConfig( - service: .developer(endpoint: .generativeLanguage), version: .v1beta - ) - ) - static let allConfigs = - [vertexV1Config, vertexV1BetaConfig, developerV1Config, developerV1BetaConfig] - // Set temperature, topP and topK to lowest allowed values to make responses more deterministic. let generationConfig = GenerationConfig(temperature: 0.0, topP: 0.0, topK: 1) let safetySettings = [ @@ -67,7 +46,7 @@ struct GenerateContentIntegrationTests { storage = Storage.storage() } - @Test(arguments: allConfigs) + @Test(arguments: InstanceConfig.allConfigs) func generateContent(_ config: InstanceConfig) async throws { let model = VertexAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, @@ -98,10 +77,10 @@ struct GenerateContentIntegrationTests { @Test( "Generate an enum and provide a system instruction", arguments: [ - vertexV1Config, - vertexV1BetaConfig, + InstanceConfig.vertexV1, + InstanceConfig.vertexV1Beta, /* System instructions are not supported on the v1 Developer API. */ - developerV1BetaConfig, + InstanceConfig.developerV1Beta, ] ) func generateContentEnum(_ config: InstanceConfig) async throws { diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/VertexAITestUtils.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift similarity index 57% rename from FirebaseVertexAI/Tests/TestApp/Tests/Utilities/VertexAITestUtils.swift rename to FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index f76bd8ff148..7c233e94f7a 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/VertexAITestUtils.swift +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -13,12 +13,27 @@ // limitations under the License. import FirebaseCore +import Testing import VertexAITestApp @testable import struct FirebaseVertexAI.APIConfig @testable import class FirebaseVertexAI.VertexAI struct InstanceConfig { + static let vertexV1 = InstanceConfig(apiConfig: APIConfig(service: .vertexAI, version: .v1)) + static let vertexV1Beta = InstanceConfig( + apiConfig: APIConfig(service: .vertexAI, version: .v1beta) + ) + static let developerV1 = InstanceConfig( + appName: FirebaseAppNames.spark, + apiConfig: APIConfig(service: .developer(endpoint: .generativeLanguage), version: .v1) + ) + static let developerV1Beta = InstanceConfig( + appName: FirebaseAppNames.spark, + apiConfig: APIConfig(service: .developer(endpoint: .generativeLanguage), version: .v1beta) + ) + static let allConfigs = [vertexV1, vertexV1Beta, developerV1, developerV1Beta] + let appName: String? let location: String? let apiConfig: APIConfig @@ -32,6 +47,35 @@ struct InstanceConfig { var app: FirebaseApp? { return appName.map { FirebaseApp.app(name: $0) } ?? FirebaseApp.app() } + + var serviceName: String { + switch apiConfig.service { + case .vertexAI: + return "Vertex AI" + case .developer: + return "Developer" + } + } + + var versionName: String { + return apiConfig.version.rawValue + } +} + +extension InstanceConfig: CustomTestStringConvertible { + var testDescription: String { + let endpointSuffix = switch apiConfig.service.endpoint { + case .firebaseVertexAIProd: + "" + case .firebaseVertexAIStaging: + " - Staging" + case .generativeLanguage: + " - Generative Language" + } + let locationSuffix = location.map { " - \($0)" } ?? "" + + return "\(serviceName) (\(versionName))\(endpointSuffix)\(locationSuffix)" + } } extension VertexAI { diff --git a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj index 29f333bf127..6f13a62472e 100644 --- a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj +++ b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 8661385C2CC943DD00F4B78E /* TestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8661385B2CC943DD00F4B78E /* TestApp.swift */; }; 8661385E2CC943DD00F4B78E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8661385D2CC943DD00F4B78E /* ContentView.swift */; }; 8661386E2CC943DE00F4B78E /* IntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8661386D2CC943DE00F4B78E /* IntegrationTests.swift */; }; + 8689CDCC2D7F8BD700BF426B /* CountTokensIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8689CDCB2D7F8BCF00BF426B /* CountTokensIntegrationTests.swift */; }; 868A7C482CCA931B00E449DD /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 868A7C462CCA931B00E449DD /* GoogleService-Info.plist */; }; 868A7C4F2CCC229F00E449DD /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868A7C4D2CCC1F4700E449DD /* Credentials.swift */; }; 868A7C522CCC263300E449DD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 868A7C502CCC263300E449DD /* Preview Assets.xcassets */; }; @@ -25,7 +26,7 @@ 86D77DFC2D7A5340003D155D /* GenerateContentIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D77DFB2D7A5340003D155D /* GenerateContentIntegrationTests.swift */; }; 86D77DFE2D7B5C86003D155D /* GoogleService-Info-Spark.plist in Resources */ = {isa = PBXBuildFile; fileRef = 86D77DFD2D7B5C86003D155D /* GoogleService-Info-Spark.plist */; }; 86D77E022D7B63AF003D155D /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D77E012D7B63AC003D155D /* Constants.swift */; }; - 86D77E042D7B6C9D003D155D /* VertexAITestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D77E032D7B6C95003D155D /* VertexAITestUtils.swift */; }; + 86D77E042D7B6C9D003D155D /* InstanceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D77E032D7B6C95003D155D /* InstanceConfig.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -46,6 +47,7 @@ 8661385D2CC943DD00F4B78E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 866138692CC943DE00F4B78E /* IntegrationTests-SPM.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "IntegrationTests-SPM.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 8661386D2CC943DE00F4B78E /* IntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationTests.swift; sourceTree = ""; }; + 8689CDCB2D7F8BCF00BF426B /* CountTokensIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountTokensIntegrationTests.swift; sourceTree = ""; }; 868A7C462CCA931B00E449DD /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 868A7C4D2CCC1F4700E449DD /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; 868A7C502CCC263300E449DD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; @@ -56,7 +58,7 @@ 86D77DFB2D7A5340003D155D /* GenerateContentIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentIntegrationTests.swift; sourceTree = ""; }; 86D77DFD2D7B5C86003D155D /* GoogleService-Info-Spark.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info-Spark.plist"; sourceTree = ""; }; 86D77E012D7B63AC003D155D /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; - 86D77E032D7B6C95003D155D /* VertexAITestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VertexAITestUtils.swift; sourceTree = ""; }; + 86D77E032D7B6C95003D155D /* InstanceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceConfig.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -134,6 +136,7 @@ 868A7C572CCC27AF00E449DD /* Integration */ = { isa = PBXGroup; children = ( + 8689CDCB2D7F8BCF00BF426B /* CountTokensIntegrationTests.swift */, 868A7C4D2CCC1F4700E449DD /* Credentials.swift */, 8661386D2CC943DE00F4B78E /* IntegrationTests.swift */, 86D77DFB2D7A5340003D155D /* GenerateContentIntegrationTests.swift */, @@ -154,7 +157,7 @@ 8698D7442CD3CEF700ABA833 /* Utilities */ = { isa = PBXGroup; children = ( - 86D77E032D7B6C95003D155D /* VertexAITestUtils.swift */, + 86D77E032D7B6C95003D155D /* InstanceConfig.swift */, 8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */, 862218802D04E08D007ED2D4 /* IntegrationTestUtils.swift */, ); @@ -283,7 +286,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 86D77E042D7B6C9D003D155D /* VertexAITestUtils.swift in Sources */, + 8689CDCC2D7F8BD700BF426B /* CountTokensIntegrationTests.swift in Sources */, + 86D77E042D7B6C9D003D155D /* InstanceConfig.swift in Sources */, 8698D7462CD3CF3600ABA833 /* FirebaseAppTestUtils.swift in Sources */, 868A7C4F2CCC229F00E449DD /* Credentials.swift in Sources */, 864F8F712D4980DD0002EA7E /* ImagenIntegrationTests.swift in Sources */, diff --git a/FirebaseVertexAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift b/FirebaseVertexAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift new file mode 100644 index 00000000000..fd03f0be5a1 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift @@ -0,0 +1,111 @@ +// Copyright 2025 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 +import XCTest + +@testable import FirebaseVertexAI + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class CountTokensRequestTests: XCTestCase { + let encoder = JSONEncoder() + + let modelResourceName = "models/test-model-name" + let textPart = TextPart("test-prompt") + let vertexAPIConfig = APIConfig(service: .vertexAI, version: .v1beta) + let developerAPIConfig = APIConfig( + service: .developer(endpoint: .firebaseVertexAIProd), + version: .v1beta + ) + let requestOptions = RequestOptions() + + override func setUp() { + encoder.outputFormatting = .init( + arrayLiteral: .prettyPrinted, .sortedKeys, .withoutEscapingSlashes + ) + } + + // MARK: CountTokensRequest Encoding + + func testEncodeCountTokensRequest_vertexAI_minimal() throws { + let content = ModelContent(role: nil, parts: [textPart]) + let generateContentRequest = GenerateContentRequest( + model: modelResourceName, + contents: [content], + generationConfig: nil, + safetySettings: nil, + tools: nil, + toolConfig: nil, + systemInstruction: nil, + apiConfig: vertexAPIConfig, + apiMethod: .countTokens, + options: requestOptions + ) + let request = CountTokensRequest(generateContentRequest: generateContentRequest) + + let jsonData = try encoder.encode(request) + + let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) + XCTAssertEqual(json, """ + { + "contents" : [ + { + "parts" : [ + { + "text" : "\(textPart.text)" + } + ] + } + ] + } + """) + } + + func testEncodeCountTokensRequest_developerAPI_minimal() throws { + let content = ModelContent(role: nil, parts: [textPart]) + let generateContentRequest = GenerateContentRequest( + model: modelResourceName, + contents: [content], + generationConfig: nil, + safetySettings: nil, + tools: nil, + toolConfig: nil, + systemInstruction: nil, + apiConfig: developerAPIConfig, + apiMethod: .countTokens, + options: requestOptions + ) + let request = CountTokensRequest(generateContentRequest: generateContentRequest) + + let jsonData = try encoder.encode(request) + + let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8)) + XCTAssertEqual(json, """ + { + "generateContentRequest" : { + "contents" : [ + { + "parts" : [ + { + "text" : "\(textPart.text)" + } + ] + } + ], + "model" : "\(modelResourceName)" + } + } + """) + } +} From 0f7ada04836a47761bdaea7254b610ee8cc41874 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 17 Mar 2025 07:46:47 -0700 Subject: [PATCH 03/21] [vertex-ai] App Version for AI Monitoring (#14568) --- FirebaseVertexAI/Sources/GenerativeAIService.swift | 10 ++++++---- .../Tests/Unit/GenerativeModelTests.swift | 11 ++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index de8a18ee333..eff35fb9b20 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -193,10 +193,12 @@ struct GenerativeAIService { urlRequest.setValue("Firebase \(authToken)", forHTTPHeaderField: "Authorization") } - // TODO: wait for release approval. -// if firebaseInfo.app.isDataCollectionDefaultEnabled { -// urlRequest.setValue(firebaseInfo.googleAppID, forHTTPHeaderField: "X-Firebase-AppId") -// } + if firebaseInfo.app.isDataCollectionDefaultEnabled { + urlRequest.setValue(firebaseInfo.googleAppID, forHTTPHeaderField: "X-Firebase-AppId") + if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + urlRequest.setValue(appVersion, forHTTPHeaderField: "X-Firebase-AppVersion") + } + } let encoder = JSONEncoder() urlRequest.httpBody = try encoder.encode(request) diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index 891e4bc359e..c5cbcb4172b 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1530,9 +1530,14 @@ final class GenerativeModelTests: XCTestCase { XCTAssert(apiClientTags.contains(GenerativeAIService.languageTag)) XCTAssert(apiClientTags.contains(GenerativeAIService.firebaseVersionTag)) XCTAssertEqual(request.value(forHTTPHeaderField: "X-Firebase-AppCheck"), appCheckToken) - // TODO: Wait for release approval - // let googleAppID = request.value(forHTTPHeaderField: "X-Firebase-AppId") - // XCTAssertEqual(googleAppID, dataCollection ? "My app ID" : nil) + + let googleAppID = request.value(forHTTPHeaderField: "X-Firebase-AppId") + let appVersion = request.value(forHTTPHeaderField: "X-Firebase-AppVersion") + let expectedAppVersion = + try? XCTUnwrap(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) + XCTAssertEqual(googleAppID, dataCollection ? "My app ID" : nil) + XCTAssertEqual(appVersion, dataCollection ? expectedAppVersion : nil) + if let authToken { XCTAssertEqual(request.value(forHTTPHeaderField: "Authorization"), "Firebase \(authToken)") } else { From 487c97a29c6de20130c8e58e0e02dd07f14e81be Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 17 Mar 2025 10:50:45 -0400 Subject: [PATCH 04/21] [Swift 6] Sessions Swift 6 changes (#14575) --- .../Sources/FirebaseSessions.swift | 5 +- .../Sources/Public/SessionsDependencies.swift | 62 ++++++++++++++++--- .../Library/FirebaseSessionsTestsBase.swift | 2 +- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/FirebaseSessions/Sources/FirebaseSessions.swift b/FirebaseSessions/Sources/FirebaseSessions.swift index 978d596a6ec..2358f586404 100644 --- a/FirebaseSessions/Sources/FirebaseSessions.swift +++ b/FirebaseSessions/Sources/FirebaseSessions.swift @@ -149,13 +149,14 @@ private enum GoogleDataTransportConfig { super.init() - for subscriberName in SessionsDependencies.dependencies { + let dependencies = SessionsDependencies.dependencies + for subscriberName in dependencies { subscriberPromises[subscriberName] = Promise.pending() } Logger .logDebug( - "Version \(FirebaseVersion()). Expecting subscriptions from: \(SessionsDependencies.dependencies)" + "Version \(FirebaseVersion()). Expecting subscriptions from: \(dependencies)" ) self.initiator.beginListening { diff --git a/FirebaseSessions/Sources/Public/SessionsDependencies.swift b/FirebaseSessions/Sources/Public/SessionsDependencies.swift index 078fd3ce397..4f366df9ba4 100644 --- a/FirebaseSessions/Sources/Public/SessionsDependencies.swift +++ b/FirebaseSessions/Sources/Public/SessionsDependencies.swift @@ -15,18 +15,62 @@ import Foundation -// Sessions Dependencies determines when a dependent SDK is -// installed in the app. The Sessions SDK uses this to figure -// out which dependencies to wait for to getting the data -// collection state. -// -// This is important because the Sessions SDK starts up before -// dependent SDKs +private final class AtomicBox { + private var _value: T + private let lock = NSLock() + + init(_ value: T) { + _value = value + } + + func value() -> T { + lock.withLock { + _value + } + } + + @discardableResult func withLock(_ body: (_ value: inout T) -> Void) -> T { + lock.withLock { + body(&_value) + return _value + } + } +} + +/// Sessions Dependencies determines when a dependent SDK is +/// installed in the app. The Sessions SDK uses this to figure +/// out which dependencies to wait for to getting the data +/// collection state. +/// +/// This is important because the Sessions SDK starts up before +/// dependent SDKs @objc(FIRSessionsDependencies) public class SessionsDependencies: NSObject { - static var dependencies: Set = .init() + #if compiler(>=6) + private nonisolated(unsafe) static let _dependencies: AtomicBox> = + AtomicBox( + Set() + ) + #else + private static let _dependencies: AtomicBox> = AtomicBox( + Set() + ) + #endif + + static var dependencies: Set { + _dependencies.value() + } @objc public static func addDependency(name: SessionsSubscriberName) { - SessionsDependencies.dependencies.insert(name) + _dependencies.withLock { dependencies in + dependencies.insert(name) + } + } + + /// For testing only. + static func removeAll() { + _dependencies.withLock { dependencies in + dependencies.removeAll() + } } } diff --git a/FirebaseSessions/Tests/Unit/Library/FirebaseSessionsTestsBase.swift b/FirebaseSessions/Tests/Unit/Library/FirebaseSessionsTestsBase.swift index aab83d2f217..fed12144691 100644 --- a/FirebaseSessions/Tests/Unit/Library/FirebaseSessionsTestsBase.swift +++ b/FirebaseSessions/Tests/Unit/Library/FirebaseSessionsTestsBase.swift @@ -77,7 +77,7 @@ class FirebaseSessionsTestsBase: XCTestCase { postLogEvent: @escaping (Result, [SessionsSubscriber]) -> Void) { // This class is static, so we need to clear global state - SessionsDependencies.dependencies.removeAll() + SessionsDependencies.removeAll() for subscriberSDK in subscriberSDKs { SessionsDependencies.addDependency(name: subscriberSDK.sessionsSubscriberName) From 5e97e05cfd442a00ada5a60a5756188fbd43d922 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 17 Mar 2025 09:46:24 -0700 Subject: [PATCH 05/21] [vertex-ai] Rename googleAppID to firebaseAppID (#14587) --- FirebaseVertexAI/Sources/FirebaseInfo.swift | 6 +++--- FirebaseVertexAI/Sources/GenerativeAIService.swift | 2 +- FirebaseVertexAI/Sources/VertexAI.swift | 2 +- FirebaseVertexAI/Tests/Unit/ChatTests.swift | 2 +- FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/FirebaseVertexAI/Sources/FirebaseInfo.swift b/FirebaseVertexAI/Sources/FirebaseInfo.swift index 8bd705e4b29..13f4db4f3e7 100644 --- a/FirebaseVertexAI/Sources/FirebaseInfo.swift +++ b/FirebaseVertexAI/Sources/FirebaseInfo.swift @@ -26,20 +26,20 @@ struct FirebaseInfo: Sendable { let auth: AuthInterop? let projectID: String let apiKey: String - let googleAppID: String + let firebaseAppID: String let app: FirebaseApp init(appCheck: AppCheckInterop? = nil, auth: AuthInterop? = nil, projectID: String, apiKey: String, - googleAppID: String, + firebaseAppID: String, firebaseApp: FirebaseApp) { self.appCheck = appCheck self.auth = auth self.projectID = projectID self.apiKey = apiKey - self.googleAppID = googleAppID + self.firebaseAppID = firebaseAppID app = firebaseApp } } diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index eff35fb9b20..daa400ce594 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -194,7 +194,7 @@ struct GenerativeAIService { } if firebaseInfo.app.isDataCollectionDefaultEnabled { - urlRequest.setValue(firebaseInfo.googleAppID, forHTTPHeaderField: "X-Firebase-AppId") + urlRequest.setValue(firebaseInfo.firebaseAppID, forHTTPHeaderField: "X-Firebase-AppId") if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { urlRequest.setValue(appVersion, forHTTPHeaderField: "X-Firebase-AppVersion") } diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index f0b427af59a..2d0bf650711 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -177,7 +177,7 @@ public class VertexAI { auth: ComponentType.instance(for: AuthInterop.self, in: app.container), projectID: projectID, apiKey: apiKey, - googleAppID: app.options.googleAppID, + firebaseAppID: app.options.googleAppID, firebaseApp: app ) self.apiConfig = apiConfig diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 4e8a1ae0f73..3e790379238 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -62,7 +62,7 @@ final class ChatTests: XCTestCase { firebaseInfo: FirebaseInfo( projectID: "my-project-id", apiKey: "API_KEY", - googleAppID: "My app ID", + firebaseAppID: "My app ID", firebaseApp: app ), apiConfig: APIConfig(service: .vertexAI, version: .v1beta), diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index c5cbcb4172b..c4947eb93e1 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -1478,7 +1478,7 @@ final class GenerativeModelTests: XCTestCase { auth: auth, projectID: "my-project-id", apiKey: "API_KEY", - googleAppID: "My app ID", + firebaseAppID: "My app ID", firebaseApp: app ) } @@ -1531,11 +1531,11 @@ final class GenerativeModelTests: XCTestCase { XCTAssert(apiClientTags.contains(GenerativeAIService.firebaseVersionTag)) XCTAssertEqual(request.value(forHTTPHeaderField: "X-Firebase-AppCheck"), appCheckToken) - let googleAppID = request.value(forHTTPHeaderField: "X-Firebase-AppId") + let firebaseAppID = request.value(forHTTPHeaderField: "X-Firebase-AppId") let appVersion = request.value(forHTTPHeaderField: "X-Firebase-AppVersion") let expectedAppVersion = try? XCTUnwrap(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) - XCTAssertEqual(googleAppID, dataCollection ? "My app ID" : nil) + XCTAssertEqual(firebaseAppID, dataCollection ? "My app ID" : nil) XCTAssertEqual(appVersion, dataCollection ? expectedAppVersion : nil) if let authToken { From 3bc52e3fc1086b7477a645a1ed2cc4a6d59b185b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 17 Mar 2025 17:20:27 -0400 Subject: [PATCH 06/21] [Vertex AI] Read golden files from `vertexai` subdirectory (#14582) --- FirebaseVertexAI.podspec | 2 +- FirebaseVertexAI/Tests/Unit/ChatTests.swift | 3 +- .../Tests/Unit/GenerativeModelTests.swift | 147 +++++++++++++----- Package.swift | 2 +- 4 files changed, 109 insertions(+), 45 deletions(-) diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index f435ff4b9bf..2b58577bd76 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -61,7 +61,7 @@ Firebase SDK. unit_tests_dir + '**/*.swift', ] unit_tests.resources = [ - unit_tests_dir + 'vertexai-sdk-test-data/mock-responses/**/*.{txt,json}', + unit_tests_dir + 'vertexai-sdk-test-data/mock-responses/vertexai', unit_tests_dir + 'Resources/**/*', ] end diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 3e790379238..5215739cb1e 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -36,7 +36,8 @@ final class ChatTests: XCTestCase { let bundle = BundleTestUtil.bundle() let fileURL = try XCTUnwrap(bundle.url( forResource: "streaming-success-basic-reply-parts", - withExtension: "txt" + withExtension: "txt", + subdirectory: "vertexai" )) // Skip tests using MockURLProtocol on watchOS; unsupported in watchOS 2 and later, see diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index c4947eb93e1..b9a53dc7732 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -60,6 +60,8 @@ final class GenerativeModelTests: XCTestCase { "projects/test-project-id/locations/test-location/publishers/google/models/test-model" let apiConfig = APIConfig(service: .vertexAI, version: .v1beta) + let vertexSubdirectory = "vertexai" + var urlSession: URLSession! var model: GenerativeModel! @@ -87,7 +89,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-long", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -109,7 +112,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -131,7 +135,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-response-long-usage-metadata", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -155,7 +160,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-citations", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let expectedPublicationDate = DateComponents( calendar: Calendar(identifier: .gregorian), @@ -199,7 +205,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-quote-reply", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -249,7 +256,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-unknown-enum-safety-ratings", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -263,7 +271,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let model = GenerativeModel( // Model name is prefixed with "models/". @@ -282,7 +291,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-function-call-empty-arguments", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -304,7 +314,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-function-call-no-arguments", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -326,7 +337,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-function-call-with-arguments", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -352,7 +364,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-function-call-parallel-calls", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -368,7 +381,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-function-call-mixed-content", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -396,6 +410,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", withExtension: "json", + subdirectory: vertexSubdirectory, appCheckToken: appCheckToken ) @@ -417,6 +432,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", withExtension: "json", + subdirectory: vertexSubdirectory, appCheckToken: appCheckToken, dataCollection: false ) @@ -437,6 +453,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", withExtension: "json", + subdirectory: vertexSubdirectory, appCheckToken: AppCheckInteropFake.placeholderTokenValue ) @@ -457,6 +474,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", withExtension: "json", + subdirectory: vertexSubdirectory, authToken: authToken ) @@ -476,6 +494,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", withExtension: "json", + subdirectory: vertexSubdirectory, authToken: nil ) @@ -495,6 +514,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", withExtension: "json", + subdirectory: vertexSubdirectory, authToken: nil ) @@ -512,7 +532,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.generateContent(testPrompt) @@ -531,6 +552,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-failure-api-key", withExtension: "json", + subdirectory: vertexSubdirectory, statusCode: expectedStatusCode ) @@ -559,6 +581,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-failure-firebasevertexai-api-not-enabled", withExtension: "json", + subdirectory: vertexSubdirectory, statusCode: expectedStatusCode ) @@ -581,7 +604,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-empty-content", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) do { @@ -604,7 +628,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-finish-reason-safety", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) do { @@ -622,7 +647,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-finish-reason-safety-no-content", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) do { @@ -642,6 +668,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-failure-image-rejected", withExtension: "json", + subdirectory: vertexSubdirectory, statusCode: 400 ) @@ -661,7 +688,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-prompt-blocked-safety", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) do { @@ -681,7 +709,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-prompt-blocked-safety-with-message", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) do { @@ -701,7 +730,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-unknown-enum-finish-reason", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let unknownFinishReason = FinishReason(rawValue: "FAKE_NEW_FINISH_REASON") @@ -720,7 +750,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-unknown-enum-prompt-blocked", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let unknownBlockReason = PromptFeedback.BlockReason(rawValue: "FAKE_NEW_BLOCK_REASON") @@ -741,6 +772,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-failure-unknown-model", withExtension: "json", + subdirectory: vertexSubdirectory, statusCode: 404 ) @@ -783,7 +815,8 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContent_failure_invalidResponse() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "unary-failure-invalid-response", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) var responseError: Error? @@ -813,7 +846,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-malformed-content", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) var responseError: Error? @@ -845,7 +879,8 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContentMissingSafetyRatings() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "unary-success-missing-safety-ratings", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let content = try await model.generateContent(testPrompt) @@ -860,6 +895,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-success-basic-reply-short", withExtension: "json", + subdirectory: vertexSubdirectory, timeout: expectedTimeout ) let requestOptions = RequestOptions(timeout: expectedTimeout) @@ -883,7 +919,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "unary-failure-api-key", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) do { @@ -913,6 +950,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-failure-firebasevertexai-api-not-enabled", withExtension: "json", + subdirectory: vertexSubdirectory, statusCode: expectedStatusCode ) @@ -937,7 +975,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-failure-empty-content", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) do { @@ -957,7 +996,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-failure-finish-reason-safety", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) do { @@ -980,7 +1020,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-failure-prompt-blocked-safety", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) do { @@ -1002,7 +1043,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-failure-prompt-blocked-safety-with-message", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) do { @@ -1024,7 +1066,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-failure-unknown-finish-enum", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) let unknownFinishReason = FinishReason(rawValue: "FAKE_ENUM") @@ -1045,7 +1088,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-success-basic-reply-long", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) var responses = 0 @@ -1062,7 +1106,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-success-basic-reply-short", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) var responses = 0 @@ -1079,7 +1124,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-success-unknown-safety-enum", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) let unknownSafetyRating = SafetyRating( category: HarmCategory(rawValue: "HARM_CATEGORY_DANGEROUS_CONTENT_NEW_ENUM"), @@ -1107,7 +1153,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-success-citations", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) let expectedPublicationDate = DateComponents( calendar: Calendar(identifier: .gregorian), @@ -1168,6 +1215,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "streaming-success-basic-reply-short", withExtension: "txt", + subdirectory: vertexSubdirectory, appCheckToken: appCheckToken ) @@ -1188,6 +1236,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "streaming-success-basic-reply-short", withExtension: "txt", + subdirectory: vertexSubdirectory, appCheckToken: AppCheckInteropFake.placeholderTokenValue ) @@ -1199,7 +1248,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-success-basic-reply-short", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) var responses = [GenerateContentResponse]() @@ -1224,7 +1274,8 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContentStream_errorMidStream() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "streaming-failure-error-mid-stream", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) var responseCount = 0 @@ -1266,7 +1317,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-failure-invalid-json", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) let stream = try model.generateContentStream(testPrompt) @@ -1290,7 +1342,8 @@ final class GenerativeModelTests: XCTestCase { MockURLProtocol .requestHandler = try httpRequestHandler( forResource: "streaming-failure-malformed-content", - withExtension: "txt" + withExtension: "txt", + subdirectory: vertexSubdirectory ) let stream = try model.generateContentStream(testPrompt) @@ -1317,6 +1370,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "streaming-success-basic-reply-short", withExtension: "txt", + subdirectory: vertexSubdirectory, timeout: expectedTimeout ) let requestOptions = RequestOptions(timeout: expectedTimeout) @@ -1344,7 +1398,8 @@ final class GenerativeModelTests: XCTestCase { func testCountTokens_succeeds() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "unary-success-total-tokens", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.countTokens("Why is the sky blue?") @@ -1356,7 +1411,8 @@ final class GenerativeModelTests: XCTestCase { func testCountTokens_succeeds_detailed() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "unary-success-detailed-token-response", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.countTokens("Why is the sky blue?") @@ -1373,7 +1429,8 @@ final class GenerativeModelTests: XCTestCase { func testCountTokens_succeeds_allOptions() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "unary-success-total-tokens", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let generationConfig = GenerationConfig( temperature: 0.5, @@ -1413,7 +1470,8 @@ final class GenerativeModelTests: XCTestCase { func testCountTokens_succeeds_noBillableCharacters() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "unary-success-no-billable-characters", - withExtension: "json" + withExtension: "json", + subdirectory: vertexSubdirectory ) let response = try await model.countTokens(InlineDataPart(data: Data(), mimeType: "image/jpeg")) @@ -1425,6 +1483,7 @@ final class GenerativeModelTests: XCTestCase { func testCountTokens_modelNotFound() async throws { MockURLProtocol.requestHandler = try httpRequestHandler( forResource: "unary-failure-model-not-found", withExtension: "json", + subdirectory: vertexSubdirectory, statusCode: 404 ) @@ -1447,6 +1506,7 @@ final class GenerativeModelTests: XCTestCase { .requestHandler = try httpRequestHandler( forResource: "unary-success-total-tokens", withExtension: "json", + subdirectory: vertexSubdirectory, timeout: expectedTimeout ) let requestOptions = RequestOptions(timeout: expectedTimeout) @@ -1506,6 +1566,7 @@ final class GenerativeModelTests: XCTestCase { private func httpRequestHandler(forResource name: String, withExtension ext: String, + subdirectory subpath: String, statusCode: Int = 200, timeout: TimeInterval = RequestOptions().timeout, appCheckToken: String? = nil, @@ -1520,7 +1581,9 @@ final class GenerativeModelTests: XCTestCase { throw XCTSkip("Custom URL protocols are unsupported in watchOS 2 and later.") #endif // os(watchOS) let bundle = BundleTestUtil.bundle() - let fileURL = try XCTUnwrap(bundle.url(forResource: name, withExtension: ext)) + let fileURL = try XCTUnwrap( + bundle.url(forResource: name, withExtension: ext, subdirectory: subpath) + ) return { request in let requestURL = try XCTUnwrap(request.url) XCTAssertEqual(requestURL.path.occurrenceCount(of: "models/"), 1) diff --git a/Package.swift b/Package.swift index fda6c2ab898..8662687cc1c 100644 --- a/Package.swift +++ b/Package.swift @@ -1318,7 +1318,7 @@ let package = Package( ], path: "FirebaseVertexAI/Tests/Unit", resources: [ - .process("vertexai-sdk-test-data/mock-responses"), + .copy("vertexai-sdk-test-data/mock-responses/vertexai"), .process("Resources"), ], cSettings: [ From 0a18469373e23a5f273156d0e3f423c60a57f4a0 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 18 Mar 2025 10:48:19 -0400 Subject: [PATCH 07/21] [Vertex AI] Add placeholders for `vertexai-sdk-test-data` files (#14593) --- .../Tests/Unit/vertexai-sdk-test-data/mock-responses | 4 ---- .../Unit/vertexai-sdk-test-data/mock-responses/developerapi | 4 ++++ .../Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) delete mode 100644 FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses create mode 100644 FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi create mode 100644 FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai diff --git a/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses b/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses deleted file mode 100644 index 66b851c0602..00000000000 --- a/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses +++ /dev/null @@ -1,4 +0,0 @@ -Placeholder file for Package.swift - required to prevent a warning. - -It should be replaced before running the FirebaseVertexAI unit tests by -running `scripts/update_vertexai_responses.sh`. diff --git a/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi b/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi new file mode 100644 index 00000000000..42eb27553f2 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi @@ -0,0 +1,4 @@ +Placeholder file for Package.swift - required to prevent a warning. + +Run `scripts/update_vertexai_responses.sh` to fetch mock Vertex AI responses +from https://github.com/FirebaseExtended/vertexai-sdk-test-data. diff --git a/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai b/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai new file mode 100644 index 00000000000..42eb27553f2 --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai @@ -0,0 +1,4 @@ +Placeholder file for Package.swift - required to prevent a warning. + +Run `scripts/update_vertexai_responses.sh` to fetch mock Vertex AI responses +from https://github.com/FirebaseExtended/vertexai-sdk-test-data. From 1107d979e997733f956d759a9abf093335f8f96a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 18 Mar 2025 11:04:56 -0400 Subject: [PATCH 08/21] [Release] Carthage for M161 (#14589) --- ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json | 1 + .../CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json | 1 + ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json | 1 + 20 files changed, 20 insertions(+) diff --git a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json index 9620e442515..afd88131f01 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseABTesting-0d992d73cce9103c.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseABTesting-bd865e6158ecfeaa.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseABTesting-1bc00d2361fabe31.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseABTesting-63e10ff7cf44578c.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseABTesting-0d51fde82d49f9e8.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseABTesting-2233510ff87da3b6.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseABTesting-4d0b187af6fd8d67.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json index 4afe24bc1ec..eb76411465a 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/Google-Mobile-Ads-SDK-89717d4c95277aa0.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/Google-Mobile-Ads-SDK-8208c48cf9486f31.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/Google-Mobile-Ads-SDK-35e22051f01c0eaa.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/Google-Mobile-Ads-SDK-de3ae4af5f64bcc2.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/Google-Mobile-Ads-SDK-4f24527af297e7f1.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/Google-Mobile-Ads-SDK-80ba4cb995505158.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/Google-Mobile-Ads-SDK-3df614a58e6a5fa6.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json index 97c0ea030b1..f8f2f552908 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAnalytics-0ada8ccba1c9ac8a.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAnalytics-640e51a50e0916d4.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAnalytics-c0c45b49d7c16d39.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseAnalytics-0b845322b94a28b6.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAnalytics-a93a6c81da535385.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAnalytics-fd2c71a90d62b88a.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAnalytics-525b465eb296d09e.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json index c999d590a17..1d976ca9b1d 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAnalyticsOnDeviceConversion-56efb7cef86436f2.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAnalyticsOnDeviceConversion-d34b43045f6de5d9.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAnalyticsOnDeviceConversion-e0b5f6e47b71efce.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseAnalyticsOnDeviceConversion-77dff0ae699ee1f6.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAnalyticsOnDeviceConversion-09d94624a2de0ac8.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAnalyticsOnDeviceConversion-918bc6e0b7a2fd94.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAnalyticsOnDeviceConversion-1640c514418a23da.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json index df7266d03d3..ec6cebbd16d 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAppCheck-696b7147e94c4910.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAppCheck-2391378293607ac0.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAppCheck-b44ecc329b8672d0.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseAppCheck-fd4219676bad21cb.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAppCheck-d0c5f46e6a2bf4a3.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAppCheck-89c39bdcf0bb90fe.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAppCheck-9b0c4a9489968b07.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json index b3fdaacb6f4..f5f85638260 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAppDistribution-11e51d1485259968.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAppDistribution-6e4980b915e8e59e.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAppDistribution-9415636f92f6e4be.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseAppDistribution-e039cee13bf8daf2.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAppDistribution-9b05f4873b275347.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAppDistribution-6d2eccaccfd3145f.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAppDistribution-20ac94ca344af731.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json index 99f4df0ec6e..3855fdd09dc 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseAuth-d62857535fd583f9.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseAuth-6880d36dd83051b8.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseAuth-41423c3255e3355e.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseAuth-b89a6c5a009a66df.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseAuth-eade26b5390baf84.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseAuth-93dd2965b3f79b98.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseAuth-5faf6dc3bb16c732.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json index 55afd71ba7f..ecf9a43d070 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseCrashlytics-d78fb9954cb2041a.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseCrashlytics-23e5ee21eff49370.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseCrashlytics-573b0427dec2b08b.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseCrashlytics-a77666e0777320c5.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseCrashlytics-13851523ad6df088.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseCrashlytics-282a6f3cf3445787.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseCrashlytics-d5c125d6416f6e0a.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json index fe3f6e035cb..6e5cd72af53 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseDatabase-b129760f802187e1.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseDatabase-5e45f5e1fd19258b.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseDatabase-64e6eeeecc70e513.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseDatabase-4ecbbbf20e609fec.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseDatabase-06dbb1f7d3c8a3e1.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseDatabase-38634b55050b94fe.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseDatabase-ed125984da534e96.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json index d076294ace1..7d7c5aba939 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseDynamicLinks-3c0298de3e855025.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseDynamicLinks-9e4c79d39080bef1.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseDynamicLinks-09b22cea086a30bc.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseDynamicLinks-d1dd0aa4cb2b5df1.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseDynamicLinks-e61c61fa80e5ea8a.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseDynamicLinks-95f7e222d8456304.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseDynamicLinks-f3f9d6cc60c8b832.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json index c86e2cff01a..44ea62e41fb 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseFirestore-9fb513238a7cadf9.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseFirestore-f19512c3374d5feb.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseFirestore-5f76b2878966eea4.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseFirestore-cb0e8707be86f01e.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseFirestore-43af85b854ac842e.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseFirestore-e1283f8cd2e0f3ec.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseFirestore-f5864e67ddbbc9e8.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json index e8ea8afd92c..9d790c52c50 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseFunctions-92bb6b6dfaf293e3.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseFunctions-5249dd848af2df99.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseFunctions-87cffbdd6cbb9512.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseFunctions-88c24c04d7a558d7.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseFunctions-307f00117c2efc62.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseFunctions-02693a7583303912.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseFunctions-8fce8623ed1c6b86.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json index d6e22acd771..c5ebf9b09f5 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/GoogleSignIn-633815a56a62ad0f.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/GoogleSignIn-3287d323d4a251ea.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/GoogleSignIn-dabfaced725377c4.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/GoogleSignIn-7afef6880b412ca5.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/GoogleSignIn-4e8837ef9594b57b.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/GoogleSignIn-8ce1c31ca2236212.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/GoogleSignIn-59eb371d148a2e3a.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json index 8cbc22d790a..36925fa5cfb 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseInAppMessaging-dcbb85b38f4032d1.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseInAppMessaging-076c8e5a966eb715.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseInAppMessaging-7aa1595a55b0f2bd.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseInAppMessaging-01cae82d542f9c95.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseInAppMessaging-6fae0a778e9d3efa.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseInAppMessaging-3a1a331c86520356.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseInAppMessaging-a8054099dd2918b3.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json index 2f4ea2b6a81..7bad308119b 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseMLModelDownloader-2656725e56950b58.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseMLModelDownloader-587e66639052095f.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseMLModelDownloader-4029775a5484e3d2.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseMLModelDownloader-04432073f4438f19.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseMLModelDownloader-d8649822e63fbf7f.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseMLModelDownloader-517f51af92733a7f.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseMLModelDownloader-069609cbcde7e789.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json index e06f16827cb..4d782d02a0a 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseMessaging-908395d688c14419.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseMessaging-31823941cc0a4e8c.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseMessaging-3edcc27744f3aa8e.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseMessaging-841a38305aa0accd.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseMessaging-70e63bb9d9590ded.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseMessaging-8a39834fead3c581.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseMessaging-2d09725e8b98d199.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json index 3130593515c..bd5301e283c 100644 --- a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebasePerformance-ab8a5b884aef1d5e.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebasePerformance-cbde910ed7498ae3.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebasePerformance-e983e4ab114b6122.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebasePerformance-d740f89dee0bfb8e.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebasePerformance-aa174ee3102722d9.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebasePerformance-a489ac7a27d9b53d.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebasePerformance-9a6f62e80c2324f4.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json index 5351c78d59f..f32db1ab3a5 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseRemoteConfig-b613ce2eed93cfc5.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseRemoteConfig-cda0d6b61b66f8e2.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseRemoteConfig-bf6cbcdd97aa9c46.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseRemoteConfig-5bac8829c8bf2dd6.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseRemoteConfig-9a298869ce3cc6db.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseRemoteConfig-940ed38696414882.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseRemoteConfig-ec432e976582d0eb.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json index 6c7b3c7d789..a2cc0c1a772 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json @@ -30,6 +30,7 @@ "10.9.0": "https://dl.google.com/dl/firebase/ios/carthage/10.9.0/FirebaseStorage-98af3136b87c351a.zip", "11.0.0": "https://dl.google.com/dl/firebase/ios/carthage/11.0.0/FirebaseStorage-baee7d21e3743cf6.zip", "11.1.0": "https://dl.google.com/dl/firebase/ios/carthage/11.1.0/FirebaseStorage-f483c715e48ec023.zip", + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseStorage-1e298876c41afe08.zip", "11.2.0": "https://dl.google.com/dl/firebase/ios/carthage/11.2.0/FirebaseStorage-b9b969b0d1254065.zip", "11.3.0": "https://dl.google.com/dl/firebase/ios/carthage/11.3.0/FirebaseStorage-0435eeaa87324cd4.zip", "11.4.0": "https://dl.google.com/dl/firebase/ios/carthage/11.4.0/FirebaseStorage-0b7a2306152984a2.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json b/ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json index 6c00a2b874a..31673af8a9b 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseVertexAIBinary.json @@ -1,4 +1,5 @@ { + "11.10.0": "https://dl.google.com/dl/firebase/ios/carthage/11.10.0/FirebaseVertexAI-db589b3eaf60b8dd.zip", "11.5.0": "https://dl.google.com/dl/firebase/ios/carthage/11.5.0/FirebaseVertexAI-d5d0ffd8010245da.zip", "11.6.0": "https://dl.google.com/dl/firebase/ios/carthage/11.6.0/FirebaseVertexAI-6f6520d750ba54c4.zip", "11.7.0": "https://dl.google.com/dl/firebase/ios/carthage/11.7.0/FirebaseVertexAI-bd6d038eb0cf85c6.zip", From e07ed046fdc9c6a1ac1610442b846d4a7169fdbe Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 18 Mar 2025 13:24:57 -0400 Subject: [PATCH 09/21] [Infra] Update versions for Release 11.11.0 (#14591) --- Firebase.podspec | 48 +++++++++---------- FirebaseABTesting.podspec | 4 +- FirebaseAnalytics.podspec | 8 ++-- FirebaseAnalyticsOnDeviceConversion.podspec | 4 +- FirebaseAppCheck.podspec | 4 +- FirebaseAppCheckInterop.podspec | 2 +- FirebaseAppDistribution.podspec | 4 +- FirebaseAuth.podspec | 6 +-- FirebaseAuthInterop.podspec | 2 +- FirebaseCombineSwift.podspec | 4 +- FirebaseCore.podspec | 4 +- FirebaseCoreExtension.podspec | 4 +- FirebaseCoreInternal.podspec | 2 +- FirebaseCrashlytics.podspec | 4 +- FirebaseDatabase.podspec | 4 +- FirebaseDynamicLinks.podspec | 4 +- FirebaseFirestore.podspec | 8 ++-- FirebaseFirestoreInternal.podspec | 4 +- FirebaseFunctions.podspec | 6 +-- FirebaseInAppMessaging.podspec | 4 +- FirebaseInstallations.podspec | 4 +- FirebaseMLModelDownloader.podspec | 6 +-- FirebaseMessaging.podspec | 4 +- FirebaseMessagingInterop.podspec | 2 +- FirebasePerformance.podspec | 4 +- FirebaseRemoteConfig.podspec | 4 +- FirebaseRemoteConfigInterop.podspec | 2 +- FirebaseSessions.podspec | 6 +-- FirebaseSharedSwift.podspec | 2 +- FirebaseStorage.podspec | 6 +-- FirebaseVertexAI.podspec | 6 +-- GoogleAppMeasurement.podspec | 4 +- ...leAppMeasurementOnDeviceConversion.podspec | 2 +- Package.swift | 2 +- .../FirebaseManifest/FirebaseManifest.swift | 2 +- 35 files changed, 93 insertions(+), 93 deletions(-) diff --git a/Firebase.podspec b/Firebase.podspec index edf2335e02b..66a5ace126d 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Firebase' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase' s.description = <<-DESC @@ -36,14 +36,14 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' - ss.ios.dependency 'FirebaseAnalytics', '~> 11.10.0' - ss.osx.dependency 'FirebaseAnalytics', '~> 11.10.0' - ss.tvos.dependency 'FirebaseAnalytics', '~> 11.10.0' + ss.ios.dependency 'FirebaseAnalytics', '~> 11.11.0' + ss.osx.dependency 'FirebaseAnalytics', '~> 11.11.0' + ss.tvos.dependency 'FirebaseAnalytics', '~> 11.11.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'CoreOnly' do |ss| - ss.dependency 'FirebaseCore', '~> 11.10.0' + ss.dependency 'FirebaseCore', '~> 11.11.0' ss.source_files = 'CoreOnly/Sources/Firebase.h' ss.preserve_paths = 'CoreOnly/Sources/module.modulemap' if ENV['FIREBASE_POD_REPO_FOR_DEV_POD'] then @@ -79,13 +79,13 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' - ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 11.10.0' + ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 11.11.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'ABTesting' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseABTesting', '~> 11.10.0' + ss.dependency 'FirebaseABTesting', '~> 11.11.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -95,13 +95,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'AppDistribution' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseAppDistribution', '~> 11.10.0-beta' + ss.ios.dependency 'FirebaseAppDistribution', '~> 11.11.0-beta' ss.ios.deployment_target = '13.0' end s.subspec 'AppCheck' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAppCheck', '~> 11.10.0' + ss.dependency 'FirebaseAppCheck', '~> 11.11.0' ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' @@ -110,7 +110,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Auth' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAuth', '~> 11.10.0' + ss.dependency 'FirebaseAuth', '~> 11.11.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -120,7 +120,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Crashlytics' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseCrashlytics', '~> 11.10.0' + ss.dependency 'FirebaseCrashlytics', '~> 11.11.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '12.0' ss.osx.deployment_target = '10.15' @@ -130,7 +130,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Database' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseDatabase', '~> 11.10.0' + ss.dependency 'FirebaseDatabase', '~> 11.11.0' # Standard platforms PLUS watchOS 7. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -140,13 +140,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'DynamicLinks' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseDynamicLinks', '~> 11.10.0' + ss.ios.dependency 'FirebaseDynamicLinks', '~> 11.11.0' ss.ios.deployment_target = '13.0' end s.subspec 'Firestore' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFirestore', '~> 11.10.0' + ss.dependency 'FirebaseFirestore', '~> 11.11.0' ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '13.0' @@ -154,7 +154,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Functions' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFunctions', '~> 11.10.0' + ss.dependency 'FirebaseFunctions', '~> 11.11.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -164,20 +164,20 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'InAppMessaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseInAppMessaging', '~> 11.10.0-beta' - ss.tvos.dependency 'FirebaseInAppMessaging', '~> 11.10.0-beta' + ss.ios.dependency 'FirebaseInAppMessaging', '~> 11.11.0-beta' + ss.tvos.dependency 'FirebaseInAppMessaging', '~> 11.11.0-beta' ss.ios.deployment_target = '13.0' ss.tvos.deployment_target = '13.0' end s.subspec 'Installations' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseInstallations', '~> 11.10.0' + ss.dependency 'FirebaseInstallations', '~> 11.11.0' end s.subspec 'Messaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMessaging', '~> 11.10.0' + ss.dependency 'FirebaseMessaging', '~> 11.11.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -187,7 +187,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'MLModelDownloader' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMLModelDownloader', '~> 11.10.0-beta' + ss.dependency 'FirebaseMLModelDownloader', '~> 11.11.0-beta' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -197,15 +197,15 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Performance' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebasePerformance', '~> 11.10.0' - ss.tvos.dependency 'FirebasePerformance', '~> 11.10.0' + ss.ios.dependency 'FirebasePerformance', '~> 11.11.0' + ss.tvos.dependency 'FirebasePerformance', '~> 11.11.0' ss.ios.deployment_target = '13.0' ss.tvos.deployment_target = '13.0' end s.subspec 'RemoteConfig' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseRemoteConfig', '~> 11.10.0' + ss.dependency 'FirebaseRemoteConfig', '~> 11.11.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' @@ -215,7 +215,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Storage' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseStorage', '~> 11.10.0' + ss.dependency 'FirebaseStorage', '~> 11.11.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '13.0' ss.osx.deployment_target = '10.15' diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index 91f1b67347a..524e2809b22 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseABTesting' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase ABTesting' s.description = <<-DESC @@ -52,7 +52,7 @@ Firebase Cloud Messaging and Firebase Remote Config in your app. 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' } - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index ce3c28cd544..80405d67469 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalytics' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Analytics for iOS' s.description = <<-DESC @@ -26,7 +26,7 @@ Pod::Spec.new do |s| s.libraries = 'c++', 'sqlite3', 'z' s.frameworks = 'StoreKit' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/MethodSwizzler', '~> 8.0' @@ -37,12 +37,12 @@ Pod::Spec.new do |s| s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement', '11.10.0' + ss.dependency 'GoogleAppMeasurement', '11.11.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end s.subspec 'WithoutAdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.10.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.11.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end diff --git a/FirebaseAnalyticsOnDeviceConversion.podspec b/FirebaseAnalyticsOnDeviceConversion.podspec index 4b252fcc0bb..c94afe4d555 100644 --- a/FirebaseAnalyticsOnDeviceConversion.podspec +++ b/FirebaseAnalyticsOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalyticsOnDeviceConversion' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'On device conversion measurement plugin for FirebaseAnalytics. Not intended for direct use.' s.description = <<-DESC @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.cocoapods_version = '>= 1.12.0' - s.dependency 'GoogleAppMeasurementOnDeviceConversion', '11.10.0' + s.dependency 'GoogleAppMeasurementOnDeviceConversion', '11.11.0' s.static_framework = true diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index 3917fd9041f..2a7e3381bf8 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheck' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase App Check SDK.' s.description = <<-DESC @@ -46,7 +46,7 @@ Pod::Spec.new do |s| s.dependency 'AppCheckCore', '~> 11.0' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseAppCheckInterop.podspec b/FirebaseAppCheckInterop.podspec index 5c15ded0a94..6074a53ef4b 100644 --- a/FirebaseAppCheckInterop.podspec +++ b/FirebaseAppCheckInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheckInterop' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Interfaces that allow other Firebase SDKs to use AppCheck functionality.' s.description = <<-DESC diff --git a/FirebaseAppDistribution.podspec b/FirebaseAppDistribution.podspec index cf39ec63695..ec762968563 100644 --- a/FirebaseAppDistribution.podspec +++ b/FirebaseAppDistribution.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppDistribution' - s.version = '11.10.0-beta' + s.version = '11.11.0-beta' s.summary = 'App Distribution for Firebase iOS SDK.' s.description = <<-DESC @@ -30,7 +30,7 @@ iOS SDK for App Distribution for Firebase. ] s.public_header_files = base_dir + 'Public/FirebaseAppDistribution/*.h' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' s.dependency 'FirebaseInstallations', '~> 11.0' diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 970e2475c33..a95e38c1a24 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuth' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Apple platform client for Firebase Authentication' s.description = <<-DESC @@ -58,8 +58,8 @@ supports email and password accounts, as well as several 3rd party authenticatio s.ios.framework = 'SafariServices' s.dependency 'FirebaseAuthInterop', '~> 11.0' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.10.0' - s.dependency 'FirebaseCoreExtension', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' + s.dependency 'FirebaseCoreExtension', '~> 11.11.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' diff --git a/FirebaseAuthInterop.podspec b/FirebaseAuthInterop.podspec index 391f93aabe6..f4ac8f3eaf6 100644 --- a/FirebaseAuthInterop.podspec +++ b/FirebaseAuthInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuthInterop' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Auth functionality.' s.description = <<-DESC diff --git a/FirebaseCombineSwift.podspec b/FirebaseCombineSwift.podspec index a69c13b9f48..ebd4b0b3ee7 100644 --- a/FirebaseCombineSwift.podspec +++ b/FirebaseCombineSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCombineSwift' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Swift extensions with Combine support for Firebase' s.description = <<-DESC @@ -51,7 +51,7 @@ for internal testing only. It should not be published. s.osx.framework = 'AppKit' s.tvos.framework = 'UIKit' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'FirebaseAuth', '~> 11.0' s.dependency 'FirebaseFunctions', '~> 11.0' s.dependency 'FirebaseFirestore', '~> 11.0' diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index 5462b954b3a..6f6b47ce817 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCore' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Core' s.description = <<-DESC @@ -53,7 +53,7 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration # Remember to also update version in `cmake/external/GoogleUtilities.cmake` s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GoogleUtilities/Logger', '~> 8.0' - s.dependency 'FirebaseCoreInternal', '~> 11.10.0' + s.dependency 'FirebaseCoreInternal', '~> 11.11.0' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', diff --git a/FirebaseCoreExtension.podspec b/FirebaseCoreExtension.podspec index 22885e43bd9..3e0dfe2882a 100644 --- a/FirebaseCoreExtension.podspec +++ b/FirebaseCoreExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreExtension' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Extended FirebaseCore APIs for Firebase product SDKs' s.description = <<-DESC @@ -34,5 +34,5 @@ Pod::Spec.new do |s| "#{s.module_name}_Privacy" => 'FirebaseCore/Extension/Resources/PrivacyInfo.xcprivacy' } - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' end diff --git a/FirebaseCoreInternal.podspec b/FirebaseCoreInternal.podspec index 033df32b75b..938c0cf2a64 100644 --- a/FirebaseCoreInternal.podspec +++ b/FirebaseCoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreInternal' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'APIs for internal FirebaseCore usage.' s.description = <<-DESC diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index c6e63214abc..046458b62a7 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCrashlytics' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Best and lightest-weight crash reporting for mobile, desktop and tvOS.' s.description = 'Firebase Crashlytics helps you track, prioritize, and fix stability issues that erode app quality.' s.homepage = 'https://firebase.google.com/' @@ -59,7 +59,7 @@ Pod::Spec.new do |s| cp -f ./Crashlytics/CrashlyticsInputFiles.xcfilelist ./CrashlyticsInputFiles.xcfilelist PREPARE_COMMAND_END - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'FirebaseSessions', '~> 11.0' s.dependency 'FirebaseRemoteConfigInterop', '~> 11.0' diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index c19fddfd96e..e117544ce63 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDatabase' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Realtime Database' s.description = <<-DESC @@ -48,7 +48,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel s.macos.frameworks = 'CFNetwork', 'Security', 'SystemConfiguration' s.watchos.frameworks = 'CFNetwork', 'Security', 'WatchKit' s.dependency 'leveldb-library', '~> 1.22' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseDynamicLinks.podspec b/FirebaseDynamicLinks.podspec index eb25ddf09b5..08bef7ffe56 100644 --- a/FirebaseDynamicLinks.podspec +++ b/FirebaseDynamicLinks.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDynamicLinks' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Dynamic Links' s.description = <<-DESC @@ -37,7 +37,7 @@ Firebase Dynamic Links are deep links that enhance user experience and increase } s.frameworks = 'QuartzCore' s.weak_framework = 'WebKit' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 6bdad5c55b3..56bd378f2f6 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestore' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC Google Cloud Firestore is a NoSQL document database built for automatic scaling, high performance, and ease of application development. @@ -35,9 +35,9 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, "#{s.module_name}_Privacy" => 'Firestore/Swift/Source/Resources/PrivacyInfo.xcprivacy' } - s.dependency 'FirebaseCore', '~> 11.10.0' - s.dependency 'FirebaseCoreExtension', '~> 11.10.0' - s.dependency 'FirebaseFirestoreInternal', '11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' + s.dependency 'FirebaseCoreExtension', '~> 11.11.0' + s.dependency 'FirebaseFirestoreInternal', '11.11.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' end diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index ca40cbafa6b..4d3c1646c49 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestoreInternal' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC @@ -93,7 +93,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, } s.dependency 'FirebaseAppCheckInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' abseil_version = '~> 1.20240722.0' s.dependency 'abseil/algorithm', abseil_version diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index c70e03349d8..1f944f9d8ec 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFunctions' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Cloud Functions for Firebase' s.description = <<-DESC @@ -35,8 +35,8 @@ Cloud Functions for Firebase. 'FirebaseFunctions/Sources/**/*.swift', ] - s.dependency 'FirebaseCore', '~> 11.10.0' - s.dependency 'FirebaseCoreExtension', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' + s.dependency 'FirebaseCoreExtension', '~> 11.11.0' s.dependency 'FirebaseAppCheckInterop', '~> 11.0' s.dependency 'FirebaseAuthInterop', '~> 11.0' s.dependency 'FirebaseMessagingInterop', '~> 11.0' diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index b260d106c29..c6818c487ee 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInAppMessaging' - s.version = '11.10.0-beta' + s.version = '11.11.0-beta' s.summary = 'Firebase In-App Messaging for iOS' s.description = <<-DESC @@ -80,7 +80,7 @@ See more product details at https://firebase.google.com/products/in-app-messagin s.framework = 'UIKit' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'FirebaseABTesting', '~> 11.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseInstallations.podspec b/FirebaseInstallations.podspec index 2288f15dbdd..445f9d9dc8e 100644 --- a/FirebaseInstallations.podspec +++ b/FirebaseInstallations.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInstallations' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Installations' s.description = <<-DESC @@ -45,7 +45,7 @@ Pod::Spec.new do |s| } s.framework = 'Security' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'PromisesObjC', '~> 2.4' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index e45316db820..ce555616627 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMLModelDownloader' - s.version = '11.10.0-beta' + s.version = '11.11.0-beta' s.summary = 'Firebase ML Model Downloader' s.description = <<-DESC @@ -36,8 +36,8 @@ Pod::Spec.new do |s| ] s.framework = 'Foundation' - s.dependency 'FirebaseCore', '~> 11.10.0' - s.dependency 'FirebaseCoreExtension', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' + s.dependency 'FirebaseCoreExtension', '~> 11.11.0' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleDataTransport', '~> 10.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.0' diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 4e1dcca5821..efa5d03ee89 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessaging' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Messaging' s.description = <<-DESC @@ -62,7 +62,7 @@ device, and it is completely free. s.osx.framework = 'SystemConfiguration' s.weak_framework = 'UserNotifications' s.dependency 'FirebaseInstallations', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.0' s.dependency 'GoogleUtilities/Reachability', '~> 8.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseMessagingInterop.podspec b/FirebaseMessagingInterop.podspec index 9d7f3465ba9..f64d1c7a525 100644 --- a/FirebaseMessagingInterop.podspec +++ b/FirebaseMessagingInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessagingInterop' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Messaging functionality.' s.description = <<-DESC diff --git a/FirebasePerformance.podspec b/FirebasePerformance.podspec index 4620b245cde..baba4474595 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebasePerformance' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Performance' s.description = <<-DESC @@ -59,7 +59,7 @@ Firebase Performance library to measure performance of Mobile and Web Apps. s.ios.framework = 'CoreTelephony' s.framework = 'QuartzCore' s.framework = 'SystemConfiguration' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'FirebaseRemoteConfig', '~> 11.0' s.dependency 'FirebaseSessions', '~> 11.0' diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index ed3069901c5..38e832dd461 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfig' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Remote Config' s.description = <<-DESC @@ -52,7 +52,7 @@ app update. } s.dependency 'FirebaseABTesting', '~> 11.0' s.dependency 'FirebaseSharedSwift', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'GoogleUtilities/NSData+zlib', '~> 8.0' diff --git a/FirebaseRemoteConfigInterop.podspec b/FirebaseRemoteConfigInterop.podspec index 6c8fe49d690..2a944097031 100644 --- a/FirebaseRemoteConfigInterop.podspec +++ b/FirebaseRemoteConfigInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfigInterop' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Remote Config functionality.' s.description = <<-DESC diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 152e3094eda..e32bce0ce1e 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSessions' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Sessions' s.description = <<-DESC @@ -39,8 +39,8 @@ Pod::Spec.new do |s| base_dir + 'SourcesObjC/**/*.{c,h,m,mm}', ] - s.dependency 'FirebaseCore', '~> 11.10.0' - s.dependency 'FirebaseCoreExtension', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' + s.dependency 'FirebaseCoreExtension', '~> 11.11.0' s.dependency 'FirebaseInstallations', '~> 11.0' s.dependency 'GoogleDataTransport', '~> 10.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseSharedSwift.podspec b/FirebaseSharedSwift.podspec index 6e1b3e544a5..bef647e23bf 100644 --- a/FirebaseSharedSwift.podspec +++ b/FirebaseSharedSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSharedSwift' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Shared Swift Extensions for Firebase' s.description = <<-DESC diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index 70b4f66ff28..0c4d41b3158 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseStorage' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Firebase Storage' s.description = <<-DESC @@ -39,8 +39,8 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas s.dependency 'FirebaseAppCheckInterop', '~> 11.0' s.dependency 'FirebaseAuthInterop', '~> 11.0' - s.dependency 'FirebaseCore', '~> 11.10.0' - s.dependency 'FirebaseCoreExtension', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' + s.dependency 'FirebaseCoreExtension', '~> 11.11.0' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 5.0' s.dependency 'GoogleUtilities/Environment', '~> 8.0' diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index 2b58577bd76..2f9a27cb262 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseVertexAI' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Vertex AI in Firebase SDK' s.description = <<-DESC @@ -46,8 +46,8 @@ Firebase SDK. s.dependency 'FirebaseAppCheckInterop', '~> 11.4' s.dependency 'FirebaseAuthInterop', '~> 11.4' - s.dependency 'FirebaseCore', '~> 11.10.0' - s.dependency 'FirebaseCoreExtension', '~> 11.10.0' + s.dependency 'FirebaseCore', '~> 11.11.0' + s.dependency 'FirebaseCoreExtension', '~> 11.11.0' s.test_spec 'unit' do |unit_tests| unit_tests_dir = 'FirebaseVertexAI/Tests/Unit/' diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index 070d0846480..eddb7125cba 100644 --- a/GoogleAppMeasurement.podspec +++ b/GoogleAppMeasurement.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurement' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = 'Shared measurement methods for Google libraries. Not intended for direct use.' s.description = <<-DESC @@ -37,7 +37,7 @@ Pod::Spec.new do |s| s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.10.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '11.11.0' ss.vendored_frameworks = 'Frameworks/GoogleAppMeasurementIdentitySupport.xcframework' end diff --git a/GoogleAppMeasurementOnDeviceConversion.podspec b/GoogleAppMeasurementOnDeviceConversion.podspec index 285a7b1ce84..b56ccd4bb04 100644 --- a/GoogleAppMeasurementOnDeviceConversion.podspec +++ b/GoogleAppMeasurementOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurementOnDeviceConversion' - s.version = '11.10.0' + s.version = '11.11.0' s.summary = <<-SUMMARY On device conversion measurement plugin for Google App Measurement. Not intended for direct use. diff --git a/Package.swift b/Package.swift index 8662687cc1c..15de6e415ba 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ import class Foundation.ProcessInfo import PackageDescription -let firebaseVersion = "11.10.0" +let firebaseVersion = "11.11.0" let package = Package( name: "Firebase", diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index a7a98fd4436..440158dfe1f 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -21,7 +21,7 @@ import Foundation /// The version and releasing fields of the non-Firebase pods should be reviewed every release. /// The array should be ordered so that any pod's dependencies precede it in the list. public let shared = Manifest( - version: "11.10.0", + version: "11.11.0", pods: [ Pod("FirebaseSharedSwift"), Pod("FirebaseCoreInternal"), From f504faece86e3bdce7d5894beb680c354113124e Mon Sep 17 00:00:00 2001 From: cherylEnkidu <96084918+cherylEnkidu@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:18:27 -0400 Subject: [PATCH 10/21] Fix priority queue assertion which enforce the item to have strict order (#14496) --- Firestore/CHANGELOG.md | 3 + .../core/src/local/leveldb_index_manager.cc | 7 ++- Firestore/core/src/model/field_index.cc | 2 + Firestore/core/src/model/field_index.h | 61 ++++++++++++++++++- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index a006d39ab7b..6a30f747984 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [fixed] Fixed the customized priority queue compare function used cache index manager. (#14496) + # 11.9.0 - [fixed] Fixed memory leak in `Query.whereField()`. (#13978) diff --git a/Firestore/core/src/local/leveldb_index_manager.cc b/Firestore/core/src/local/leveldb_index_manager.cc index eb41c79ad0b..0455bf88898 100644 --- a/Firestore/core/src/local/leveldb_index_manager.cc +++ b/Firestore/core/src/local/leveldb_index_manager.cc @@ -188,10 +188,15 @@ LevelDbIndexManager::LevelDbIndexManager(const User& user, // The contract for this comparison expected by priority queue is // `std::less`, but std::priority_queue's default order is descending. // We change the order to be ascending by doing left >= right instead. + // Note: priority queue has to have a strict ordering, so here using unique_id + // to order Field Indexes having same `sequence_number` and `collection_group` auto cmp = [](FieldIndex* left, FieldIndex* right) { if (left->index_state().sequence_number() == right->index_state().sequence_number()) { - return left->collection_group() >= right->collection_group(); + if (left->collection_group() == right->collection_group()) { + return left->unique_id() > right->unique_id(); + } + return left->collection_group() > right->collection_group(); } return left->index_state().sequence_number() > right->index_state().sequence_number(); diff --git a/Firestore/core/src/model/field_index.cc b/Firestore/core/src/model/field_index.cc index 9257a4fb66f..4cf58e4d988 100644 --- a/Firestore/core/src/model/field_index.cc +++ b/Firestore/core/src/model/field_index.cc @@ -20,6 +20,8 @@ namespace firebase { namespace firestore { namespace model { +std::atomic FieldIndex::ref_count_{0}; + util::ComparisonResult Segment::CompareTo(const Segment& rhs) const { auto result = field_path().CompareTo(rhs.field_path()); if (result != util::ComparisonResult::Same) { diff --git a/Firestore/core/src/model/field_index.h b/Firestore/core/src/model/field_index.h index 57f159e9c67..992aa941767 100644 --- a/Firestore/core/src/model/field_index.h +++ b/Firestore/core/src/model/field_index.h @@ -243,7 +243,9 @@ class FieldIndex { static util::ComparisonResult SemanticCompare(const FieldIndex& left, const FieldIndex& right); - FieldIndex() : index_id_(UnknownId()) { + FieldIndex() + : index_id_(UnknownId()), + unique_id_(ref_count_.fetch_add(1, std::memory_order_acq_rel)) { } FieldIndex(int32_t index_id, @@ -253,7 +255,50 @@ class FieldIndex { : index_id_(index_id), collection_group_(std::move(collection_group)), segments_(std::move(segments)), - state_(std::move(state)) { + state_(std::move(state)), + unique_id_(ref_count_.fetch_add(1, std::memory_order_acq_rel)) { + } + + // Copy constructor + FieldIndex(const FieldIndex& other) + : index_id_(other.index_id_), + collection_group_(other.collection_group_), + segments_(other.segments_), + state_(other.state_), + unique_id_(ref_count_.fetch_add(1, std::memory_order_acq_rel)) { + } + + // Copy assignment operator + FieldIndex& operator=(const FieldIndex& other) { + if (this != &other) { + index_id_ = other.index_id_; + collection_group_ = other.collection_group_; + segments_ = other.segments_; + state_ = other.state_; + unique_id_ = ref_count_.fetch_add(1, std::memory_order_acq_rel); + } + return *this; + } + + // Move constructor + FieldIndex(FieldIndex&& other) noexcept + : index_id_(other.index_id_), + collection_group_(std::move(other.collection_group_)), + segments_(std::move(other.segments_)), + state_(std::move(other.state_)), + unique_id_(ref_count_.fetch_add(1, std::memory_order_acq_rel)) { + } + + // Move assignment operator + FieldIndex& operator=(FieldIndex&& other) noexcept { + if (this != &other) { + index_id_ = other.index_id_; + collection_group_ = std::move(other.collection_group_); + segments_ = std::move(other.segments_); + state_ = std::move(other.state_); + unique_id_ = ref_count_.fetch_add(1, std::memory_order_acq_rel); + } + return *this; } /** @@ -285,6 +330,14 @@ class FieldIndex { /** Returns the ArrayContains/ArrayContainsAny segment for this index. */ absl::optional GetArraySegment() const; + /** + * Returns the unique identifier for this object, ensuring a strict ordering + * in the priority queue's comparison function. + */ + int unique_id() const { + return unique_id_; + } + /** * A type that can be used as the "Compare" template parameter of ordered * collections to have the elements ordered using @@ -308,6 +361,10 @@ class FieldIndex { std::string collection_group_; std::vector segments_; IndexState state_; + int unique_id_; + + // TODO(C++17): Replace with inline static std::atomic ref_count_ = 0; + static std::atomic ref_count_; }; inline bool operator==(const FieldIndex& lhs, const FieldIndex& rhs) { From f49b37ff30bb1ac06e76ef046e020ca97a65f694 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:26:53 -0400 Subject: [PATCH 11/21] [Swift 6] Address low-hanging Swift concurrency errors in Functions (#14597) --- FirebaseFunctions/Sources/Functions.swift | 157 ++++++++++++------ .../Sources/FunctionsError.swift | 2 +- .../Sources/HTTPSCallableOptions.swift | 2 +- .../Sources/Internal/FunctionsContext.swift | 8 +- 4 files changed, 115 insertions(+), 54 deletions(-) diff --git a/FirebaseFunctions/Sources/Functions.swift b/FirebaseFunctions/Sources/Functions.swift index d9e00afb34a..151e538a80f 100644 --- a/FirebaseFunctions/Sources/Functions.swift +++ b/FirebaseFunctions/Sources/Functions.swift @@ -27,6 +27,36 @@ import Foundation // Avoids exposing internal FirebaseCore APIs to Swift users. @_implementationOnly import FirebaseCoreExtension +final class AtomicBox { + private var _value: T + private let lock = NSLock() + + public init(_ value: T) { + _value = value + } + + public func value() -> T { + lock.withLock { + _value + } + } + + @discardableResult + public func withLock(_ mutatingBody: (_ value: inout T) -> Void) -> T { + lock.withLock { + mutatingBody(&_value) + return _value + } + } + + @discardableResult + public func withLock(_ mutatingBody: (_ value: inout T) throws -> R) rethrows -> R { + try lock.withLock { + try mutatingBody(&_value) + } + } +} + /// File specific constants. private enum Constants { static let appCheckTokenHeader = "X-Firebase-AppCheck" @@ -53,10 +83,12 @@ enum FunctionsConstants { /// A map of active instances, grouped by app. Keys are FirebaseApp names and values are arrays /// containing all instances of Functions associated with the given app. - private static var instances: [String: [Functions]] = [:] - - /// Lock to manage access to the instances array to avoid race conditions. - private static var instancesLock: os_unfair_lock = .init() + #if compiler(>=6.0) + private nonisolated(unsafe) static var instances: AtomicBox<[String: [Functions]]> = + AtomicBox([:]) + #else + private static var instances: AtomicBox<[String: [Functions]]> = AtomicBox([:]) + #endif /// The custom domain to use for all functions references (optional). let customDomain: String? @@ -304,30 +336,28 @@ enum FunctionsConstants { guard let app else { fatalError("`FirebaseApp.configure()` needs to be called before using Functions.") } - os_unfair_lock_lock(&instancesLock) - - // Unlock before the function returns. - defer { os_unfair_lock_unlock(&instancesLock) } - - if let associatedInstances = instances[app.name] { - for instance in associatedInstances { - // Domains may be nil, so handle with care. - var equalDomains = false - if let instanceCustomDomain = instance.customDomain { - equalDomains = instanceCustomDomain == customDomain - } else { - equalDomains = customDomain == nil - } - // Check if it's a match. - if instance.region == region, equalDomains { - return instance + + return instances.withLock { instances in + if let associatedInstances = instances[app.name] { + for instance in associatedInstances { + // Domains may be nil, so handle with care. + var equalDomains = false + if let instanceCustomDomain = instance.customDomain { + equalDomains = instanceCustomDomain == customDomain + } else { + equalDomains = customDomain == nil + } + // Check if it's a match. + if instance.region == region, equalDomains { + return instance + } } } + let newInstance = Functions(app: app, region: region, customDomain: customDomain) + let existingInstances = instances[app.name, default: []] + instances[app.name] = existingInstances + [newInstance] + return newInstance } - let newInstance = Functions(app: app, region: region, customDomain: customDomain) - let existingInstances = instances[app.name, default: []] - instances[app.name] = existingInstances + [newInstance] - return newInstance } @objc init(projectID: String, @@ -576,34 +606,65 @@ enum FunctionsConstants { } } - @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) - private func callableStreamResult(fromResponseData data: Data, - endpointURL url: URL) throws -> JSONStreamResponse { - let data = try processedData(fromResponseData: data, endpointURL: url) + #if compiler(>=6.0) + @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) + private func callableStreamResult(fromResponseData data: Data, + endpointURL url: URL) throws -> sending JSONStreamResponse { + let data = try processedData(fromResponseData: data, endpointURL: url) + + let responseJSONObject: Any + do { + responseJSONObject = try JSONSerialization.jsonObject(with: data) + } catch { + throw FunctionsError(.dataLoss, userInfo: [NSUnderlyingErrorKey: error]) + } - let responseJSONObject: Any - do { - responseJSONObject = try JSONSerialization.jsonObject(with: data) - } catch { - throw FunctionsError(.dataLoss, userInfo: [NSUnderlyingErrorKey: error]) - } + guard let responseJSON = responseJSONObject as? [String: Any] else { + let userInfo = [NSLocalizedDescriptionKey: "Response was not a dictionary."] + throw FunctionsError(.dataLoss, userInfo: userInfo) + } - guard let responseJSON = responseJSONObject as? [String: Any] else { - let userInfo = [NSLocalizedDescriptionKey: "Response was not a dictionary."] - throw FunctionsError(.dataLoss, userInfo: userInfo) + if let _ = responseJSON["result"] { + return .result(responseJSON) + } else if let _ = responseJSON["message"] { + return .message(responseJSON) + } else { + throw FunctionsError( + .dataLoss, + userInfo: [NSLocalizedDescriptionKey: "Response is missing result or message field."] + ) + } } + #else + @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *) + private func callableStreamResult(fromResponseData data: Data, + endpointURL url: URL) throws -> JSONStreamResponse { + let data = try processedData(fromResponseData: data, endpointURL: url) + + let responseJSONObject: Any + do { + responseJSONObject = try JSONSerialization.jsonObject(with: data) + } catch { + throw FunctionsError(.dataLoss, userInfo: [NSUnderlyingErrorKey: error]) + } - if let _ = responseJSON["result"] { - return .result(responseJSON) - } else if let _ = responseJSON["message"] { - return .message(responseJSON) - } else { - throw FunctionsError( - .dataLoss, - userInfo: [NSLocalizedDescriptionKey: "Response is missing result or message field."] - ) + guard let responseJSON = responseJSONObject as? [String: Any] else { + let userInfo = [NSLocalizedDescriptionKey: "Response was not a dictionary."] + throw FunctionsError(.dataLoss, userInfo: userInfo) + } + + if let _ = responseJSON["result"] { + return .result(responseJSON) + } else if let _ = responseJSON["message"] { + return .message(responseJSON) + } else { + throw FunctionsError( + .dataLoss, + userInfo: [NSLocalizedDescriptionKey: "Response is missing result or message field."] + ) + } } - } + #endif // compiler(>=6.0) private func jsonData(jsonText: String) throws -> Data { guard let data = jsonText.data(using: .utf8) else { diff --git a/FirebaseFunctions/Sources/FunctionsError.swift b/FirebaseFunctions/Sources/FunctionsError.swift index 34e896b63d4..f495f51e68e 100644 --- a/FirebaseFunctions/Sources/FunctionsError.swift +++ b/FirebaseFunctions/Sources/FunctionsError.swift @@ -25,7 +25,7 @@ public let FunctionsErrorDetailsKey: String = "details" * canonical error codes for Google APIs, as documented here: * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto#L26 */ -@objc(FIRFunctionsErrorCode) public enum FunctionsErrorCode: Int { +@objc(FIRFunctionsErrorCode) public enum FunctionsErrorCode: Int, Sendable { /** The operation completed successfully. */ case OK = 0 diff --git a/FirebaseFunctions/Sources/HTTPSCallableOptions.swift b/FirebaseFunctions/Sources/HTTPSCallableOptions.swift index 5ca9a694d9d..371180ddb3e 100644 --- a/FirebaseFunctions/Sources/HTTPSCallableOptions.swift +++ b/FirebaseFunctions/Sources/HTTPSCallableOptions.swift @@ -15,7 +15,7 @@ import Foundation /// Configuration options for a ``HTTPSCallable`` instance. -@objc(FIRHTTPSCallableOptions) public class HTTPSCallableOptions: NSObject { +@objc(FIRHTTPSCallableOptions) public class HTTPSCallableOptions: NSObject, @unchecked Sendable { /// Whether or not to protect the callable function with a limited-use App Check token. @objc public let requireLimitedUseAppCheckTokens: Bool diff --git a/FirebaseFunctions/Sources/Internal/FunctionsContext.swift b/FirebaseFunctions/Sources/Internal/FunctionsContext.swift index 9225ca6e2f5..dea21a9eb5c 100644 --- a/FirebaseFunctions/Sources/Internal/FunctionsContext.swift +++ b/FirebaseFunctions/Sources/Internal/FunctionsContext.swift @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAppCheckInterop -import FirebaseAuthInterop -import FirebaseMessagingInterop +@preconcurrency import FirebaseAppCheckInterop +@preconcurrency import FirebaseAuthInterop +@preconcurrency import FirebaseMessagingInterop import Foundation /// `FunctionsContext` is a helper object that holds metadata for a function call. @@ -25,7 +25,7 @@ struct FunctionsContext { let limitedUseAppCheckToken: String? } -struct FunctionsContextProvider { +struct FunctionsContextProvider: Sendable { private let auth: AuthInterop? private let messaging: MessagingInterop? private let appCheck: AppCheckInterop? From b01a8f24f2cc94db004909adb9c3125db22e4b92 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 19 Mar 2025 10:33:42 -0400 Subject: [PATCH 12/21] [Swift 6] Add AtomicBox to CoreInternal (#14594) --- .../HeartbeatLogging/HeartbeatStorage.swift | 20 ++++----- .../Sources/Utilities/AtomicBox.swift | 45 +++++++++++++++++++ 2 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 FirebaseCore/Internal/Sources/Utilities/AtomicBox.swift diff --git a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift index 07088a5cf68..3e428f8a88e 100644 --- a/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift +++ b/FirebaseCore/Internal/Sources/HeartbeatLogging/HeartbeatStorage.swift @@ -59,27 +59,23 @@ final class HeartbeatStorage: Sendable, HeartbeatStorageProtocol { // `nonisolated(unsafe)` to disable concurrency-safety checks. The // property's access is protected by an external synchronization mechanism // (see `instancesLock` property). - private nonisolated(unsafe) static var cachedInstances: [ - String: WeakContainer - ] = [:] + private nonisolated(unsafe) static var cachedInstances: AtomicBox< + [String: WeakContainer] + > = AtomicBox([:]) #else // TODO(Xcode 16): Delete this block when minimum supported Xcode is // Xcode 16. - private static var cachedInstances: [ - String: WeakContainer - ] = [:] + static var cachedInstances: AtomicBox<[String: WeakContainer]> = + AtomicBox([:]) #endif // compiler(>=6) - /// Used to synchronize concurrent access to the `cachedInstances` property. - private static let instancesLock = NSLock() - /// Gets an existing `HeartbeatStorage` instance with the given `id` if one exists. Otherwise, /// makes a new instance with the given `id`. /// /// - Parameter id: A string identifier. /// - Returns: A `HeartbeatStorage` instance. static func getInstance(id: String) -> HeartbeatStorage { - instancesLock.withLock { + cachedInstances.withLock { cachedInstances in if let cachedInstance = cachedInstances[id]?.object { return cachedInstance } else { @@ -110,8 +106,8 @@ final class HeartbeatStorage: Sendable, HeartbeatStorageProtocol { deinit { // Removes the instance if it was cached. - _ = Self.instancesLock.withLock { - Self.cachedInstances.removeValue(forKey: id) + Self.cachedInstances.withLock { value in + value.removeValue(forKey: id) } } diff --git a/FirebaseCore/Internal/Sources/Utilities/AtomicBox.swift b/FirebaseCore/Internal/Sources/Utilities/AtomicBox.swift new file mode 100644 index 00000000000..6346d05701c --- /dev/null +++ b/FirebaseCore/Internal/Sources/Utilities/AtomicBox.swift @@ -0,0 +1,45 @@ +// Copyright 2025 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 + +final class AtomicBox { + private var _value: T + private let lock = NSLock() + + public init(_ value: T) { + _value = value + } + + public func value() -> T { + lock.withLock { + _value + } + } + + @discardableResult + public func withLock(_ mutatingBody: (_ value: inout T) -> Void) -> T { + lock.withLock { + mutatingBody(&_value) + return _value + } + } + + @discardableResult + public func withLock(_ mutatingBody: (_ value: inout T) throws -> R) rethrows -> R { + try lock.withLock { + try mutatingBody(&_value) + } + } +} From d8f2ed8c0ed791273f0a7371c300d82dfb5571e6 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 20 Mar 2025 18:27:37 -0400 Subject: [PATCH 13/21] [NFC] Unmutated vars (#14604) --- FirebaseAuth/Sources/Swift/Auth/Auth.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAuth/Sources/Swift/Auth/Auth.swift b/FirebaseAuth/Sources/Swift/Auth/Auth.swift index de22db9c955..d9b4eb38b56 100644 --- a/FirebaseAuth/Sources/Swift/Auth/Auth.swift +++ b/FirebaseAuth/Sources/Swift/Auth/Auth.swift @@ -2348,7 +2348,7 @@ extension Auth: AuthInterop { /// The configuration object comprising of parameters needed to make a request to Firebase /// Auth's backend. - var requestConfiguration: AuthRequestConfiguration + let requestConfiguration: AuthRequestConfiguration let backend: AuthBackend From 0b5b99089b157feadbab3c4db1ad1138e879e043 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 24 Mar 2025 07:37:04 -0700 Subject: [PATCH 14/21] Release notes for 11.11.0 (#14609) --- Firestore/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 6a30f747984..d06b932af10 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 11.11.0 - [fixed] Fixed the customized priority queue compare function used cache index manager. (#14496) # 11.9.0 From 5dcf7aaf140bdc9582c77e87f755e90d8a659077 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 24 Mar 2025 18:02:26 -0400 Subject: [PATCH 15/21] [Vertex AI] Log warning for unsupported model names (#14610) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- FirebaseVertexAI/CHANGELOG.md | 4 ++++ FirebaseVertexAI/Sources/GenerativeModel.swift | 10 ++++++++++ .../Sources/Types/Public/Imagen/ImagenModel.swift | 10 ++++++++++ FirebaseVertexAI/Sources/VertexLog.swift | 2 ++ 4 files changed, 26 insertions(+) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 2c84aaaafa3..cd38e2bec67 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -1,3 +1,7 @@ +# 11.11.0 +- [added] Emits a warning when attempting to use an incompatible model with + `GenerativeModel` or `ImagenModel`. (#14610) + # 11.10.0 - [feature] The Vertex AI SDK no longer requires `@preconcurrency` when imported in Swift 6. - [feature] The Vertex AI Sample App now includes an image generation example. diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index 3d37be52061..6df2620bc7f 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -20,6 +20,9 @@ import Foundation /// content based on various input types. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public final class GenerativeModel: Sendable { + /// Model name prefix to identify Gemini models. + static let geminiModelNamePrefix = "gemini-" + /// The resource name of the model in the backend; has the format "models/model-name". let modelResourceName: String @@ -71,6 +74,13 @@ public final class GenerativeModel: Sendable { systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, urlSession: URLSession = .shared) { + if !name.starts(with: GenerativeModel.geminiModelNamePrefix) { + VertexLog.warning(code: .unsupportedGeminiModel, """ + Unsupported Gemini model "\(name)"; see \ + https://firebase.google.com/docs/vertex-ai/models for a list supported Gemini model names. + """) + } + modelResourceName = name self.apiConfig = apiConfig generativeAIService = GenerativeAIService( diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift b/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift index f0224ec8fdb..13b0b0dba35 100644 --- a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift +++ b/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift @@ -28,6 +28,9 @@ import Foundation /// could change in backwards-incompatible ways. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public final class ImagenModel { + /// Model name prefix to identify Imagen models. + static let imagenModelNamePrefix = "imagen-" + /// The resource name of the model in the backend; has the format "models/model-name". let modelResourceName: String @@ -51,6 +54,13 @@ public final class ImagenModel { safetySettings: ImagenSafetySettings?, requestOptions: RequestOptions, urlSession: URLSession = .shared) { + if !name.starts(with: ImagenModel.imagenModelNamePrefix) { + VertexLog.warning(code: .unsupportedImagenModel, """ + Unsupported Imagen model "\(name)"; see \ + https://firebase.google.com/docs/vertex-ai/models for a list supported Imagen model names. + """) + } + modelResourceName = name self.apiConfig = apiConfig generativeAIService = GenerativeAIService( diff --git a/FirebaseVertexAI/Sources/VertexLog.swift b/FirebaseVertexAI/Sources/VertexLog.swift index 9332aa87961..ff94a2d435e 100644 --- a/FirebaseVertexAI/Sources/VertexLog.swift +++ b/FirebaseVertexAI/Sources/VertexLog.swift @@ -33,8 +33,10 @@ enum VertexLog { // Generative Model Configuration case generativeModelInitialized = 1000 + case unsupportedGeminiModel = 1001 // Imagen Model Configuration + case unsupportedImagenModel = 1200 case imagenInvalidJPEGCompressionQuality = 1201 // Network Errors From 70c8908029c8eef1e7e9350d6556f6d5d47e4b9d Mon Sep 17 00:00:00 2001 From: pcfba <111909874+pcfba@users.noreply.github.com> Date: Mon, 24 Mar 2025 18:28:53 -0700 Subject: [PATCH 16/21] Analytics 11.11.0 (#14612) --- FirebaseAnalytics.podspec | 2 +- GoogleAppMeasurement.podspec | 2 +- GoogleAppMeasurementOnDeviceConversion.podspec | 2 +- Package.swift | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index 80405d67469..659418b5f08 100644 --- a/FirebaseAnalytics.podspec +++ b/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/f4ae251137d1f29b/FirebaseAnalytics-11.10.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/9b3638b3f6db3f4f/FirebaseAnalytics-11.11.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index eddb7125cba..da330bbef01 100644 --- a/GoogleAppMeasurement.podspec +++ b/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/06d095fb684cf7af/GoogleAppMeasurement-11.10.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/48714e583cf2cf63/GoogleAppMeasurement-11.11.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' diff --git a/GoogleAppMeasurementOnDeviceConversion.podspec b/GoogleAppMeasurementOnDeviceConversion.podspec index b56ccd4bb04..683d4680bc7 100644 --- a/GoogleAppMeasurementOnDeviceConversion.podspec +++ b/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/786bb10ec1d1728b/GoogleAppMeasurementOnDeviceConversion-11.10.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/a7f86a77e0392409/GoogleAppMeasurementOnDeviceConversion-11.11.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' diff --git a/Package.swift b/Package.swift index 15de6e415ba..5b935d4572e 100644 --- a/Package.swift +++ b/Package.swift @@ -303,8 +303,8 @@ let package = Package( ), .binaryTarget( name: "FirebaseAnalytics", - url: "https://dl.google.com/firebase/ios/swiftpm/11.10.0/FirebaseAnalytics.zip", - checksum: "bed64b5e7bf04fd039d6fe815412fea306c0b344379193c3a14a6d2f0dd97a8c" + url: "https://dl.google.com/firebase/ios/swiftpm/11.11.0/FirebaseAnalytics.zip", + checksum: "a9c3b86fa4a6fe8e85b1235cea135ea147c733108f95d6aee80616dd18dde856" ), .testTarget( name: "AnalyticsSwiftUnit", @@ -1341,7 +1341,7 @@ func googleAppMeasurementDependency() -> Package.Dependency { return .package(url: appMeasurementURL, branch: "main") } - return .package(url: appMeasurementURL, exact: "11.10.0") + return .package(url: appMeasurementURL, exact: "11.11.0") } func abseilDependency() -> Package.Dependency { From 6191e9d4a3d05ec7d15c5f7b16f71ee17ac5590b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 25 Mar 2025 10:35:33 -0400 Subject: [PATCH 17/21] [Vertex AI] Swift Testing `generateContentStream` integration test (#14611) --- .../TestApp/Sources/FirebaseAppUtils.swift | 37 +++++++++ .../Tests/TestApp/Sources/TestApp.swift | 16 ++-- .../GenerateContentIntegrationTests.swift | 77 +++++++++++++++++++ .../Tests/Integration/IntegrationTests.swift | 59 +------------- .../Utilities/FirebaseAppTestUtils.swift | 40 ---------- .../Tests/Utilities/InstanceConfig.swift | 9 +++ .../VertexAITestApp.xcodeproj/project.pbxproj | 8 +- 7 files changed, 136 insertions(+), 110 deletions(-) create mode 100644 FirebaseVertexAI/Tests/TestApp/Sources/FirebaseAppUtils.swift delete mode 100644 FirebaseVertexAI/Tests/TestApp/Tests/Utilities/FirebaseAppTestUtils.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/FirebaseAppUtils.swift b/FirebaseVertexAI/Tests/TestApp/Sources/FirebaseAppUtils.swift new file mode 100644 index 00000000000..fc32065d2bf --- /dev/null +++ b/FirebaseVertexAI/Tests/TestApp/Sources/FirebaseAppUtils.swift @@ -0,0 +1,37 @@ +// Copyright 2025 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 FirebaseCore + +extension FirebaseApp { + /// Configures a Firebase app with the specified name and Google Service Info plist file name. + /// + /// - Parameters: + /// - appName: The Firebase app's name; see ``FirebaseAppNames`` for app names with special + /// meanings in the TestApp. + /// - plistName: The file name of the Google Service Info plist, excluding the file extension; + /// for the default app this is typically called `GoogleService-Info` but any file name may be + /// used for other apps. + static func configure(appName: String, plistName: String) { + assert(!plistName.hasSuffix(".plist"), "The .plist file extension must be omitted.") + guard let plistPath = + Bundle.main.path(forResource: plistName, ofType: "plist") else { + fatalError("The file '\(plistName).plist' was not found.") + } + guard let options = FirebaseOptions(contentsOfFile: plistPath) else { + fatalError("Failed to parse options from '\(plistName).plist'.") + } + FirebaseApp.configure(name: appName, options: options) + } +} diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift b/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift index b466503417d..d1424111769 100644 --- a/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift +++ b/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift @@ -24,15 +24,15 @@ struct TestApp: App { // Configure default Firebase App FirebaseApp.configure() + // Configure a Firebase App that is the same as the default app but without App Check. + // This is used for tests that should fail when App Check is not configured. + FirebaseApp.configure( + appName: FirebaseAppNames.appCheckNotConfigured, + plistName: "GoogleService-Info" + ) + // Configure a Firebase App without a billing account (i.e., the "Spark" plan). - guard let plistPath = - Bundle.main.path(forResource: "GoogleService-Info-Spark", ofType: "plist") else { - fatalError("The file 'GoogleService-Info-Spark.plist' was not found.") - } - guard let options = FirebaseOptions(contentsOfFile: plistPath) else { - fatalError("Failed to parse options from 'GoogleService-Info-Spark.plist'.") - } - FirebaseApp.configure(name: FirebaseAppNames.spark, options: options) + FirebaseApp.configure(appName: FirebaseAppNames.spark, plistName: "GoogleService-Info-Spark") } var body: some Scene { diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index 92ebe09d159..d0c6ca3f5bd 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift @@ -115,4 +115,81 @@ struct GenerateContentIntegrationTests { #expect(candidatesTokensDetails.modality == .text) #expect(candidatesTokensDetails.tokenCount == usageMetadata.candidatesTokenCount) } + + // MARK: Streaming Tests + + @Test(arguments: InstanceConfig.allConfigs) + func generateContentStream(_ config: InstanceConfig) async throws { + let expectedText = """ + 1. Mercury + 2. Venus + 3. Earth + 4. Mars + 5. Jupiter + 6. Saturn + 7. Uranus + 8. Neptune + """ + let prompt = """ + What are the names of the planets in the solar system, ordered from closest to furthest from + the sun? Answer with a Markdown numbered list of the names and no other text. + """ + let model = VertexAI.componentInstance(config).generativeModel( + modelName: ModelNames.gemini2FlashLite, + generationConfig: generationConfig, + safetySettings: safetySettings + ) + let chat = model.startChat() + + let stream = try chat.sendMessageStream(prompt) + var textValues = [String]() + for try await value in stream { + try textValues.append(#require(value.text)) + } + + let userHistory = try #require(chat.history.first) + #expect(userHistory.role == "user") + #expect(userHistory.parts.count == 1) + let promptTextPart = try #require(userHistory.parts.first as? TextPart) + #expect(promptTextPart.text == prompt) + let modelHistory = try #require(chat.history.last) + #expect(modelHistory.role == "model") + #expect(modelHistory.parts.count == 1) + let modelTextPart = try #require(modelHistory.parts.first as? TextPart) + let modelText = modelTextPart.text.trimmingCharacters(in: .whitespacesAndNewlines) + #expect(modelText == expectedText) + #expect(textValues.count > 1) + let text = textValues.joined().trimmingCharacters(in: .whitespacesAndNewlines) + #expect(text == expectedText) + } + + // MARK: - App Check Tests + + @Test(arguments: [ + InstanceConfig.vertexV1AppCheckNotConfigured, + InstanceConfig.vertexV1BetaAppCheckNotConfigured, + // App Check is not supported on the Generative Language Developer API endpoint since it + // bypasses the Vertex AI in Firebase proxy. + ]) + func generateContent_appCheckNotConfigured_shouldFail(_ config: InstanceConfig) async throws { + let model = VertexAI.componentInstance(config).generativeModel( + modelName: ModelNames.gemini2Flash + ) + let prompt = "Where is Google headquarters located? Answer with the city name only." + + try await #require { + _ = try await model.generateContent(prompt) + } throws: { + guard let error = $0 as? GenerateContentError else { + Issue.record("Expected a \(GenerateContentError.self); got \($0.self).") + return false + } + guard case let .internalError(underlyingError) = error else { + Issue.record("Expected a GenerateContentError.internalError(...); got \(error.self).") + return false + } + + return String(describing: underlyingError).contains("Firebase App Check token is invalid") + } + } } diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift index 4bac229088e..e3da2d07496 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift @@ -67,62 +67,6 @@ final class IntegrationTests: XCTestCase { storage = Storage.storage() } - // MARK: - Generate Content - - func testGenerateContentStream() async throws { - let expectedText = """ - 1. Mercury - 2. Venus - 3. Earth - 4. Mars - 5. Jupiter - 6. Saturn - 7. Uranus - 8. Neptune - """ - let prompt = """ - What are the names of the planets in the solar system, ordered from closest to furthest from - the sun? Answer with a Markdown numbered list of the names and no other text. - """ - let chat = model.startChat() - - let stream = try chat.sendMessageStream(prompt) - var textValues = [String]() - for try await value in stream { - try textValues.append(XCTUnwrap(value.text)) - } - - let userHistory = try XCTUnwrap(chat.history.first) - XCTAssertEqual(userHistory.role, "user") - XCTAssertEqual(userHistory.parts.count, 1) - let promptTextPart = try XCTUnwrap(userHistory.parts.first as? TextPart) - XCTAssertEqual(promptTextPart.text, prompt) - let modelHistory = try XCTUnwrap(chat.history.last) - XCTAssertEqual(modelHistory.role, "model") - XCTAssertEqual(modelHistory.parts.count, 1) - let modelTextPart = try XCTUnwrap(modelHistory.parts.first as? TextPart) - let modelText = modelTextPart.text.trimmingCharacters(in: .whitespacesAndNewlines) - XCTAssertEqual(modelText, expectedText) - XCTAssertGreaterThan(textValues.count, 1) - let text = textValues.joined().trimmingCharacters(in: .whitespacesAndNewlines) - XCTAssertEqual(text, expectedText) - } - - func testGenerateContent_appCheckNotConfigured_shouldFail() async throws { - let app = try FirebaseApp.defaultNamedCopy(name: FirebaseAppNames.appCheckNotConfigured) - addTeardownBlock { await app.delete() } - let vertex = VertexAI.vertexAI(app: app) - let model = vertex.generativeModel(modelName: "gemini-2.0-flash") - let prompt = "Where is Google headquarters located? Answer with the city name only." - - do { - _ = try await model.generateContent(prompt) - XCTFail("Expected a Firebase App Check error; none thrown.") - } catch let GenerateContentError.internalError(error) { - XCTAssertTrue(String(describing: error).contains("Firebase App Check token is invalid")) - } - } - // MARK: - Count Tokens func testCountTokens_text() async throws { @@ -285,8 +229,7 @@ final class IntegrationTests: XCTestCase { } func testCountTokens_appCheckNotConfigured_shouldFail() async throws { - let app = try FirebaseApp.defaultNamedCopy(name: FirebaseAppNames.appCheckNotConfigured) - addTeardownBlock { await app.delete() } + let app = try XCTUnwrap(FirebaseApp.app(name: FirebaseAppNames.appCheckNotConfigured)) let vertex = VertexAI.vertexAI(app: app) let model = vertex.generativeModel(modelName: "gemini-2.0-flash") let prompt = "Why is the sky blue?" diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/FirebaseAppTestUtils.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/FirebaseAppTestUtils.swift deleted file mode 100644 index 5bd0fbec1e7..00000000000 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/FirebaseAppTestUtils.swift +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2024 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 FirebaseCore - -extension FirebaseApp { - /// Configures another `FirebaseApp` with the specified `name` and the same `FirebaseOptions`. - func namedCopy(name: String) throws -> FirebaseApp { - FirebaseApp.configure(name: name, options: options) - guard let app = FirebaseApp.app(name: name) else { - throw AppNotFound(name: name) - } - return app - } - - /// Configures an app with the specified `name` and the same `FirebaseOptions` as the default app. - static func defaultNamedCopy(name: String) throws -> FirebaseApp { - guard FirebaseApp.isDefaultAppConfigured(), let defaultApp = FirebaseApp.app() else { - throw DefaultAppNotConfigured() - } - return try defaultApp.namedCopy(name: name) - } - - struct AppNotFound: Error { - let name: String - } - - struct DefaultAppNotConfigured: Error {} -} diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index 7c233e94f7a..0c1a3728673 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift +++ b/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -34,6 +34,15 @@ struct InstanceConfig { ) static let allConfigs = [vertexV1, vertexV1Beta, developerV1, developerV1Beta] + static let vertexV1AppCheckNotConfigured = InstanceConfig( + appName: FirebaseAppNames.appCheckNotConfigured, + apiConfig: APIConfig(service: .vertexAI, version: .v1) + ) + static let vertexV1BetaAppCheckNotConfigured = InstanceConfig( + appName: FirebaseAppNames.appCheckNotConfigured, + apiConfig: APIConfig(service: .vertexAI, version: .v1beta) + ) + let appName: String? let location: String? let apiConfig: APIConfig diff --git a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj index 6f13a62472e..0012ad35261 100644 --- a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj +++ b/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj @@ -21,8 +21,8 @@ 8692F29A2CC9477800539E8F /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F2992CC9477800539E8F /* FirebaseAuth */; }; 8692F29C2CC9477800539E8F /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F29B2CC9477800539E8F /* FirebaseStorage */; }; 8692F29E2CC9477800539E8F /* FirebaseVertexAI in Frameworks */ = {isa = PBXBuildFile; productRef = 8692F29D2CC9477800539E8F /* FirebaseVertexAI */; }; - 8698D7462CD3CF3600ABA833 /* FirebaseAppTestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */; }; 8698D7482CD4332B00ABA833 /* TestAppCheckProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */; }; + 86CC31352D91EE9E0087E964 /* FirebaseAppUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86CC31342D91EE9E0087E964 /* FirebaseAppUtils.swift */; }; 86D77DFC2D7A5340003D155D /* GenerateContentIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D77DFB2D7A5340003D155D /* GenerateContentIntegrationTests.swift */; }; 86D77DFE2D7B5C86003D155D /* GoogleService-Info-Spark.plist in Resources */ = {isa = PBXBuildFile; fileRef = 86D77DFD2D7B5C86003D155D /* GoogleService-Info-Spark.plist */; }; 86D77E022D7B63AF003D155D /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86D77E012D7B63AC003D155D /* Constants.swift */; }; @@ -53,8 +53,8 @@ 868A7C502CCC263300E449DD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 868A7C532CCC26B500E449DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 868A7C552CCC271300E449DD /* TestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TestApp.entitlements; sourceTree = ""; }; - 8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAppTestUtils.swift; sourceTree = ""; }; 8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAppCheckProviderFactory.swift; sourceTree = ""; }; + 86CC31342D91EE9E0087E964 /* FirebaseAppUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseAppUtils.swift; sourceTree = ""; }; 86D77DFB2D7A5340003D155D /* GenerateContentIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateContentIntegrationTests.swift; sourceTree = ""; }; 86D77DFD2D7B5C86003D155D /* GoogleService-Info-Spark.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info-Spark.plist"; sourceTree = ""; }; 86D77E012D7B63AC003D155D /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; @@ -129,6 +129,7 @@ 8698D7472CD4332B00ABA833 /* TestAppCheckProviderFactory.swift */, 8661385D2CC943DD00F4B78E /* ContentView.swift */, 86D77E012D7B63AC003D155D /* Constants.swift */, + 86CC31342D91EE9E0087E964 /* FirebaseAppUtils.swift */, ); path = Sources; sourceTree = ""; @@ -158,7 +159,6 @@ isa = PBXGroup; children = ( 86D77E032D7B6C95003D155D /* InstanceConfig.swift */, - 8698D7452CD3CF2F00ABA833 /* FirebaseAppTestUtils.swift */, 862218802D04E08D007ED2D4 /* IntegrationTestUtils.swift */, ); path = Utilities; @@ -275,6 +275,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 86CC31352D91EE9E0087E964 /* FirebaseAppUtils.swift in Sources */, 8661385E2CC943DD00F4B78E /* ContentView.swift in Sources */, 8661385C2CC943DD00F4B78E /* TestApp.swift in Sources */, 8698D7482CD4332B00ABA833 /* TestAppCheckProviderFactory.swift in Sources */, @@ -288,7 +289,6 @@ files = ( 8689CDCC2D7F8BD700BF426B /* CountTokensIntegrationTests.swift in Sources */, 86D77E042D7B6C9D003D155D /* InstanceConfig.swift in Sources */, - 8698D7462CD3CF3600ABA833 /* FirebaseAppTestUtils.swift in Sources */, 868A7C4F2CCC229F00E449DD /* Credentials.swift in Sources */, 864F8F712D4980DD0002EA7E /* ImagenIntegrationTests.swift in Sources */, 862218812D04E098007ED2D4 /* IntegrationTestUtils.swift in Sources */, From 2a5fe43a6df9d694bad7d88e7b29024c6c97310e Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 25 Mar 2025 10:57:15 -0400 Subject: [PATCH 18/21] [Vertex AI] Fix typo in 11.9.0 changelog and add link to guide (#14615) --- FirebaseVertexAI/CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index cd38e2bec67..d70368aa708 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -11,12 +11,12 @@ are required. (#14558) # 11.9.0 -- [feature] **Public Preview**: Added support for generating images using the - Imagen 3 model. +- [feature] **Public Preview**: Added support for + [generating images](https://firebase.google.com/docs/vertex-ai/generate-images-imagen?platform=ios) + using the Imagen 3 models.

- Note: This feature is in Public Preview, which means that the it is not - subject to any SLA or deprecation policy and could change in - backwards-incompatible ways. + Note: This feature is in Public Preview, which means that it is not subject to + any SLA or deprecation policy and could change in backwards-incompatible ways. - [feature] Added support for modality-based token count. (#14406) # 11.6.0 From dfe68ba1f6016a93473dca940dddcca8ba6d9174 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 25 Mar 2025 16:01:00 -0700 Subject: [PATCH 19/21] [11.11.0] Firestore binary update (#14617) --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 5b935d4572e..11679cfabf3 100644 --- a/Package.swift +++ b/Package.swift @@ -1510,8 +1510,8 @@ func firestoreTargets() -> [Target] { } else { return .binaryTarget( name: "FirebaseFirestoreInternal", - url: "https://dl.google.com/firebase/ios/bin/firestore/11.10.0/rc0/FirebaseFirestoreInternal.zip", - checksum: "a3a9f67c65c513346dce2d3b26072258ce4d42507d2b4e81db6c9e6869542c66" + url: "https://dl.google.com/firebase/ios/bin/firestore/11.11.0/rc0/FirebaseFirestoreInternal.zip", + checksum: "7f4c24cee332af39d2a93987788ea50212c55d06b1c06794587e7068f96e70c8" ) } }() From b20d8125b736e48793213da25691026bad4fb4fb Mon Sep 17 00:00:00 2001 From: Adam Duke <94930+adamvduke@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:30:54 -0400 Subject: [PATCH 20/21] consolidate FIRCLSRedactUUID tests for clarity (#14621) --- Crashlytics/UnitTests/FIRCLSUtilityTests.m | 106 +++++++++++---------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/Crashlytics/UnitTests/FIRCLSUtilityTests.m b/Crashlytics/UnitTests/FIRCLSUtilityTests.m index d2114530ca3..ac59f8b6aea 100644 --- a/Crashlytics/UnitTests/FIRCLSUtilityTests.m +++ b/Crashlytics/UnitTests/FIRCLSUtilityTests.m @@ -77,56 +77,60 @@ - (void)testHexToStringWithNonPrintableCharacters { XCTAssertEqualObjects([NSString stringWithUTF8String:string], @"52d04e1f", @""); } -- (void)testRedactUUIDWithExpectedPattern { - const char* readonly = "CoreSimulator 704.12.1 - Device: iPhone SE (2nd generation) " - "(45D62CC2-CFB5-4E33-AB61-B0684627F1B6) - Runtime: iOS 13.4 (17E8260) - " - "DeviceType: iPhone SE (2nd generation)"; - size_t len = strlen(readonly); - char message[len]; - strcpy(message, readonly); - - FIRCLSRedactUUID(message); - - NSString* actual = [NSString stringWithUTF8String:message]; - NSString* expected = @"CoreSimulator 704.12.1 - Device: iPhone SE (2nd generation) " - @"(********-****-****-****-************) - Runtime: iOS 13.4 (17E8260) - " - @"DeviceType: iPhone SE (2nd generation)"; - - XCTAssertEqualObjects(actual, expected); -} - -- (void)testRedactUUIDWithMalformedPattern { - const char* readonly = "CoreSimulator 704.12.1 - Device: iPhone SE (2nd generation) " - "(45D62CC2-CFB5-4E33-AB61-B0684627F1B6"; - size_t len = strlen(readonly); - char message[len]; - strcpy(message, readonly); - - FIRCLSRedactUUID(message); - - NSString* actual = [NSString stringWithUTF8String:message]; - NSString* expected = @"CoreSimulator 704.12.1 - Device: iPhone SE (2nd generation) " - @"(45D62CC2-CFB5-4E33-AB61-B0684627F1B6"; - - XCTAssertEqualObjects(actual, expected); -} - -- (void)testRedactUUIDWithoutUUID { - const char* readonly = "Fatal error: file /Users/test/src/foo/bar/ViewController.swift, line 25"; - size_t len = strlen(readonly); - char message[len]; - strcpy(message, readonly); - - FIRCLSRedactUUID(message); - - NSString* actual = [NSString stringWithUTF8String:message]; - NSString* expected = @"Fatal error: file /Users/test/src/foo/bar/ViewController.swift, line 25"; - - XCTAssertEqualObjects(actual, expected); -} - -- (void)testRedactUUIDWithNull { - char* message = NULL; - XCTAssertNoThrow(FIRCLSRedactUUID(message)); +- (void)testRedactUUID { + // Define a local struct to hold the test data + struct TestCase { + const char *name; // Name of the test case + const char *readonly; // Input string + const char *expected; // Expected output string + }; + + // Initialize an array of test cases + struct TestCase tests[] = { + {.name = "Test with valid UUID", + .readonly = "CoreSimulator 704.12.1 - Device: iPhone SE (2nd generation) " + "(45D62CC2-CFB5-4E33-AB61-B0684627F1B6) - Runtime: iOS 13.4 (17E8260) - " + "DeviceType: iPhone SE (2nd generation)", + .expected = "CoreSimulator 704.12.1 - Device: iPhone SE (2nd generation) " + "(********-****-****-****-************) - Runtime: iOS 13.4 (17E8260) - " + "DeviceType: iPhone SE (2nd generation)"}, + {.name = "Test with no UUID", + .readonly = "Example with no UUID", + .expected = "Example with no UUID"}, + {.name = "Test with only UUID", + .readonly = "(12345678-1234-1234-1234-123456789012)", + .expected = "(********-****-****-****-************)"}, + {.name = "Test with malformed UUID pattern", + .readonly = "CoreSimulator 704.12.1 - Device: iPhone SE (2nd generation) " + "(45D62CC2-CFB5-4E33-AB61-B0684627F1B6", + .expected = "CoreSimulator 704.12.1 - Device: iPhone SE (2nd generation) " + "(45D62CC2-CFB5-4E33-AB61-B0684627F1B6"}, + {.name = "Test with other error string", + .readonly = "Fatal error: file " + "/Users/test/src/foo/bar/ViewController.swift, line 25", + .expected = "Fatal error: file " + "/Users/test/src/foo/bar/ViewController.swift, line 25"}, + {.name = "Test with NULL input", .readonly = NULL, .expected = NULL}}; + + // Loop over the array of test cases + for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + if (tests[i].readonly == NULL) { + XCTAssertNoThrow(FIRCLSRedactUUID(NULL)); + continue; + } + + // copy the message because the function modifies the input + // and the input is const + char message[256]; + snprintf(message, sizeof(message), "%s", tests[i].readonly); + + // Redact the UUID + FIRCLSRedactUUID(message); + + // Compare the result with the expected output + NSString *actual = [NSString stringWithUTF8String:message]; + NSString *expected = [NSString stringWithUTF8String:tests[i].expected]; + XCTAssertEqualObjects(actual, expected, @"Test named: '%s' failed", tests[i].name); + } } @end From d1f7c7e8eaa74d7e44467184dc5f592268247d33 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 28 Mar 2025 15:48:41 -0400 Subject: [PATCH 21/21] [Vertex AI] Fix unsupported model name check introduced in #14610 (#14629) --- .../Sources/GenerativeModel.swift | 16 ++++------- .../Types/Public/Imagen/ImagenModel.swift | 11 ++------ FirebaseVertexAI/Sources/VertexAI.swift | 18 ++++++++++-- FirebaseVertexAI/Tests/Unit/ChatTests.swift | 2 +- .../Tests/Unit/GenerativeModelTests.swift | 28 +++++++++---------- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseVertexAI/Sources/GenerativeModel.swift index 6df2620bc7f..8ec905cc436 100644 --- a/FirebaseVertexAI/Sources/GenerativeModel.swift +++ b/FirebaseVertexAI/Sources/GenerativeModel.swift @@ -53,7 +53,8 @@ public final class GenerativeModel: Sendable { /// Initializes a new remote model with the given parameters. /// /// - Parameters: - /// - name: The name of the model to use, for example `"gemini-1.0-pro"`. + /// - modelResourceName: The resource name of the model to use, for example + /// `"projects/{project-id}/locations/{location-id}/publishers/google/models/{model-name}"`. /// - firebaseInfo: Firebase data used by the SDK, including project ID and API key. /// - apiConfig: Configuration for the backend API used by this model. /// - generationConfig: The content generation parameters your model should use. @@ -64,7 +65,7 @@ public final class GenerativeModel: Sendable { /// only text content is supported. /// - requestOptions: Configuration parameters for sending requests to the backend. /// - urlSession: The `URLSession` to use for requests; defaults to `URLSession.shared`. - init(name: String, + init(modelResourceName: String, firebaseInfo: FirebaseInfo, apiConfig: APIConfig, generationConfig: GenerationConfig? = nil, @@ -74,14 +75,7 @@ public final class GenerativeModel: Sendable { systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, urlSession: URLSession = .shared) { - if !name.starts(with: GenerativeModel.geminiModelNamePrefix) { - VertexLog.warning(code: .unsupportedGeminiModel, """ - Unsupported Gemini model "\(name)"; see \ - https://firebase.google.com/docs/vertex-ai/models for a list supported Gemini model names. - """) - } - - modelResourceName = name + self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( firebaseInfo: firebaseInfo, @@ -108,7 +102,7 @@ public final class GenerativeModel: Sendable { `\(VertexLog.enableArgumentKey)` as a launch argument in Xcode. """) } - VertexLog.debug(code: .generativeModelInitialized, "Model \(name) initialized.") + VertexLog.debug(code: .generativeModelInitialized, "Model \(modelResourceName) initialized.") } /// Generates content from String and/or image inputs, given to the model as a prompt, that are diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift b/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift index 13b0b0dba35..90e5bb9f715 100644 --- a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift +++ b/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift @@ -47,21 +47,14 @@ public final class ImagenModel { /// Configuration parameters for sending requests to the backend. let requestOptions: RequestOptions - init(name: String, + init(modelResourceName: String, firebaseInfo: FirebaseInfo, apiConfig: APIConfig, generationConfig: ImagenGenerationConfig?, safetySettings: ImagenSafetySettings?, requestOptions: RequestOptions, urlSession: URLSession = .shared) { - if !name.starts(with: ImagenModel.imagenModelNamePrefix) { - VertexLog.warning(code: .unsupportedImagenModel, """ - Unsupported Imagen model "\(name)"; see \ - https://firebase.google.com/docs/vertex-ai/models for a list supported Imagen model names. - """) - } - - modelResourceName = name + self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( firebaseInfo: firebaseInfo, diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index 2d0bf650711..6edf8378b4d 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -70,8 +70,15 @@ public class VertexAI { systemInstruction: ModelContent? = nil, requestOptions: RequestOptions = RequestOptions()) -> GenerativeModel { + if !modelName.starts(with: GenerativeModel.geminiModelNamePrefix) { + VertexLog.warning(code: .unsupportedGeminiModel, """ + Unsupported Gemini model "\(modelName)"; see \ + https://firebase.google.com/docs/vertex-ai/models for a list supported Gemini model names. + """) + } + return GenerativeModel( - name: modelResourceName(modelName: modelName), + modelResourceName: modelResourceName(modelName: modelName), firebaseInfo: firebaseInfo, apiConfig: apiConfig, generationConfig: generationConfig, @@ -102,8 +109,15 @@ public class VertexAI { public func imagenModel(modelName: String, generationConfig: ImagenGenerationConfig? = nil, safetySettings: ImagenSafetySettings? = nil, requestOptions: RequestOptions = RequestOptions()) -> ImagenModel { + if !modelName.starts(with: ImagenModel.imagenModelNamePrefix) { + VertexLog.warning(code: .unsupportedImagenModel, """ + Unsupported Imagen model "\(modelName)"; see \ + https://firebase.google.com/docs/vertex-ai/models for a list supported Imagen model names. + """) + } + return ImagenModel( - name: modelResourceName(modelName: modelName), + modelResourceName: modelResourceName(modelName: modelName), firebaseInfo: firebaseInfo, apiConfig: apiConfig, generationConfig: generationConfig, diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseVertexAI/Tests/Unit/ChatTests.swift index 5215739cb1e..3bf56ab9069 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseVertexAI/Tests/Unit/ChatTests.swift @@ -59,7 +59,7 @@ final class ChatTests: XCTestCase { options: FirebaseOptions(googleAppID: "ignore", gcmSenderID: "ignore")) let model = GenerativeModel( - name: "my-model", + modelResourceName: "my-model", firebaseInfo: FirebaseInfo( projectID: "my-project-id", apiKey: "API_KEY", diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index b9a53dc7732..33dc84a866e 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -70,7 +70,7 @@ final class GenerativeModelTests: XCTestCase { configuration.protocolClasses = [MockURLProtocol.self] urlSession = try XCTUnwrap(URLSession(configuration: configuration)) model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(), apiConfig: apiConfig, tools: nil, @@ -276,7 +276,7 @@ final class GenerativeModelTests: XCTestCase { ) let model = GenerativeModel( // Model name is prefixed with "models/". - name: "models/test-model", + modelResourceName: "models/test-model", firebaseInfo: testFirebaseInfo(), apiConfig: apiConfig, tools: nil, @@ -399,7 +399,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContent_appCheck_validToken() async throws { let appCheckToken = "test-valid-token" model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(appCheck: AppCheckInteropFake(token: appCheckToken)), apiConfig: apiConfig, tools: nil, @@ -420,7 +420,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContent_dataCollectionOff() async throws { let appCheckToken = "test-valid-token" model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(appCheck: AppCheckInteropFake(token: appCheckToken), privateAppID: true), apiConfig: apiConfig, @@ -442,7 +442,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContent_appCheck_tokenRefreshError() async throws { model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(appCheck: AppCheckInteropFake(error: AppCheckErrorFake())), apiConfig: apiConfig, tools: nil, @@ -463,7 +463,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContent_auth_validAuthToken() async throws { let authToken = "test-valid-token" model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(auth: AuthInteropFake(token: authToken)), apiConfig: apiConfig, tools: nil, @@ -483,7 +483,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContent_auth_nilAuthToken() async throws { model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(auth: AuthInteropFake(token: nil)), apiConfig: apiConfig, tools: nil, @@ -503,7 +503,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContent_auth_authTokenRefreshError() async throws { model = GenerativeModel( - name: "my-model", + modelResourceName: "my-model", firebaseInfo: testFirebaseInfo(auth: AuthInteropFake(error: AuthErrorFake())), apiConfig: apiConfig, tools: nil, @@ -900,7 +900,7 @@ final class GenerativeModelTests: XCTestCase { ) let requestOptions = RequestOptions(timeout: expectedTimeout) model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(), apiConfig: apiConfig, tools: nil, @@ -1204,7 +1204,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContentStream_appCheck_validToken() async throws { let appCheckToken = "test-valid-token" model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(appCheck: AppCheckInteropFake(token: appCheckToken)), apiConfig: apiConfig, tools: nil, @@ -1225,7 +1225,7 @@ final class GenerativeModelTests: XCTestCase { func testGenerateContentStream_appCheck_tokenRefreshError() async throws { model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(appCheck: AppCheckInteropFake(error: AppCheckErrorFake())), apiConfig: apiConfig, tools: nil, @@ -1375,7 +1375,7 @@ final class GenerativeModelTests: XCTestCase { ) let requestOptions = RequestOptions(timeout: expectedTimeout) model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(), apiConfig: apiConfig, tools: nil, @@ -1451,7 +1451,7 @@ final class GenerativeModelTests: XCTestCase { parts: "You are a calculator. Use the provided tools." ) model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(), apiConfig: apiConfig, generationConfig: generationConfig, @@ -1511,7 +1511,7 @@ final class GenerativeModelTests: XCTestCase { ) let requestOptions = RequestOptions(timeout: expectedTimeout) model = GenerativeModel( - name: testModelResourceName, + modelResourceName: testModelResourceName, firebaseInfo: testFirebaseInfo(), apiConfig: apiConfig, tools: nil,