diff --git a/packages/react-native/scripts/cocoapods/__tests__/utils-test.rb b/packages/react-native/scripts/cocoapods/__tests__/utils-test.rb index 8432512de07b..1fb8eb477c87 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/utils-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/utils-test.rb @@ -39,6 +39,8 @@ def teardown ENV['USE_FRAMEWORKS'] = nil ENV['RCT_REMOVE_LEGACY_MODULE_INTEROP'] = nil ENV['RCT_REMOVE_LEGACY_COMPONENT_INTEROP'] = nil + ENV['RN_STRICT_API'] = nil + ENV['RN_ALLOW_FRAMEWORKS'] = nil system_reset_commands $RN_PLATFORMS = nil end @@ -613,6 +615,79 @@ def test_setLegacyInteropRemovalFlags_whenEnabledWithPrebuiltRNCore_warns assert(Pod::UI.collected_warns.first.include?("RCT_USE_PREBUILT_RNCORE=0")) end + # ==================================== # + # Test - Set strict C++ API flags # + # ==================================== # + + def strict_cxx_api_installer_mock(user_project_mock, pods_project_mock) + return InstallerMock.new(pods_project_mock, [AggregatedProjectMock.new(user_project_mock)]) + end + + def strict_cxx_api_pods_project_mock + return PodsProjectMock.new([], {"hermes-engine" => {}}, "test/path-pod.xcodeproj", [ + BuildConfigurationMock.new("Debug"), + BuildConfigurationMock.new("Release"), + ]) + end + + def assert_strict_cxx_api_flags(project_mock, strict:, allow_frameworks:) + project_mock.build_configurations.each do |config| + ["OTHER_CFLAGS", "OTHER_CPLUSPLUSFLAGS"].each do |key| + setting = config.build_settings[key] + assert_equal(strict, setting.include?("-DRN_STRICT_API")) + assert_equal(allow_frameworks, setting.include?("-DRN_ALLOW_FRAMEWORKS")) + end + end + end + + def run_set_strict_cxx_api_flags + user_project_mock = prepare_empty_user_project_mock() + pods_project_mock = strict_cxx_api_pods_project_mock() + ReactNativePodsUtils.set_strict_cxx_api_flags( + strict_cxx_api_installer_mock(user_project_mock, pods_project_mock) + ) + return [user_project_mock, pods_project_mock] + end + + def test_setStrictCxxApiFlags_whenUnset_addsNothing + run_set_strict_cxx_api_flags().each do |project_mock| + assert_strict_cxx_api_flags(project_mock, strict: false, allow_frameworks: false) + end + end + + def test_setStrictCxxApiFlags_whenStrictEnabled_addsRnStrictApiToUserAndPodsProjects + ENV['RN_STRICT_API'] = '1' + run_set_strict_cxx_api_flags().each do |project_mock| + assert_strict_cxx_api_flags(project_mock, strict: true, allow_frameworks: false) + end + end + + def test_setStrictCxxApiFlags_whenAllowFrameworksEnabled_addsRnAllowFrameworksToUserAndPodsProjects + ENV['RN_ALLOW_FRAMEWORKS'] = '1' + run_set_strict_cxx_api_flags().each do |project_mock| + assert_strict_cxx_api_flags(project_mock, strict: false, allow_frameworks: true) + end + end + + def test_setStrictCxxApiFlags_whenDisabled_removesPreviouslyAddedFlags + # Arrange + ENV['RN_STRICT_API'] = '1' + ENV['RN_ALLOW_FRAMEWORKS'] = '1' + user_project_mock = prepare_empty_user_project_mock() + pods_project_mock = strict_cxx_api_pods_project_mock() + installer = strict_cxx_api_installer_mock(user_project_mock, pods_project_mock) + ReactNativePodsUtils.set_strict_cxx_api_flags(installer) + + # Act + ENV['RN_STRICT_API'] = '0' + ENV['RN_ALLOW_FRAMEWORKS'] = '0' + ReactNativePodsUtils.set_strict_cxx_api_flags(installer) + + # Assert + assert_strict_cxx_api_flags(user_project_mock, strict: false, allow_frameworks: false) + assert_strict_cxx_api_flags(pods_project_mock, strict: false, allow_frameworks: false) + end + # ==================================== # # Test - Set build setting # # ==================================== # diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb index 119ce41c24b0..e89f5a22a121 100644 --- a/packages/react-native/scripts/cocoapods/utils.rb +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -24,6 +24,14 @@ class ReactNativePodsUtils 'RCT_REMOVE_LEGACY_COMPONENT_INTEROP', ] + # Opt-in C++ stable API macros. + # RN_STRICT_API arms the react/cxxstableapi guards; RN_ALLOW_FRAMEWORKS + # silences their "for frameworks" warning. + STRICT_CXX_API_FLAGS = [ + 'RN_STRICT_API', + 'RN_ALLOW_FRAMEWORKS', + ] + # URI::File.build validates path components as ASCII, so escape the filesystem path first. def self.local_file_uri(path) 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:) end end + # The macros reach every target that inherits the project settings: the app, + # third-party pods and app codegen. React Native's own pods define RN_BUILDING, + # which keeps the guards inert for them. + def self.set_strict_cxx_api_flags(installer) + STRICT_CXX_API_FLAGS.each do |macro| + if ENV[macro] == '1' + self.add_compiler_flag_to_project(installer, "-D#{macro}") + else + self.remove_compiler_flag_from_project(installer, "-D#{macro}") + end + end + end + def self.add_compiler_flag_to_pods(installer, flag, configuration: nil) installer.target_installation_results.pod_target_installation_results.each do |pod_name, target_installation_result| target_installation_result.native_target.build_configurations.each do |config| diff --git a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap index 6bc79e6875e4..351e2cc715d0 100644 --- a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap +++ b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap @@ -103,7 +103,9 @@ Pod::Spec.new do |s| # This guard prevent to install the dependencies when we run \`pod install\` in the old architecture. s.pod_target_xcconfig = { \\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(), - \\"DEFINES_MODULE\\" => \\"YES\\" + \\"DEFINES_MODULE\\" => \\"YES\\", + # Generated code: see ReactCodegen.podspec. + \\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\" } s.dependency \\"ReactCodegen\\" @@ -458,7 +460,10 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { \\"HEADER_SEARCH_PATHS\\" => header_search_paths.join(' '), \\"FRAMEWORK_SEARCH_PATHS\\" => framework_search_paths, - \\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\" + \\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\", + # Generated code: nobody can act on a C++ stable API \\"for frameworks\\" warning + # here. Libraries are held to the API in their own pods. + \\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\" } s.dependency \\"React-jsiexecutor\\" @@ -615,7 +620,9 @@ Pod::Spec.new do |s| # This guard prevent to install the dependencies when we run \`pod install\` in the old architecture. s.pod_target_xcconfig = { \\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(), - \\"DEFINES_MODULE\\" => \\"YES\\" + \\"DEFINES_MODULE\\" => \\"YES\\", + # Generated code: see ReactCodegen.podspec. + \\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\" } s.dependency \\"ReactCodegen\\" @@ -942,7 +949,10 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { \\"HEADER_SEARCH_PATHS\\" => header_search_paths.join(' '), \\"FRAMEWORK_SEARCH_PATHS\\" => framework_search_paths, - \\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\" + \\"OTHER_CPLUSPLUSFLAGS\\" => \\"$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}\\", + # Generated code: nobody can act on a C++ stable API \\"for frameworks\\" warning + # here. Libraries are held to the API in their own pods. + \\"GCC_PREPROCESSOR_DEFINITIONS\\" => \\"$(inherited) RN_ALLOW_FRAMEWORKS=1\\" } s.dependency \\"React-jsiexecutor\\" diff --git a/packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template b/packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template index 327bc9c102e3..077e57734cd3 100644 --- a/packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template +++ b/packages/react-native/scripts/codegen/templates/ReactAppDependencyProvider.podspec.template @@ -27,7 +27,9 @@ Pod::Spec.new do |s| # This guard prevent to install the dependencies when we run `pod install` in the old architecture. s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), - "DEFINES_MODULE" => "YES" + "DEFINES_MODULE" => "YES", + # Generated code: see ReactCodegen.podspec. + "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) RN_ALLOW_FRAMEWORKS=1" } s.dependency "ReactCodegen" diff --git a/packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template b/packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template index ff57a460ffd1..5a6ab6ba86d9 100644 --- a/packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template +++ b/packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template @@ -62,7 +62,10 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), "FRAMEWORK_SEARCH_PATHS" => framework_search_paths, - "OTHER_CPLUSPLUSFLAGS" => "$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}" + "OTHER_CPLUSPLUSFLAGS" => "$(inherited) #{folly_compiler_flags} #{boost_compiler_flags}", + # Generated code: nobody can act on a C++ stable API "for frameworks" warning + # here. Libraries are held to the API in their own pods. + "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) RN_ALLOW_FRAMEWORKS=1" } s.dependency "React-jsiexecutor" diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 90d14ae976a3..317652ca69ed 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -122,6 +122,10 @@ def use_react_native! ( # layers are still supported. Both will default to 1 in a future React Native release. ENV['RCT_REMOVE_LEGACY_MODULE_INTEROP'] = ENV['RCT_REMOVE_LEGACY_MODULE_INTEROP'] == '1' ? '1' : '0' ENV['RCT_REMOVE_LEGACY_COMPONENT_INTEROP'] = ENV['RCT_REMOVE_LEGACY_COMPONENT_INTEROP'] == '1' ? '1' : '0' + # Enforcing the C++ stable API is opt-in. + ENV['RN_STRICT_API'] = ENV['RN_STRICT_API'] == '1' ? '1' : '0' + # Silences the stable API's "for frameworks" warning. + ENV['RN_ALLOW_FRAMEWORKS'] = ENV['RN_ALLOW_FRAMEWORKS'] == '1' ? '1' : '0' ReactNativePodsUtils.check_minimum_required_xcode() @@ -631,6 +635,8 @@ def react_native_post_install( build_rncore_from_source: ReactNativeCoreUtils.build_rncore_from_source() ) + ReactNativePodsUtils.set_strict_cxx_api_flags(installer) + ReactNativePodsUtils.set_ccache_compiler_and_linker_build_settings(installer, react_native_path, ccache_enabled) ReactNativePodsUtils.updateOSDeploymentTarget(installer) ReactNativePodsUtils.set_dynamic_frameworks_flags(installer)