diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb new file mode 100644 index 00000000000000..0237391a2efed7 --- /dev/null +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -0,0 +1,622 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require_relative "./helpers.rb" + +# Utilities class for React Native Cocoapods +class ReactNativePodsUtils + def self.warn_if_not_on_arm64 + if SysctlChecker.new().call_sysctl_arm64() == 1 && !Environment.new().ruby_platform().include?('arm64') + Pod::UI.warn 'Do not use "pod install" from inside Rosetta2 (x86_64 emulation on arm64).' + Pod::UI.warn ' - Emulated x86_64 is slower than native arm64' + Pod::UI.warn ' - May result in mixed architectures in rubygems (eg: ffi_c.bundle files may be x86_64 with an arm64 interpreter)' + Pod::UI.warn 'Run "env /usr/bin/arch -arm64 /bin/bash --login" then try again.' + end + end + + # deprecated. These checks are duplicated in the react_native_pods function + # and we don't really need them. Removing this function will make it easy to + # move forward. + def self.get_default_flags + flags = { + :fabric_enabled => false, + :hermes_enabled => true, + :flipper_configuration => FlipperConfiguration.disabled + } + + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' + flags[:fabric_enabled] = true + flags[:hermes_enabled] = true + end + + if ENV['USE_HERMES'] == '0' + flags[:hermes_enabled] = false + end + + return flags + end + + def self.has_pod(installer, name) + installer.pods_project.pod_group(name) != nil + end + + def self.set_gcc_preprocessor_definition_for_React_hermes(installer) + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-hermes", "Debug") + self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "hermes-engine", "Debug") + end + + def self.turn_off_resource_bundle_react_core(installer) + # this is needed for Xcode 14, see more details here https://github.com/facebook/react-native/issues/34673 + # we should be able to remove this once CocoaPods catches up to it, see more details here https://github.com/CocoaPods/CocoaPods/issues/11402 + installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result| + if pod_name.to_s == 'React-Core' + target_installation_result.resource_bundle_targets.each do |resource_bundle_target| + resource_bundle_target.build_configurations.each do |config| + config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' + end + end + end + end + end + + def self.exclude_i386_architecture_while_using_hermes(installer) + is_using_hermes = self.has_pod(installer, 'hermes-engine') + + if is_using_hermes + key = "EXCLUDED_ARCHS[sdk=iphonesimulator*]" + + projects = self.extract_projects(installer) + + projects.each do |project| + project.build_configurations.each do |config| + current_setting = config.build_settings[key] || "" + + excluded_archs_includes_I386 = current_setting.include?("i386") + + if !excluded_archs_includes_I386 + # Hermes does not support 'i386' architecture + config.build_settings[key] = "#{current_setting} i386".strip + end + end + + project.save() + end + end + end + + def self.fix_flipper_for_xcode_15_3(installer) + installer.pods_project.targets.each do |target| + if target.name == 'Flipper' + file_path = 'Pods/Flipper/xplat/Flipper/FlipperTransportTypes.h' + if !File.exist(file_path) + return + end + + contents = File.read(file_path) + if contents.include?('#include ') + return + end + mod_content = contents.gsub("#pragma once", "#pragma once\n#include ") + File.chmod(0755, file_path) + File.open(file_path, 'w') do |file| + file.puts(mod_content) + end + end + end + end + + def self.set_use_hermes_build_setting(installer, hermes_enabled) + Pod::UI.puts("Setting USE_HERMES build settings") + projects = self.extract_projects(installer) + + projects.each do |project| + project.build_configurations.each do |config| + config.build_settings["USE_HERMES"] = hermes_enabled + end + + project.save() + end + end + + def self.set_node_modules_user_settings(installer, react_native_path) + Pod::UI.puts("Setting REACT_NATIVE build settings") + projects = self.extract_projects(installer) + + projects.each do |project| + project.build_configurations.each do |config| + config.build_settings["REACT_NATIVE_PATH"] = File.join("${PODS_ROOT}", "..", react_native_path) + end + + project.save() + end + end + + def self.fix_library_search_paths(installer) + projects = self.extract_projects(installer) + + projects.each do |project| + project.build_configurations.each do |config| + self.fix_library_search_path(config) + end + project.native_targets.each do |target| + target.build_configurations.each do |config| + self.fix_library_search_path(config) + end + end + project.save() + end + end + + def self.apply_mac_catalyst_patches(installer) + # Fix bundle signing issues + installer.pods_project.targets.each do |target| + if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle" + target.build_configurations.each do |config| + config.build_settings['CODE_SIGN_IDENTITY[sdk=macosx*]'] = '-' + end + end + end + + installer.aggregate_targets.each do |aggregate_target| + aggregate_target.user_project.native_targets.each do |target| + target.build_configurations.each do |config| + # Explicitly set dead code stripping flags + config.build_settings['DEAD_CODE_STRIPPING'] = 'YES' + config.build_settings['PRESERVE_DEAD_CODE_INITS_AND_TERMS'] = 'YES' + # Modify library search paths + config.build_settings['LIBRARY_SEARCH_PATHS'] = ['$(SDKROOT)/usr/lib/swift', '$(SDKROOT)/System/iOSSupport/usr/lib/swift', '$(inherited)'] + end + end + aggregate_target.user_project.save() + end + end + + def self.apply_xcode_15_patch(installer, xcodebuild_manager: Xcodebuild) + projects = self.extract_projects(installer) + + other_ld_flags_key = 'OTHER_LDFLAGS' + xcode15_compatibility_flags = '-Wl -ld_classic ' + + projects.each do |project| + project.build_configurations.each do |config| + # fix for weak linking + self.safe_init(config, other_ld_flags_key) + if self.is_using_xcode15_0(:xcodebuild_manager => xcodebuild_manager) + self.add_value_to_setting_if_missing(config, other_ld_flags_key, xcode15_compatibility_flags) + else + self.remove_value_to_setting_if_present(config, other_ld_flags_key, xcode15_compatibility_flags) + end + end + project.save() + end + + end + + def self.apply_flags_for_fabric(installer, fabric_enabled: false) + fabric_flag = "-DRN_FABRIC_ENABLED" + if fabric_enabled + self.add_compiler_flag_to_project(installer, fabric_flag) + else + self.remove_compiler_flag_from_project(installer, fabric_flag) + end + end + + private + + def self.add_build_settings_to_pod(installer, settings_name, settings_value, target_pod_name, configuration) + installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result| + if pod_name.to_s == target_pod_name + target_installation_result.native_target.build_configurations.each do |config| + if configuration == nil || (configuration != nil && configuration == config.name) + config.build_settings[settings_name] ||= '$(inherited) ' + config.build_settings[settings_name] << settings_value + end + end + end + end + end + + def self.fix_library_search_path(config) + lib_search_paths = config.build_settings["LIBRARY_SEARCH_PATHS"] + + if lib_search_paths == nil + # No search paths defined, return immediately + return + end + + if lib_search_paths.include?("$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)") || lib_search_paths.include?("\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"") + # $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) causes problem with Xcode 12.5 + arm64 (Apple Silicon) + # since the libraries there are only built for x86_64 and i386. + lib_search_paths.delete("$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)") + lib_search_paths.delete("\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"") + end + + if !(lib_search_paths.include?("$(SDKROOT)/usr/lib/swift") || lib_search_paths.include?("\"$(SDKROOT)/usr/lib/swift\"")) + # however, $(SDKROOT)/usr/lib/swift is required, at least if user is not running CocoaPods 1.11 + lib_search_paths.insert(0, "$(SDKROOT)/usr/lib/swift") + end + end + + def self.create_xcode_env_if_missing(file_manager: File) + relative_path = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) + file_path = file_manager.join(relative_path, '.xcode.env') + + if !file_manager.exist?(file_path) + system("echo 'export NODE_BINARY=$(command -v node)' > #{file_path}") + end + + if !file_manager.exist?("#{file_path}.local") + node_binary = `command -v node` + system("echo 'export NODE_BINARY=#{node_binary}' > #{file_path}.local") + end + end + + # It examines the target_definition property and sets the appropriate value for + # ENV['USE_FRAMEWORKS'] variable. + # + # - parameter target_definition: The current target definition + def self.detect_use_frameworks(target_definition) + if ENV['USE_FRAMEWORKS'] != nil + return + end + + framework_build_type = target_definition.build_type.to_s + + Pod::UI.puts("Framework build type is #{framework_build_type}") + + if framework_build_type === "static framework" + ENV['USE_FRAMEWORKS'] = 'static' + elsif framework_build_type === "dynamic framework" + ENV['USE_FRAMEWORKS'] = 'dynamic' + else + ENV['USE_FRAMEWORKS'] = nil + end + end + + def self.create_header_search_path_for_frameworks(base_folder, pod_name, framework_name, additional_paths, include_base_path = true) + platforms = $RN_PLATFORMS != nil ? $RN_PLATFORMS : [] + search_paths = [] + + if platforms.empty?() || platforms.length() == 1 + base_path = File.join("${#{base_folder}}", pod_name, "#{framework_name}.framework", "Headers") + self.add_search_path_to_result(search_paths, base_path, additional_paths, include_base_path) + else + platforms.each { |platform| + base_path = File.join("${#{base_folder}}", "#{pod_name}-#{platform}", "#{framework_name}.framework", "Headers") + self.add_search_path_to_result(search_paths, base_path, additional_paths, include_base_path) + } + end + return search_paths + end + + # Add a new dependency to an existing spec, configuring also the headers search paths + def self.add_dependency(spec, dependency_name, base_folder_for_frameworks, framework_name, additional_paths: [], version: nil, subspec_dependency: nil) + # Update Search Path + optional_current_search_path = spec.to_hash["pod_target_xcconfig"]["HEADER_SEARCH_PATHS"] + current_search_paths = (optional_current_search_path != nil ? optional_current_search_path : "") + .split(" ") + create_header_search_path_for_frameworks(base_folder_for_frameworks, dependency_name, framework_name, additional_paths) + .each { |path| + wrapped_path = "\"#{path}\"" + current_search_paths << wrapped_path + } + current_pod_target_xcconfig = spec.to_hash["pod_target_xcconfig"] + current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] = current_search_paths.join(" ") + spec.pod_target_xcconfig = current_pod_target_xcconfig + + actual_dependency = subspec_dependency != nil ? "#{dependency_name}/#{subspec_dependency}" : dependency_name + # Set Dependency + if !version + spec.dependency actual_dependency + else + spec.dependency actual_dependency, version + end + end + + def self.update_search_paths(installer) + return if ENV['USE_FRAMEWORKS'] == nil + + projects = self.extract_projects(installer) + + projects.each do |project| + project.build_configurations.each do |config| + + header_search_paths = config.build_settings["HEADER_SEARCH_PATHS"] ||= "$(inherited)" + + ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon-Samples", "ReactCommon_Samples", ["platform/ios"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"], false)) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-graphics", "React_graphics", ["react/renderer/graphics/platform/ios"])) + .each{ |search_path| + header_search_paths = self.add_search_path_if_not_included(header_search_paths, search_path) + } + + config.build_settings["HEADER_SEARCH_PATHS"] = header_search_paths + end + + project.save() + end + + installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result| + if self.react_native_pods.include?(pod_name) || pod_name.include?("Pod") || pod_name.include?("Tests") + next + end + + self.set_rctfolly_search_paths(target_installation_result) + self.set_codegen_search_paths(target_installation_result) + self.set_reactcommon_searchpaths(target_installation_result) + self.set_rctfabric_search_paths(target_installation_result) + self.set_imagemanager_search_path(target_installation_result) + end + end + + def self.updateOSDeploymentTarget(installer) + installer.target_installation_results.pod_target_installation_results + .each do |pod_name, target_installation_result| + target_installation_result.native_target.build_configurations.each do |config| + old_iphone_deploy_target = config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] ? + config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] : + Helpers::Constants.min_ios_version_supported + config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = [Helpers::Constants.min_ios_version_supported.to_f, old_iphone_deploy_target.to_f].max.to_s + end + end + end + + # ========= # + # Utilities # + # ========= # + + def self.extract_projects(installer) + return installer.aggregate_targets + .map{ |t| t.user_project } + .uniq{ |p| p.path } + .push(installer.pods_project) + end + + def self.safe_init(config, setting_name) + old_config = config.build_settings[setting_name] + if old_config == nil + config.build_settings[setting_name] ||= '$(inherited) ' + end + end + + def self.add_value_to_setting_if_missing(config, setting_name, value) + old_config = config.build_settings[setting_name] + if old_config.is_a?(Array) + old_config = old_config.join(" ") + end + + trimmed_value = value.strip() + if !old_config.include?(trimmed_value) + config.build_settings[setting_name] = "#{old_config.strip()} #{trimmed_value}".strip() + end + end + + def self.remove_value_to_setting_if_present(config, setting_name, value) + old_config = config.build_settings[setting_name] + if old_config.is_a?(Array) + old_config = old_config.join(" ") + end + + trimmed_value = value.strip() + if old_config.include?(trimmed_value) + new_config = old_config.gsub(trimmed_value, "") + config.build_settings[setting_name] = new_config.strip() + end + end + + def self.is_using_xcode15_0(xcodebuild_manager: Xcodebuild) + xcodebuild_version = xcodebuild_manager.version + + # The output of xcodebuild -version is something like + # Xcode 15.0 + # or + # Xcode 14.3.1 + # We want to capture the version digits + regex = /(\d+)\.(\d+)(?:\.(\d+))?/ + if match_data = xcodebuild_version.match(regex) + major = match_data[1].to_i + minor = match_data[2].to_i + return major == 15 && minor == 0 + end + + return false + end + + def self.add_compiler_flag_to_project(installer, flag, configuration: nil) + projects = self.extract_projects(installer) + + projects.each do |project| + project.build_configurations.each do |config| + self.set_flag_in_config(config, flag, configuration: configuration) + end + project.save() + end + end + + def self.remove_compiler_flag_from_project(installer, flag, configuration: nil) + projects = self.extract_projects(installer) + + projects.each do |project| + project.build_configurations.each do |config| + self.remove_flag_in_config(config, flag, configuration: configuration) + end + project.save() + end + end + + def self.add_compiler_flag_to_pods(installer, flag, configuration: nil) + installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result| + target_installation_result.native_target.build_configurations.each do |config| + self.set_flag_in_config(config, flag, configuration: configuration) + end + end + end + + def self.set_flag_in_config(config, flag, configuration: nil) + if configuration == nil || config.name == configuration + self.add_flag_for_key(config, flag, "OTHER_CFLAGS") + self.add_flag_for_key(config, flag, "OTHER_CPLUSPLUSFLAGS") + end + end + + def self.remove_flag_in_config(config, flag, configuration: nil) + if configuration == nil || config.name == configuration + self.remove_flag_for_key(config, flag, "OTHER_CFLAGS") + self.remove_flag_for_key(config, flag, "OTHER_CPLUSPLUSFLAGS") + end + end + + + def self.add_flag_for_key(config, flag, key) + current_setting = config.build_settings[key] ? config.build_settings[key] : "$(inherited)" + + if current_setting.kind_of?(Array) + current_setting = current_setting + .map { |s| s.gsub('"', '') } + .map { |s| s.gsub('\"', '') } + .join(" ") + end + + if !current_setting.include?(flag) + current_setting = "#{current_setting} #{flag}" + end + + config.build_settings[key] = current_setting + end + + def self.remove_flag_for_key(config, flag, key) + current_setting = config.build_settings[key] ? config.build_settings[key] : "$(inherited)" + + if current_setting.kind_of?(Array) + current_setting = current_setting + .map { |s| s.gsub('"', '') } + .map { |s| s.gsub('\"', '') } + .join(" ") + end + + if current_setting.include?(flag) + current_setting.slice! flag + end + + config.build_settings[key] = current_setting + end + + def self.add_search_path_if_not_included(current_search_paths, new_search_path) + if !current_search_paths.include?(new_search_path) + current_search_paths << " #{new_search_path}" + end + return current_search_paths + end + + def self.update_header_paths_if_depends_on(target_installation_result, dependency_name, header_paths) + depends_on_framework = target_installation_result.native_target.dependencies.any? { |d| d.name == dependency_name } + if depends_on_framework + target_installation_result.native_target.build_configurations.each do |config| + header_search_path = config.build_settings["HEADER_SEARCH_PATHS"] != nil ? config.build_settings["HEADER_SEARCH_PATHS"] : "$(inherited)" + header_paths.each { |header| header_search_path = ReactNativePodsUtils.add_search_path_if_not_included(header_search_path, header) } + config.build_settings["HEADER_SEARCH_PATHS"] = header_search_path + end + end + end + + def self.set_rctfolly_search_paths(target_installation_result) + ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "RCT-Folly", [ + "\"$(PODS_ROOT)/RCT-Folly\"", + "\"$(PODS_ROOT)/DoubleConversion\"", + "\"$(PODS_ROOT)/fmt/include\"", + "\"$(PODS_ROOT)/boost\"" + ]) + end + + def self.set_codegen_search_paths(target_installation_result) + header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Codegen", "React_Codegen", []) + .map { |search_path| "\"#{search_path}\"" } + ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-Codegen", header_search_paths) + end + + def self.set_reactcommon_searchpaths(target_installation_result) + header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]) + .map { |search_path| "\"#{search_path}\"" } + ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "ReactCommon", header_search_paths) + end + + def self.set_rctfabric_search_paths(target_installation_result) + header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", []) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", [])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Graphics", "React_graphics", ["react/renderer/graphics/platform/ios"])) + .map { |search_path| "\"#{search_path}\"" } + + ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-RCTFabric", header_search_paths) + end + + def self.set_imagemanager_search_path(target_installation_result) + header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/imagemanager/platform/ios"]) + .map { |search_path| "\"#{search_path}\"" } + ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-ImageManager", header_search_paths) + end + + def self.react_native_pods + return [ + "DoubleConversion", + "FBLazyVector", + "RCT-Folly", + "RCTRequired", + "RCTTypeSafety", + "React", + "React-Codegen", + "React-Core", + "React-CoreModules", + "React-Fabric", + "React-FabricImage", + "React-ImageManager", + "React-RCTActionSheet", + "React-RCTAnimation", + "React-RCTAppDelegate", + "React-RCTBlob", + "React-RCTFabric", + "React-RCTImage", + "React-RCTLinking", + "React-RCTNetwork", + "React-RCTPushNotification", + "React-RCTSettings", + "React-RCTText", + "React-RCTTest", + "React-RCTVibration", + "React-callinvoker", + "React-cxxreact", + "React-graphics", + "React-jsc", + "React-jsi", + "React-jsiexecutor", + "React-jsinspector", + "React-logger", + "React-perflogger", + "React-rncore", + "React-runtimeexecutor", + "ReactCommon", + "Yoga", + "boost", + "fmt", + "glog", + "hermes-engine", + "libevent", + "React-hermes", + ] + end + + def self.add_search_path_to_result(result, base_path, additional_paths, include_base_path) + if (include_base_path) + result << base_path + end + + additional_paths.each { |extra_path| + result << File.join(base_path, extra_path) + } + return result + end +end diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb new file mode 100644 index 00000000000000..be8b0cef664d54 --- /dev/null +++ b/packages/react-native/scripts/react_native_pods.rb @@ -0,0 +1,419 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require 'json' +require 'open3' +require 'pathname' +require_relative './react_native_pods_utils/script_phases.rb' +require_relative './cocoapods/jsengine.rb' +require_relative './cocoapods/flipper.rb' +require_relative './cocoapods/fabric.rb' +require_relative './cocoapods/codegen.rb' +require_relative './cocoapods/codegen_utils.rb' +require_relative './cocoapods/utils.rb' +require_relative './cocoapods/new_architecture.rb' +require_relative './cocoapods/local_podspec_patch.rb' +require_relative './cocoapods/runtime.rb' +require_relative './cocoapods/helpers.rb' + +$CODEGEN_OUTPUT_DIR = 'build/generated/ios' +$CODEGEN_COMPONENT_DIR = 'react/renderer/components' +$CODEGEN_MODULE_DIR = '.' +$FOLLY_VERSION = '2022.05.16.00' + +$START_TIME = Time.now.to_i + +# `@react-native-community/cli-platform-ios/native_modules` defines +# use_native_modules. We use node to resolve its path to allow for +# different packager and workspace setups. This is reliant on +# `@react-native-community/cli-platform-ios` being a direct dependency +# of `react-native`. +require Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "@react-native-community/cli-platform-ios/native_modules.rb", + {paths: [process.argv[1]]}, + )', __dir__]).strip + + +def min_ios_version_supported + return Helpers::Constants.min_ios_version_supported +end + +# This function returns the min supported OS versions supported by React Native +# By using this function, you won't have to manually change your Podfile +# when we change the minimum version supported by the framework. +def min_supported_versions + return { :ios => min_ios_version_supported } +end + +# This function prepares the project for React Native, before processing +# all the target exposed by the framework. +def prepare_react_native_project! + # Temporary solution to suppress duplicated GUID error. + # Can be removed once we move to generate files outside pod install. + install! 'cocoapods', :deterministic_uuids => false + + ReactNativePodsUtils.create_xcode_env_if_missing +end + +# Function that setup all the react native dependencies +#  +# Parameters +# - path: path to react_native installation. +# - fabric_enabled: whether fabric should be enabled or not. +# - new_arch_enabled: whether the new architecture should be enabled or not. +# - :production [DEPRECATED] whether the dependencies must be installed to target a Debug or a Release build. +# - hermes_enabled: whether Hermes should be enabled or not. +# - flipper_configuration: The configuration to use for flipper. +# - app_path: path to the React Native app. Required by the New Architecture. +# - config_file_dir: directory of the `package.json` file, required by the New Architecture. +# - ios_folder: the folder where the iOS code base lives. For a template app, it is `ios`, the default. For RNTester, it is `.`. +def use_react_native! ( + path: "../node_modules/react-native", + fabric_enabled: false, + new_arch_enabled: NewArchitectureHelper.new_arch_enabled, + production: false, # deprecated + hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true, + flipper_configuration: FlipperConfiguration.disabled, + app_path: '..', + config_file_dir: '', + ios_folder: 'ios' +) + + # Set the app_path as env variable so the podspecs can access it. + ENV['APP_PATH'] = app_path + ENV['REACT_NATIVE_PATH'] = path + + # Current target definition is provided by Cocoapods and it refers to the target + # that has invoked the `use_react_native!` function. + ReactNativePodsUtils.detect_use_frameworks(current_target_definition) + + CodegenUtils.clean_up_build_folder(path, app_path, ios_folder, $CODEGEN_OUTPUT_DIR) + + # We are relying on this flag also in third parties libraries to proper install dependencies. + # Better to rely and enable this environment flag if the new architecture is turned on using flags. + relative_path_from_current = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) + react_native_version = NewArchitectureHelper.extract_react_native_version(File.join(relative_path_from_current, path)) + ENV['RCT_NEW_ARCH_ENABLED'] = NewArchitectureHelper.compute_new_arch_enabled(new_arch_enabled, react_native_version) + + fabric_enabled = fabric_enabled || NewArchitectureHelper.new_arch_enabled + ENV['RCT_FABRIC_ENABLED'] = fabric_enabled ? "1" : "0" + ENV['USE_HERMES'] = hermes_enabled ? "1" : "0" + + prefix = path + + ReactNativePodsUtils.warn_if_not_on_arm64() + + # The Pods which should be included in all projects + pod 'FBLazyVector', :path => "#{prefix}/Libraries/FBLazyVector" + pod 'FBReactNativeSpec', :path => "#{prefix}/React/FBReactNativeSpec" if !NewArchitectureHelper.new_arch_enabled + pod 'RCTRequired', :path => "#{prefix}/Libraries/RCTRequired" + pod 'RCTTypeSafety', :path => "#{prefix}/Libraries/TypeSafety", :modular_headers => true + pod 'React', :path => "#{prefix}/" + pod 'React-Core', :path => "#{prefix}/" + pod 'React-CoreModules', :path => "#{prefix}/React/CoreModules" + pod 'React-RCTAppDelegate', :path => "#{prefix}/Libraries/AppDelegate" + pod 'React-RCTActionSheet', :path => "#{prefix}/Libraries/ActionSheetIOS" + pod 'React-RCTAnimation', :path => "#{prefix}/Libraries/NativeAnimation" + pod 'React-RCTBlob', :path => "#{prefix}/Libraries/Blob" + pod 'React-RCTImage', :path => "#{prefix}/Libraries/Image" + pod 'React-RCTLinking', :path => "#{prefix}/Libraries/LinkingIOS" + pod 'React-RCTNetwork', :path => "#{prefix}/Libraries/Network" + pod 'React-RCTSettings', :path => "#{prefix}/Libraries/Settings" + pod 'React-RCTText', :path => "#{prefix}/Libraries/Text" + pod 'React-RCTVibration', :path => "#{prefix}/Libraries/Vibration" + pod 'React-Core/RCTWebSocket', :path => "#{prefix}/" + pod 'React-rncore', :path => "#{prefix}/ReactCommon" + pod 'React-cxxreact', :path => "#{prefix}/ReactCommon/cxxreact" + pod 'React-debug', :path => "#{prefix}/ReactCommon/react/debug" + pod 'React-utils', :path => "#{prefix}/ReactCommon/react/utils" + pod 'React-Mapbuffer', :path => "#{prefix}/ReactCommon" + pod 'React-jserrorhandler', :path => "#{prefix}/ReactCommon/jserrorhandler" + pod "React-nativeconfig", :path => "#{prefix}/ReactCommon" + + if hermes_enabled + setup_hermes!(:react_native_path => prefix) + else + setup_jsc!(:react_native_path => prefix, :fabric_enabled => fabric_enabled) + end + + pod 'React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor" + pod 'React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector-modern" + + pod 'React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker" + pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor" + pod 'React-runtimescheduler', :path => "#{prefix}/ReactCommon/react/renderer/runtimescheduler" + pod 'React-rendererdebug', :path => "#{prefix}/ReactCommon/react/renderer/debug" + pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger" + pod 'React-logger', :path => "#{prefix}/ReactCommon/logger" + pod 'ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon", :modular_headers => true + pod 'React-NativeModulesApple', :path => "#{prefix}/ReactCommon/react/nativemodule/core/platform/ios", :modular_headers => true + pod 'Yoga', :path => "#{prefix}/ReactCommon/yoga", :modular_headers => true + + pod 'DoubleConversion', :podspec => "#{prefix}/third-party-podspecs/DoubleConversion.podspec" + pod 'glog', :podspec => "#{prefix}/third-party-podspecs/glog.podspec" + pod 'boost', :podspec => "#{prefix}/third-party-podspecs/boost.podspec" + pod 'RCT-Folly', :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec", :modular_headers => true + + run_codegen!( + app_path, + config_file_dir, + :new_arch_enabled => NewArchitectureHelper.new_arch_enabled, + :disable_codegen => ENV['DISABLE_CODEGEN'] == '1', + :react_native_path => prefix, + :fabric_enabled => fabric_enabled, + :hermes_enabled => hermes_enabled, + :codegen_output_dir => $CODEGEN_OUTPUT_DIR, + :package_json_file => File.join(__dir__, "..", "package.json"), + :folly_version => $FOLLY_VERSION + ) + + pod 'React-Codegen', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true + + # Always need fabric to access the RCTSurfacePresenterBridgeAdapter which allow to enable the RuntimeScheduler + # If the New Arch is turned off, we will use the Old Renderer, though. + # RNTester always installed Fabric, this change is required to make the template work. + setup_fabric!(:react_native_path => prefix) + checkAndGenerateEmptyThirdPartyProvider!(prefix, NewArchitectureHelper.new_arch_enabled) + + if !fabric_enabled + relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) + build_codegen!(prefix, relative_installation_root) + end + + if NewArchitectureHelper.new_arch_enabled + setup_bridgeless!(:react_native_path => prefix, :use_hermes => hermes_enabled) + end + + # Flipper now build in Release mode but it is not linked to the Release binary (as specified by the Configuration option) + if flipper_configuration.flipper_enabled + install_flipper_dependencies(prefix) + use_flipper_pods(flipper_configuration.versions, :configurations => flipper_configuration.configurations) + end + + pods_to_update = LocalPodspecPatch.pods_to_update(:react_native_path => prefix) + if !pods_to_update.empty? + if Pod::Lockfile.public_instance_methods.include?(:detect_changes_with_podfile) + Pod::Lockfile.prepend(LocalPodspecPatch) + else + Pod::UI.warn "Automatically updating #{pods_to_update.join(", ")} has failed, please run `pod update #{pods_to_update.join(" ")} --no-repo-update` manually to fix the issue." + end + end +end + +# Getter to retrieve the folly flags in case contributors need to apply them manually. +# +# Returns: the folly compiler flags +def folly_flags() + return NewArchitectureHelper.folly_compiler_flags +end + +# Add a dependency to a spec, making sure that the HEADER_SERACH_PATHS are set properly. +# This function automate the requirement to specify the HEADER_SEARCH_PATHS which was error prone +# and hard to pull out properly to begin with. +# Secondly, it prepares the podspec to work also with other platforms, because this function is +# able to generate search paths that are compatible with macOS and other platform if specified by +# the $RN_PLATFORMS variable. +# To generate Header Search Paths for multiple platforms, define in your Podfile or Ruby infra a +# $RN_PLATFORMS static variable with the list of supported platforms, for example: +# `$RN_PLATFORMS = ["iOS", "macOS"]` +# +# Parameters: +# - spec: the spec that needs to be modified +# - pod_name: the name of the dependency we had to add to the spec +# - additional_framework_paths: additional sub paths we had to add to the HEADER_SEARCH_PATH +# - framework_name: the name of the framework in case it is different from the pod_name +# - version: the version of the pod_name the spec needs to depend on +# - base_dir: Base directory from where we need to start looking. Defaults to PODS_CONFIGURATION_BUILD_DIR +def add_dependency(spec, pod_name, subspec: nil, additional_framework_paths: [], framework_name: nil, version: nil, base_dir: "PODS_CONFIGURATION_BUILD_DIR") + fixed_framework_name = framework_name != nil ? framework_name : pod_name.gsub("-", "_") # frameworks can't have "-" in their name + ReactNativePodsUtils.add_dependency(spec, pod_name, base_dir, fixed_framework_name, :additional_paths => additional_framework_paths, :version => version) +end + +# This function generates an array of HEADER_SEARCH_PATH that can be added to the HEADER_SEARCH_PATH property when use_frameworks! is enabled +# +# Parameters: +# - pod_name: the name of the dependency we had to add to the spec +# - additional_framework_paths: additional sub paths we had to add to the HEADER_SEARCH_PATH +# - framework_name: the name of the framework in case it is different from the pod_name +# - base_dir: Base directory from where we need to start looking. Defaults to PODS_CONFIGURATION_BUILD_DIR +# - include_base_folder: whether the array must include the base import path or only the additional_framework_paths +def create_header_search_path_for_frameworks(pod_name, additional_framework_paths: [], framework_name: nil, base_dir: "PODS_CONFIGURATION_BUILD_DIR", include_base_folder: true) + fixed_framework_name = framework_name != nil ? framework_name : pod_name.gsub("-", "_") + return ReactNativePodsUtils.create_header_search_path_for_frameworks(base_dir, pod_name, fixed_framework_name, additional_framework_paths, include_base_folder) +end + +# This function can be used by library developer to prepare their modules for the New Architecture. +# It passes the Folly Flags to the module, it configures the search path and installs some New Architecture specific dependencies. +# +# Parameters: +# - spec: The spec that has to be configured with the New Architecture code +# - new_arch_enabled: Whether the module should install dependencies for the new architecture +def install_modules_dependencies(spec, new_arch_enabled: NewArchitectureHelper.new_arch_enabled) + NewArchitectureHelper.install_modules_dependencies(spec, new_arch_enabled, $FOLLY_VERSION) +end + +# It returns the default flags. +# deprecated. +def get_default_flags() + warn 'get_default_flags is deprecated. Please remove the keys from the `use_react_native!` function' + warn 'if you are using the default already and pass the value you need in case you don\'t want the default' + return ReactNativePodsUtils.get_default_flags() +end + +# It installs the flipper dependencies into the project. +# +# Parameters +# - versions: a dictionary of Flipper Library -> Versions that can be used to customize which version of Flipper to install. +# - configurations: an array of configuration where to install the dependencies. +def use_flipper!(versions = {}, configurations: ['Debug']) + Pod::UI.warn "use_flipper is deprecated, use the flipper_configuration option in the use_react_native function" + use_flipper_pods(versions, :configurations => configurations) +end + +# Function that executes after React Native has been installed to configure some flags and build settings. +# +# Parameters +# - installer: the Cocoapod object that allows to customize the project. +# - react_native_path: path to React Native. +# - mac_catalyst_enabled: whether we are running the Pod on a Mac Catalyst project or not. +# - enable_hermes_profiler: whether the hermes profiler should be turned on in Release mode +def react_native_post_install( + installer, + react_native_path = "../node_modules/react-native", + mac_catalyst_enabled: false +) + ReactNativePodsUtils.turn_off_resource_bundle_react_core(installer) + + ReactNativePodsUtils.apply_mac_catalyst_patches(installer) if mac_catalyst_enabled + + if ReactNativePodsUtils.has_pod(installer, 'Flipper') + flipper_post_install(installer) + end + + fabric_enabled = ENV['RCT_FABRIC_ENABLED'] == '1' + hermes_enabled = ENV['USE_HERMES'] == '1' + + if hermes_enabled + ReactNativePodsUtils.set_gcc_preprocessor_definition_for_React_hermes(installer) + ReactNativePodsUtils.exclude_i386_architecture_while_using_hermes(installer) + end + + ReactNativePodsUtils.fix_library_search_paths(installer) + ReactNativePodsUtils.update_search_paths(installer) + ReactNativePodsUtils.set_use_hermes_build_setting(installer, hermes_enabled) + ReactNativePodsUtils.set_node_modules_user_settings(installer, react_native_path) + ReactNativePodsUtils.apply_flags_for_fabric(installer, fabric_enabled: fabric_enabled) + ReactNativePodsUtils.apply_xcode_15_patch(installer) + ReactNativePodsUtils.updateOSDeploymentTarget(installer) + ReactNativePodsUtils.fix_flipper_for_xcode_15_3(installer) + + NewArchitectureHelper.set_clang_cxx_language_standard_if_needed(installer) + NewArchitectureHelper.modify_flags_for_new_architecture(installer, NewArchitectureHelper.new_arch_enabled) + + + Pod::UI.puts "Pod install took #{Time.now.to_i - $START_TIME} [s] to run".green +end + +# === LEGACY METHOD === +# We need to keep this while we continue to support the old architecture. +# ===================== +def use_react_native_codegen!(spec, options={}) + return if NewArchitectureHelper.new_arch_enabled + # TODO: Once the new codegen approach is ready for use, we should output a warning here to let folks know to migrate. + + # The prefix to react-native + react_native_path = options[:react_native_path] ||= ".." + + # Library name (e.g. FBReactNativeSpec) + library_name = options[:library_name] ||= "#{spec.name.gsub('_','-').split('-').collect(&:capitalize).join}Spec" + Pod::UI.puts "[Codegen] Found #{library_name}" + + relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd) + output_dir = options[:output_dir] ||= $CODEGEN_OUTPUT_DIR + output_dir_module = "#{output_dir}/#{$CODEGEN_MODULE_DIR}" + output_dir_component = "#{output_dir}/#{$CODEGEN_COMPONENT_DIR}" + + codegen_config = { + "modules" => { + :js_srcs_pattern => "Native*.js", + :generated_dir => "#{relative_installation_root}/#{output_dir_module}/#{library_name}", + :generated_files => [ + "#{library_name}.h", + "#{library_name}-generated.mm" + ] + }, + "components" => { + :js_srcs_pattern => "*NativeComponent.js", + :generated_dir => "#{relative_installation_root}/#{output_dir_component}/#{library_name}", + :generated_files => [ + "ComponentDescriptors.h", + "EventEmitters.cpp", + "EventEmitters.h", + "Props.cpp", + "Props.h", + "States.cpp", + "States.h", + "RCTComponentViewHelpers.h", + "ShadowNodes.cpp", + "ShadowNodes.h" + ] + } + } + + # The path to JavaScript files + js_srcs_dir = options[:js_srcs_dir] ||= "./" + library_type = options[:library_type] + + if library_type + if !codegen_config[library_type] + raise "[Codegen] invalid library_type: #{library_type}. Check your podspec to make sure it's set to 'modules' or 'components'. Removing the option will generate files for both" + end + js_srcs_pattern = codegen_config[library_type][:js_srcs_pattern] + end + + if library_type + generated_dirs = [ codegen_config[library_type][:generated_dir] ] + generated_files = codegen_config[library_type][:generated_files].map { |filename| "#{codegen_config[library_type][:generated_dir]}/#{filename}" } + else + generated_dirs = [ codegen_config["modules"][:generated_dir], codegen_config["components"][:generated_dir] ] + generated_files = codegen_config["modules"][:generated_files].map { |filename| "#{codegen_config["modules"][:generated_dir]}/#{filename}" } + generated_files = generated_files.concat(codegen_config["components"][:generated_files].map { |filename| "#{codegen_config["components"][:generated_dir]}/#{filename}" }) + end + + if js_srcs_pattern + file_list = `find #{js_srcs_dir} -type f -name #{js_srcs_pattern}`.split("\n").sort + input_files = file_list.map { |filename| "${PODS_TARGET_SRCROOT}/#{filename}" } + else + input_files = [ js_srcs_dir ] + end + + # Prepare filesystem by creating empty files that will be picked up as references by CocoaPods. + prepare_command = "mkdir -p #{generated_dirs.join(" ")} && touch -a #{generated_files.join(" ")}" + system(prepare_command) # Always run prepare_command when a podspec uses the codegen, as CocoaPods may skip invoking this command in certain scenarios. Replace with pre_integrate_hook after updating to CocoaPods 1.11 + spec.prepare_command = prepare_command + + env_files = ["$PODS_ROOT/../.xcode.env.local", "$PODS_ROOT/../.xcode.env"] + + spec.script_phase = { + :name => 'Generate Specs', + :input_files => input_files + env_files, # This also needs to be relative to Xcode + :output_files => ["${DERIVED_FILE_DIR}/codegen-#{library_name}.log"].concat(generated_files.map { |filename| "${PODS_TARGET_SRCROOT}/#{filename}"} ), + # The final generated files will be created when this script is invoked at Xcode build time. + :script => get_script_phases_no_codegen_discovery( + react_native_path: react_native_path, + codegen_output_dir: output_dir, + codegen_module_dir: output_dir_module, + codegen_component_dir: output_dir_component, + library_name: library_name, + library_type: library_type, + js_srcs_pattern: js_srcs_pattern, + js_srcs_dir: js_srcs_dir, + file_list: file_list + ), + :execution_position => :before_compile, + :show_env_vars_in_log => true + } +end