Skip to content

Commit 8202e06

Browse files
coadometa-codesync[bot]
authored andcommitted
Add CocoaPods opt-in for RN_STRICT_API (#59037)
Summary: Pull Request resolved: #59037 Let iOS apps that use CocoaPods enforce React Native's C++ stable API. Setting `RN_STRICT_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. 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 da7c29f commit 8202e06

6 files changed

Lines changed: 123 additions & 6 deletions

File tree

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

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ 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['RN_STRICT_API'] = nil
43+
ENV['RN_ALLOW_FRAMEWORKS'] = nil
4244
system_reset_commands
4345
$RN_PLATFORMS = nil
4446
end
@@ -613,6 +615,79 @@ def test_setLegacyInteropRemovalFlags_whenEnabledWithPrebuiltRNCore_warns
613615
assert(Pod::UI.collected_warns.first.include?("RCT_USE_PREBUILT_RNCORE=0"))
614616
end
615617

618+
# ==================================== #
619+
# Test - Set strict C++ API flags #
620+
# ==================================== #
621+
622+
def strict_cxx_api_installer_mock(user_project_mock, pods_project_mock)
623+
return InstallerMock.new(pods_project_mock, [AggregatedProjectMock.new(user_project_mock)])
624+
end
625+
626+
def strict_cxx_api_pods_project_mock
627+
return PodsProjectMock.new([], {"hermes-engine" => {}}, "test/path-pod.xcodeproj", [
628+
BuildConfigurationMock.new("Debug"),
629+
BuildConfigurationMock.new("Release"),
630+
])
631+
end
632+
633+
def assert_strict_cxx_api_flags(project_mock, strict:, allow_frameworks:)
634+
project_mock.build_configurations.each do |config|
635+
["OTHER_CFLAGS", "OTHER_CPLUSPLUSFLAGS"].each do |key|
636+
setting = config.build_settings[key]
637+
assert_equal(strict, setting.include?("-DRN_STRICT_API"))
638+
assert_equal(allow_frameworks, setting.include?("-DRN_ALLOW_FRAMEWORKS"))
639+
end
640+
end
641+
end
642+
643+
def run_set_strict_cxx_api_flags
644+
user_project_mock = prepare_empty_user_project_mock()
645+
pods_project_mock = strict_cxx_api_pods_project_mock()
646+
ReactNativePodsUtils.set_strict_cxx_api_flags(
647+
strict_cxx_api_installer_mock(user_project_mock, pods_project_mock)
648+
)
649+
return [user_project_mock, pods_project_mock]
650+
end
651+
652+
def test_setStrictCxxApiFlags_whenUnset_addsNothing
653+
run_set_strict_cxx_api_flags().each do |project_mock|
654+
assert_strict_cxx_api_flags(project_mock, strict: false, allow_frameworks: false)
655+
end
656+
end
657+
658+
def test_setStrictCxxApiFlags_whenStrictEnabled_addsRnStrictApiToUserAndPodsProjects
659+
ENV['RN_STRICT_API'] = '1'
660+
run_set_strict_cxx_api_flags().each do |project_mock|
661+
assert_strict_cxx_api_flags(project_mock, strict: true, allow_frameworks: false)
662+
end
663+
end
664+
665+
def test_setStrictCxxApiFlags_whenAllowFrameworksEnabled_addsRnAllowFrameworksToUserAndPodsProjects
666+
ENV['RN_ALLOW_FRAMEWORKS'] = '1'
667+
run_set_strict_cxx_api_flags().each do |project_mock|
668+
assert_strict_cxx_api_flags(project_mock, strict: false, allow_frameworks: true)
669+
end
670+
end
671+
672+
def test_setStrictCxxApiFlags_whenDisabled_removesPreviouslyAddedFlags
673+
# Arrange
674+
ENV['RN_STRICT_API'] = '1'
675+
ENV['RN_ALLOW_FRAMEWORKS'] = '1'
676+
user_project_mock = prepare_empty_user_project_mock()
677+
pods_project_mock = strict_cxx_api_pods_project_mock()
678+
installer = strict_cxx_api_installer_mock(user_project_mock, pods_project_mock)
679+
ReactNativePodsUtils.set_strict_cxx_api_flags(installer)
680+
681+
# Act
682+
ENV['RN_STRICT_API'] = '0'
683+
ENV['RN_ALLOW_FRAMEWORKS'] = '0'
684+
ReactNativePodsUtils.set_strict_cxx_api_flags(installer)
685+
686+
# Assert
687+
assert_strict_cxx_api_flags(user_project_mock, strict: false, allow_frameworks: false)
688+
assert_strict_cxx_api_flags(pods_project_mock, strict: false, allow_frameworks: false)
689+
end
690+
616691
# ==================================== #
617692
# Test - Set build setting #
618693
# ==================================== #

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

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

27+
# Opt-in C++ stable API macros, each enabled by the env var of the same name.
28+
# RN_STRICT_API arms the react/cxxstableapi guards; RN_ALLOW_FRAMEWORKS
29+
# silences their "for frameworks" warning.
30+
STRICT_CXX_API_FLAGS = [
31+
'RN_STRICT_API',
32+
'RN_ALLOW_FRAMEWORKS',
33+
]
34+
2735
# URI::File.build validates path components as ASCII, so escape the filesystem path first.
2836
def self.local_file_uri(path)
2937
URI::File.build(path: URI::DEFAULT_PARSER.escape(path)).to_s
@@ -598,6 +606,19 @@ def self.set_legacy_interop_removal_flags(installer, build_rncore_from_source:)
598606
end
599607
end
600608

609+
# The macros reach every target that inherits the project settings: the app,
610+
# third-party pods and app codegen. React Native's own pods define RN_BUILDING,
611+
# which keeps the guards inert for them.
612+
def self.set_strict_cxx_api_flags(installer)
613+
STRICT_CXX_API_FLAGS.each do |macro|
614+
if ENV[macro] == '1'
615+
self.add_compiler_flag_to_project(installer, "-D#{macro}")
616+
else
617+
self.remove_compiler_flag_from_project(installer, "-D#{macro}")
618+
end
619+
end
620+
end
621+
601622
def self.add_compiler_flag_to_pods(installer, flag, configuration: nil)
602623
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
603624
target_installation_result.native_target.build_configurations.each do |config|

‎packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ Pod::Spec.new do |s|
103103
# This guard prevent to install the dependencies when we run \`pod install\` in the old architecture.
104104
s.pod_target_xcconfig = {
105105
\\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(),
106-
\\"DEFINES_MODULE\\" => \\"YES\\"
106+
\\"DEFINES_MODULE\\" => \\"YES\\",
107+
# Generated code: see ReactCodegen.podspec.
108+
\\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\"
107109
}
108110
109111
s.dependency \\"ReactCodegen\\"
@@ -458,7 +460,10 @@ Pod::Spec.new do |s|
458460
s.pod_target_xcconfig = {
459461
\\"HEADER_SEARCH_PATHS\\" => header_search_paths.join(' '),
460462
\\"FRAMEWORK_SEARCH_PATHS\\" => framework_search_paths,
461-
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\"
463+
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\",
464+
# Generated code: nobody can act on a C++ stable API \\"for frameworks\\" warning
465+
# here. Libraries are held to the API in their own pods.
466+
\\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\"
462467
}
463468
464469
s.dependency \\"React-jsiexecutor\\"
@@ -615,7 +620,9 @@ Pod::Spec.new do |s|
615620
# This guard prevent to install the dependencies when we run \`pod install\` in the old architecture.
616621
s.pod_target_xcconfig = {
617622
\\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(),
618-
\\"DEFINES_MODULE\\" => \\"YES\\"
623+
\\"DEFINES_MODULE\\" => \\"YES\\",
624+
# Generated code: see ReactCodegen.podspec.
625+
\\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\"
619626
}
620627
621628
s.dependency \\"ReactCodegen\\"
@@ -942,7 +949,10 @@ Pod::Spec.new do |s|
942949
s.pod_target_xcconfig = {
943950
\\"HEADER_SEARCH_PATHS\\" => header_search_paths.join(' '),
944951
\\"FRAMEWORK_SEARCH_PATHS\\" => framework_search_paths,
945-
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\"
952+
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\",
953+
# Generated code: nobody can act on a C++ stable API \\"for frameworks\\" warning
954+
# here. Libraries are held to the API in their own pods.
955+
\\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\"
946956
}
947957
948958
s.dependency \\"React-jsiexecutor\\"

‎packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ Pod::Spec.new do |s|
2727
# This guard prevent to install the dependencies when we run `pod install` in the old architecture.
2828
s.pod_target_xcconfig = {
2929
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
30-
"DEFINES_MODULE" => "YES"
30+
"DEFINES_MODULE" => "YES",
31+
# Generated code: see ReactCodegen.podspec.
32+
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) RN_ALLOW_FRAMEWORKS=1"
3133
}
3234

