Skip to content

Commit 0dbd621

Browse files
cipolleschifacebook-github-bot
authored andcommitted
Fix Xcode 15 RC issues (#39474)
Summary: Pull Request resolved: #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. ## Changelog: [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
1 parent 21763e8 commit 0dbd621

File tree

4 files changed

+214
-25
lines changed

4 files changed

+214
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
6+
class XcodebuildMock < Xcodebuild
7+
@@version = ""
8+
@@version_invocation_count = 0
9+
10+
def self.set_version=(v)
11+
@@version = v
12+
end
13+
14+
def self.version
15+
@@version_invocation_count += 1
16+
@@version
17+
end
18+
19+
def self.version_invocation_count
20+
@@version_invocation_count
21+
end
22+
23+
def self.reset()
24+
@@version_invocation_count = 0
25+
end
26+
end

packages/react-native/scripts/cocoapods/__tests__/utils-test.rb

+114-17
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
require_relative "./test_utils/PathnameMock.rb"
1616
require_relative "./test_utils/TargetDefinitionMock.rb"
1717
require_relative "./test_utils/XcodeprojMock.rb"
18+
require_relative "./test_utils/XcodebuildMock.rb"
1819

1920
class UtilsTests < Test::Unit::TestCase
2021
def setup
@@ -30,6 +31,7 @@ def teardown
3031
SysctlChecker.reset()
3132
Environment.reset()
3233
Xcodeproj::Plist.reset()
34+
XcodebuildMock.reset()
3335
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
3436
ENV['USE_HERMES'] = '1'
3537
ENV['USE_FRAMEWORKS'] = nil
@@ -526,9 +528,56 @@ def test_applyMacCatalystPatches_correctlyAppliesNecessaryPatches
526528
# ================================= #
527529
# Test - Apply Xcode 15 Patch #
528530
# ================================= #
531+
def test_applyXcode15Patch_whenXcodebuild14_correctlyAppliesNecessaryPatch
532+
# Arrange
533+
XcodebuildMock.set_version = "Xcode 14.3"
534+
first_target = prepare_target("FirstTarget")
535+
second_target = prepare_target("SecondTarget")
536+
third_target = TargetMock.new("ThirdTarget", [
537+
BuildConfigurationMock.new("Debug", {
538+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
539+
}),
540+
BuildConfigurationMock.new("Release", {
541+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
542+
}),
543+
], nil)
529544

530-
def test_applyXcode15Patch_correctlyAppliesNecessaryPatch
545+
user_project_mock = UserProjectMock.new("/a/path", [
546+
prepare_config("Debug"),
547+
prepare_config("Release"),
548+
],
549+
:native_targets => [
550+
first_target,
551+
second_target
552+
]
553+
)
554+
pods_projects_mock = PodsProjectMock.new([], {"hermes-engine" => {}}, :native_targets => [
555+
third_target
556+
])
557+
installer = InstallerMock.new(pods_projects_mock, [
558+
AggregatedProjectMock.new(user_project_mock)
559+
])
560+
561+
# Act
562+
user_project_mock.build_configurations.each do |config|
563+
assert_nil(config.build_settings["OTHER_LDFLAGS"])
564+
end
565+
566+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
567+
568+
# Assert
569+
user_project_mock.build_configurations.each do |config|
570+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
571+
assert_equal("$(inherited) ", config.build_settings["OTHER_LDFLAGS"])
572+
end
573+
574+
# User project and Pods project
575+
assert_equal(2, XcodebuildMock.version_invocation_count)
576+
end
577+
578+
def test_applyXcode15Patch_whenXcodebuild15_correctlyAppliesNecessaryPatch
531579
# Arrange
580+
XcodebuildMock.set_version = "Xcode 15.0"
532581
first_target = prepare_target("FirstTarget")
533582
second_target = prepare_target("SecondTarget")
534583
third_target = TargetMock.new("ThirdTarget", [
@@ -557,24 +606,70 @@ def test_applyXcode15Patch_correctlyAppliesNecessaryPatch
557606
])
558607

559608
# Act
560-
ReactNativePodsUtils.apply_xcode_15_patch(installer)
609+
user_project_mock.build_configurations.each do |config|
610+
assert_nil(config.build_settings["OTHER_LDFLAGS"])
611+
end
612+
613+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
561614

562615
# Assert
563-
first_target.build_configurations.each do |config|
564-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
565-
'$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
566-
)
616+
user_project_mock.build_configurations.each do |config|
617+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
618+
assert_equal("$(inherited) -Wl -ld_classic ", config.build_settings["OTHER_LDFLAGS"])
567619
end
568-
second_target.build_configurations.each do |config|
569-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
570-
'$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
571-
)
620+
621+
# User project and Pods project
622+
assert_equal(2, XcodebuildMock.version_invocation_count)
623+
end
624+
625+
def test_applyXcode15Patch_whenXcodebuild14ButProjectHasSettings_correctlyRemovesNecessaryPatch
626+
# Arrange
627+
XcodebuildMock.set_version = "Xcode 14.3"
628+
first_target = prepare_target("FirstTarget")
629+
second_target = prepare_target("SecondTarget")
630+
third_target = TargetMock.new("ThirdTarget", [
631+
BuildConfigurationMock.new("Debug", {
632+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
633+
}),
634+
BuildConfigurationMock.new("Release", {
635+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
636+
}),
637+
], nil)
638+
639+
debug_config = prepare_config("Debug", {"OTHER_LDFLAGS" => "$(inherited) -Wl -ld_classic "})
640+
release_config = prepare_config("Release", {"OTHER_LDFLAGS" => "$(inherited) -Wl -ld_classic "})
641+
642+
user_project_mock = UserProjectMock.new("/a/path", [
643+
debug_config,
644+
release_config,
645+
],
646+
:native_targets => [
647+
first_target,
648+
second_target
649+
]
650+
)
651+
pods_projects_mock = PodsProjectMock.new([debug_config.clone, release_config.clone], {"hermes-engine" => {}}, :native_targets => [
652+
third_target
653+
])
654+
installer = InstallerMock.new(pods_projects_mock, [
655+
AggregatedProjectMock.new(user_project_mock)
656+
])
657+
658+
# Act
659+
user_project_mock.build_configurations.each do |config|
660+
assert_equal("$(inherited) -Wl -ld_classic ", config.build_settings["OTHER_LDFLAGS"])
572661
end
573-
third_target.build_configurations.each do |config|
574-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
575-
'$(inherited) "SomeFlag=1" "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
576-
)
662+
663+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
664+
665+
# Assert
666+
user_project_mock.build_configurations.each do |config|
667+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
668+
assert_equal("$(inherited) ", config.build_settings["OTHER_LDFLAGS"])
577669
end
670+
671+
# User project and Pods project
672+
assert_equal(2, XcodebuildMock.version_invocation_count)
578673
end
579674

