Skip to content

Commit e61a4cf

Browse files
coadofacebook-github-bot
authored andcommitted
Add CocoaPods opt-in for RN_STRICT_API
Summary: Let iOS apps that use CocoaPods enforce React Native's C++ stable API. Setting `RCT_STRICT_CXX_API=1` before `pod install` defines `RN_STRICT_API` in the app project and the Pods project, so the `react/cxxstableapi` guards apply to the app, third-party pods and app codegen. React Native's own pods are unaffected because they define `RN_BUILDING`. Under the flag, a direct include of a public module header is an error that points to the module's umbrella (`<React/<Module>.h>`), a private header is an error, and a "for frameworks" header is a warning that `RN_ALLOW_FRAMEWORKS` silences. The flag is off by default: libraries that still include React Native's internal headers would stop compiling. Turning it back off removes the define on the next `pod install`. This mirrors the existing `RCT_REMOVE_LEGACY_*` opt-ins and matches the scope of the Android opt-in. Changelog: [iOS][Added] - Add `RCT_STRICT_CXX_API` CocoaPods opt-in to enforce React Native's C++ stable API in app and library native code Differential Revision: D124979668
1 parent 96a89f9 commit e61a4cf

3 files changed

Lines changed: 106 additions & 0 deletions

File tree

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def teardown
3939
ENV['USE_FRAMEWORKS'] = nil
4040
ENV['RCT_REMOVE_LEGACY_MODULE_INTEROP'] = nil
4141
ENV['RCT_REMOVE_LEGACY_COMPONENT_INTEROP'] = nil
42+
ENV['RCT_STRICT_CXX_API'] = nil
4243
system_reset_commands
4344
$RN_PLATFORMS = nil
4445
end
@@ -613,6 +614,92 @@ def test_setLegacyInteropRemovalFlags_whenEnabledWithPrebuiltRNCore_warns
613614
assert(Pod::UI.collected_warns.first.include?("RCT_USE_PREBUILT_RNCORE=0"))
614615
end
615616

617+
# ==================================== #
618+
# Test - Set strict C++ API flag #
619+
# ==================================== #
620+
621+
def strict_cxx_api_installer_mock(user_project_mock, pods_project_mock)
622+
return InstallerMock.new(pods_project_mock, [AggregatedProjectMock.new(user_project_mock)])
623+
end
624+
625+
def strict_cxx_api_pods_project_mock
626+
return PodsProjectMock.new([], {"hermes-engine" => {}}, "test/path-pod.xcodeproj", [
627+
BuildConfigurationMock.new("Debug"),
628+
BuildConfigurationMock.new("Release"),
629+
])
630+
end
631+
632+
def assert_strict_cxx_api_flag(project_mock, enabled)
633+
project_mock.build_configurations.each do |config|
634+
["OTHER_CFLAGS", "OTHER_CPLUSPLUSFLAGS"].each do |key|
635+
assert_equal(enabled, config.build_settings[key].include?("-DRN_STRICT_API"))
636+
end
637+
end
638+
end
639+
640+
def test_setStrictCxxApiFlag_whenUnset_doesNotAddFlag
641+
# Arrange
642+
user_project_mock = prepare_empty_user_project_mock()
643+
pods_project_mock = strict_cxx_api_pods_project_mock()
644+
installer = strict_cxx_api_installer_mock(user_project_mock, pods_project_mock)
645+
646+
# Act
647+
ReactNativePodsUtils.set_strict_cxx_api_flag(installer)
648+
649+
# Assert
650+
assert_strict_cxx_api_flag(user_project_mock, false)
651+
assert_strict_cxx_api_flag(pods_project_mock, false)
652+
end
653+
654+
def test_setStrictCxxApiFlag_whenEnabled_addsFlagToUserAndPodsProjects
655+
# Arrange
656+
ENV['RCT_STRICT_CXX_API'] = '1'
657+
user_project_mock = prepare_empty_user_project_mock()
658+
pods_project_mock = strict_cxx_api_pods_project_mock()
659+
installer = strict_cxx_api_installer_mock(user_project_mock, pods_project_mock)
660+
661+
# Act
662+
ReactNativePodsUtils.set_strict_cxx_api_flag(installer)
663+
664+
# Assert
665+
assert_strict_cxx_api_flag(user_project_mock, true)
666+
assert_strict_cxx_api_flag(pods_project_mock, true)
667+
end
668+
669+
def test_setStrictCxxApiFlag_whenCalledTwice_addsFlagOnce
670+
# Arrange
671+
ENV['RCT_STRICT_CXX_API'] = '1'
672+
user_project_mock = prepare_empty_user_project_mock()
673+
pods_project_mock = strict_cxx_api_pods_project_mock()
674+
installer = strict_cxx_api_installer_mock(user_project_mock, pods_project_mock)
675+
676+
# Act
677+
ReactNativePodsUtils.set_strict_cxx_api_flag(installer)
678+
ReactNativePodsUtils.set_strict_cxx_api_flag(installer)
679+
680+
# Assert
681+
user_project_mock.build_configurations.each do |config|
682+
assert_equal(1, config.build_settings["OTHER_CPLUSPLUSFLAGS"].scan("-DRN_STRICT_API").length)
683+
end
684+
end
685+
686+
def test_setStrictCxxApiFlag_whenDisabled_removesPreviouslyAddedFlag
687+
# Arrange
688+
ENV['RCT_STRICT_CXX_API'] = '1'
689+
user_project_mock = prepare_empty_user_project_mock()
690+
pods_project_mock = strict_cxx_api_pods_project_mock()
691+
installer = strict_cxx_api_installer_mock(user_project_mock, pods_project_mock)
692+
ReactNativePodsUtils.set_strict_cxx_api_flag(installer)
693+
694+
# Act
695+
ENV['RCT_STRICT_CXX_API'] = '0'
696+
ReactNativePodsUtils.set_strict_cxx_api_flag(installer)
697+
698+
# Assert
699+
assert_strict_cxx_api_flag(user_project_mock, false)
700+
assert_strict_cxx_api_flag(pods_project_mock, false)
701+
end
702+
616703
# ==================================== #
617704
# Test - Set build setting #
618705
# ==================================== #

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class ReactNativePodsUtils
2424
'RCT_REMOVE_LEGACY_COMPONENT_INTEROP',
2525
]
2626

