Skip to content

Commit bd84060

Browse files
coadometa-codesync[bot]
authored andcommitted
Add CocoaPods opt-ins for RN_STRICT_API and RN_ALLOW_FRAMEWORKS (#59037)
Summary: Pull Request resolved: #59037 Let iOS apps that use CocoaPods enforce React Native's C++ stable API, and control its "for frameworks" warning, with two env vars read at `pod install`: - `RN_STRICT_API=1` defines the `RN_STRICT_API` macro, which arms the `react/cxxstableapi` guards. A direct include of a public module header is an error pointing to the module's umbrella (`<React/<Module>.h>`), a private header is an error, and a "for frameworks" header is a warning. - `RN_ALLOW_FRAMEWORKS=1` defines the `RN_ALLOW_FRAMEWORKS` macro, which silences that warning. Each variable is named after the macro it defines, matching the Android opt-in, which passes `RN_STRICT_API` to CMake. Both macros are added to the app project and the Pods project, so they reach the app, third-party pods and app codegen. React Native's own pods are unaffected because they define `RN_BUILDING`. Both are off by default; running `pod install` without a variable removes its define again. This mirrors the existing `RCT_REMOVE_LEGACY_*` opt-ins and the Android opt-in. Who silences the "for frameworks" warning for which code: - A library that uses for-frameworks headers declares `RN_ALLOW_FRAMEWORKS` for its own pod (`pod_target_xcconfig`). That covers its own build only and never reaches the app or other pods. - The app author sets `RN_ALLOW_FRAMEWORKS=1` before `pod install` for the app's own code. It also silences libraries that use those headers without declaring it, which is on the library. - The generated `ReactCodegen` and `ReactAppDependencyProvider` pods always define `RN_ALLOW_FRAMEWORKS`: nobody can act on a warning in generated code, and libraries are already held to the API in their own pods. Changelog: [iOS][Added] - Add `RN_STRICT_API` and `RN_ALLOW_FRAMEWORKS` CocoaPods opt-ins to enforce React Native's C++ stable API in app and library native code Reviewed By: cipolleschi Differential Revision: D124979668
1 parent 51d3e67 commit bd84060

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.
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
@@ -600,6 +608,19 @@ def self.set_legacy_interop_removal_flags(installer, build_rncore_from_source:)
600608
end
601609
end
602610

611+
# The macros reach every target that inherits the project settings: the app,
612+
# third-party pods and app codegen. React Native's own pods define RN_BUILDING,
613+
# which keeps the guards inert for them.
614+
def self.set_strict_cxx_api_flags(installer)
615+
STRICT_CXX_API_FLAGS.each do |macro|
616+
if ENV[macro] == '1'
617+
self.add_compiler_flag_to_project(installer, "-D#{macro}")
618+
else
619+
self.remove_compiler_flag_from_project(installer, "-D#{macro}")
620+
end
621+
end
622+
end
623+
603624
def self.add_compiler_flag_to_pods(installer, flag, configuration: nil)
604625
installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result|
605626
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\\"
@@ -472,7 +474,10 @@ Pod::Spec.new do |s|
472474
s.pod_target_xcconfig = {
473475
\\"HEADER_SEARCH_PATHS\\" => header_search_paths.join(' '),
474476
\\"FRAMEWORK_SEARCH_PATHS\\" => framework_search_paths,
475-
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\"
477+
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\",
478+
# Generated code: nobody can act on a C++ stable API \\"for frameworks\\" warning
479+
# here. Libraries are held to the API in their own pods.
480+
\\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\"
476481
}
477482
478483
s.dependency \\"React-jsiexecutor\\"
@@ -629,7 +634,9 @@ Pod::Spec.new do |s|
629634
# This guard prevent to install the dependencies when we run \`pod install\` in the old architecture.
630635
s.pod_target_xcconfig = {
631636
\\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(),
632-
\\"DEFINES_MODULE\\" => \\"YES\\"
637+
\\"DEFINES_MODULE\\" => \\"YES\\",
638+
# Generated code: see ReactCodegen.podspec.
639+
\\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\"
633640
}
634641
635642
s.dependency \\"ReactCodegen\\"
@@ -970,7 +977,10 @@ Pod::Spec.new do |s|
970977
s.pod_target_xcconfig = {
971978
\\"HEADER_SEARCH_PATHS\\" => header_search_paths.join(' '),
972979
\\"FRAMEWORK_SEARCH_PATHS\\" => framework_search_paths,
973-
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\"
980+
\\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\",
981+
# Generated code: nobody can act on a C++ stable API \\"for frameworks\\" warning
982+
# here. Libraries are held to the API in their own pods.
983+
\\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\"
974984
}
975985
976986
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)