From df282a0538224258a48ef77ebf9b9d2ed77ed5d5 Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Mon, 3 Mar 2025 09:52:26 -0800 Subject: [PATCH] Add prerelease staging API for iOS (#49650) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49650 - Added a new template to React Native's Feature Flag's script for Canary and Experimental prerelease stages iOS - Overload initWithDelegate to add optional releaseLevel parameter - Add Obj-C enum for Release Level - Use static variables to keep the context on whether a ReactNative Factory has been created and with which flags it has been created. - Crash in case we try to create multiple factories with different feature flags. - Creating multiple factories with the same feature flags is allowed Changelog: [iOS] [Added] - On `RCTReactNativeFactory` add `initWithDelegate` overload with argument to specify release level for an application Reviewed By: cipolleschi Differential Revision: D70106210 fbshipit-source-id: 14fe4c8571621a75a7064f1ffd02a07270cea43a --- .../AppDelegate/RCTReactNativeFactory.h | 4 + .../AppDelegate/RCTReactNativeFactory.mm | 62 +++++---- ...eactNativeFeatureFlagsOverridesOSSCanary.h | 129 ++++++++++++++++++ ...tiveFeatureFlagsOverridesOSSExperimental.h | 117 ++++++++++++++++ ...eactNativeFeatureFlagsOverridesOSSStable.h | 30 ++++ .../featureflags/generateCommonCxxModules.js | 10 ++ ...tureFlagsOverridesOSS_Stage_.h-template.js | 76 +++++++++++ 7 files changed, 402 insertions(+), 26 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h create mode 100644 packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h create mode 100644 packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSStable.h create mode 100644 packages/react-native/scripts/featureflags/templates/common-cxx/ReactNativeFeatureFlagsOverridesOSS_Stage_.h-template.js diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h index a21beb87fcc..14304a97eb9 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.h @@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable }; + @protocol RCTReactNativeFactoryDelegate < RCTBridgeDelegate, RCTUIConfiguratorProtocol, @@ -86,6 +88,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithDelegate:(id)delegate; +- (instancetype)initWithDelegate:(id)delegate releaseLevel:(RCTReleaseLevel)releaseLevel; + - (void)startReactNativeWithModuleName:(NSString *)moduleName inWindow:(UIWindow *_Nullable)window; - (void)startReactNativeWithModuleName:(NSString *)moduleName diff --git a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm index 93c301fc477..2d185d8433d 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTReactNativeFactory.mm @@ -13,8 +13,9 @@ #import #import #import -#import -#import +#import +#import +#import #import #import "RCTAppSetupUtils.h" @@ -41,10 +42,15 @@ using namespace facebook::react; @implementation RCTReactNativeFactory - (instancetype)initWithDelegate:(id)delegate +{ + return [self initWithDelegate:delegate releaseLevel:Stable]; +} + +- (instancetype)initWithDelegate:(id)delegate releaseLevel:(RCTReleaseLevel)releaseLevel { if (self = [super init]) { self.delegate = delegate; - [self _setUpFeatureFlags]; + [self _setUpFeatureFlags:releaseLevel]; auto newArchEnabled = [self newArchEnabled]; auto fabricEnabled = [self fabricEnabled]; @@ -287,32 +293,36 @@ using namespace facebook::react; #pragma mark - Feature Flags -class RCTAppDelegateBridgelessFeatureFlags : public ReactNativeFeatureFlagsDefaults { - public: - bool enableBridgelessArchitecture() override - { - return true; - } - bool enableFabricRenderer() override - { - return true; - } - bool useTurboModules() override - { - return true; - } - bool useNativeViewConfigsInBridgelessMode() override - { - return true; - } -}; - -- (void)_setUpFeatureFlags +- (void)_setUpFeatureFlags:(RCTReleaseLevel)releaseLevel { + static BOOL initialized = NO; + static RCTReleaseLevel chosenReleaseLevel; + NSLog(@"_setUpFeatureFlags called with release level %li", releaseLevel); + if (!initialized) { + chosenReleaseLevel = releaseLevel; + initialized = YES; + } else if (chosenReleaseLevel != releaseLevel) { + [NSException + raise:@"RCTReactNativeFactory::_setUpFeatureFlags releaseLevel mismatch between React Native instances" + format:@"The releaseLevel (%li) of the new instance does not match the previous instance's releaseLevel (%li)", + releaseLevel, + chosenReleaseLevel]; + } + static dispatch_once_t setupFeatureFlagsToken; dispatch_once(&setupFeatureFlagsToken, ^{ - if ([self bridgelessEnabled]) { - ReactNativeFeatureFlags::override(std::make_unique()); + switch (releaseLevel) { + case Stable: + if ([self bridgelessEnabled]) { + ReactNativeFeatureFlags::override(std::make_unique()); + } + break; + case Canary: + ReactNativeFeatureFlags::override(std::make_unique()); + break; + case Experimental: + ReactNativeFeatureFlags::override(std::make_unique()); + break; } }); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h new file mode 100644 index 00000000000..75aaff061b7 --- /dev/null +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h @@ -0,0 +1,129 @@ +/* + * 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<> + */ + +/** + * IMPORTANT: Do NOT modify this file directly. + * + * To change the definition of the flags, edit + * packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js. + * + * To regenerate this code, run the following script from the repo root: + * yarn featureflags --update + */ + +#pragma once + +#include + +namespace facebook::react { + +class ReactNativeFeatureFlagsOverridesOSSCanary : public ReactNativeFeatureFlagsOverridesOSSStable { + public: + ReactNativeFeatureFlagsOverridesOSSCanary() = default; + + + + + + + + bool enableBridgelessArchitecture() override { + return true; + } + + + + + + + + bool enableFabricRenderer() override { + return true; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bool useFabricInterop() override { + return true; + } + + bool useNativeViewConfigsInBridgelessMode() override { + return true; + } + + + + + + bool useTurboModuleInterop() override { + return true; + } + + bool useTurboModules() override { + return true; + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h new file mode 100644 index 00000000000..6925f5f629f --- /dev/null +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h @@ -0,0 +1,117 @@ +/* + * 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<> + */ + +/** + * IMPORTANT: Do NOT modify this file directly. + * + * To change the definition of the flags, edit + * packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js. + * + * To regenerate this code, run the following script from the repo root: + * yarn featureflags --update + */ + +#pragma once + +#include + +namespace facebook::react { + +class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatureFlagsOverridesOSSCanary { + public: + ReactNativeFeatureFlagsOverridesOSSExperimental() = default; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSStable.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSStable.h new file mode 100644 index 00000000000..305dd837379 --- /dev/null +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSStable.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook::react { + +class ReactNativeFeatureFlagsOverridesOSSStable + : public ReactNativeFeatureFlagsDefaults { + public: + bool enableBridgelessArchitecture() override { + return true; + } + bool enableFabricRenderer() override { + return true; + } + bool useTurboModules() override { + return true; + } + bool useNativeViewConfigsInBridgelessMode() override { + return true; + } +}; +} // namespace facebook::react diff --git a/packages/react-native/scripts/featureflags/generateCommonCxxModules.js b/packages/react-native/scripts/featureflags/generateCommonCxxModules.js index 8f12bca1ddf..65864e813a4 100644 --- a/packages/react-native/scripts/featureflags/generateCommonCxxModules.js +++ b/packages/react-native/scripts/featureflags/generateCommonCxxModules.js @@ -16,6 +16,7 @@ import ReactNativeFeatureFlagsAccessorCPP from './templates/common-cxx/ReactNati import ReactNativeFeatureFlagsAccessorH from './templates/common-cxx/ReactNativeFeatureFlagsAccessor.h-template'; import ReactNativeFeatureFlagsDefaultsH from './templates/common-cxx/ReactNativeFeatureFlagsDefaults.h-template'; import ReactNativeFeatureFlagsDynamicProviderH from './templates/common-cxx/ReactNativeFeatureFlagsDynamicProvider.h-template'; +import ReactNativeFeatureFlagsOverrides from './templates/common-cxx/ReactNativeFeatureFlagsOverridesOSS_Stage_.h-template'; import ReactNativeFeatureFlagsProviderH from './templates/common-cxx/ReactNativeFeatureFlagsProvider.h-template'; import path from 'path'; @@ -35,6 +36,15 @@ export default function generateCommonCxxModules( ReactNativeFeatureFlagsAccessorCPP(featureFlagDefinitions), [path.join(commonCxxPath, 'ReactNativeFeatureFlagsDefaults.h')]: ReactNativeFeatureFlagsDefaultsH(featureFlagDefinitions), + [path.join( + commonCxxPath, + 'ReactNativeFeatureFlagsOverridesOSSExperimental.h', + )]: ReactNativeFeatureFlagsOverrides( + featureFlagDefinitions, + 'experimental', + ), + [path.join(commonCxxPath, 'ReactNativeFeatureFlagsOverridesOSSCanary.h')]: + ReactNativeFeatureFlagsOverrides(featureFlagDefinitions, 'canary'), [path.join(commonCxxPath, 'ReactNativeFeatureFlagsProvider.h')]: ReactNativeFeatureFlagsProviderH(featureFlagDefinitions), [path.join(commonCxxPath, 'ReactNativeFeatureFlagsDynamicProvider.h')]: diff --git a/packages/react-native/scripts/featureflags/templates/common-cxx/ReactNativeFeatureFlagsOverridesOSS_Stage_.h-template.js b/packages/react-native/scripts/featureflags/templates/common-cxx/ReactNativeFeatureFlagsOverridesOSS_Stage_.h-template.js new file mode 100644 index 00000000000..8b659ad5160 --- /dev/null +++ b/packages/react-native/scripts/featureflags/templates/common-cxx/ReactNativeFeatureFlagsOverridesOSS_Stage_.h-template.js @@ -0,0 +1,76 @@ +/** + * 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. + * + * @flow strict + * @format + */ + +import type {FeatureFlagDefinitions, OSSReleaseStageValue} from '../../types'; + +import {DO_NOT_MODIFY_COMMENT, getCxxTypeFromDefaultValue} from '../../utils'; +import signedsource from 'signedsource'; + +function getClassName(ossReleaseStage: OSSReleaseStageValue): string { + if (ossReleaseStage === 'experimental') { + return 'ReactNativeFeatureFlagsOverridesOSSExperimental'; + } else if (ossReleaseStage === 'canary') { + return 'ReactNativeFeatureFlagsOverridesOSSCanary'; + } + + return 'ReactNativeFeatureFlagsOverridesOSSStable'; +} + +function getParentClassName(ossReleaseStage: OSSReleaseStageValue): string { + if (ossReleaseStage === 'experimental') { + return 'ReactNativeFeatureFlagsOverridesOSSCanary'; + } else if (ossReleaseStage === 'canary') { + return 'ReactNativeFeatureFlagsOverridesOSSStable'; + } + + return 'ReactNativeFeatureFlagsProvider'; +} + +export default function ( + definitions: FeatureFlagDefinitions, + ossReleaseStage: OSSReleaseStageValue, +): string { + return signedsource.signFile(`/* + * 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. + * + * ${signedsource.getSigningToken()} + */ + +${DO_NOT_MODIFY_COMMENT} + +#pragma once + +#include + +namespace facebook::react { + +class ${getClassName(ossReleaseStage)} : public ${getParentClassName(ossReleaseStage)} { + public: + ${getClassName(ossReleaseStage)}() = default; + +${Object.entries(definitions.common) + .map(([flagName, flagConfig]) => { + if (flagConfig.ossReleaseStage === ossReleaseStage) { + return ` ${getCxxTypeFromDefaultValue( + flagConfig.metadata.expectedReleaseValue, + )} ${flagName}() override { + return ${JSON.stringify(flagConfig.metadata.expectedReleaseValue)}; + }`; + } + }) + .join('\n\n')} +}; + +} // namespace facebook::react +`); +}