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_relative "./helpers.rb"
7
8# Utilities class for React Native Cocoapods
9class ReactNativePodsUtils
10    def self.warn_if_not_on_arm64
11        if SysctlChecker.new().call_sysctl_arm64() == 1 && !Environment.new().ruby_platform().include?('arm64')
12            Pod::UI.warn 'Do not use "pod install" from inside Rosetta2 (x86_64 emulation on arm64).'
13            Pod::UI.warn ' - Emulated x86_64 is slower than native arm64'
14            Pod::UI.warn ' - May result in mixed architectures in rubygems (eg: ffi_c.bundle files may be x86_64 with an arm64 interpreter)'
15            Pod::UI.warn 'Run "env /usr/bin/arch -arm64 /bin/bash --login" then try again.'
16        end
17    end
18
19    def self.get_default_flags
20        flags = {
21            :fabric_enabled => false,
22            :hermes_enabled => true,
23            :flipper_configuration => FlipperConfiguration.disabled
24        }
25
26        if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
27            flags[:fabric_enabled] = true
28            flags[:hermes_enabled] = true
29        end
30
31        if ENV['USE_HERMES'] == '0'
32            flags[:hermes_enabled] = false
33        end
34
35        return flags
36    end
37
38    def self.has_pod(installer, name)
39        installer.pods_project.pod_group(name) != nil
40    end
41
42    def self.turn_off_resource_bundle_react_core(installer)
43        # this is needed for Xcode 14, see more details here https://github.com/facebook/react-native/issues/34673
44        # we should be able to remove this once CocoaPods catches up to it, see more details here https://github.com/CocoaPods/CocoaPods/issues/11402
45        installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
46            if pod_name.to_s == 'React-Core'
47                target_installation_result.resource_bundle_targets.each do |resource_bundle_target|
48                    resource_bundle_target.build_configurations.each do |config|
49                        config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
50                    end
51                end
52            end
53        end
54    end
55
56    def self.exclude_i386_architecture_while_using_hermes(installer)
57        projects = self.extract_projects(installer)
58
59        # Hermes does not support `i386` architecture
60        excluded_archs_default = self.has_pod(installer, 'hermes-engine') ? "i386" : ""
61
62        projects.each do |project|
63            project.build_configurations.each do |config|
64                config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = excluded_archs_default
65            end
66
67            project.save()
68        end
69    end
70
71    def self.set_node_modules_user_settings(installer, react_native_path)
72        Pod::UI.puts("Setting REACT_NATIVE build settings")
73        projects = self.extract_projects(installer)
74
75        projects.each do |project|
76            project.build_configurations.each do |config|
77                config.build_settings["REACT_NATIVE_PATH"] = File.join("${PODS_ROOT}", "..", react_native_path)
78            end
79
80            project.save()
81        end
82    end
83
84    def self.fix_library_search_paths(installer)
85        projects = self.extract_projects(installer)
86
87        projects.each do |project|
88            project.build_configurations.each do |config|
89                self.fix_library_search_path(config)
90            end
91            project.native_targets.each do |target|
92                target.build_configurations.each do |config|
93                    self.fix_library_search_path(config)
94                end
95            end
96            project.save()
97        end
98    end
99
100    def self.apply_mac_catalyst_patches(installer)
101        # Fix bundle signing issues
102        installer.pods_project.targets.each do |target|
103            if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
104                target.build_configurations.each do |config|
105                    config.build_settings['CODE_SIGN_IDENTITY[sdk=macosx*]'] = '-'
106                end
107            end
108        end
109
110        installer.aggregate_targets.each do |aggregate_target|
111            aggregate_target.user_project.native_targets.each do |target|
112                target.build_configurations.each do |config|
113                    # Explicitly set dead code stripping flags
114                    config.build_settings['DEAD_CODE_STRIPPING'] = 'YES'
115                    config.build_settings['PRESERVE_DEAD_CODE_INITS_AND_TERMS'] = 'YES'
116                    # Modify library search paths
117                    config.build_settings['LIBRARY_SEARCH_PATHS'] = ['$(SDKROOT)/usr/lib/swift', '$(SDKROOT)/System/iOSSupport/usr/lib/swift', '$(inherited)']
118                end
119            end
120            aggregate_target.user_project.save()
121        end
122    end
123
124    def self.apply_xcode_15_patch(installer)
125        installer.target_installation_results.pod_target_installation_results
126            .each do |pod_name, target_installation_result|
127                target_installation_result.native_target.build_configurations.each do |config|
128                    # unary_function and binary_function are no longer provided in C++17 and newer standard modes as part of Xcode 15. They can be re-enabled with setting _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION
129                    # Ref: https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Deprecations
130                    config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= '$(inherited) '
131                    config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '"_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION" '
132            end
133        end
134    end
135
136    def self.apply_flags_for_fabric(installer, fabric_enabled: false)
137        fabric_flag = "-DRN_FABRIC_ENABLED"
138        if fabric_enabled
139            self.add_compiler_flag_to_project(installer, fabric_flag)
140        else
141            self.remove_compiler_flag_from_project(installer, fabric_flag)
142        end
143    end
144
145    private
146
147    def self.fix_library_search_path(config)
148        lib_search_paths = config.build_settings["LIBRARY_SEARCH_PATHS"]
149
150        if lib_search_paths == nil
151            # No search paths defined, return immediately
152            return
153        end
154
155        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)\"")
156            # $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) causes problem with Xcode 12.5 + arm64 (Apple M1)
157            # since the libraries there are only built for x86_64 and i386.
158            lib_search_paths.delete("$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)")
159            lib_search_paths.delete("\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"")
160        end
161
162        if !(lib_search_paths.include?("$(SDKROOT)/usr/lib/swift") || lib_search_paths.include?("\"$(SDKROOT)/usr/lib/swift\""))
163            # however, $(SDKROOT)/usr/lib/swift is required, at least if user is not running CocoaPods 1.11
164            lib_search_paths.insert(0, "$(SDKROOT)/usr/lib/swift")
165        end
166    end
167
168    def self.create_xcode_env_if_missing(file_manager: File)
169        relative_path = Pod::Config.instance.installation_root.relative_path_from(Pathname.pwd)
170        file_path = file_manager.join(relative_path, '.xcode.env')
171        if file_manager.exist?(file_path)
172            return
173        end
174
175        system("echo 'export NODE_BINARY=$(command -v node)' > #{file_path}")
176    end
177
178    # It examines the target_definition property and sets the appropriate value for
179    # ENV['USE_FRAMEWORKS'] variable.
180    #
181    # - parameter target_definition: The current target definition
182    def self.detect_use_frameworks(target_definition)
183        if ENV['USE_FRAMEWORKS'] != nil
184            return
185        end
186
187        framework_build_type = target_definition.build_type.to_s
188
189        Pod::UI.puts("Framework build type is #{framework_build_type}")
190
191        if framework_build_type === "static framework"
192            ENV['USE_FRAMEWORKS'] = 'static'
193        elsif framework_build_type === "dynamic framework"
194            ENV['USE_FRAMEWORKS'] = 'dynamic'
195        else
196            ENV['USE_FRAMEWORKS'] = nil
197        end
198    end
199
200    def self.update_search_paths(installer)
201        return if ENV['USE_FRAMEWORKS'] == nil
202
203        projects = self.extract_projects(installer)
204
205        projects.each do |project|
206            project.build_configurations.each do |config|
207
208                header_search_paths = config.build_settings["HEADER_SEARCH_PATHS"] ||= "$(inherited)"
209
210                header_search_paths = self.add_search_path_if_not_included(header_search_paths, "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers")
211                header_search_paths = self.add_search_path_if_not_included(header_search_paths, "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core")
212                header_search_paths = self.add_search_path_if_not_included(header_search_paths, "${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios")
213                header_search_paths = self.add_search_path_if_not_included(header_search_paths, "${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers")
214                header_search_paths = self.add_search_path_if_not_included(header_search_paths, "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios")
215
216                config.build_settings["HEADER_SEARCH_PATHS"] = header_search_paths
217            end
218
219            project.save()
220        end
221
222        installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
223            if self.react_native_pods.include?(pod_name) || pod_name.include?("Pod") || pod_name.include?("Tests")
224                next
225            end
226
227            self.set_rctfolly_search_paths(target_installation_result)
228            self.set_codegen_search_paths(target_installation_result)
229            self.set_reactcommon_searchpaths(target_installation_result)
230            self.set_rctfabric_search_paths(target_installation_result)
231            self.set_imagemanager_search_path(target_installation_result)
232        end
233    end
234
235    # ========= #
236    # Utilities #
237    # ========= #
238
239    def self.extract_projects(installer)
240        return installer.aggregate_targets
241            .map{ |t| t.user_project }
242            .uniq{ |p| p.path }
243            .push(installer.pods_project)
244    end
245
246    def self.add_compiler_flag_to_project(installer, flag, configuration: nil)
247        projects = self.extract_projects(installer)
248
249        projects.each do |project|
250            project.build_configurations.each do |config|
251                self.set_flag_in_config(config, flag, configuration: configuration)
252            end
253            project.save()
254        end
255    end
256
257    def self.remove_compiler_flag_from_project(installer, flag, configuration: nil)
258        projects = self.extract_projects(installer)
259
260        projects.each do |project|
261            project.build_configurations.each do |config|
262                self.remove_flag_in_config(config, flag, configuration: configuration)
263            end
264            project.save()
265        end
266    end
267
268    def self.add_compiler_flag_to_pods(installer, flag, configuration: nil)
269        installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
270            target_installation_result.native_target.build_configurations.each do |config|
271                self.set_flag_in_config(config, flag, configuration: configuration)
272            end
273        end
274    end
275
276    def self.set_flag_in_config(config, flag, configuration: nil)
277        if configuration == nil || config.name == configuration
278            self.add_flag_for_key(config, flag, "OTHER_CFLAGS")
279            self.add_flag_for_key(config, flag, "OTHER_CPLUSPLUSFLAGS")
280        end
281    end
282
283    def self.remove_flag_in_config(config, flag, configuration: nil)
284        if configuration == nil || config.name == configuration
285            self.remove_flag_for_key(config, flag, "OTHER_CFLAGS")
286            self.remove_flag_for_key(config, flag, "OTHER_CPLUSPLUSFLAGS")
287        end
288    end
289
290
291    def self.add_flag_for_key(config, flag, key)
292        current_setting = config.build_settings[key] ? config.build_settings[key] : "$(inherited)"
293
294        if current_setting.kind_of?(Array)
295            current_setting = current_setting
296            .map { |s| s.gsub('"', '') }
297            .map { |s| s.gsub('\"', '') }
298            .join(" ")
299        end
300
301        if !current_setting.include?(flag)
302            current_setting = "#{current_setting} #{flag}"
303        end
304
305        config.build_settings[key] = current_setting
306    end
307
308    def self.remove_flag_for_key(config, flag, key)
309        current_setting = config.build_settings[key] ? config.build_settings[key] : "$(inherited)"
310
311        if current_setting.kind_of?(Array)
312            current_setting = current_setting
313            .map { |s| s.gsub('"', '') }
314            .map { |s| s.gsub('\"', '') }
315            .join(" ")
316        end
317
318        if current_setting.include?(flag)
319            current_setting.slice! flag
320        end
321
322        config.build_settings[key] = current_setting
323    end
324
325    def self.add_search_path_if_not_included(current_search_paths, new_search_path)
326        if !current_search_paths.include?(new_search_path)
327            current_search_paths << " #{new_search_path}"
328        end
329        return current_search_paths
330    end
331
332    def self.update_header_paths_if_depends_on(target_installation_result, dependency_name, header_paths)
333        depends_on_framework = target_installation_result.native_target.dependencies.any? { |d| d.name == dependency_name }
334        if depends_on_framework
335            target_installation_result.native_target.build_configurations.each do |config|
336                header_search_path = config.build_settings["HEADER_SEARCH_PATHS"] != nil ? config.build_settings["HEADER_SEARCH_PATHS"] : "$(inherited)"
337                header_paths.each { |header| header_search_path = ReactNativePodsUtils.add_search_path_if_not_included(header_search_path, header) }
338                config.build_settings["HEADER_SEARCH_PATHS"] = header_search_path
339            end
340        end
341    end
342
343    def self.set_rctfolly_search_paths(target_installation_result)
344        ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "RCT-Folly", [
345            "\"$(PODS_ROOT)/RCT-Folly\"",
346            "\"$(PODS_ROOT)/DoubleConversion\"",
347            "\"$(PODS_ROOT)/boost\""
348        ])
349    end
350
351    def self.set_codegen_search_paths(target_installation_result)
352        ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "ABI49_0_0React-Codegen", [
353            "\"${PODS_CONFIGURATION_BUILD_DIR}/ABI49_0_0React-Codegen/React_Codegen.framework/Headers\"",
354        ])
355    end
356
357    def self.set_reactcommon_searchpaths(target_installation_result)
358        ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "ReactCommon", [
359            "\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers\"",
360            "\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core\"",
361        ])
362
363    end
364
365    def self.set_rctfabric_search_paths(target_installation_result)
366        ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-RCTFabric", [
367            "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTFabric/RCTFabric.framework/Headers\"",
368            "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers\"",
369            "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Graphics/React_graphics.framework/Headers\"",
370            "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios\"",
371        ])
372    end
373
374    def self.set_imagemanager_search_path(target_installation_result)
375        ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-ImageManager", [
376            "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/imagemanager/platform/ios\""
377        ])
378    end
379
380    def self.react_native_pods
381        return [
382            "DoubleConversion",
383            "FBLazyVector",
384            "RCT-Folly",
385            "RCTRequired",
386            "RCTTypeSafety",
387            "React",
388            "ABI49_0_0React-Codegen",
389            "React-Core",
390            "React-CoreModules",
391            "React-Fabric",
392            "React-ImageManager",
393            "React-RCTActionSheet",
394            "React-RCTAnimation",
395            "React-RCTAppDelegate",
396            "React-RCTBlob",
397            "React-RCTFabric",
398            "React-RCTImage",
399            "React-RCTLinking",
400            "React-RCTNetwork",
401            "React-RCTPushNotification",
402            "React-RCTSettings",
403            "React-RCTText",
404            "React-RCTTest",
405            "React-RCTVibration",
406            "React-callinvoker",
407            "React-cxxreact",
408            "React-graphics",
409            "React-jsc",
410            "React-jsi",
411            "React-jsiexecutor",
412            "React-jsinspector",
413            "React-logger",
414            "React-perflogger",
415            "React-rncore",
416            "React-runtimeexecutor",
417            "ReactCommon",
418            "Yoga",
419            "boost",
420            "fmt",
421            "glog",
422            "hermes-engine",
423            "libevent",
424            "React-hermes",
425        ]
426    end
427end
428