mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Fix Xcode 15 RC issues (#39474)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/39474 When it comes to Xcode 15 RC, we are aware of two issues: 1. `unary_function` and `binary_function` not available in Cxx17 2. [Weak linking](https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Linking) is not supported anymore. This change should fix both of the issues, adding the flags to allow for `unary_function`and `binary_function` to be called and adding the `-Wl -ld_classic` flag to `OTHER_LDFLAGS` in case Xcode 15 is detected. [Internal] - add the `_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION` and the `-Wl -ld_classic` flags to projects when needed Reviewed By: dmytrorykun Differential Revision: D49319256 fbshipit-source-id: bb895f1e60db915db79684f71fa436ce80b42111
This commit is contained in:
committed by
Thibault Malbranche
parent
a5e110adcf
commit
8ccdb2cbe6
@@ -0,0 +1,26 @@
|
||||
# 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.
|
||||
|
||||
class XcodebuildMock < Xcodebuild
|
||||
@@version = ""
|
||||
@@version_invocation_count = 0
|
||||
|
||||
def self.set_version=(v)
|
||||
@@version = v
|
||||
end
|
||||
|
||||
def self.version
|
||||
@@version_invocation_count += 1
|
||||
@@version
|
||||
end
|
||||
|
||||
def self.version_invocation_count
|
||||
@@version_invocation_count
|
||||
end
|
||||
|
||||
def self.reset()
|
||||
@@version_invocation_count = 0
|
||||
end
|
||||
end
|
||||
@@ -14,6 +14,7 @@ require_relative "./test_utils/FileMock.rb"
|
||||
require_relative "./test_utils/systemUtils.rb"
|
||||
require_relative "./test_utils/PathnameMock.rb"
|
||||
require_relative "./test_utils/TargetDefinitionMock.rb"
|
||||
require_relative "./test_utils/XcodebuildMock.rb"
|
||||
|
||||
class UtilsTests < Test::Unit::TestCase
|
||||
def setup
|
||||
@@ -28,6 +29,7 @@ class UtilsTests < Test::Unit::TestCase
|
||||
Pod::Config.reset()
|
||||
SysctlChecker.reset()
|
||||
Environment.reset()
|
||||
XcodebuildMock.reset()
|
||||
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
|
||||
ENV['USE_HERMES'] = '1'
|
||||
ENV['USE_FRAMEWORKS'] = nil
|
||||
@@ -437,9 +439,9 @@ class UtilsTests < Test::Unit::TestCase
|
||||
# ================================= #
|
||||
# Test - Apply Xcode 15 Patch #
|
||||
# ================================= #
|
||||
|
||||
def test_applyXcode15Patch_correctlyAppliesNecessaryPatch
|
||||
def test_applyXcode15Patch_whenXcodebuild14_correctlyAppliesNecessaryPatch
|
||||
# Arrange
|
||||
XcodebuildMock.set_version = "Xcode 14.3"
|
||||
first_target = prepare_target("FirstTarget")
|
||||
second_target = prepare_target("SecondTarget")
|
||||
third_target = TargetMock.new("ThirdTarget", [
|
||||
@@ -468,24 +470,117 @@ class UtilsTests < Test::Unit::TestCase
|
||||
])
|
||||
|
||||
# Act
|
||||
ReactNativePodsUtils.apply_xcode_15_patch(installer)
|
||||
user_project_mock.build_configurations.each do |config|
|
||||
assert_nil(config.build_settings["OTHER_LDFLAGS"])
|
||||
end
|
||||
|
||||
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
|
||||
|
||||
# Assert
|
||||
first_target.build_configurations.each do |config|
|
||||
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
|
||||
'$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
|
||||
)
|
||||
user_project_mock.build_configurations.each do |config|
|
||||
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
|
||||
assert_equal("$(inherited) ", config.build_settings["OTHER_LDFLAGS"])
|
||||
end
|
||||
second_target.build_configurations.each do |config|
|
||||
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
|
||||
'$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
|
||||
)
|
||||
|
||||
# User project and Pods project
|
||||
assert_equal(2, XcodebuildMock.version_invocation_count)
|
||||
end
|
||||
|
||||
def test_applyXcode15Patch_whenXcodebuild15_correctlyAppliesNecessaryPatch
|
||||
# Arrange
|
||||
XcodebuildMock.set_version = "Xcode 15.0"
|
||||
first_target = prepare_target("FirstTarget")
|
||||
second_target = prepare_target("SecondTarget")
|
||||
third_target = TargetMock.new("ThirdTarget", [
|
||||
BuildConfigurationMock.new("Debug", {
|
||||
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
|
||||
}),
|
||||
BuildConfigurationMock.new("Release", {
|
||||
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
|
||||
}),
|
||||
], nil)
|
||||
|
||||
user_project_mock = UserProjectMock.new("/a/path", [
|
||||
prepare_config("Debug"),
|
||||
prepare_config("Release"),
|
||||
],
|
||||
:native_targets => [
|
||||
first_target,
|
||||
second_target
|
||||
]
|
||||
)
|
||||
pods_projects_mock = PodsProjectMock.new([], {"hermes-engine" => {}}, :native_targets => [
|
||||
third_target
|
||||
])
|
||||
installer = InstallerMock.new(pods_projects_mock, [
|
||||
AggregatedProjectMock.new(user_project_mock)
|
||||
])
|
||||
|
||||
# Act
|
||||
user_project_mock.build_configurations.each do |config|
|
||||
assert_nil(config.build_settings["OTHER_LDFLAGS"])
|
||||
end
|
||||
third_target.build_configurations.each do |config|
|
||||
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
|
||||
'$(inherited) "SomeFlag=1" "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
|
||||
)
|
||||
|
||||
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
|
||||
|
||||
# Assert
|
||||
user_project_mock.build_configurations.each do |config|
|
||||
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
|
||||
assert_equal("$(inherited) -Wl -ld_classic ", config.build_settings["OTHER_LDFLAGS"])
|
||||
end
|
||||
|
||||
# User project and Pods project
|
||||
assert_equal(2, XcodebuildMock.version_invocation_count)
|
||||
end
|
||||
|
||||
def test_applyXcode15Patch_whenXcodebuild14ButProjectHasSettings_correctlyRemovesNecessaryPatch
|
||||
# Arrange
|
||||
XcodebuildMock.set_version = "Xcode 14.3"
|
||||
first_target = prepare_target("FirstTarget")
|
||||
second_target = prepare_target("SecondTarget")
|
||||
third_target = TargetMock.new("ThirdTarget", [
|
||||
BuildConfigurationMock.new("Debug", {
|
||||
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
|
||||
}),
|
||||
BuildConfigurationMock.new("Release", {
|
||||
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
|
||||
}),
|
||||
], nil)
|
||||
|
||||
debug_config = prepare_config("Debug", {"OTHER_LDFLAGS" => "$(inherited) -Wl -ld_classic "})
|
||||
release_config = prepare_config("Release", {"OTHER_LDFLAGS" => "$(inherited) -Wl -ld_classic "})
|
||||
|
||||
user_project_mock = UserProjectMock.new("/a/path", [
|
||||
debug_config,
|
||||
release_config,
|
||||
],
|
||||
:native_targets => [
|
||||
first_target,
|
||||
second_target
|
||||
]
|
||||
)
|
||||
pods_projects_mock = PodsProjectMock.new([debug_config.clone, release_config.clone], {"hermes-engine" => {}}, :native_targets => [
|
||||
third_target
|
||||
])
|
||||
installer = InstallerMock.new(pods_projects_mock, [
|
||||
AggregatedProjectMock.new(user_project_mock)
|
||||
])
|
||||
|
||||
# Act
|
||||
user_project_mock.build_configurations.each do |config|
|
||||
assert_equal("$(inherited) -Wl -ld_classic ", config.build_settings["OTHER_LDFLAGS"])
|
||||
end
|
||||
|
||||
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
|
||||
|
||||
# Assert
|
||||
user_project_mock.build_configurations.each do |config|
|
||||
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
|
||||
assert_equal("$(inherited) ", config.build_settings["OTHER_LDFLAGS"])
|
||||
end
|
||||
|
||||
# User project and Pods project
|
||||
assert_equal(2, XcodebuildMock.version_invocation_count)
|
||||
end
|
||||
|
||||
# ==================================== #
|
||||
@@ -744,12 +839,14 @@ def prepare_empty_user_project_mock
|
||||
])
|
||||
end
|
||||
|
||||
def prepare_config(config_name)
|
||||
return BuildConfigurationMock.new(config_name, {"LIBRARY_SEARCH_PATHS" => [
|
||||
def prepare_config(config_name, extra_config = {})
|
||||
config = {"LIBRARY_SEARCH_PATHS" => [
|
||||
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
|
||||
"another/path",
|
||||
]})
|
||||
]}.merge(extra_config)
|
||||
|
||||
return BuildConfigurationMock.new(config_name, config)
|
||||
end
|
||||
|
||||
def prepare_target(name, product_type = nil, dependencies = [])
|
||||
|
||||
@@ -11,6 +11,14 @@ class SysctlChecker
|
||||
end
|
||||
end
|
||||
|
||||
# Helper class that is used to easily send commands to Xcodebuild
|
||||
# And that can be subclassed for testing purposes.
|
||||
class Xcodebuild
|
||||
def self.version
|
||||
`xcodebuild -version`
|
||||
end
|
||||
end
|
||||
|
||||
# Helper object to wrap system properties like RUBY_PLATFORM
|
||||
# This makes it easier to mock the behaviour in tests
|
||||
class Environment
|
||||
|
||||
@@ -121,16 +121,31 @@ class ReactNativePodsUtils
|
||||
end
|
||||
end
|
||||
|
||||
def self.apply_xcode_15_patch(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|
|
||||
# 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
|
||||
# Ref: https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Deprecations
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= '$(inherited) '
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '"_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION" '
|
||||
def self.apply_xcode_15_patch(installer, xcodebuild_manager: Xcodebuild)
|
||||
projects = self.extract_projects(installer)
|
||||
|
||||
gcc_preprocessor_definition_key = 'GCC_PREPROCESSOR_DEFINITIONS'
|
||||
other_ld_flags_key = 'OTHER_LDFLAGS'
|
||||
libcpp_cxx17_fix = '_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION'
|
||||
xcode15_compatibility_flags = '-Wl -ld_classic '
|
||||
|
||||
projects.each do |project|
|
||||
project.build_configurations.each do |config|
|
||||
# fix for unary_function and binary_function
|
||||
self.safe_init(config, gcc_preprocessor_definition_key)
|
||||
self.add_value_to_setting_if_missing(config, gcc_preprocessor_definition_key, libcpp_cxx17_fix)
|
||||
|
||||
# fix for weak linking
|
||||
self.safe_init(config, other_ld_flags_key)
|
||||
if self.is_using_xcode15_or_greter(: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)
|
||||
@@ -274,6 +289,49 @@ class ReactNativePodsUtils
|
||||
.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.include?(value)
|
||||
config.build_settings[setting_name] << value
|
||||
end
|
||||
end
|
||||
|
||||
def self.remove_value_to_setting_if_present(config, setting_name, value)
|
||||
old_config = config.build_settings[setting_name]
|
||||
if old_config.include?(value)
|
||||
# Old config can be either an Array or a String
|
||||
if old_config.is_a?(Array)
|
||||
old_config = old_config.join(" ")
|
||||
end
|
||||
new_config = old_config.gsub(value, "")
|
||||
config.build_settings[setting_name] = new_config
|
||||
end
|
||||
end
|
||||
|
||||
def self.is_using_xcode15_or_greter(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
|
||||
return major >= 15
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
def self.add_compiler_flag_to_project(installer, flag, configuration: nil)
|
||||
projects = self.extract_projects(installer)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user