Skip to content

Commit 9974cca

Browse files
cipolleschiTitozzz
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. [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 6ca4dff commit 9974cca

File tree

4 files changed

+222
-26
lines changed

4 files changed

+222
-26
lines changed
Lines changed: 26 additions & 0 deletions
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

Lines changed: 122 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
require_relative "./test_utils/systemUtils.rb"
1515
require_relative "./test_utils/PathnameMock.rb"
1616
require_relative "./test_utils/TargetDefinitionMock.rb"
17+
require_relative "./test_utils/XcodebuildMock.rb"
1718

1819
class UtilsTests < Test::Unit::TestCase
1920
def setup
@@ -28,6 +29,7 @@ def teardown
2829
Pod::Config.reset()
2930
SysctlChecker.reset()
3031
Environment.reset()
32+
XcodebuildMock.reset()
3133
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
3234
ENV['USE_HERMES'] = '1'
3335
ENV['USE_FRAMEWORKS'] = nil
@@ -437,9 +439,9 @@ def test_applyMacCatalystPatches_correctlyAppliesNecessaryPatches
437439
# ================================= #
438440
# Test - Apply Xcode 15 Patch #
439441
# ================================= #
440-
441-
def test_applyXcode15Patch_correctlyAppliesNecessaryPatch
442+
def test_applyXcode15Patch_whenXcodebuild14_correctlyAppliesNecessaryPatch
442443
# Arrange
444+
XcodebuildMock.set_version = "Xcode 14.3"
443445
first_target = prepare_target("FirstTarget")
444446
second_target = prepare_target("SecondTarget")
445447
third_target = TargetMock.new("ThirdTarget", [
@@ -468,24 +470,117 @@ def test_applyXcode15Patch_correctlyAppliesNecessaryPatch
468470
])
469471

470472
# Act
471-
ReactNativePodsUtils.apply_xcode_15_patch(installer)
473+
user_project_mock.build_configurations.each do |config|
474+
assert_nil(config.build_settings["OTHER_LDFLAGS"])
475+
end
476+
477+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
472478

473479
# Assert
474-
first_target.build_configurations.each do |config|
475-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
476-
'$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
477-
)
480+
user_project_mock.build_configurations.each do |config|
481+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
482+
assert_equal("$(inherited) ", config.build_settings["OTHER_LDFLAGS"])
478483
end
479-
second_target.build_configurations.each do |config|
480-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
481-
'$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
482-
)
484+
485+
# User project and Pods project
486+
assert_equal(2, XcodebuildMock.version_invocation_count)
487+
end
488+
489+
def test_applyXcode15Patch_whenXcodebuild15_correctlyAppliesNecessaryPatch
490+
# Arrange
491+
XcodebuildMock.set_version = "Xcode 15.0"
492+
first_target = prepare_target("FirstTarget")
493+
second_target = prepare_target("SecondTarget")
494+
third_target = TargetMock.new("ThirdTarget", [
495+
BuildConfigurationMock.new("Debug", {
496+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
497+
}),
498+
BuildConfigurationMock.new("Release", {
499+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
500+
}),
501+
], nil)
502+
503+
user_project_mock = UserProjectMock.new("/a/path", [
504+
prepare_config("Debug"),
505+
prepare_config("Release"),
506+
],
507+
:native_targets => [
508+
first_target,
509+
second_target
510+
]
511+
)
512+
pods_projects_mock = PodsProjectMock.new([], {"hermes-engine" => {}}, :native_targets => [
513+
third_target
514+
])
515+
installer = InstallerMock.new(pods_projects_mock, [
516+
AggregatedProjectMock.new(user_project_mock)
517+
])
518+
519+
# Act
520+
user_project_mock.build_configurations.each do |config|
521+
assert_nil(config.build_settings["OTHER_LDFLAGS"])
483522
end
484-
third_target.build_configurations.each do |config|
485-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
486-
'$(inherited) "SomeFlag=1" "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
487-
)
523+
524+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
525+
526+
# Assert
527+
user_project_mock.build_configurations.each do |config|
528+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
529+
assert_equal("$(inherited) -Wl -ld_classic ", config.build_settings["OTHER_LDFLAGS"])
530+
end
531+
532+
# User project and Pods project
533+
assert_equal(2, XcodebuildMock.version_invocation_count)
534+
end
535+
536+
def test_applyXcode15Patch_whenXcodebuild14ButProjectHasSettings_correctlyRemovesNecessaryPatch
537+
# Arrange
538+
XcodebuildMock.set_version = "Xcode 14.3"
539+
first_target = prepare_target("FirstTarget")
540+
second_target = prepare_target("SecondTarget")
541+
third_target = TargetMock.new("ThirdTarget", [
542+
BuildConfigurationMock.new("Debug", {
543+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
544+
}),
545+
BuildConfigurationMock.new("Release", {
546+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
547+
}),
548+
], nil)
549+
550+
debug_config = prepare_config("Debug", {"OTHER_LDFLAGS" => "$(inherited) -Wl -ld_classic "})
551+
release_config = prepare_config("Release", {"OTHER_LDFLAGS" => "$(inherited) -Wl -ld_classic "})
552+
553+
user_project_mock = UserProjectMock.new("/a/path", [
554+
debug_config,
555+
release_config,
556+
],
557+
:native_targets => [
558+
first_target,
559+
second_target
560+
]
561+
)
562+
pods_projects_mock = PodsProjectMock.new([debug_config.clone, release_config.clone], {"hermes-engine" => {}}, :native_targets => [
563+
third_target
564+
])
565+
installer = InstallerMock.new(pods_projects_mock, [
566+
AggregatedProjectMock.new(user_project_mock)
567+
])
568+
569+
# Act
570+
user_project_mock.build_configurations.each do |config|
571+
assert_equal("$(inherited) -Wl -ld_classic ", config.build_settings["OTHER_LDFLAGS"])
488572
end
573+
574+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
575+
576+
# Assert
577+
user_project_mock.build_configurations.each do |config|
578+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
579+
assert_equal("$(inherited) ", config.build_settings["OTHER_LDFLAGS"])
580+
end
581+
582+
# User project and Pods project
583+
assert_equal(2, XcodebuildMock.version_invocation_count)
489584
end
490585

