1# Copyright (c) Meta Platforms, Inc. and affiliates.
2#
3# This source code is licensed under the MIT license found in the
4# LICENSE file in the root directory of this source tree.
5
6require 'json'
7require 'open3'
8require 'pathname'
9require_relative './react_native_pods_utils/script_phases.rb'
10require_relative './cocoapods/jsengine.rb'
11require_relative './cocoapods/flipper.rb'
12require_relative './cocoapods/fabric.rb'
13require_relative './cocoapods/codegen.rb'
14require_relative './cocoapods/codegen_utils.rb'
15require_relative './cocoapods/utils.rb'
16require_relative './cocoapods/new_architecture.rb'
17require_relative './cocoapods/local_podspec_patch.rb'
18
19$ABI48_0_0CODEGEN_OUTPUT_DIR = 'versioned-react-native/ABI48_0_0/ReactNative/codegen/ios'
20$CODEGEN_COMPONENT_DIR = 'react/renderer/components'
21$CODEGEN_MODULE_DIR = '.'
22$FOLLY_VERSION = '2021.07.22.00'
23
24$START_TIME = Time.now.to_i
25
26# This function returns the min iOS version supported by React Native
27# By using this function, you won't have to manualy change your Podfile
28# when we change the minimum version supported by the framework.
29def min_ios_version_supported
30  return '12.4'
31end
32
33# This function prepares the project for React Native, before processing
34# all the target exposed by the framework.
35def prepare_react_native_project!
36  # Temporary solution to suppress duplicated GUID error.
37  # Can be removed once we move to generate files outside pod install.
38  install! 'cocoapods', :deterministic_uuids => false
39
40  ReactNativePodsUtils.create_xcode_env_if_missing
41end
42
43# Function that setup all the react native dependencies
44
45# Parameters
46# - path: path to react_native installation.
47# - fabric_enabled: whether fabric should be enabled or not.
48# - new_arch_enabled: whether the new architecture should be enabled or not.
49# - production: whether the dependencies must be installed to target a Debug or a Release build.
50# - hermes_enabled: whether Hermes should be enabled or not.
51# - flipper_configuration: The configuration to use for flipper.
52# - app_path: path to the React Native app. Required by the New Architecture.
53# - config_file_dir: directory of the `package.json` file, required by the New Architecture.
54# - ios_folder: the folder where the iOS code base lives. For a template app, it is `ios`, the default. For RNTester, it is `.`.
55def use_react_native_ABI48_0_0! (
56  path: "../node_modules/react-native",
57  fabric_enabled: false,
58  new_arch_enabled: ENV['RCT_NEW_ARCH_ENABLED'] == '1',
59  production: ENV['PRODUCTION'] == '1',
60  hermes_enabled: ENV['USE_HERMES'] && ENV['USE_HERMES'] == '0' ? false : true,
61  flipper_configuration: FlipperConfiguration.disabled,
62  app_path: '..',
63  config_file_dir: '',
64  ios_folder: 'ios'
65)
66
67  # Current target definition is provided by Cocoapods and it refers to the target
68  # that has invoked the `use_react_native!` function.
69  ReactNativePodsUtils.detect_use_frameworks(current_target_definition)
70
71
72  # We are relying on this flag also in third parties libraries to proper install dependencies.
73  # Better to rely and enable this environment flag if the new architecture is turned on using flags.
74  ENV['RCT_NEW_ARCH_ENABLED'] = new_arch_enabled ? "1" : "0"
75  fabric_enabled = fabric_enabled || new_arch_enabled
76  ENV['USE_HERMES'] = hermes_enabled ? "1" : "0"
77
78  prefix = path
79
80  ReactNativePodsUtils.warn_if_not_on_arm64()
81
82  # The Pods which should be included in all projects
83  pod 'ABI48_0_0FBLazyVector', :path => "#{prefix}/Libraries/FBLazyVector", :project_name => 'ABI48_0_0'
84  pod 'ABI48_0_0FBReactNativeSpec', :path => "#{prefix}/React/FBReactNativeSpec", :project_name => 'ABI48_0_0'
85  pod 'ABI48_0_0RCTRequired', :path => "#{prefix}/Libraries/RCTRequired", :project_name => 'ABI48_0_0'
86  pod 'ABI48_0_0RCTTypeSafety', :path => "#{prefix}/Libraries/TypeSafety", :project_name => 'ABI48_0_0', :modular_headers => true
87  pod 'ABI48_0_0React', :path => "#{prefix}/", :project_name => 'ABI48_0_0'
88  pod 'ABI48_0_0React-Core', :path => "#{prefix}/", :project_name => 'ABI48_0_0'
89  pod 'ABI48_0_0React-CoreModules', :path => "#{prefix}/React/CoreModules", :project_name => 'ABI48_0_0'
90  pod 'ABI48_0_0React-RCTAppDelegate', :path => "#{prefix}/Libraries/AppDelegate", :project_name => 'ABI48_0_0'
91  pod 'ABI48_0_0React-RCTActionSheet', :path => "#{prefix}/Libraries/ActionSheetIOS", :project_name => 'ABI48_0_0'
92  pod 'ABI48_0_0React-RCTAnimation', :path => "#{prefix}/Libraries/NativeAnimation", :project_name => 'ABI48_0_0'
93  pod 'ABI48_0_0React-RCTBlob', :path => "#{prefix}/Libraries/Blob", :project_name => 'ABI48_0_0'
94  pod 'ABI48_0_0React-RCTImage', :path => "#{prefix}/Libraries/Image", :project_name => 'ABI48_0_0'
95  pod 'ABI48_0_0React-RCTLinking', :path => "#{prefix}/Libraries/LinkingIOS", :project_name => 'ABI48_0_0'
96  pod 'ABI48_0_0React-RCTNetwork', :path => "#{prefix}/Libraries/Network", :project_name => 'ABI48_0_0'
97  pod 'ABI48_0_0React-RCTSettings', :path => "#{prefix}/Libraries/Settings", :project_name => 'ABI48_0_0'
98  pod 'ABI48_0_0React-RCTText', :path => "#{prefix}/Libraries/Text", :project_name => 'ABI48_0_0'
99  pod 'ABI48_0_0React-RCTVibration', :path => "#{prefix}/Libraries/Vibration", :project_name => 'ABI48_0_0'
100  pod 'ABI48_0_0React-Core/RCTWebSocket', :path => "#{prefix}/", :project_name => 'ABI48_0_0'
101
102  pod 'ABI48_0_0React-cxxreact', :path => "#{prefix}/ReactCommon/cxxreact", :project_name => 'ABI48_0_0'
103
104  if hermes_enabled
105    setup_hermes_ABI48_0_0!(:react_native_path => prefix, :fabric_enabled => fabric_enabled)
106  else
107    setup_jsc_ABI48_0_0!(:react_native_path => prefix, :fabric_enabled => fabric_enabled)
108  end
109
110  pod 'ABI48_0_0React-jsiexecutor', :path => "#{prefix}/ReactCommon/jsiexecutor", :project_name => 'ABI48_0_0'
111  pod 'ABI48_0_0React-jsinspector', :path => "#{prefix}/ReactCommon/jsinspector", :project_name => 'ABI48_0_0'
112
113  pod 'ABI48_0_0React-callinvoker', :path => "#{prefix}/ReactCommon/callinvoker", :project_name => 'ABI48_0_0'
114  pod 'ABI48_0_0React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor", :project_name => 'ABI48_0_0'
115  pod 'ABI48_0_0React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger", :project_name => 'ABI48_0_0'
116  pod 'ABI48_0_0React-logger', :path => "#{prefix}/ReactCommon/logger", :project_name => 'ABI48_0_0'
117  pod 'ABI48_0_0ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon", :project_name => 'ABI48_0_0', :modular_headers => true
118  pod 'ABI48_0_0Yoga', :path => "#{prefix}/ReactCommon/yoga", :project_name => 'ABI48_0_0', :modular_headers => true
119
120  # pod 'ABI48_0_0DoubleConversion', :podspec => "#{prefix}/third-party-podspecs/DoubleConversion.podspec"
121  # pod 'ABI48_0_0glog', :podspec => "#{prefix}/third-party-podspecs/glog.podspec"
122  # pod 'ABI48_0_0boost', :podspec => "#{prefix}/third-party-podspecs/boost.podspec"
123  # pod 'ABI48_0_0RCT-Folly', :podspec => "#{prefix}/third-party-podspecs/RCT-Folly.podspec", :modular_headers => true
124
125  run_codegen_ABI48_0_0!(
126    app_path,
127    config_file_dir,
128    :new_arch_enabled => new_arch_enabled,
129    :disable_codegen => ENV['DISABLE_CODEGEN'] == '1',
130    :react_native_path => prefix,
131    :fabric_enabled => fabric_enabled,
132    :hermes_enabled => hermes_enabled,
133    :codegen_output_dir => $ABI48_0_0CODEGEN_OUTPUT_DIR,
134    :package_json_file => File.join(__dir__, "..", "package.json"),
135    :folly_version => $FOLLY_VERSION
136  )
137
138  pod 'ABI48_0_0React-Codegen', :path => $ABI48_0_0CODEGEN_OUTPUT_DIR, :modular_headers => true
139
140  if fabric_enabled
141    checkAndGenerateEmptyThirdPartyProvider!(prefix, new_arch_enabled, $ABI48_0_0CODEGEN_OUTPUT_DIR)
142    setup_fabric!(:react_native_path => prefix)
143  end
144
145  # CocoaPods `configurations` option ensures that the target is copied only for the specified configurations,
146  # but those dependencies are still built.
147  # Flipper doesn't currently compile for release https://github.com/facebook/react-native/issues/33764
148  # Setting the production flag to true when build for production make sure that we don't install Flipper in the app in the first place.
149  if flipper_configuration.flipper_enabled && !production
150    install_flipper_dependencies(prefix)
151    use_flipper_pods(flipper_configuration.versions, :configurations => flipper_configuration.configurations)
152  end
153
154  pods_to_update = LocalPodspecPatch.pods_to_update(:react_native_path => prefix)
155  if !pods_to_update.empty?
156    if Pod::Lockfile.public_instance_methods.include?(:detect_changes_with_podfile)
157      Pod::Lockfile.prepend(LocalPodspecPatch)
158    else
159      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."
160    end
161  end
162end
163
164# Getter to retrieve the folly flags in case contributors need to apply them manually.
165#
166# Returns: the folly compiler flags
167def folly_flags()
168  return NewArchitectureHelper.folly_compiler_flags
169end
170
171# This function can be used by library developer to prepare their modules for the New Architecture.
172# It passes the Folly Flags to the module, it configures the search path and installs some New Architecture specific dependencies.
173#
174# Parameters:
175# - spec: The spec that has to be configured with the New Architecture code
176# - new_arch_enabled: Whether the module should install dependencies for the new architecture
177def install_modules_dependencies(spec, new_arch_enabled: ENV['RCT_NEW_ARCH_ENABLED'] == "1")
178  NewArchitectureHelper.install_modules_dependencies(spec, new_arch_enabled, $FOLLY_VERSION)
179end
180
181# It returns the default flags.
182def get_default_flags()
183  return ReactNativePodsUtils.get_default_flags()
184end
185
186# It installs the flipper dependencies into the project.
187#
188# Parameters
189# - versions: a dictionary of Flipper Library -> Versions that can be used to customize which version of Flipper to install.
190# - configurations: an array of configuration where to install the dependencies.
191def use_flipper!(versions = {}, configurations: ['Debug'])
192  Pod::UI.warn "use_flipper is deprecated, use the flipper_configuration option in the use_react_native function"
193  use_flipper_pods(versions, :configurations => configurations)
194end
195
196# Function that executes after React Native has been installed to configure some flags and build settings.
197#
198# Parameters
199# - installer: the Cocoapod object that allows to customize the project.
200# - react_native_path: path to React Native.
201# - mac_catalyst_enabled: whether we are running the Pod on a Mac Catalyst project or not.
202def react_native_post_install(installer, react_native_path = "../node_modules/react-native", mac_catalyst_enabled: false)
203  ReactNativePodsUtils.turn_off_resource_bundle_react_core(installer)
204
205  ReactNativePodsUtils.apply_mac_catalyst_patches(installer) if mac_catalyst_enabled
206
207  if ReactNativePodsUtils.has_pod(installer, 'Flipper')
208    flipper_post_install(installer)
209  end
210
211  package = JSON.parse(File.read(File.join(react_native_path, "package.json")))
212  version = package['version']
213
214  if ReactNativePodsUtils.has_pod(installer, 'hermes-engine') && is_building_hermes_from_source(version, react_native_path)
215    add_copy_hermes_framework_script_phase(installer, react_native_path)
216  else
217    remove_copy_hermes_framework_script_phase(installer, react_native_path)
218    remove_hermesc_build_dir(react_native_path)
219  end
220
221  ReactNativePodsUtils.exclude_i386_architecture_while_using_hermes(installer)
222  ReactNativePodsUtils.fix_library_search_paths(installer)
223  ReactNativePodsUtils.set_node_modules_user_settings(installer, react_native_path)
224
225  NewArchitectureHelper.set_clang_cxx_language_standard_if_needed(installer)
226  is_new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == "1"
227  NewArchitectureHelper.modify_flags_for_new_architecture(installer, is_new_arch_enabled)
228
229  Pod::UI.puts "Pod install took #{Time.now.to_i - $START_TIME} [s] to run".green
230end
231
232# === LEGACY METHOD ===
233# We need to keep this while we continue to support the old architecture.
234# =====================
235def use_react_native_codegen_ABI48_0_0!(spec, options={})
236  return if ENV['RCT_NEW_ARCH_ENABLED'] == "1"
237  # TODO: Once the new codegen approach is ready for use, we should output a warning here to let folks know to migrate.
238
239  # The prefix to react-native
240  react_native_path = options[:react_native_path] ||= "../.."
241
242  # Library name (e.g. FBReactNativeSpec)
243  library_name = options[:library_name] ||= "#{spec.name.gsub('_','-').split('-').collect(&:capitalize).join}Spec"
244  Pod::UI.puts "[Codegen] Found #{library_name}"
245
246  relative_installation_root = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd)
247  output_dir = options[:output_dir] ||= $ABI48_0_0CODEGEN_OUTPUT_DIR
248  output_dir_module = "#{output_dir}/#{$CODEGEN_MODULE_DIR}"
249  output_dir_component = "#{output_dir}/#{$CODEGEN_COMPONENT_DIR}"
250
251  codegen_config = {
252    "modules" => {
253      :js_srcs_pattern => "Native*.js",
254      :generated_dir => "#{relative_installation_root}/#{output_dir_module}/#{library_name}",
255      :generated_files => [
256        "#{library_name}.h",
257        "#{library_name}-generated.mm"
258      ]
259    },
260    "components" => {
261      :js_srcs_pattern => "*NativeComponent.js",
262      :generated_dir => "#{relative_installation_root}/#{output_dir_component}/#{library_name}",
263      :generated_files => [
264        "ComponentDescriptors.h",
265        "EventEmitters.cpp",
266        "EventEmitters.h",
267        "Props.cpp",
268        "Props.h",
269        "States.cpp",
270        "States.h",
271        "RCTComponentViewHelpers.h",
272        "ShadowNodes.cpp",
273        "ShadowNodes.h"
274      ]
275    }
276  }
277
278  # The path to JavaScript files
279  js_srcs_dir = options[:js_srcs_dir] ||= "./"
280  library_type = options[:library_type]
281
282  if library_type
283    if !codegen_config[library_type]
284      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"
285    end
286    js_srcs_pattern = codegen_config[library_type][:js_srcs_pattern]
287  end
288
289  if library_type
290    generated_dirs = [ codegen_config[library_type][:generated_dir] ]
291    generated_files = codegen_config[library_type][:generated_files].map { |filename| "#{codegen_config[library_type][:generated_dir]}/#{filename}" }
292  else
293    generated_dirs = [ codegen_config["modules"][:generated_dir], codegen_config["components"][:generated_dir] ]
294    generated_files = codegen_config["modules"][:generated_files].map { |filename| "#{codegen_config["modules"][:generated_dir]}/#{filename}" }
295    generated_files = generated_files.concat(codegen_config["components"][:generated_files].map { |filename| "#{codegen_config["components"][:generated_dir]}/#{filename}" })
296  end
297
298  if js_srcs_pattern
299    file_list = `find #{js_srcs_dir} -type f -name #{js_srcs_pattern}`.split("\n").sort
300    input_files = file_list.map { |filename| "${PODS_TARGET_SRCROOT}/#{filename}" }
301  else
302    input_files = [ js_srcs_dir ]
303  end
304
305  # Prepare filesystem by creating empty files that will be picked up as references by CocoaPods.
306  prepare_command = "mkdir -p #{generated_dirs.join(" ")} && touch -a #{generated_files.join(" ")}"
307  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
308  spec.prepare_command = prepare_command
309
310  env_files = ["$PODS_ROOT/../.xcode.env.local", "$PODS_ROOT/../.xcode.env"]
311
312  spec.script_phase = {
313    :name => 'Generate Specs',
314    :input_files => input_files + env_files, # This also needs to be relative to Xcode
315    :output_files => ["${DERIVED_FILE_DIR}/codegen-#{library_name}.log"].concat(generated_files.map { |filename| "${PODS_TARGET_SRCROOT}/#{filename}"} ),
316    # The final generated files will be created when this script is invoked at Xcode build time.
317    :script => get_script_phases_no_codegen_discovery_ABI48_0_0(
318      react_native_path: react_native_path,
319      codegen_output_dir: $ABI48_0_0CODEGEN_OUTPUT_DIR,
320      codegen_module_dir: $CODEGEN_MODULE_DIR,
321      codegen_component_dir: $CODEGEN_COMPONENT_DIR,
322      library_name: library_name,
323      library_type: library_type,
324      js_srcs_pattern: js_srcs_pattern,
325      js_srcs_dir: js_srcs_dir,
326      file_list: file_list
327    ),
328    :execution_position => :before_compile,
329    :show_env_vars_in_log => true
330  }
331end
332
333# This provides a post_install workaround for build issues related Xcode 12.5 and Apple Silicon (M1) machines.
334# Call this in the app's main Podfile's post_install hook.
335# See https://github.com/facebook/react-native/issues/31480#issuecomment-902912841 for more context.
336# Actual fix was authored by https://github.com/mikehardy.
337# New app template will call this for now until the underlying issue is resolved.
338def __apply_Xcode_12_5_M1_post_install_workaround(installer)
339  # Flipper podspecs are still targeting an older iOS deployment target, and may cause an error like:
340  #   "error: thread-local storage is not supported for the current target"
341  # The most reliable known workaround is to bump iOS deployment target to match react-native (iOS 11 now).
342  installer.pods_project.targets.each do |target|
343    target.build_configurations.each do |config|
344      # ensure IPHONEOS_DEPLOYMENT_TARGET is at least 11.0
345      deployment_target = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f
346      should_upgrade = deployment_target < 11.0 && deployment_target != 0.0
347      if should_upgrade
348        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
349      end
350    end
351  end
352
353  # But... doing so caused another issue in Flipper:
354  #   "Time.h:52:17: error: typedef redefinition with different types"
355  # We need to make a patch to RCT-Folly - remove the `__IPHONE_OS_VERSION_MIN_REQUIRED` check.
356  # See https://github.com/facebook/flipper/issues/834 for more details.
357  time_header = "#{Pod::Config.instance.installation_root.to_s}/Pods/RCT-Folly/folly/portability/Time.h"
358  `sed -i -e  $'s/ && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0)//' '#{time_header}'`
359end
360