580675
# ==================================== #
@@ -923,12 +1018,14 @@ def prepare_user_project_mock_with_plists
9231018
])
9241019
end
9251020

926-
def prepare_config(config_name)
927-
return BuildConfigurationMock.new(config_name, {"LIBRARY_SEARCH_PATHS" => [
1021+
def prepare_config(config_name, extra_config = {})
1022+
config = {"LIBRARY_SEARCH_PATHS" => [
9281023
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
9291024
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
9301025
"another/path",
931-
]})
1026+
]}.merge(extra_config)
1027+
1028+
return BuildConfigurationMock.new(config_name, config)
9321029
end
9331030

9341031
def prepare_target(name, product_type = nil, dependencies = [])

packages/react-native/scripts/cocoapods/helpers.rb

+8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ def call_sysctl_arm64
1111
end
1212
end
1313

14+
# Helper class that is used to easily send commands to Xcodebuild
15+
# And that can be subclassed for testing purposes.
16+
class Xcodebuild
17+
def self.version
18+
`xcodebuild -version`
19+
end
20+
end
21+
1422
# Helper object to wrap system properties like RUBY_PLATFORM
1523
# This makes it easier to mock the behaviour in tests
1624
class Environment

packages/react-native/scripts/cocoapods/utils.rb

+66-8
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,31 @@ def self.apply_mac_catalyst_patches(installer)
152152
end
153153
end
154154

