diff --git a/CMakeLists.txt b/CMakeLists.txt index c1bbe08a9b..88f166f12c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,8 +96,14 @@ set(FIREBASE_XCODE_TARGET_FORMAT "frameworks" CACHE STRING set(FIREBASE_CPP_SDK_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}) project (firebase NONE) + +set(CMAKE_Swift_LANGUAGE_VERSION 6.2) + enable_language(C) enable_language(CXX) +if(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "tvOS") + enable_language(Swift) +endif() if(NOT DEFINED CMAKE_CXX_COMPILER_LAUNCHER) find_program(CCACHE_PROGRAM ccache) @@ -477,6 +483,7 @@ if(DESKTOP) -DUSE_LIBUV=1 ) elseif(APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_DISABLE_AVAILABILITY") set(websockets_additional_defines ${websockets_additional_defines} -DUSE_LIBUV=1 diff --git a/analytics/CMakeLists.txt b/analytics/CMakeLists.txt index 63d33b125f..0065008dd7 100644 --- a/analytics/CMakeLists.txt +++ b/analytics/CMakeLists.txt @@ -72,7 +72,8 @@ set(android_SRCS # Source files used by the iOS implementation. set(ios_SRCS - src/analytics_ios.mm) + src/analytics_ios.mm + src/ios/swift/AppleTransactionVerifier.swift) # Source files used by the desktop / stub implementation. set(desktop_SRCS @@ -88,8 +89,12 @@ if(ANDROID) set(analytics_platform_SRCS "${android_SRCS}") elseif(IOS) + if(CMAKE_GENERATOR STREQUAL "Unix Makefiles") + message(FATAL_ERROR "Swift is not supported by the 'Unix Makefiles' generator on iOS. Please use the Xcode generator (-G Xcode) or Ninja (-G Ninja).") + endif() set(analytics_platform_SRCS "${ios_SRCS}") + else() set(analytics_platform_SRCS "${desktop_SRCS}") @@ -99,6 +104,9 @@ add_library(firebase_analytics STATIC ${common_SRCS} ${analytics_platform_SRCS}) + +add_dependencies(firebase_analytics FIREBASE_ANALYTICS_GENERATED_HEADERS) + set_property(TARGET firebase_analytics PROPERTY FOLDER "Firebase Cpp") # Set up the dependency on Firebase App. @@ -122,20 +130,86 @@ target_compile_definitions(firebase_analytics -DINTERNAL_EXPERIMENTAL=1 ) # Automatically include headers that might not be declared. -if(MSVC) - add_definitions(/FI"assert.h" /FI"string.h" /FI"stdint.h") +if(IOS) + if(MSVC) + target_compile_options(firebase_analytics PRIVATE + $<$>:/FI"assert.h"> + $<$>:/FI"string.h"> + $<$>:/FI"stdint.h">) + else() + target_compile_options(firebase_analytics PRIVATE + $<$>:SHELL:-include assert.h -include string.h> + ) + endif() else() - add_definitions(-include assert.h -include string.h) + if(MSVC) + target_compile_options(firebase_analytics PRIVATE + /FI"assert.h" /FI"string.h" /FI"stdint.h") + else() + target_compile_options(firebase_analytics PRIVATE + SHELL:-include assert.h -include string.h + ) + endif() endif() if(ANDROID) firebase_cpp_proguard_file(analytics) elseif(IOS) - # Enable Automatic Reference Counting (ARC) and Bitcode. + # Enable Automatic Reference Counting (ARC) and Bitcode specifically for Objective-C++ files. + # Note: -fembed-bitcode is placed here for src/analytics_ios.mm so that it is not passed + # to the Swift compiler, which does not support the flag. + set_source_files_properties(src/analytics_ios.mm PROPERTIES COMPILE_OPTIONS "-fobjc-arc;-fembed-bitcode") + + if(CMAKE_GENERATOR STREQUAL "Xcode") + target_include_directories(firebase_analytics PRIVATE "$(DERIVED_FILE_DIR)") + target_compile_options(firebase_analytics PRIVATE + "-I$(OBJECT_FILE_DIR_normal)/$(CURRENT_ARCH)" + ) + else() + target_include_directories(firebase_analytics PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + endif() + + # Swift needs to find the FirebaseAnalytics module from CocoaPods + set(pods_dir "${FIREBASE_POD_DIR}/Pods") + + # Point to the base directories containing the .xcframework folders. + # Xcode natively handles XCFrameworks and will pick the right slice automatically. + # Determine the xcframework architecture slice based on the target platform + # and if it is running on simulator or device. + string(TOLOWER "${CMAKE_OSX_SYSROOT}" sysroot_lower) + if(CMAKE_SYSTEM_NAME STREQUAL "tvOS") + if(sysroot_lower MATCHES "simulator") + set(analytics_slice "tvos-arm64_x86_64-simulator") + else() + set(analytics_slice "tvos-arm64") + endif() + else() + if(sysroot_lower MATCHES "simulator") + set(analytics_slice "ios-arm64_x86_64-simulator") + else() + set(analytics_slice "ios-arm64") + endif() + endif() + + set(analytics_framework_dir "${pods_dir}/FirebaseAnalytics/Frameworks/FirebaseAnalytics.xcframework/${analytics_slice}") + set(measurement_framework_dir "${pods_dir}/GoogleAppMeasurement/Frameworks/GoogleAppMeasurement.xcframework/${analytics_slice}") + target_compile_options(firebase_analytics - PUBLIC "-fobjc-arc" "-fembed-bitcode") - target_link_libraries(firebase_analytics - PUBLIC "-fembed-bitcode") + PRIVATE + $<$:-F${analytics_framework_dir}> + $<$:-F${measurement_framework_dir}> + ) + + target_link_options(firebase_analytics + PUBLIC + "-F${analytics_framework_dir}" + "-F${measurement_framework_dir}" + ) + + # Prevent Xcode from trying to build or evaluate headers for unused architectures + set_target_properties(firebase_analytics PROPERTIES + XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH "YES" + ) setup_pod_headers( firebase_analytics diff --git a/analytics/integration_test/readme.md b/analytics/integration_test/readme.md index 18599e5952..4083b8edd9 100644 --- a/analytics/integration_test/readme.md +++ b/analytics/integration_test/readme.md @@ -64,6 +64,37 @@ Building and Running the sample "Analytics" tab accessible from [https://firebase.google.com/console/](https://firebase.google.com/console/). +#### iOS Testing LogAppleTransaction + +To test the log apple transaction function, you should use the existing test app and xcode's simulated transactions. +The manual test will involve running the integration test: `firebase_analytics_test/TestLogAppleTransaction` and verifying that it logs a transaction to the console. + + - Step 1: Set up the Local Xcode Environment + - In Xcode, go to File > New > File from Template and create a StoreKit Configuration File (.storekit). + - Give the configuration any name. + - Target both integration_test and integration_test_tvos + - Add at least one dummy product to this file. + - Do this by selecting the file in xcode and clicking the + button in the bottom left corner. + - Choose a Non-Consumable in app purchase product. + - Give it a Reference name of your choice (e.g. "ReferenceAppleIapProduct"). + - Give it a Product ID of your choice (e.g. "com.example.nonconsumable"). + - Make the app use the store kit file. In the top bar go to Product > Scheme > Edit Scheme... + - In the left hand menu select Run + - Select the Options tab on the right + - Set the StoreKit Configuration dropdown to your new .storekit file. + - Step 2: Validate logging transactions + - Try running the test app with the dummy transaction ID. It should return an error from the + LogAppleTransactions function. + - After runnign the app once you can create a simulated transaction for testing. + - To create a simulated transaction ID: + - Go to Debug > StoreKit > Manage Transactions. + - Click the + button in the bottom left corner. + - Select the Non-Consumable in app purchase product. + - Copy the transaction ID to the test case and replace 'dummy_transaction_id' with your new transaction ID. e.g. '0' + - Make sure to update the testcase to now expect success. + - Then try running the test app again with the simulated transaction ID. + - It should log the transaction to the console. Both the Xcode console and firebase console should show a log for an in app purchase. + ### Android - Register your Android app with Firebase. - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach diff --git a/analytics/integration_test/src/integration_test.cc b/analytics/integration_test/src/integration_test.cc index 54016135d9..462b9b5912 100644 --- a/analytics/integration_test/src/integration_test.cc +++ b/analytics/integration_test/src/integration_test.cc @@ -22,6 +22,10 @@ #include #include +#if defined(__APPLE__) +#include +#endif + #include "app_framework.h" // NOLINT #include "firebase/analytics.h" #include "firebase/analytics/event_names.h" @@ -299,6 +303,21 @@ TEST_F(FirebaseAnalyticsTest, TestDesktopDebugMode) { firebase::analytics::SetDesktopDebugMode(false); } +TEST_F(FirebaseAnalyticsTest, TestLogAppleTransaction) { + auto future = + firebase::analytics::LogAppleTransaction("dummy_transaction_id"); + WaitForCompletionAnyResult(future, "LogAppleTransaction"); +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV) + // On iOS/tvOS, passing a dummy transaction ID will fail to find a verified + // transaction. + EXPECT_NE(future.error(), 0); +#else + // On Android and Desktop (including macOS), LogAppleTransaction is a no-op + // that returns success. + EXPECT_EQ(future.error(), 0); +#endif +} + TEST_F(FirebaseAnalyticsTest, TestLogEvents) { // Log an event with no parameters. firebase::analytics::LogEvent(firebase::analytics::kEventLogin); diff --git a/analytics/src/analytics_android.cc b/analytics/src/analytics_android.cc index fe1ba4235a..9d097a847c 100644 --- a/analytics/src/analytics_android.cc +++ b/analytics/src/analytics_android.cc @@ -502,6 +502,30 @@ void LogEvent(const char* name) { LogEvent(name, nullptr, static_cast(0)); } +/// Log an Apple StoreKit 2 transaction. This is a no-op on Android and returns +/// success. +Future LogAppleTransaction(const char* transaction_id) { + auto* api = internal::FutureData::Get() ? internal::FutureData::Get()->api() + : nullptr; + if (!api) { + return Future(); + } + const auto future_handle = + api->SafeAlloc(internal::kAnalyticsFnLogAppleTransaction); + api->Complete(future_handle, 0, ""); + return Future(api, future_handle.get()); +} + +Future LogAppleTransactionLastResult() { + auto* api = internal::FutureData::Get() ? internal::FutureData::Get()->api() + : nullptr; + if (!api) { + return Future(); + } + return static_cast&>( + api->LastResult(internal::kAnalyticsFnLogAppleTransaction)); +} + // Log an event with associated parameters. void LogEvent(const char* name, const Parameter* parameters, size_t number_of_parameters) { diff --git a/analytics/src/analytics_common.h b/analytics/src/analytics_common.h index 90de297d7a..199a28759f 100644 --- a/analytics/src/analytics_common.h +++ b/analytics/src/analytics_common.h @@ -26,7 +26,8 @@ namespace internal { enum AnalyticsFn { kAnalyticsFnGetAnalyticsInstanceId, kAnalyticsFnGetSessionId, - kAnalyticsFnCount + kAnalyticsFnLogAppleTransaction, + kAnalyticsFnCount, }; // Data structure which holds the Future API for this module. diff --git a/analytics/src/analytics_desktop.cc b/analytics/src/analytics_desktop.cc index 880c3936a9..c3181621e9 100644 --- a/analytics/src/analytics_desktop.cc +++ b/analytics/src/analytics_desktop.cc @@ -467,6 +467,30 @@ void LogEvent(const char* name) { LogEvent(name, static_cast(nullptr), 0); } +/// Log an Apple StoreKit 2 transaction. This is a no-op on Desktop and returns +/// success. +Future LogAppleTransaction(const char* transaction_id) { + auto* api = internal::FutureData::Get() ? internal::FutureData::Get()->api() + : nullptr; + if (!api) { + return Future(); + } + const auto future_handle = + api->SafeAlloc(internal::kAnalyticsFnLogAppleTransaction); + api->Complete(future_handle, 0, ""); + return Future(api, future_handle.get()); +} + +Future LogAppleTransactionLastResult() { + auto* api = internal::FutureData::Get() ? internal::FutureData::Get()->api() + : nullptr; + if (!api) { + return Future(); + } + return static_cast&>( + api->LastResult(internal::kAnalyticsFnLogAppleTransaction)); +} + void LogEvent(const char* name, const char* parameter_name, const char* parameter_value) { if (parameter_name == nullptr) { diff --git a/analytics/src/analytics_ios.mm b/analytics/src/analytics_ios.mm index 38aa5275e7..d51bc9bfa7 100644 --- a/analytics/src/analytics_ios.mm +++ b/analytics/src/analytics_ios.mm @@ -20,9 +20,12 @@ #import "FIRAnalytics+OnDevice.h" #import "FIRAnalytics.h" +#include "analytics/src/analytics_common.h" #include "analytics/src/include/firebase/analytics.h" -#include "analytics/src/analytics_common.h" +// Include the generated Swift header for the C++ bridge. +#include "firebase_analytics-Swift.h" + #include "app/src/assert.h" #include "app/src/include/firebase/internal/mutex.h" #include "app/src/include/firebase/version.h" @@ -231,6 +234,42 @@ void LogEvent(const char* name) { [FIRAnalytics logEventWithName:@(name) parameters:@{}]; } +Future LogAppleTransaction(const char* transaction_id) { + MutexLock lock(g_mutex); + FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); + + auto* api = internal::FutureData::Get()->api(); + const auto future_handle = api->SafeAlloc(internal::kAnalyticsFnLogAppleTransaction); + + if (!transaction_id) { + api->Complete(future_handle, -1, "Transaction ID is null"); + return Future(api, future_handle.get()); + } + + [AppleTransactionVerifier + verifyWithTransactionId:SafeString(transaction_id) + completion:^(BOOL isFound) { + MutexLock lock(g_mutex); + if (!internal::IsInitialized()) return; + + auto* api = internal::FutureData::Get()->api(); + if (isFound) { + api->Complete(future_handle, 0, ""); + } else { + api->Complete(future_handle, -1, "StoreKit 2 transaction not found."); + } + }]; + + return Future(api, future_handle.get()); +} + +Future LogAppleTransactionLastResult() { + MutexLock lock(g_mutex); + FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); + return static_cast&>( + internal::FutureData::Get()->api()->LastResult(internal::kAnalyticsFnLogAppleTransaction)); +} + // Declared here so that it can be used, defined below. NSDictionary* MapToDictionary(const std::map& map); diff --git a/analytics/src/include/firebase/analytics.h b/analytics/src/include/firebase/analytics.h index 47a5aab72b..fcdb723403 100644 --- a/analytics/src/include/firebase/analytics.h +++ b/analytics/src/include/firebase/analytics.h @@ -455,6 +455,37 @@ void LogEvent(const char* name, const char* parameter_name, /// @endif void LogEvent(const char* name); +/// @brief Logs an in-app purchase transaction specifically for Apple's +/// StoreKit 2. +/// +/// This function is intended for developers on iOS who process transactions +/// via custom native plugins or engines and need to securely log those +/// transactions natively through Google Analytics. The provided ID must map 1:1 +/// with the native Apple `Transaction.id`. If a matching transaction is not +/// found in the Apple device's purchase history, nothing will be logged to +/// Analytics. +/// +/// @note Finished consumable transactions are removed from the local +/// transaction history and cannot be retrieved by this function once +/// finished. Developers should either call this function before finishing +/// the transaction or use `FirebaseAnalytics.LogEvent` directly as a +/// fallback. +/// +/// @param transaction_id The native Apple transaction identifier as a +/// null-terminated string. +/// +/// @returns A Future that completes successfully when the native +/// StoreKit 2 transaction is found and logged. If the transaction +/// cannot be found, the Future will complete with a non-zero error(). +Future LogAppleTransaction(const char* transaction_id); + +/// @brief Get the result of the most recent LogAppleTransaction() call. +/// +/// @returns A Future that completes successfully when the native +/// StoreKit 2 transaction is found and logged. If the transaction +/// cannot be found, the Future will complete with a non-zero error(). +Future LogAppleTransactionLastResult(); + /// @brief Log an event with associated parameters. /// /// An Event is an important occurrence in your app that you want to diff --git a/analytics/src/ios/swift/AppleTransactionVerifier.swift b/analytics/src/ios/swift/AppleTransactionVerifier.swift new file mode 100644 index 0000000000..edbb38c31f --- /dev/null +++ b/analytics/src/ios/swift/AppleTransactionVerifier.swift @@ -0,0 +1,45 @@ +// Copyright 2026 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation +import StoreKit +import FirebaseAnalytics + +@objc public class AppleTransactionVerifier: NSObject { + + @objc public static func verify(transactionId: String, completion: @escaping @Sendable (Bool) -> Void) { + if #available(iOS 15.0, tvOS 15.0, macOS 12.0, watchOS 8.0, *) { + Task { + // Using a manual while loop instead of `for await` avoids a Swift 6 / Xcode 16 + // compiler issue. Using `for await` causes the compiler to emit a call to the + // new `next(isolation:)` signature on the generic AsyncIteratorProtocol witness table. + // This symbol does not exist in the iOS 16 `libswift_Concurrency.dylib` runtime, + // which causes an immediate `dyld: Symbol not found` crash upon launch. + var iterator = Transaction.all.makeAsyncIterator() + while let verificationResult = await iterator.next() { + if case .verified(let transaction) = verificationResult { + if String(transaction.id) == transactionId { + Analytics.logTransaction(transaction) + completion(true) + return + } + } + } + completion(false) + } + } else { + completion(false) + } + } +} diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index c92f9124f8..d72948f18d 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -308,6 +308,26 @@ else() set(app_flatbuffers_lib flatbuffers) endif() +# Automatically include headers that might not be declared. +# We use add_compile_options (instead of target_compile_options) so these +# implicit includes apply globally to all targets in this directory, +# including tests. Generator expressions ($<$) are +# used to ensure these C/C++ headers are not passed to the Swift compiler. +if(MSVC) + add_compile_options( + $<$:/FI"assert.h" /FI"string.h" /FI"stdint.h">) +elseif(APPLE) + add_compile_options( + "$<$:SHELL:-include assert.h>" + "$<$:SHELL:-include string.h>" + "$<$:SHELL:-include assert.h>" + "$<$:SHELL:-include string.h>") +else() + add_compile_options( + "$<$:SHELL:-include assert.h>" + "$<$:SHELL:-include string.h>") +endif() + add_library(firebase_app STATIC ${log_SRCS} ${log_HDRS} @@ -320,6 +340,9 @@ add_library(firebase_app STATIC ${FIREBASE_GEN_FILE_DIR}/app/google_services_generated.h ${app_flatbuffers_srcs}) +add_dependencies(firebase_app FIREBASE_APP_GENERATED_HEADERS) +add_dependencies(firebase_app app_generated_google_services_includes) + set_property(TARGET firebase_app PROPERTY FOLDER "Firebase Cpp") # Disable exceptions in std @@ -364,22 +387,15 @@ target_link_libraries(firebase_app ${app_flatbuffers_lib} ${LIBSECRET_LIBRARIES} ) -# Automatically include headers that might not be declared. -if(MSVC) - add_definitions(/FI"assert.h" /FI"string.h" /FI"stdint.h") -else() - add_definitions(-include assert.h -include string.h) -endif() if(ANDROID) firebase_cpp_proguard_file(app) elseif(IOS) - # Enable Automatic Reference Counting (ARC) and Bitcode. + # Enable Automatic Reference Counting (ARC) for Objective-C/Swift interop. + # Note: Bitcode (-fembed-bitcode) was removed because it was deprecated in + # Xcode 14 and is completely unsupported by modern Swift toolchains. target_compile_options(firebase_app - PUBLIC "-fobjc-arc" "-fembed-bitcode") - target_link_libraries(firebase_app - PUBLIC "-fembed-bitcode") - + PUBLIC $<$>:-fobjc-arc>) # Add empty include path to get root include folder '.' setup_pod_headers( firebase_app @@ -417,38 +433,17 @@ if (IOS) ) endif() - # Generate the analytics header file - # Copy from analytics/CMakeList.txt - function(generate_analytics_header OBJC_FILE CPP_FILE) - add_custom_command( - OUTPUT ${CPP_FILE} - COMMAND ${FIREBASE_PYTHON_EXECUTABLE} "${FIREBASE_SOURCE_DIR}/analytics/generate_constants.py" - "--objc_header=${OBJC_FILE}" - "--cpp_header=${CPP_FILE}" - DEPENDS ${OBJC_FILE} - COMMENT "Generating ${CPP_FILE}" - ) - endfunction() - set(analytics_generated_headers_dir - ${FIREBASE_GEN_FILE_DIR}/analytics/src/include/firebase/analytics) - file(MAKE_DIRECTORY ${FIREBASE_GEN_FILE_DIR}/analytics/src/include/firebase/analytics) - generate_analytics_header( - "${FIREBASE_SOURCE_DIR}/analytics/ios_headers/FIREventNames.h" - "${analytics_generated_headers_dir}/event_names.h" - ) - generate_analytics_header( - "${FIREBASE_SOURCE_DIR}/analytics/ios_headers/FIRParameterNames.h" - "${analytics_generated_headers_dir}/parameter_names.h" - ) - generate_analytics_header( - "${FIREBASE_SOURCE_DIR}/analytics/ios_headers/FIRUserPropertyNames.h" - "${analytics_generated_headers_dir}/user_property_names.h" - ) - set(analytics_HDRS - ${FIREBASE_SOURCE_DIR}/analytics/src/include/firebase/analytics.h - ${analytics_generated_headers_dir}/event_names.h - ${analytics_generated_headers_dir}/parameter_names.h - ${analytics_generated_headers_dir}/user_property_names.h) + if (FIREBASE_XCODE_TARGET_FORMAT STREQUAL "frameworks") + if (FIREBASE_INCLUDE_ANALYTICS) + set(analytics_HDRS + ${FIREBASE_SOURCE_DIR}/analytics/src/include/firebase/analytics.h + ${FIREBASE_GEN_FILE_DIR}/analytics/src/include/firebase/analytics/event_names.h + ${FIREBASE_GEN_FILE_DIR}/analytics/src/include/firebase/analytics/parameter_names.h + ${FIREBASE_GEN_FILE_DIR}/analytics/src/include/firebase/analytics/user_property_names.h) + else() + set(analytics_HDRS "") + endif() + endif() set(app_check_HDRS ${FIREBASE_SOURCE_DIR}/app_check/src/include/firebase/app_check.h ${FIREBASE_SOURCE_DIR}/app_check/src/include/firebase/app_check/app_attest_provider.h diff --git a/build_scripts/ios/build.sh b/build_scripts/ios/build.sh index 73af95608f..c594db826d 100755 --- a/build_scripts/ios/build.sh +++ b/build_scripts/ios/build.sh @@ -132,10 +132,12 @@ if ${generateMakefiles}; then echo "generate Makefiles start" mkdir -p ${buildpath}/ios_build_file/${platform}-${arch} && cd ${buildpath}/ios_build_file/${platform}-${arch} - cmake -DCMAKE_SYSTEM_NAME=iOS \ + cmake -G Xcode -DCMAKE_SYSTEM_NAME=iOS \ ${sysroot_arg} \ -DCMAKE_OSX_ARCHITECTURES=${arch} \ -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=${buildpath}/${frameworkspath}/${platform}-${arch} \ + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG=${buildpath}/${frameworkspath}/${platform}-${arch} \ + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE=${buildpath}/${frameworkspath}/${platform}-${arch} \ ${sourcepath} echo "generate Makefiles end" done diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 6c963edd74..4bdf08fc37 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -617,6 +617,7 @@ code. - Changes - Functions (general): Add in support for Limited Use Tokens. - Storage: Added `List` API for all platforms, which gets the list of items under a `StorageReference` + - Analytics: Add in `LogAppleTransaction` for logging Storekit 2 transactions on iOS. ### 13.5.0 - Changes diff --git a/scripts/gha/build_ios_tvos.py b/scripts/gha/build_ios_tvos.py index 7fe86343e6..b7ba68d252 100644 --- a/scripts/gha/build_ios_tvos.py +++ b/scripts/gha/build_ios_tvos.py @@ -85,6 +85,26 @@ def arrange_frameworks(archive_output_path): archive_output_path (str): Output path containing frameworks. Subdirectories should be the various target frameworks. """ + if not os.path.exists(archive_output_path): + logging.warning('Skipping arrange_frameworks for missing path: ' + archive_output_path) + return + + # Pull output out of Debug subdirectory if using multi-configuration generator (Xcode) + for entry in os.listdir(archive_output_path): + if entry.startswith('Debug') and os.path.isdir(os.path.join(archive_output_path, entry)): + debug_path = os.path.join(archive_output_path, entry) + logging.info('Pulling output out of subdirectory ' + debug_path) + for sub_entry in os.listdir(debug_path): + src = os.path.join(debug_path, sub_entry) + dest = os.path.join(archive_output_path, sub_entry) + if os.path.exists(dest): + if os.path.isdir(dest): + shutil.rmtree(dest) + else: + os.remove(dest) + shutil.move(src, archive_output_path) + os.rmdir(debug_path) + archive_output_dir_entries = os.listdir(archive_output_path) if not 'firebase.framework' in archive_output_dir_entries: # Rename firebase_app path to firebase path @@ -456,7 +476,7 @@ def cmake_configure(source_path, build_path, toolchain, archive_output_path, system. Used when building for ios/tvos. (eg:'arm64', 'x86_64') apple_os (str, optional): The Apple OS to build for. """ - cmd = ['cmake', '-S', source_path, '-B', build_path] + cmd = ['cmake', '-S', source_path, '-B', build_path, '-G', 'Xcode'] if toolchain: cmd.append('-DCMAKE_TOOLCHAIN_FILE={0}'.format(toolchain)) elif apple_os == 'ios': @@ -468,6 +488,8 @@ def cmake_configure(source_path, build_path, toolchain, archive_output_path, if platform_variant == 'simulator': cmd.append('-DCMAKE_OSX_SYSROOT=appletvsimulator') cmd.append('-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={0}'.format(archive_output_path)) + cmd.append('-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={0}'.format(archive_output_path)) + cmd.append('-DCMAKE_RUNTIME_OUTPUT_DIRECTORY={0}'.format(archive_output_path)) if architecture: cmd.append('-DCMAKE_OSX_ARCHITECTURES={0}'.format(architecture)) utils.run_command(cmd) diff --git a/test_mac_ios_simulator.sh b/test_mac_ios_simulator.sh old mode 100644 new mode 100755 index 7fc9fa5e3f..ec98fb58fc --- a/test_mac_ios_simulator.sh +++ b/test_mac_ios_simulator.sh @@ -25,11 +25,8 @@ set -x mkdir -p mac_ios_simulator_build cd mac_ios_simulator_build -# Configure cmake with tests enabled -# and disable use of libsecret due to not working on kokoro builders -cmake -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator .. -DCLANG_ENABLE_MODULES=YES -DFIREBASE_CPP_BUILD_TESTS=ON -DFIREBASE_FORCE_FAKE_SECURE_STORAGE=ON "$@" +cmake -G Xcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator .. -DCLANG_ENABLE_MODULES=YES -DFIREBASE_CPP_BUILD_TESTS=ON -DFIREBASE_FORCE_FAKE_SECURE_STORAGE=ON "$@" # Build the SDK and the tests -cpus=$(sysctl -n hw.ncpu) -cmake --build . -- -j $(cpus) +cmake --build . diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index e0da74d416..ee68f42769 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -158,3 +158,5 @@ target_link_libraries(firebase_testing gtest gmock ) + +add_dependencies(firebase_testing generate_testing_fps) diff --git a/testing/sample_framework/src/app_framework.cc b/testing/sample_framework/src/app_framework.cc index bed0d01526..69363b97d9 100644 --- a/testing/sample_framework/src/app_framework.cc +++ b/testing/sample_framework/src/app_framework.cc @@ -15,6 +15,9 @@ #include "app_framework.h" // NOLINT #include +#include +#include +#include #include #include @@ -23,6 +26,25 @@ #include #include +#if defined(__APPLE__) +// Workaround for Xcode 15 / Swift 5.10 Concurrency and C++ verbose abort +// crash on older iOS versions (iOS 15/16). +// By providing these symbols in our own binary, we prevent dyld from +// crashing when they are missing from the system libraries on older OSs. +namespace std { +inline namespace __1 { +__attribute__((weak)) void __libcpp_verbose_abort(const char* format, + ...) noexcept { + va_list list; + va_start(list, format); + vfprintf(stderr, format, list); + va_end(list); + abort(); +} +} // namespace __1 +} // namespace std +#endif + namespace app_framework { // Base logging methods, implemented by platform-specific files.