491586
# ==================================== #
@@ -744,12 +839,21 @@ def prepare_empty_user_project_mock
744839
])
745840
end
746841

747-
def prepare_config(config_name)
748-
return BuildConfigurationMock.new(config_name, {"LIBRARY_SEARCH_PATHS" => [
842+
def prepare_user_project_mock_with_plists
843+
return UserProjectMock.new(:files => [
844+
PBXFileRefMock.new("Info.plist"),
845+
PBXFileRefMock.new("Extension-Info.plist"),
846+
])
847+
end
848+
849+
def prepare_config(config_name, extra_config = {})
850+
config = {"LIBRARY_SEARCH_PATHS" => [
749851
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
750852
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
751853
"another/path",
752-
]})
854+
]}.merge(extra_config)
855+
856+
return BuildConfigurationMock.new(config_name, config)
753857
end
754858

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

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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,31 @@ def self.apply_mac_catalyst_patches(installer)
121121
end
122122
end
123123

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" '
124+
def self.apply_xcode_15_patch(installer, xcodebuild_manager: Xcodebuild)
125+
projects = self.extract_projects(installer)
126+
127+
gcc_preprocessor_definition_key = 'GCC_PREPROCESSOR_DEFINITIONS'
128+
other_ld_flags_key = 'OTHER_LDFLAGS'
129+
libcpp_cxx17_fix = '_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION'
130+
xcode15_compatibility_flags = '-Wl -ld_classic '
131+
132+
projects.each do |project|
133+
project.build_configurations.each do |config|
134+
# fix for unary_function and binary_function
135+
self.safe_init(config, gcc_preprocessor_definition_key)
136+
self.add_value_to_setting_if_missing(config, gcc_preprocessor_definition_key, libcpp_cxx17_fix)
137+
138+
# fix for weak linking
139+
self.safe_init(config, other_ld_flags_key)
140+
if self.is_using_xcode15_or_greter(:xcodebuild_manager => xcodebuild_manager)
141+
self.add_value_to_setting_if_missing(config, other_ld_flags_key, xcode15_compatibility_flags)
142+
else
143+
self.remove_value_to_setting_if_present(config, other_ld_flags_key, xcode15_compatibility_flags)
144+
end
132145
end
146+
project.save()
133147
end
148+
134149
end
135150

136151
def self.apply_flags_for_fabric(installer, fabric_enabled: false)
@@ -274,6 +289,49 @@ def self.extract_projects(installer)
274289
.push(installer.pods_project)
275290
end
276291

292+
def self.safe_init(config, setting_name)
293+
old_config = config.build_settings[setting_name]
294+
if old_config == nil
295+
config.build_settings[setting_name] ||= '$(inherited) '
296+
end
297+
end
298+
299+
def self.add_value_to_setting_if_missing(config, setting_name, value)
300+
old_config = config.build_settings[setting_name]
301+
if !old_config.include?(value)
302+
config.build_settings[setting_name] << value
303+
end
304+
end
305+
306+
def self.remove_value_to_setting_if_present(config, setting_name, value)
307+
old_config = config.build_settings[setting_name]
308+
if old_config.include?(value)
309+
# Old config can be either an Array or a String
310+
if old_config.is_a?(Array)
311+
old_config = old_config.join(" ")
312+
end
313+
new_config = old_config.gsub(value, "")
314+
config.build_settings[setting_name] = new_config
315+
end
316+
end
317+
318+
def self.is_using_xcode15_or_greter(xcodebuild_manager: Xcodebuild)
319+
xcodebuild_version = xcodebuild_manager.version
320+
321+
# The output of xcodebuild -version is something like
322+
# Xcode 15.0
323+
# or
324+
# Xcode 14.3.1
325+
# We want to capture the version digits
326+
regex = /(\d+)\.(\d+)(?:\.(\d+))?/
327+
if match_data = xcodebuild_version.match(regex)
328+
major = match_data[1].to_i
329+
return major >= 15
330+
end
331+
332+
return false
333+
end
334+
277335
def self.add_compiler_flag_to_project(installer, flag, configuration: nil)
278336
projects = self.extract_projects(installer)
279337

0 commit comments

Comments
 (0)