Skip to content

Commit 740c401

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 b8b9b60 commit 740c401

File tree

4 files changed

+229
-18
lines changed

4 files changed

+229
-18
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

scripts/cocoapods/__tests__/utils-test.rb

Lines changed: 115 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
require_relative "./test_utils/InstallerMock.rb"
1111
require_relative "./test_utils/EnvironmentMock.rb"
1212
require_relative "./test_utils/SysctlCheckerMock.rb"
13+
require_relative "./test_utils/XcodebuildMock.rb"
1314

1415
class UtilsTests < Test::Unit::TestCase
1516
def teardown
1617
Pod::UI.reset()
1718
SysctlChecker.reset()
1819
Environment.reset()
20+
XcodebuildMock.reset()
1921
ENV['RCT_NEW_ARCH_ENABLED'] = '0'
2022
ENV['USE_HERMES'] = '1'
2123
end
@@ -477,9 +479,9 @@ def test_applyMacCatalystPatches_correctlyAppliesNecessaryPatches
477479
# ================================= #
478480
# Test - Apply Xcode 15 Patch #
479481
# ================================= #
480-
481-
def test_applyXcode15Patch_correctlyAppliesNecessaryPatch
482+
def test_applyXcode15Patch_whenXcodebuild14_correctlyAppliesNecessaryPatch
482483
# Arrange
484+
XcodebuildMock.set_version = "Xcode 14.3"
483485
first_target = prepare_target("FirstTarget")
484486
second_target = prepare_target("SecondTarget")
485487
third_target = TargetMock.new("ThirdTarget", [
@@ -508,24 +510,117 @@ def test_applyXcode15Patch_correctlyAppliesNecessaryPatch
508510
])
509511

510512
# Act
511-
ReactNativePodsUtils.apply_xcode_15_patch(installer)
513+
user_project_mock.build_configurations.each do |config|
514+
assert_nil(config.build_settings["OTHER_LDFLAGS"])
515+
end
516+
517+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
512518

513519
# Assert
514-
first_target.build_configurations.each do |config|
515-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
516-
'$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
517-
)
520+
user_project_mock.build_configurations.each do |config|
521+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
522+
assert_equal("$(inherited) ", config.build_settings["OTHER_LDFLAGS"])
518523
end
519-
second_target.build_configurations.each do |config|
520-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
521-
'$(inherited) "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
522-
)
524+
525+
# User project and Pods project
526+
assert_equal(2, XcodebuildMock.version_invocation_count)
527+
end
528+
529+
def test_applyXcode15Patch_whenXcodebuild15_correctlyAppliesNecessaryPatch
530+
# Arrange
531+
XcodebuildMock.set_version = "Xcode 15.0"
532+
first_target = prepare_target("FirstTarget")
533+
second_target = prepare_target("SecondTarget")
534+
third_target = TargetMock.new("ThirdTarget", [
535+
BuildConfigurationMock.new("Debug", {
536+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
537+
}),
538+
BuildConfigurationMock.new("Release", {
539+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
540+
}),
541+
], nil)
542+
543+
user_project_mock = UserProjectMock.new("/a/path", [
544+
prepare_config("Debug"),
545+
prepare_config("Release"),
546+
],
547+
:native_targets => [
548+
first_target,
549+
second_target
550+
]
551+
)
552+
pods_projects_mock = PodsProjectMock.new([], {"hermes-engine" => {}}, :native_targets => [
553+
third_target
554+
])
555+
installer = InstallerMock.new(pods_projects_mock, [
556+
AggregatedProjectMock.new(user_project_mock)
557+
])
558+
559+
# Act
560+
user_project_mock.build_configurations.each do |config|
561+
assert_nil(config.build_settings["OTHER_LDFLAGS"])
523562
end
524-
third_target.build_configurations.each do |config|
525-
assert_equal(config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"].strip,
526-
'$(inherited) "SomeFlag=1" "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"'
527-
)
563+
564+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
565+
566+
# Assert
567+
user_project_mock.build_configurations.each do |config|
568+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
569+
assert_equal("$(inherited) -Wl -ld_classic ", config.build_settings["OTHER_LDFLAGS"])
570+
end
571+
572+
# User project and Pods project
573+
assert_equal(2, XcodebuildMock.version_invocation_count)
574+
end
575+
576+
def test_applyXcode15Patch_whenXcodebuild14ButProjectHasSettings_correctlyRemovesNecessaryPatch
577+
# Arrange
578+
XcodebuildMock.set_version = "Xcode 14.3"
579+
first_target = prepare_target("FirstTarget")
580+
second_target = prepare_target("SecondTarget")
581+
third_target = TargetMock.new("ThirdTarget", [
582+
BuildConfigurationMock.new("Debug", {
583+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
584+
}),
585+
BuildConfigurationMock.new("Release", {
586+
"GCC_PREPROCESSOR_DEFINITIONS" => '$(inherited) "SomeFlag=1" '
587+
}),
588+
], nil)
589+
590+
debug_config = prepare_config("Debug", {"OTHER_LDFLAGS" => "$(inherited) -Wl -ld_classic "})
591+
release_config = prepare_config("Release", {"OTHER_LDFLAGS" => "$(inherited) -Wl -ld_classic "})
592+
593+
user_project_mock = UserProjectMock.new("/a/path", [
594+
debug_config,
595+
release_config,
596+
],
597+
:native_targets => [
598+
first_target,
599+
second_target
600+
]
601+
)
602+
pods_projects_mock = PodsProjectMock.new([debug_config.clone, release_config.clone], {"hermes-engine" => {}}, :native_targets => [
603+
third_target
604+
])
605+
installer = InstallerMock.new(pods_projects_mock, [
606+
AggregatedProjectMock.new(user_project_mock)
607+
])
608+
609+
# Act
610+
user_project_mock.build_configurations.each do |config|
611+
assert_equal("$(inherited) -Wl -ld_classic ", config.build_settings["OTHER_LDFLAGS"])
612+
end
613+
614+
ReactNativePodsUtils.apply_xcode_15_patch(installer, :xcodebuild_manager => XcodebuildMock)
615+
616+
# Assert
617+
user_project_mock.build_configurations.each do |config|
618+
assert_equal("$(inherited) _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION", config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"])
619+
assert_equal("$(inherited) ", config.build_settings["OTHER_LDFLAGS"])
528620
end
621+
622+
# User project and Pods project
623+
assert_equal(2, XcodebuildMock.version_invocation_count)
529624
end
530625

531626
# ==================================== #
@@ -562,12 +657,14 @@ def prepare_empty_user_project_mock
562657
])
563658
end
564659

