Skip to content

Commit 5e66f41

Browse files
rubennortefacebook-github-bot
authored andcommitted
Implement new feature flag system (re-land) (#42678)
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
1 parent 6c4ef54 commit 5e66f41

File tree

73 files changed

+3140
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+3140
-4
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"flow": "flow",
2222
"format-check": "prettier --list-different \"./**/*.{js,md,yml,ts,tsx}\"",
2323
"format": "npm run prettier && npm run clang-format",
24+
"featureflags-check": "cd packages/react-native && yarn featureflags-check",
25+
"featureflags-update": "cd packages/react-native && yarn featureflags-update",
2426
"lint-ci": "./scripts/circleci/analyze_code.sh && yarn shellcheck",
2527
"lint-java": "node ./scripts/lint-java.js",
2628
"lint": "eslint .",

packages/react-native/React-Core.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ Pod::Spec.new do |s|
129129
s.dependency "React-jsi"
130130
s.dependency "React-jsiexecutor"
131131
s.dependency "React-utils"
132+
s.dependency "React-featureflags"
132133
s.dependency "SocketRocket", socket_rocket_version
133134
s.dependency "React-runtimescheduler"
134135
s.dependency "Yoga"

packages/react-native/ReactAndroid/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ android {
526526
"runtimeexecutor",
527527
"react_codegen_rncore",
528528
"react_debug",
529+
"react_featureflags",
529530
"react_utils",
530531
"react_render_componentregistry",
531532
"react_newarchdefaults",
@@ -540,6 +541,7 @@ android {
540541
"jsi",
541542
"glog",
542543
"fabricjni",
544+
"featureflagsjni",
543545
"react_render_mapbuffer",
544546
"yoga",
545547
"folly_runtime",
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @generated SignedSource<<c201b2b577ab4aa4bf6071d6b99b43c9>>
8+
*/
9+
10+
/**
11+
* IMPORTANT: Do NOT modify this file directly.
12+
*
13+
* To change the definition of the flags, edit
14+
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
15+
*
16+
* To regenerate this code, run the following script from the repo root:
17+
* yarn featureflags-update
18+
*/
19+
20+
package com.facebook.react.internal.featureflags
21+
22+
/**
23+
* This object provides access to internal React Native feature flags.
24+
*
25+
* All the methods are thread-safe if you handle `override` correctly.
26+
*/
27+
object ReactNativeFeatureFlags {
28+
private var accessorProvider: () -> ReactNativeFeatureFlagsAccessor = { ReactNativeFeatureFlagsCxxAccessor() }
29+
private var accessor: ReactNativeFeatureFlagsAccessor = accessorProvider()
30+
31+
/**
32+
* Common flag for testing. Do NOT modify.
33+
*/
34+
fun commonTestFlag() = accessor.commonTestFlag()
35+
36+
/**
37+
* Overrides the feature flags with the ones provided by the given provider
38+
* (generally one that extends `ReactNativeFeatureFlagsDefaults`).
39+
*
40+
* This method must be called before you initialize the React Native runtime.
41+
*
42+
* @example
43+
*
44+
* ```
45+
* ReactNativeFeatureFlags.override(object : ReactNativeFeatureFlagsDefaults() {
46+
* override fun someFlag(): Boolean = true // or a dynamic value
47+
* })
48+
* ```
49+
*/
50+
fun override(provider: ReactNativeFeatureFlagsProvider) = accessor.override(provider)
51+
52+
/**
53+
* Removes the overridden feature flags and makes the API return default
54+
* values again.
55+
*
56+
* This should only be called if you destroy the React Native runtime and
57+
* need to create a new one with different overrides. In that case,
58+
* call `dangerouslyReset` after destroying the runtime and `override`
59+
* again before initializing the new one.
60+
*/
61+
fun dangerouslyReset() {
62+
// This is necessary when the accessor interops with C++ and we need to
63+
// remove the overrides set there.
64+
accessor.dangerouslyReset()
65+
66+
// This discards the cached values and the overrides set in the JVM.
67+
accessor = accessorProvider()
68+
}
69+
70+
/**
71+
* This is just used to replace the default ReactNativeFeatureFlagsCxxAccessor
72+
* that uses JNI with a version that doesn't, to simplify testing.
73+
*/
74+
internal fun setAccessorProvider(newAccessorProvider: () -> ReactNativeFeatureFlagsAccessor) {
75+
accessorProvider = newAccessorProvider
76+
accessor = accessorProvider()
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.internal.featureflags
9+
10+
interface ReactNativeFeatureFlagsAccessor : ReactNativeFeatureFlagsProvider {
11+
fun override(provider: ReactNativeFeatureFlagsProvider)
12+
13+
fun dangerouslyReset()
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @generated SignedSource<<961f1fb0a7ad802a492437f15b1f2dcb>>
8+
*/
9+
10+
/**
11+
* IMPORTANT: Do NOT modify this file directly.
12+
*
13+
* To change the definition of the flags, edit
14+
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
15+
*
16+
* To regenerate this code, run the following script from the repo root:
17+
* yarn featureflags-update
18+
*/
19+
20+
package com.facebook.react.internal.featureflags
21+
22+
class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccessor {
23+
private var commonTestFlagCache: Boolean? = null
24+
25+
override fun commonTestFlag(): Boolean {
26+
var cached = commonTestFlagCache
27+
if (cached == null) {
28+
cached = ReactNativeFeatureFlagsCxxInterop.commonTestFlag()
29+
commonTestFlagCache = cached
30+
}
31+
return cached
32+
}
33+
34+
override fun override(provider: ReactNativeFeatureFlagsProvider) =
35+
ReactNativeFeatureFlagsCxxInterop.override(provider as Any)
36+
37+
override fun dangerouslyReset() = ReactNativeFeatureFlagsCxxInterop.dangerouslyReset()
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @generated SignedSource<<c2e41ac8c3d9471b4cb79f6147cc2bf2>>
8+
*/
9+
10+
/**
11+
* IMPORTANT: Do NOT modify this file directly.
12+
*
13+
* To change the definition of the flags, edit
14+
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
15+
*
16+
* To regenerate this code, run the following script from the repo root:
17+
* yarn featureflags-update
18+
*/
19+
20+
package com.facebook.react.internal.featureflags
21+
22+
import com.facebook.proguard.annotations.DoNotStrip
23+
import com.facebook.soloader.SoLoader
24+
25+
@DoNotStrip
26+
object ReactNativeFeatureFlagsCxxInterop {
27+
init {
28+
SoLoader.loadLibrary("reactfeatureflagsjni")
29+
}
30+
31+
@DoNotStrip @JvmStatic external fun commonTestFlag(): Boolean
32+
33+
@DoNotStrip @JvmStatic external fun override(provider: Any)
34+
35+
@DoNotStrip @JvmStatic external fun dangerouslyReset()
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @generated SignedSource<<aa1eaeee7b715e5b1d3cbcf9b7a7062e>>
8+
*/
9+
10+
/**
11+
* IMPORTANT: Do NOT modify this file directly.
12+
*
13+
* To change the definition of the flags, edit
14+
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
15+
*
16+
* To regenerate this code, run the following script from the repo root:
17+
* yarn featureflags-update
18+
*/
19+
20+
package com.facebook.react.internal.featureflags
21+
22+
open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvider {
23+
// We could use JNI to get the defaults from C++,
24+
// but that is more expensive than just duplicating the defaults here.
25+
26+
override fun commonTestFlag(): Boolean = false
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.internal.featureflags
9+
10+
object ReactNativeFeatureFlagsForTests {
11+
fun setUp() {
12+
ReactNativeFeatureFlags.setAccessorProvider({ ReactNativeFeatureFlagsLocalAccessor() })
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @generated SignedSource<<8bbd7e8cc2c50cfbf44ba6671d095f23>>
8+
*/
9+
10+
/**
11+
* IMPORTANT: Do NOT modify this file directly.
12+
*
13+
* To change the definition of the flags, edit
14+
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.json.
15+
*
16+
* To regenerate this code, run the following script from the repo root:
17+
* yarn featureflags-update
18+
*/
19+
20+
package com.facebook.react.internal.featureflags
21+
22+
class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAccessor {
23+
private var currentProvider: ReactNativeFeatureFlagsProvider = ReactNativeFeatureFlagsDefaults()
24+
25+
private val accessedFeatureFlags = mutableSetOf<String>()
26+
27+
private var commonTestFlagCache: Boolean? = null
28+
29+
override fun commonTestFlag(): Boolean {
30+
var cached = commonTestFlagCache
31+
if (cached == null) {
32+
cached = currentProvider.commonTestFlag()
33+
accessedFeatureFlags.add("commonTestFlag")
34+
commonTestFlagCache = cached
35+
}
36+
return cached
37+
}
38+
39+
override fun override(provider: ReactNativeFeatureFlagsProvider) {
40+
if (accessedFeatureFlags.isNotEmpty()) {
41+
val accessedFeatureFlagsStr = accessedFeatureFlags.joinToString(separator = ", ") { it }
42+
throw IllegalStateException(
43+
"Feature flags were accessed before being overridden: $accessedFeatureFlagsStr")
44+
}
45+
currentProvider = provider
46+
}
47+
48+
override fun dangerouslyReset() {
49+
// We don't need to do anything here because `ReactNativeFeatureFlags` will
50+
// just create a new instance of this class.
51+
}
52+
}

0 commit comments

Comments
 (0)