155-
def self.apply_xcode_15_patch(installer)
156-
installer.target_installation_results.pod_target_installation_results
157-
.each do |pod_name, target_installation_result|
158-
target_installation_result.native_target.build_configurations.each do |config|
159-
# unary_function and binary_function are no longer provided in c++20 and newer standard modes as part of Xcode 15. They can be re-enabled with setting _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION
160-
# Ref: https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#Deprecations
161-
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= '$(inherited) '
162-
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '"_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION" '
155+
def self.apply_xcode_15_patch(installer, xcodebuild_manager: Xcodebuild)
156+
projects = self.extract_projects(installer)
157+
158+
gcc_preprocessor_definition_key = 'GCC_PREPROCESSOR_DEFINITIONS'
159+
other_ld_flags_key = 'OTHER_LDFLAGS'
160+
libcpp_cxx17_fix = '_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION'
161+
xcode15_compatibility_flags = '-Wl -ld_classic '
162+
163+
projects.each do |project|
164+
project.build_configurations.each do |config|
165+
# fix for unary_function and binary_function
166+
self.safe_init(config, gcc_preprocessor_definition_key)
167+
self.add_value_to_setting_if_missing(config, gcc_preprocessor_definition_key, libcpp_cxx17_fix)
168+
169+
# fix for weak linking
170+
self.safe_init(config, other_ld_flags_key)
171+
if self.is_using_xcode15_or_greter(:xcodebuild_manager => xcodebuild_manager)
172+
self.add_value_to_setting_if_missing(config, other_ld_flags_key, xcode15_compatibility_flags)
173+
else
174+
self.remove_value_to_setting_if_present(config, other_ld_flags_key, xcode15_compatibility_flags)
175+
end
163176
end
177+
project.save()
164178
end
179+
165180
end
166181

167182
def self.apply_flags_for_fabric(installer, fabric_enabled: false)
@@ -323,6 +338,49 @@ def self.extract_projects(installer)
323338
.push(installer.pods_project)
324339
end
325340

341+
def self.safe_init(config, setting_name)
342+
old_config = config.build_settings[setting_name]
343+
if old_config == nil
344+
config.build_settings[setting_name] ||= '$(inherited) '
345+
end
346+
end
347+
348+
def self.add_value_to_setting_if_missing(config, setting_name, value)
349+
old_config = config.build_settings[setting_name]
350+
if !old_config.include?(value)
351+
config.build_settings[setting_name] << value
352+
end
353+
end
354+
355+
def self.remove_value_to_setting_if_present(config, setting_name, value)
356+
old_config = config.build_settings[setting_name]
357+
if old_config.include?(value)
358+
# Old config can be either an Array or a String
359+
if old_config.is_a?(Array)
360+
old_config = old_config.join(" ")
361+
end
362+
new_config = old_config.gsub(value, "")
363+
config.build_settings[setting_name] = new_config
364+
end
365+
end
366+
367+
def self.is_using_xcode15_or_greter(xcodebuild_manager: Xcodebuild)
368+
xcodebuild_version = xcodebuild_manager.version
369+
370+
# The output of xcodebuild -version is something like
371+
# Xcode 15.0
372+
# or
373+
# Xcode 14.3.1
374+
# We want to capture the version digits
375+
regex = /(\d+)\.(\d+)(?:\.(\d+))?/
376+
if match_data = xcodebuild_version.match(regex)
377+
major = match_data[1].to_i
378+
return major >= 15
379+
end
380+
381+
return false
382+
end
383+
326384
def self.add_compiler_flag_to_project(installer, flag, configuration: nil)
327385
projects = self.extract_projects(installer)
328386

0 commit comments

Comments
 (0)