3335
s.dependency "ReactCodegen"

‎packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ Pod::Spec.new do |s|
6262
s.pod_target_xcconfig = {
6363
"HEADER_SEARCH_PATHS" => header_search_paths.join(' '),
6464
"FRAMEWORK_SEARCH_PATHS" => framework_search_paths,
65-
"OTHER_CPLUSPLUSFLAGS" => "$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}"
65+
"OTHER_CPLUSPLUSFLAGS" => "$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}",
66+
# Generated code: nobody can act on a C++ stable API "for frameworks" warning
67+
# here. Libraries are held to the API in their own pods.
68+
"GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) RN_ALLOW_FRAMEWORKS=1"
6669
}
6770

6871
s.dependency "React-jsiexecutor"

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ 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.
126+
ENV['RN_STRICT_API'] = ENV['RN_STRICT_API'] == '1' ? '1' : '0'
127+
# Silences the stable API's "for frameworks" warning.
128+
ENV['RN_ALLOW_FRAMEWORKS'] = ENV['RN_ALLOW_FRAMEWORKS'] == '1' ? '1' : '0'
125129

126130
ReactNativePodsUtils.check_minimum_required_xcode()
127131

@@ -631,6 +635,8 @@ def react_native_post_install(
631635
build_rncore_from_source: ReactNativeCoreUtils.build_rncore_from_source()
632636
)
633637

638+
ReactNativePodsUtils.set_strict_cxx_api_flags(installer)
639+
634640
ReactNativePodsUtils.set_ccache_compiler_and_linker_build_settings(installer, react_native_path, ccache_enabled)
635641
ReactNativePodsUtils.updateOSDeploymentTarget(installer)
636642
ReactNativePodsUtils.set_dynamic_frameworks_flags(installer)

0 commit comments

Comments
 (0)