565-
def prepare_config(config_name)
566-
return BuildConfigurationMock.new(config_name, {"LIBRARY_SEARCH_PATHS" => [
660+
def prepare_config(config_name, extra_config = {})
661+
config = {"LIBRARY_SEARCH_PATHS" => [
567662
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
568663
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
569664
"another/path",
570-
]})
665+
]}.merge(extra_config)
666+
667+
return BuildConfigurationMock.new(config_name, config)
571668
end
572669

573670
def prepare_target(name, product_type = nil)

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

scripts/cocoapods/utils.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,32 @@ def self.apply_mac_catalyst_patches(installer)
155155
end
156156
end
157157

158+
def self.apply_xcode_15_patch(installer, xcodebuild_manager: Xcodebuild)
159+
projects = self.extract_projects(installer)
160+
161+
gcc_preprocessor_definition_key = 'GCC_PREPROCESSOR_DEFINITIONS'
162+
other_ld_flags_key = 'OTHER_LDFLAGS'
163+
libcpp_cxx17_fix = '_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION'
164+
xcode15_compatibility_flags = '-Wl -ld_classic '
165+
166+
projects.each do |project|
167+
project.build_configurations.each do |config|
168+
# fix for unary_function and binary_function
169+
self.safe_init(config, gcc_preprocessor_definition_key)
170+
self.add_value_to_setting_if_missing(config, gcc_preprocessor_definition_key, libcpp_cxx17_fix)
171+
172+
# fix for weak linking
173+
self.safe_init(config, other_ld_flags_key)
174+
if self.is_using_xcode15_or_greter(:xcodebuild_manager => xcodebuild_manager)
175+
self.add_value_to_setting_if_missing(config, other_ld_flags_key, xcode15_compatibility_flags)
176+
else
177+
self.remove_value_to_setting_if_present(config, other_ld_flags_key, xcode15_compatibility_flags)
178+
end
179+
end
180+
project.save()
181+
end
182+
end
183+
158184
private
159185

160186
def self.fix_library_search_path(config)
@@ -208,4 +234,58 @@ def self.updateIphoneOSDeploymentTarget(installer)
208234
end
209235
end
210236
end
237+
238+
# ========= #
239+
# Utilities #
240+
# ========= #
241+
242+
def self.extract_projects(installer)
243+
return installer.aggregate_targets
244+
.map{ |t| t.user_project }
245+
.uniq{ |p| p.path }
246+
.push(installer.pods_project)
247+
end
248+
249+
def self.safe_init(config, setting_name)
250+
old_config = config.build_settings[setting_name]
251+
if old_config == nil
252+
config.build_settings[setting_name] ||= '$(inherited) '
253+
end
254+
end
255+
256+
def self.add_value_to_setting_if_missing(config, setting_name, value)
257+
old_config = config.build_settings[setting_name]
258+
if !old_config.include?(value)
259+
config.build_settings[setting_name] << value
260+
end
261+
end
262+
263+
def self.remove_value_to_setting_if_present(config, setting_name, value)
264+
old_config = config.build_settings[setting_name]
265+
if old_config.include?(value)
266+
# Old config can be either an Array or a String
267+
if old_config.is_a?(Array)
268+
old_config = old_config.join(" ")
269+
end
270+
new_config = old_config.gsub(value, "")
271+
config.build_settings[setting_name] = new_config
272+
end
273+
end
274+
275+
def self.is_using_xcode15_or_greter(xcodebuild_manager: Xcodebuild)
276+
xcodebuild_version = xcodebuild_manager.version
277+
278+
# The output of xcodebuild -version is something like
279+
# Xcode 15.0
280+
# or
281+
# Xcode 14.3.1
282+
# We want to capture the version digits
283+
regex = /(\d+)\.(\d+)(?:\.(\d+))?/
284+
if match_data = xcodebuild_version.match(regex)
285+
major = match_data[1].to_i
286+
return major >= 15
287+
end
288+
289+
return false
290+
end
211291
end

0 commit comments

Comments
 (0)