27+
# Opt-in enforcement of React Native's C++ stable API.
28+
STRICT_CXX_API_FLAG = 'RCT_STRICT_CXX_API'
29+
2730
# URI::File.build validates path components as ASCII, so escape the filesystem path first.
2831
def self.local_file_uri(path)
2932
URI::File.build(path: URI::DEFAULT_PARSER.escape(path)).to_s
@@ -598,6 +601,17 @@ def self.set_legacy_interop_removal_flags(installer, build_rncore_from_source:)
598601
end
599602
end
600603

604+
# RN_STRICT_API arms the react/cxxstableapi guards for every target that inherits the
605+
# project settings: the app, third-party pods and app codegen. React Native's own pods
606+
# define RN_BUILDING, which keeps the guards inert for them.
607+
def self.set_strict_cxx_api_flag(installer)
608+
if ENV[STRICT_CXX_API_FLAG] == '1'
609+
self.add_compiler_flag_to_project(installer, "-DRN_STRICT_API")
610+
else
611+
self.remove_compiler_flag_from_project(installer, "-DRN_STRICT_API")
612+
end
613+
end
614+
601615
def self.add_compiler_flag_to_pods(installer, flag, configuration: nil)
602616
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
603617
target_installation_result.native_target.build_configurations.each do |config|

‎packages/react-native/scripts/react_native_pods.rb‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ def use_react_native! (
122122
# layers are still supported. Both will default to 1 in a future React Native release.
123123
ENV['RCT_REMOVE_LEGACY_MODULE_INTEROP'] = ENV['RCT_REMOVE_LEGACY_MODULE_INTEROP'] == '1' ? '1' : '0'
124124
ENV['RCT_REMOVE_LEGACY_COMPONENT_INTEROP'] = ENV['RCT_REMOVE_LEGACY_COMPONENT_INTEROP'] == '1' ? '1' : '0'
125+
# Enforcing the C++ stable API is opt-in: third-party libraries that still include
126+
# React Native's internal headers would stop compiling.
127+
ENV['RCT_STRICT_CXX_API'] = ENV['RCT_STRICT_CXX_API'] == '1' ? '1' : '0'
125128

126129
ReactNativePodsUtils.check_minimum_required_xcode()
127130

@@ -631,6 +634,8 @@ def react_native_post_install(
631634
build_rncore_from_source: ReactNativeCoreUtils.build_rncore_from_source()
632635
)
633636

637+
ReactNativePodsUtils.set_strict_cxx_api_flag(installer)
638+
634639
ReactNativePodsUtils.set_ccache_compiler_and_linker_build_settings(installer, react_native_path, ccache_enabled)
635640
ReactNativePodsUtils.updateOSDeploymentTarget(installer)
636641
ReactNativePodsUtils.set_dynamic_frameworks_flags(installer)

0 commit comments

Comments
 (0)