diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index f114120386ea..5924836d2245 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -29,7 +29,9 @@ import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings import com.facebook.react.utils.JdkConfiguratorUtils.configureJavaToolChains import com.facebook.react.utils.JsonUtils import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk +import com.facebook.react.utils.NdkConfiguratorUtils.configureStrictCxxApiForLibrary import com.facebook.react.utils.NdkConfiguratorUtils.configureStubPchGeneration +import com.facebook.react.utils.ProjectUtils.isStrictCxxApiEnabled import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson import com.facebook.react.utils.PropertyUtils import com.facebook.react.utils.findPackageJsonFile @@ -136,6 +138,9 @@ class ReactPlugin : Plugin { subproject.pluginManager.withPlugin("com.android.library") { if (!subproject.state.executed) { configureNamespaceForLibraries(subproject) + if (project.isStrictCxxApiEnabled) { + configureStrictCxxApiForLibrary(subproject) + } } } } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt index dbbb5497fea4..f873fe373d7e 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt @@ -8,10 +8,12 @@ package com.facebook.react.utils import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.android.build.api.variant.Variant import com.facebook.react.ReactExtension import com.facebook.react.internal.StubPchBuildService import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures +import com.facebook.react.utils.ProjectUtils.isStrictCxxApiEnabled import java.io.File import org.gradle.api.Project @@ -55,6 +57,9 @@ internal object NdkConfiguratorUtils { if (cmakeArgs.none { it.startsWith("-DANDROID_STL") }) { cmakeArgs.add("-DANDROID_STL=c++_shared") } + if (project.isStrictCxxApiEnabled && cmakeArgs.none { it.startsWith("-DRN_STRICT_API") }) { + cmakeArgs.add("-DRN_STRICT_API=ON") + } val architectures = project.getReactNativeArchitectures() // abiFilters are split ABI are not compatible each other, so we set the abiFilters @@ -66,6 +71,14 @@ internal object NdkConfiguratorUtils { } } + /** Defines RN_STRICT_API for the library's own native code. */ + fun configureStrictCxxApiForLibrary(project: Project) { + project.extensions.getByType(LibraryAndroidComponentsExtension::class.java).onVariants { variant + -> + variant.externalNativeBuild?.cppFlags?.add("-DRN_STRICT_API") + } + } + /** * The codegen targets share a precompiled header, which only a real build produces. Android * Studio's C++ engine needs one at sync time, so during a sync we write stubs for the variant diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt index 2a4d6cf7115d..855e34df90ab 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/ProjectUtils.kt @@ -15,7 +15,9 @@ import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES import com.facebook.react.utils.PropertyUtils.SCOPED_EDGE_TO_EDGE_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES +import com.facebook.react.utils.PropertyUtils.SCOPED_STRICT_CXX_API_ENABLED import com.facebook.react.utils.PropertyUtils.SCOPED_USE_THIRD_PARTY_JSC +import com.facebook.react.utils.PropertyUtils.STRICT_CXX_API_ENABLED import com.facebook.react.utils.PropertyUtils.USE_THIRD_PARTY_JSC import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty @@ -56,6 +58,13 @@ internal object ProjectUtils { (project.hasProperty(SCOPED_EDGE_TO_EDGE_ENABLED) && project.property(SCOPED_EDGE_TO_EDGE_ENABLED).toString().toBoolean()) + internal val Project.isStrictCxxApiEnabled: Boolean + get() = + (project.hasProperty(STRICT_CXX_API_ENABLED) && + project.property(STRICT_CXX_API_ENABLED).toString().toBoolean()) || + (project.hasProperty(SCOPED_STRICT_CXX_API_ENABLED) && + project.property(SCOPED_STRICT_CXX_API_ENABLED).toString().toBoolean()) + internal val Project.useThirdPartyJSC: Boolean get() = (project.hasProperty(USE_THIRD_PARTY_JSC) && diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt index a0c57e759734..32dd486eaf77 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PropertyUtils.kt @@ -29,6 +29,13 @@ object PropertyUtils { const val EDGE_TO_EDGE_ENABLED = "edgeToEdgeEnabled" const val SCOPED_EDGE_TO_EDGE_ENABLED = "react.edgeToEdgeEnabled" + /** + * Public property that enforces the C++ stable API for the native code of the app and its + * libraries by defining RN_STRICT_API + */ + const val STRICT_CXX_API_ENABLED = "strictCxxApiEnabled" + const val SCOPED_STRICT_CXX_API_ENABLED = "react.strictCxxApiEnabled" + /** Public property that excludes jsctooling from core */ const val USE_THIRD_PARTY_JSC = "useThirdPartyJSC" const val SCOPED_USE_THIRD_PARTY_JSC = "react.useThirdPartyJSC" diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt index 491dd9b9545d..64fb15c42494 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/ProjectUtilsTest.kt @@ -15,6 +15,7 @@ import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures import com.facebook.react.utils.ProjectUtils.isEdgeToEdgeEnabled import com.facebook.react.utils.ProjectUtils.isHermesEnabled import com.facebook.react.utils.ProjectUtils.isNewArchEnabled +import com.facebook.react.utils.ProjectUtils.isStrictCxxApiEnabled import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson import java.io.File import org.assertj.core.api.Assertions.assertThat @@ -115,6 +116,39 @@ class ProjectUtilsTest { assertThat(project.isEdgeToEdgeEnabled).isFalse() } + @Test + fun isStrictCxxApiEnabled_returnsFalseByDefault() { + assertThat(createProject().isStrictCxxApiEnabled).isFalse() + } + + @Test + fun isStrictCxxApiEnabled_withEnabledViaProperty_returnsTrue() { + val project = createProject() + project.extensions.extraProperties.set("strictCxxApiEnabled", "true") + assertThat(project.isStrictCxxApiEnabled).isTrue() + } + + @Test + fun isStrictCxxApiEnabled_withEnabledViaScopedProperty_returnsTrue() { + val project = createProject() + project.extensions.extraProperties.set("react.strictCxxApiEnabled", "true") + assertThat(project.isStrictCxxApiEnabled).isTrue() + } + + @Test + fun isStrictCxxApiEnabled_withDisabledViaProperty_returnsFalse() { + val project = createProject() + project.extensions.extraProperties.set("strictCxxApiEnabled", "false") + assertThat(project.isStrictCxxApiEnabled).isFalse() + } + + @Test + fun isStrictCxxApiEnabled_withInvalidViaProperty_returnsFalse() { + val project = createProject() + project.extensions.extraProperties.set("strictCxxApiEnabled", "¯\\_(ツ)_/¯") + assertThat(project.isStrictCxxApiEnabled).isFalse() + } + @Test fun needsCodegenFromPackageJson_withCodegenConfigInPackageJson_returnsTrue() { val project = createProject() diff --git a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake index 7201024de326..7791dacd6a96 100644 --- a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +++ b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake @@ -89,6 +89,11 @@ target_link_libraries(${CMAKE_PROJECT_NAME} add_library(common_flags INTERFACE) target_compile_options(common_flags INTERFACE ${folly_FLAGS}) +if(RN_STRICT_API) + target_compile_definitions(common_flags INTERFACE RN_STRICT_API) + target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE RN_STRICT_API) +endif() + # Defines the `reactnative_pch` target and `target_reuse_reactnative_pch()`, so # the codegen targets below share a single precompiled header. Has to come after # `common_flags`, as the precompiled header is built with the same flags as its diff --git a/packages/react-native/ReactAndroid/cmake-utils/precompiled-header/pch.h b/packages/react-native/ReactAndroid/cmake-utils/precompiled-header/pch.h index e5ea7310a482..88e26e7285aa 100644 --- a/packages/react-native/ReactAndroid/cmake-utils/precompiled-header/pch.h +++ b/packages/react-native/ReactAndroid/cmake-utils/precompiled-header/pch.h @@ -7,15 +7,10 @@ #pragma once -#include -#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include