diff --git a/package.json b/package.json index f68f1854ad7d1b..81bed804c06295 100644 --- a/package.json +++ b/package.json @@ -55,10 +55,7 @@ "scripts/react_native_pods_utils/script_phases.rb", "scripts/react_native_pods_utils/script_phases.sh", "scripts/react_native_pods.rb", - "scripts/cocoapods/codegen.rb", - "scripts/cocoapods/fabric.rb", - "scripts/cocoapods/flipper.rb", - "scripts/cocoapods/new_architecture.rb", + "scripts/cocoapods", "scripts/react-native-xcode.sh", "sdks/.hermesversion", "sdks/hermes-engine", diff --git a/scripts/.npmignore b/scripts/.npmignore new file mode 100644 index 00000000000000..ee653fdf1e65c8 --- /dev/null +++ b/scripts/.npmignore @@ -0,0 +1,2 @@ +# Make sure we never publish __test__ folders (Gradle output) +**/__tests__/ diff --git a/scripts/cocoapods/__tests__/test_utils/EnvironmentMock.rb b/scripts/cocoapods/__tests__/test_utils/EnvironmentMock.rb new file mode 100644 index 00000000000000..5b142224419a3a --- /dev/null +++ b/scripts/cocoapods/__tests__/test_utils/EnvironmentMock.rb @@ -0,0 +1,21 @@ +# 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. + +# Mock object for the Environment +class Environment + @@RUBY_PLATFORM = "arm64-darwin21" + + def ruby_platform + return @@RUBY_PLATFORM + end + + def self.set_ruby_platform(newPlatform) + @@RUBY_PLATFORM = newPlatform + end + + def self.reset() + @@RUBY_PLATFORM = "arm64-darwin21" + end +end diff --git a/scripts/cocoapods/__tests__/test_utils/InstallerMock.rb b/scripts/cocoapods/__tests__/test_utils/InstallerMock.rb index 2366981f688421..32a1594e22f80f 100644 --- a/scripts/cocoapods/__tests__/test_utils/InstallerMock.rb +++ b/scripts/cocoapods/__tests__/test_utils/InstallerMock.rb @@ -54,9 +54,22 @@ def target_with_name(name) class PodsProjectMock attr_reader :targets + attr_reader :path + attr_reader :build_configurations + @pod_group - def initialize(targets = []) + def initialize(targets = [], pod_group = {}, path = "test/path-pod.xcodeproj", build_configurations = []) @targets = targets + @pod_group = pod_group + @path = path + @build_configurations = build_configurations + end + + def pod_group(name) + return @pod_group[name] + end + + def save() end end diff --git a/scripts/cocoapods/__tests__/test_utils/PodMock.rb b/scripts/cocoapods/__tests__/test_utils/PodMock.rb index 4f110a868ce18c..0171c2dd4dd032 100644 --- a/scripts/cocoapods/__tests__/test_utils/PodMock.rb +++ b/scripts/cocoapods/__tests__/test_utils/PodMock.rb @@ -41,17 +41,27 @@ def relative_path_from(path) class UI @@collected_messages = [] + @@collected_warns = [] def self.puts(message) @@collected_messages.push(message) end + def self.warn(warn) + @@collected_warns.push(warn) + end + def self.collected_messages() return @@collected_messages end + def self.collected_warns() + return @@collected_warns + end + def self.reset() @@collected_messages = [] + @@collected_warns = [] end end diff --git a/scripts/cocoapods/__tests__/test_utils/SysctlCheckerMock.rb b/scripts/cocoapods/__tests__/test_utils/SysctlCheckerMock.rb new file mode 100644 index 00000000000000..212c7fb4af65f8 --- /dev/null +++ b/scripts/cocoapods/__tests__/test_utils/SysctlCheckerMock.rb @@ -0,0 +1,21 @@ +# 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. + +# Mock object for SysctlChecker +class SysctlChecker + @@call_sysctl_arm64_return_value = 1 + + def call_sysctl_arm64 + return @@call_sysctl_arm64_return_value + end + + def self.set_call_sysctl_arm64_return_value(newValue) + @@call_sysctl_arm64_return_value = newValue + end + + def self.reset() + @@call_sysctl_arm64_return_value = 1 + end +end diff --git a/scripts/cocoapods/__tests__/utils-test.rb b/scripts/cocoapods/__tests__/utils-test.rb new file mode 100644 index 00000000000000..f0a4702a2a4174 --- /dev/null +++ b/scripts/cocoapods/__tests__/utils-test.rb @@ -0,0 +1,189 @@ +# 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 "test/unit" +require_relative "../utils.rb" +require_relative "../flipper.rb" +require_relative "./test_utils/PodMock.rb" +require_relative "./test_utils/InstallerMock.rb" +require_relative "./test_utils/EnvironmentMock.rb" +require_relative "./test_utils/SysctlCheckerMock.rb" + +class UtilsTests < Test::Unit::TestCase + def teardown + Pod::UI.reset() + SysctlChecker.reset() + Environment.reset() + ENV['RCT_NEW_ARCH_ENABLED'] = '0' + ENV['USE_HERMES'] = '0' + end + + # ======================= # + # TEST - warnIfNotOnArm64 # + # ======================= # + + def test_warnIfNotOnArm64_whenSysctlReturnsNot1_printsNothing + # Arrange + SysctlChecker.set_call_sysctl_arm64_return_value(23) + Environment.set_ruby_platform("something") + + # Act + ReactNativePodsUtils.warn_if_not_on_arm64() + + # Assert + assert_equal(Pod::UI.collected_messages, []) + assert_equal(Pod::UI.collected_warns, []) + + end + + def test_warnIfNotOnArm64_whenSysctlReturns1AndRubyIncludeArm64_printsNothing + # Arrange + SysctlChecker.set_call_sysctl_arm64_return_value(1) + Environment.set_ruby_platform("arm64-darwin21") + + # Act + ReactNativePodsUtils.warn_if_not_on_arm64() + + # Assert + assert_equal(Pod::UI.collected_messages, []) + assert_equal(Pod::UI.collected_warns, []) + end + + def test_warnIfNotOnArm64_whenSysctlReturns1AndRubyNotIncludeArm64_warns + # Arrange + SysctlChecker.set_call_sysctl_arm64_return_value(1) + Environment.set_ruby_platform("something else") + + # Act + ReactNativePodsUtils.warn_if_not_on_arm64() + + # Assert + assert_equal(Pod::UI.collected_messages, []) + assert_equal(Pod::UI.collected_warns, [ + 'Do not use "pod install" from inside Rosetta2 (x86_64 emulation on arm64).', + ' - Emulated x86_64 is slower than native arm64', + ' - May result in mixed architectures in rubygems (eg: ffi_c.bundle files may be x86_64 with an arm64 interpreter)', + 'Run "env /usr/bin/arch -arm64 /bin/bash --login" then try again.', + ]) + end + + # ====================== # + # TEST - getDefaultFlags # + # ====================== # + def test_getDefaultFlag_whenOldArchitecture() + # Arrange + ENV['RCT_NEW_ARCH_ENABLED'] = '0' + ENV['USE_HERMES'] = '0' + # Act + flags = ReactNativePodsUtils.get_default_flags() + + # Assert + assert_equal(flags, { + :fabric_enabled => false, + :hermes_enabled => false, + :flipper_configuration => FlipperConfiguration.disabled + }) + end + + def test_getDefaultFlag_whenOldArchitectureButHermesEnabled() + # Arrange + ENV['RCT_NEW_ARCH_ENABLED'] = '0' + ENV['USE_HERMES'] = '1' + + # Act + flags = ReactNativePodsUtils.get_default_flags() + + # Assert + assert_equal(flags, { + :fabric_enabled => false, + :hermes_enabled => true, + :flipper_configuration => FlipperConfiguration.disabled + }) + end + + def test_getDefaultFlag_whenNewArchitecture() + # Arrange + ENV['RCT_NEW_ARCH_ENABLED'] = '1' + + # Act + flags = ReactNativePodsUtils.get_default_flags() + + # Assert + assert_equal(flags, { + :fabric_enabled => true, + :hermes_enabled => true, + :flipper_configuration => FlipperConfiguration.disabled + }) + end + + # ============== # + # TEST - has_pod # + # ============== # + def test_hasPod_whenInstallerDoesNotHavePod_returnFalse + # Arrange + installer = InstallerMock.new(PodsProjectMock.new([], {"other_pod" => {}})) + + # Act + result = ReactNativePodsUtils.has_pod(installer, "some_pod") + + # Assert + assert_equal(result, false) + + end + + def test_hasPod_whenInstallerHasPod_returnTrue + # Arrange + installer = InstallerMock.new(PodsProjectMock.new([], {"some_pod" => {}})) + + # Act + result = ReactNativePodsUtils.has_pod(installer, "some_pod") + + # Assert + assert_equal(result, true) + end + + # ============================ # + # Test - Exclude Architectures # + # ============================ # + def test_excludeArchitectures_whenHermesEngineIsNotIncluded_excludeNothing + # Arrange + user_project_mock = UserProjectMock.new("a/path", [ + BuildConfigurationMock.new("Debug"), + BuildConfigurationMock.new("Release"), + ]) + installer = InstallerMock.new(PodsProjectMock.new(), [ + AggregatedProjectMock.new(user_project_mock) + ]) + + # Act + ReactNativePodsUtils.exclude_i386_architecture_while_using_hermes(installer) + + # Assert + user_project_mock.build_configurations.each do |config| + assert_equal(config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"], "") + end + + end + + def test_excludeArchitectures_whenHermesEngineIsIncluded_excludeI386 + # Arrange + user_project_mock = UserProjectMock.new("a/path", [ + BuildConfigurationMock.new("Debug"), + BuildConfigurationMock.new("Release"), + ]) + installer = InstallerMock.new(PodsProjectMock.new([], {"hermes-engine" => {}}), [ + AggregatedProjectMock.new(user_project_mock) + ]) + + # Act + ReactNativePodsUtils.exclude_i386_architecture_while_using_hermes(installer) + + # Assert + user_project_mock.build_configurations.each do |config| + assert_equal(config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"], "i386") + end + end + +end diff --git a/scripts/cocoapods/flipper.rb b/scripts/cocoapods/flipper.rb index 4e901fb3a0aad2..e2bfe76fdf67bf 100644 --- a/scripts/cocoapods/flipper.rb +++ b/scripts/cocoapods/flipper.rb @@ -109,4 +109,10 @@ def self.enabled(configurations = ["Debug"], versions = {}) def self.disabled FlipperConfiguration.new(false, [], {}) end + + def == (other) + return @flipper_enabled == other.flipper_enabled && + @configurations == other.configurations && + @versions == other.versions + end end diff --git a/scripts/cocoapods/helpers.rb b/scripts/cocoapods/helpers.rb new file mode 100644 index 00000000000000..9008968393bbff --- /dev/null +++ b/scripts/cocoapods/helpers.rb @@ -0,0 +1,20 @@ +# 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. + +# Helper object to wrap the invocation of sysctl +# This makes it easier to mock the behaviour in tests +class SysctlChecker + def call_sysctl_arm64 + return `/usr/sbin/sysctl -n hw.optional.arm64 2>&1`.to_i + end +end + +# Helper object to wrap system properties like RUBY_PLATFORM +# This makes it easier to mock the behaviour in tests +class Environment + def ruby_platform + return RUBY_PLATFORM + end +end diff --git a/scripts/cocoapods/utils.rb b/scripts/cocoapods/utils.rb new file mode 100644 index 00000000000000..8e411f07c53df5 --- /dev/null +++ b/scripts/cocoapods/utils.rb @@ -0,0 +1,60 @@ +# 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 + + def self.get_default_flags + flags = { + :fabric_enabled => false, + :hermes_enabled => false, + :flipper_configuration => FlipperConfiguration.disabled + } + + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' + flags[:fabric_enabled] = true + flags[:hermes_enabled] = true + end + + if ENV['USE_HERMES'] == '1' + flags[:hermes_enabled] = true + end + + return flags + end + + def self.has_pod(installer, name) + installer.pods_project.pod_group(name) != nil + end + + def self.exclude_i386_architecture_while_using_hermes(installer) + projects = installer.aggregate_targets + .map{ |t| t.user_project } + .uniq{ |p| p.path } + .push(installer.pods_project) + + + # Hermes does not support `i386` architecture + excluded_archs_default = ReactNativePodsUtils.has_pod(installer, 'hermes-engine') ? "i386" : "" + + projects.each do |project| + project.build_configurations.each do |config| + config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = excluded_archs_default + end + + project.save() + end + end +end diff --git a/scripts/react_native_pods.rb b/scripts/react_native_pods.rb index 7e7bad6542927b..0e2932b65f222d 100644 --- a/scripts/react_native_pods.rb +++ b/scripts/react_native_pods.rb @@ -10,6 +10,7 @@ require_relative './cocoapods/flipper.rb' require_relative './cocoapods/fabric.rb' require_relative './cocoapods/codegen.rb' +require_relative './cocoapods/utils.rb' $CODEGEN_OUTPUT_DIR = 'build/generated/ios' $CODEGEN_COMPONENT_DIR = 'react/renderer/components' @@ -39,12 +40,7 @@ def use_react_native! (options={}) flipper_configuration = options[:flipper_configuration] ||= FlipperConfiguration.disabled - if `/usr/sbin/sysctl -n hw.optional.arm64 2>&1`.to_i == 1 && !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 + ReactNativePodsUtils.warn_if_not_on_arm64() # The Pods which should be included in all projects pod 'FBLazyVector', :path => "#{prefix}/Libraries/FBLazyVector" @@ -137,22 +133,7 @@ def use_react_native! (options={}) end def get_default_flags() - flags = { - :fabric_enabled => false, - :hermes_enabled => false, - :flipper_configuration => FlipperConfiguration.disabled - } - - if ENV['RCT_NEW_ARCH_ENABLED'] == '1' - flags[:fabric_enabled] = true - flags[:hermes_enabled] = true - end - - if ENV['USE_HERMES'] == '1' - flags[:hermes_enabled] = true - end - - return flags + return ReactNativePodsUtils.get_default_flags() end def use_flipper!(versions = {}, configurations: ['Debug']) @@ -160,28 +141,6 @@ def use_flipper!(versions = {}, configurations: ['Debug']) use_flipper_pods(versions, :configurations => configurations) end -def has_pod(installer, name) - installer.pods_project.pod_group(name) != nil -end - -def exclude_architectures(installer) - projects = installer.aggregate_targets - .map{ |t| t.user_project } - .uniq{ |p| p.path } - .push(installer.pods_project) - - # Hermes does not support `i386` architecture - excluded_archs_default = has_pod(installer, 'hermes-engine') ? "i386" : "" - - projects.each do |project| - project.build_configurations.each do |config| - config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = excluded_archs_default - end - - project.save() - end -end - def fix_library_search_paths(installer) def fix_config(config) lib_search_paths = config.build_settings["LIBRARY_SEARCH_PATHS"] @@ -234,11 +193,11 @@ def set_node_modules_user_settings(installer, react_native_path) end def react_native_post_install(installer, react_native_path = "../node_modules/react-native") - if has_pod(installer, 'Flipper') + if ReactNativePodsUtils.has_pod(installer, 'Flipper') flipper_post_install(installer) end - exclude_architectures(installer) + ReactNativePodsUtils.exclude_i386_architecture_while_using_hermes(installer) fix_library_search_paths(installer) cpp_flags = DEFAULT_OTHER_CPLUSPLUSFLAGS