Skip to content

Commit

Permalink
Implement new feature flag system (re-land) (#42678)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #42678

Changelog: [internal]

This is a re-application of #42430, which had to be reverted because of crashes in optimized builds on Android:

```
Abort Reason: terminating due to uncaught exception of type facebook::jni::JniException: java.lang.ClassNotFoundException: com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsProvider
```

The root cause of that was that that class was removed because it wasn't statically referenced from Kotlin/Java, but it was dynamically referenced from C++ (in `ReactNativeFeatureFlagsProviderHolder.cpp`).

This applies the same changes + adds `DoNotStrip` annotations for the affected class and all its methods.

Reviewed By: huntie

Differential Revision: D53122992

fbshipit-source-id: efc4d5636a3f2d39b86e9c098bff408b6688b80b
  • Loading branch information
rubennorte authored and facebook-github-bot committed Jan 26, 2024
1 parent 6c4ef54 commit 5e66f41
Show file tree
Hide file tree
Showing 73 changed files with 3,140 additions and 4 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"flow": "flow",
"format-check": "prettier --list-different \"./**/*.{js,md,yml,ts,tsx}\"",
"format": "npm run prettier && npm run clang-format",
"featureflags-check": "cd packages/react-native && yarn featureflags-check",
"featureflags-update": "cd packages/react-native && yarn featureflags-update",
"lint-ci": "./scripts/circleci/analyze_code.sh && yarn shellcheck",
"lint-java": "node ./scripts/lint-java.js",
"lint": "eslint .",
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/React-Core.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ Pod::Spec.new do |s|
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
s.dependency "React-utils"
s.dependency "React-featureflags"
s.dependency "SocketRocket", socket_rocket_version
s.dependency "React-runtimescheduler"
s.dependency "Yoga"
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/ReactAndroid/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ android {
"runtimeexecutor",
"react_codegen_rncore",
"react_debug",
"react_featureflags",
"react_utils",
"react_render_componentregistry",
"react_newarchdefaults",
Expand All @@ -540,6 +541,7 @@ android {
"jsi",
"glog",
"fabricjni",
"featureflagsjni",
"react_render_mapbuffer",
"yoga",
"folly_runtime",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c201b2b577ab4aa4bf6071d6b99b43c9>>
*/

/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags-update
*/

package com.facebook.react.internal.featureflags

/**
* This object provides access to internal React Native feature flags.
*
* All the methods are thread-safe if you handle `override` correctly.
*/
object ReactNativeFeatureFlags {
private var accessorProvider: () -> ReactNativeFeatureFlagsAccessor = { ReactNativeFeatureFlagsCxxAccessor() }
private var accessor: ReactNativeFeatureFlagsAccessor = accessorProvider()

/**
* Common flag for testing. Do NOT modify.
*/
fun commonTestFlag() = accessor.commonTestFlag()

/**
* Overrides the feature flags with the ones provided by the given provider
* (generally one that extends `ReactNativeFeatureFlagsDefaults`).
*
* This method must be called before you initialize the React Native runtime.
*
* @example
*
* ```
* ReactNativeFeatureFlags.override(object : ReactNativeFeatureFlagsDefaults() {
* override fun someFlag(): Boolean = true // or a dynamic value
* })
* ```
*/
fun override(provider: ReactNativeFeatureFlagsProvider) = accessor.override(provider)

/**
* Removes the overridden feature flags and makes the API return default
* values again.
*
* This should only be called if you destroy the React Native runtime and
* need to create a new one with different overrides. In that case,
* call `dangerouslyReset` after destroying the runtime and `override`
* again before initializing the new one.
*/
fun dangerouslyReset() {
// This is necessary when the accessor interops with C++ and we need to
// remove the overrides set there.
accessor.dangerouslyReset()

// This discards the cached values and the overrides set in the JVM.
accessor = accessorProvider()
}

/**
* This is just used to replace the default ReactNativeFeatureFlagsCxxAccessor
* that uses JNI with a version that doesn't, to simplify testing.
*/
internal fun setAccessorProvider(newAccessorProvider: () -> ReactNativeFeatureFlagsAccessor) {
accessorProvider = newAccessorProvider
accessor = accessorProvider()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.internal.featureflags

interface ReactNativeFeatureFlagsAccessor : ReactNativeFeatureFlagsProvider {
fun override(provider: ReactNativeFeatureFlagsProvider)

fun dangerouslyReset()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<961f1fb0a7ad802a492437f15b1f2dcb>>
*/

/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags-update
*/

package com.facebook.react.internal.featureflags

class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccessor {
private var commonTestFlagCache: Boolean? = null

override fun commonTestFlag(): Boolean {
var cached = commonTestFlagCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.commonTestFlag()
commonTestFlagCache = cached
}
return cached
}

override fun override(provider: ReactNativeFeatureFlagsProvider) =
ReactNativeFeatureFlagsCxxInterop.override(provider as Any)

override fun dangerouslyReset() = ReactNativeFeatureFlagsCxxInterop.dangerouslyReset()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c2e41ac8c3d9471b4cb79f6147cc2bf2>>
*/

/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags-update
*/

package com.facebook.react.internal.featureflags

import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.soloader.SoLoader

@DoNotStrip
object ReactNativeFeatureFlagsCxxInterop {
init {
SoLoader.loadLibrary("reactfeatureflagsjni")
}

@DoNotStrip @JvmStatic external fun commonTestFlag(): Boolean

@DoNotStrip @JvmStatic external fun override(provider: Any)

@DoNotStrip @JvmStatic external fun dangerouslyReset()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<aa1eaeee7b715e5b1d3cbcf9b7a7062e>>
*/

/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags-update
*/

package com.facebook.react.internal.featureflags

open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvider {
// We could use JNI to get the defaults from C++,
// but that is more expensive than just duplicating the defaults here.

override fun commonTestFlag(): Boolean = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.internal.featureflags

object ReactNativeFeatureFlagsForTests {
fun setUp() {
ReactNativeFeatureFlags.setAccessorProvider({ ReactNativeFeatureFlagsLocalAccessor() })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8bbd7e8cc2c50cfbf44ba6671d095f23>>
*/

/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags-update
*/

package com.facebook.react.internal.featureflags

class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAccessor {
private var currentProvider: ReactNativeFeatureFlagsProvider = ReactNativeFeatureFlagsDefaults()

private val accessedFeatureFlags = mutableSetOf<String>()

private var commonTestFlagCache: Boolean? = null

override fun commonTestFlag(): Boolean {
var cached = commonTestFlagCache
if (cached == null) {
cached = currentProvider.commonTestFlag()
accessedFeatureFlags.add("commonTestFlag")
commonTestFlagCache = cached
}
return cached
}

override fun override(provider: ReactNativeFeatureFlagsProvider) {
if (accessedFeatureFlags.isNotEmpty()) {
val accessedFeatureFlagsStr = accessedFeatureFlags.joinToString(separator = ", ") { it }
throw IllegalStateException(
"Feature flags were accessed before being overridden: $accessedFeatureFlagsStr")
}
currentProvider = provider
}

override fun dangerouslyReset() {
// We don't need to do anything here because `ReactNativeFeatureFlags` will
// just create a new instance of this class.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8fae1537ff23fbd7dacd81cc2521cd3c>>
*/

/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags-update
*/

package com.facebook.react.internal.featureflags

import com.facebook.proguard.annotations.DoNotStrip

@DoNotStrip
interface ReactNativeFeatureFlagsProvider {
@DoNotStrip fun commonTestFlag(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ add_react_common_subdir(hermes/inspector-modern)
add_react_common_subdir(react/renderer/runtimescheduler)
add_react_common_subdir(react/debug)
add_react_common_subdir(react/config)
add_react_common_subdir(react/featureflags)
add_react_common_subdir(react/renderer/animations)
add_react_common_subdir(react/renderer/attributedstring)
add_react_common_subdir(react/renderer/componentregistry)
Expand Down Expand Up @@ -118,6 +119,7 @@ add_react_android_subdir(src/main/jni/react/uimanager)
add_react_android_subdir(src/main/jni/react/mapbuffer)
add_react_android_subdir(src/main/jni/react/reactnativeblob)
add_react_android_subdir(src/main/jni/react/fabric)
add_react_android_subdir(src/main/jni/react/featureflags)
add_react_android_subdir(src/main/jni/react/newarchdefaults)
add_react_android_subdir(src/main/jni/react/hermes/reactexecutor)
add_react_android_subdir(src/main/jni/react/hermes/instrumentation/)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ target_link_libraries(
mapbufferjni
react_codegen_rncore
react_debug
react_featureflags
react_render_animations
react_render_attributedstring
react_render_componentregistry
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

cmake_minimum_required(VERSION 3.13)

file(GLOB featureflagsjni_SRCS CONFIGURE_DEPENDS *.cpp)

add_library(
featureflagsjni
SHARED
${featureflagsjni_SRCS}
)

target_include_directories(featureflagsjni PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

target_link_libraries(
featureflagsjni
fb
fbjni
react_featureflags
reactnativejni
)

target_compile_options(
featureflagsjni
PRIVATE
-DLOG_TAG=\"ReactNative\"
-fexceptions
-frtti
-std=c++20
-Wall
)
Loading

0 comments on commit 5e66f41

Please sign in to comment.