diff --git a/Gemfile.lock b/Gemfile.lock index 822b619cb..8e1f8a4a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -53,4 +53,4 @@ DEPENDENCIES xcodeproj! BUNDLED WITH - 2.2.22 + 2.3.11 diff --git a/android/app/build.gradle b/android/app/build.gradle index 791173700..c845e7c72 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -6,8 +6,8 @@ buildscript { plugins { id("com.android.application") - id("kotlin-android") - id("kotlin-kapt") + id("org.jetbrains.kotlin.android") version "${kotlinVersion}" + id("org.jetbrains.kotlin.kapt") version "${kotlinVersion}" } // `react-native run-android` is hard-coded to look for the output APK at a very @@ -15,8 +15,8 @@ plugins { // https://github.com/react-native-community/cli/blob/6cf12b00c02aca6d4bc843446394331d71a9749e/packages/platform-android/src/commands/runAndroid/index.ts#L180 buildDir = "${rootDir}/${name}/build" -def reactNativeDir = findNodeModulesPath(rootDir, "react-native") -def reactNativeVersion = getReactNativeVersionNumber(rootDir) +def reactNativeDir = findNodeModulesPath("react-native", rootDir) +def reactNativeVersion = getPackageVersionNumber("react-native", rootDir) repositories { maven { @@ -51,11 +51,14 @@ apply(from: "${projectDir}/../../test-app.gradle") applyTestAppModule(project) project.ext.react = [ - appName : getAppName(), - applicationId: getApplicationId(), - enableFabric : isFabricEnabled(rootDir), - enableFlipper: getFlipperVersion(rootDir), - enableHermes : true, + abiSplit : false, + appName : getAppName(), + applicationId : getApplicationId(), + architectures : ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"], + enableFabric : isFabricEnabled(project), + enableFlipper : getFlipperVersion(rootDir), + enableHermes : true, + enableNewArchitecture: isNewArchitectureEnabled(project), ] project.ext.signingConfigs = getSigningConfigs() @@ -63,9 +66,8 @@ project.ext.signingConfigs = getSigningConfigs() android { compileSdkVersion project.ext.compileSdkVersion - // We need only set `ndkVersion` when building react-native from source. - if (hasProperty("ANDROID_NDK_VERSION")) { - ndkVersion ANDROID_NDK_VERSION + if (project.hasProperty("ndkVersion")) { + ndkVersion project.ext.ndkVersion } // TODO: Remove this block when minSdkVersion >= 24. See @@ -110,6 +112,69 @@ android { resValue "string", "app_name", project.ext.react.appName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + if (project.ext.react.enableNewArchitecture) { + externalNativeBuild { + ndkBuild { + arguments "APP_PLATFORM=android-${project.ext.minSdkVersion}", + "APP_STL=c++_shared", + "NDK_TOOLCHAIN_VERSION=clang", + "GENERATED_SRC_DIR=${buildDir}/generated/source", + "NODE_MODULES_DIR=${reactNativeDir}/..", + "PROJECT_BUILD_DIR=${buildDir}", + "REACT_ANDROID_DIR=${reactNativeDir}/ReactAndroid", + "REACT_ANDROID_BUILD_DIR=${reactNativeDir}/ReactAndroid/build" + cFlags "-Wall", "-Werror", "-frtti", "-fexceptions", "-DWITH_INSPECTOR=1" + cppFlags "-std=c++17" + targets "reacttestapp_appmodules" + } + } + if (!project.ext.react.abiSplit) { + ndk { + abiFilters(*project.ext.react.architectures) + } + } + } + } + + if (project.ext.react.enableNewArchitecture) { + externalNativeBuild { + ndkBuild { + path "${projectDir}/src/main/jni/Android.mk" + } + } + + def reactAndroidProjectDir = project(":ReactAndroid").projectDir + def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck") + from("${reactAndroidProjectDir}/src/main/jni/prebuilt/lib") + into("${buildDir}/react-ndk/exported") + } + def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) { + dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck") + from("${reactAndroidProjectDir}/src/main/jni/prebuilt/lib") + into("${buildDir}/react-ndk/exported") + } + + afterEvaluate { + preDebugBuild.dependsOn(packageReactNdkDebugLibs) + preReleaseBuild.dependsOn(packageReactNdkReleaseLibs) + + // Due to a bug in AGP, we have to explicitly set a dependency + // between configureNdkBuild* tasks and the preBuild tasks. This can + // be removed once this issue is resolved: + // https://issuetracker.google.com/issues/207403732 + configureNdkBuildRelease.dependsOn(preReleaseBuild) + configureNdkBuildDebug.dependsOn(preDebugBuild) + project.ext.react.architectures.each { architecture -> + tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure { + dependsOn("preDebugBuild") + } + tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure { + dependsOn("preReleaseBuild") + } + } + } } lintOptions { @@ -158,6 +223,13 @@ android { main.java.srcDirs += "src/no-fabric/java" } + // TODO: Remove this block when we drop support for 0.65. + if (project.ext.react.enableNewArchitecture) { + main.java.srcDirs += "src/turbomodule/java" + } else { + main.java.srcDirs += "src/no-turbomodule/java" + } + // TODO: Remove this block when we drop support for 0.67. // https://github.com/facebook/react-native/commit/ce74aa4ed335d4c36ce722d47937b582045e05c4 if (reactNativeVersion < 6800) { @@ -166,6 +238,15 @@ android { main.java.srcDirs += "src/reactinstanceeventlistener-0.68/java" } } + + splits { + abi { + reset() + enable(project.ext.react.abiSplit) + universalApk(false) + include(*project.ext.react.architectures) + } + } } dependencies { @@ -175,8 +256,8 @@ dependencies { // TODO: Remove this block when we drop support for 0.68. if (reactNativeVersion < 6900) { def hermesEngineDir = - findNodeModulesPath(file(reactNativeDir), "hermes-engine") - ?: findNodeModulesPath(file(reactNativeDir), "hermesvm") + findNodeModulesPath("hermes-engine", file(reactNativeDir)) + ?: findNodeModulesPath("hermesvm", file(reactNativeDir)) if (hermesEngineDir == null) { throw new GradleException("Could not find 'hermes-engine'. Please make sure you've added it to 'package.json'.") } @@ -236,3 +317,22 @@ dependencies { } } } + +if (project.ext.react.enableNewArchitecture) { + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("com.facebook.react:react-native")) + .using(project(":ReactAndroid")) + .because("On New Architecture, we are building React Native from source") + substitute(module("com.facebook.react:hermes-engine")) + .using(project(":ReactAndroid:hermes-engine")) + .because("On New Architecture, we are building Hermes from source") + } + } +} + +// `@react-native-community/cli` currently requires this function to be defined. +// See https://github.com/react-native-community/cli/blob/a87fb9014635fe84ab19a1a88d6ecbbc530eb4e2/packages/platform-android/native_modules.gradle#L497 +def isNewArchitectureEnabled() { + return isFabricEnabled(project) +} diff --git a/android/app/src/fabric/java/com/microsoft/reacttestapp/fabric/FabricJSIModulePackage.kt b/android/app/src/fabric/java/com/microsoft/reacttestapp/fabric/FabricJSIModulePackage.kt index 1c8139c7d..0ab1d8172 100644 --- a/android/app/src/fabric/java/com/microsoft/reacttestapp/fabric/FabricJSIModulePackage.kt +++ b/android/app/src/fabric/java/com/microsoft/reacttestapp/fabric/FabricJSIModulePackage.kt @@ -34,6 +34,8 @@ class FabricJSIModulePackage(reactNativeHost: ReactNativeHost) : JSIModulePackag val componentFactory = ComponentFactory() CoreComponentsRegistry.register(componentFactory) + ComponentsRegistry.register(componentFactory) + return FabricJSIModuleProvider( reactApplicationContext, componentFactory, diff --git a/android/app/src/main/java/com/microsoft/reacttestapp/react/TestAppReactNativeHost.kt b/android/app/src/main/java/com/microsoft/reacttestapp/react/TestAppReactNativeHost.kt index d1e15d337..7cf1f3ba2 100644 --- a/android/app/src/main/java/com/microsoft/reacttestapp/react/TestAppReactNativeHost.kt +++ b/android/app/src/main/java/com/microsoft/reacttestapp/react/TestAppReactNativeHost.kt @@ -6,8 +6,6 @@ import android.content.Context import android.util.Log import com.facebook.react.PackageList import com.facebook.react.ReactInstanceManager -import com.facebook.react.ReactNativeHost -import com.facebook.react.ReactPackage import com.facebook.react.bridge.JSIModulePackage import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReactMarker @@ -22,6 +20,7 @@ import com.facebook.soloader.SoLoader import com.microsoft.reacttestapp.BuildConfig import com.microsoft.reacttestapp.R import com.microsoft.reacttestapp.compat.ReactInstanceEventListener +import com.microsoft.reacttestapp.compat.ReactNativeHostCompat import com.microsoft.reacttestapp.fabric.FabricJSIModulePackage import java.util.concurrent.CountDownLatch @@ -49,7 +48,7 @@ sealed class BundleSource { class TestAppReactNativeHost( application: Application, private val reactBundleNameProvider: ReactBundleNameProvider -) : ReactNativeHost(application) { +) : ReactNativeHostCompat(application) { var source: BundleSource = if (reactBundleNameProvider.bundleName == null || isPackagerRunning(application)) { BundleSource.Server @@ -166,7 +165,7 @@ class TestAppReactNativeHost( override fun getUseDeveloperSupport() = source == BundleSource.Server - override fun getPackages(): List = PackageList(application).packages + override fun getPackages() = PackageList(application).packages private fun addCustomDevOptions(devSupportManager: DevSupportManager) { val bundleOption = application.resources.getString( diff --git a/android/app/src/main/jni/Android.mk b/android/app/src/main/jni/Android.mk new file mode 100644 index 000000000..fab7f35cd --- /dev/null +++ b/android/app/src/main/jni/Android.mk @@ -0,0 +1,58 @@ +THIS_DIR := $(call my-dir) + +include $(REACT_ANDROID_DIR)/Android-prebuilt.mk + +# If you wish to add a custom TurboModule or Fabric component in your app you +# will have to include the following autogenerated makefile. +# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk + +# Makefile for autolinked libraries +include $(PROJECT_BUILD_DIR)/generated/rncli/src/main/jni/Android-rncli.mk + +include $(CLEAR_VARS) + +LOCAL_PATH := $(THIS_DIR) + +# You can customize the name of your application .so file here. +LOCAL_MODULE := reacttestapp_appmodules + +LOCAL_C_INCLUDES := $(LOCAL_PATH) +LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) + +# If you wish to add a custom TurboModule or Fabric component in your app you +# will have to uncomment those lines to include the generated source +# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni) +# +# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni +# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp) +# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni + +# Autolinked TurboModules and Fabric components +LOCAL_C_INCLUDES += $(PROJECT_BUILD_DIR)/generated/rncli/src/main/jni +LOCAL_SRC_FILES += $(wildcard $(PROJECT_BUILD_DIR)/generated/rncli/src/main/jni/*.cpp) +LOCAL_EXPORT_C_INCLUDES += $(PROJECT_BUILD_DIR)/generated/rncli/src/main/jni + +# Here you should add any native library you wish to depend on. +LOCAL_SHARED_LIBRARIES := \ + libfabricjni \ + libfbjni \ + libfolly_runtime \ + libglog \ + libjsi \ + libreact_codegen_rncore \ + libreact_debug \ + libreact_nativemodule_core \ + libreact_render_componentregistry \ + libreact_render_core \ + libreact_render_debug \ + libreact_render_graphics \ + librrc_view \ + libruntimeexecutor \ + libturbomodulejsijni \ + libyoga \ + $(call import-codegen-modules) + +LOCAL_CFLAGS := -std=c++17 -Wall -frtti -fexceptions -DLOG_TAG=\"ReactNative\" + +include $(BUILD_SHARED_LIBRARY) diff --git a/android/app/src/main/jni/ComponentsRegistry.cpp b/android/app/src/main/jni/ComponentsRegistry.cpp new file mode 100644 index 000000000..40ceba656 --- /dev/null +++ b/android/app/src/main/jni/ComponentsRegistry.cpp @@ -0,0 +1,55 @@ +#include "ComponentsRegistry.h" + +#include +#include + +#include + +#include +#include +#include + +using facebook::react::ComponentDescriptorParameters; +using facebook::react::ComponentDescriptorProviderRegistry; +using facebook::react::ComponentDescriptorRegistry; +using facebook::react::ComponentFactory; +using facebook::react::ContextContainer; +using facebook::react::CoreComponentsRegistry; +using facebook::react::EventDispatcher; +using facebook::react::UnimplementedNativeViewComponentDescriptor; +using ReactTestApp::ComponentsRegistry; + +void ComponentsRegistry::registerNatives() +{ + registerHybrid({makeNativeMethod("initHybrid", ComponentsRegistry::initHybrid)}); +} + +ComponentsRegistry::ComponentsRegistry(ComponentFactory *) +{ +} + +facebook::jni::local_ref +ComponentsRegistry::initHybrid(facebook::jni::alias_ref, ComponentFactory *delegate) +{ + delegate->buildRegistryFunction = [](EventDispatcher::Weak const &eventDispatcher, + ContextContainer::Shared const &contextContainer) + -> ComponentDescriptorRegistry::Shared { + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + + // Register providers generated by `@react-native-community/cli` + rncli_registerProviders(providerRegistry); + + auto registry = providerRegistry->createComponentDescriptorRegistry( + {eventDispatcher, contextContainer}); + + auto mutableRegistry = std::const_pointer_cast(registry); + + mutableRegistry->setFallbackComponentDescriptor( + std::make_shared( + ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr})); + + return registry; + }; + + return makeCxxInstance(delegate); +} diff --git a/android/app/src/main/jni/ComponentsRegistry.h b/android/app/src/main/jni/ComponentsRegistry.h new file mode 100644 index 000000000..6ecf99a5d --- /dev/null +++ b/android/app/src/main/jni/ComponentsRegistry.h @@ -0,0 +1,26 @@ +#ifndef REACTTESTAPP_JNI_COMPONENTSREGISTRY_H_ +#define REACTTESTAPP_JNI_COMPONENTSREGISTRY_H_ + +#include + +#include + +namespace ReactTestApp +{ + class ComponentsRegistry : public facebook::jni::HybridClass + { + public: + constexpr static auto kJavaDescriptor = + "Lcom/microsoft/reacttestapp/fabric/ComponentsRegistry;"; + + static void registerNatives(); + + ComponentsRegistry(facebook::react::ComponentFactory *delegate); + + private: + static facebook::jni::local_ref + initHybrid(facebook::jni::alias_ref, facebook::react::ComponentFactory *delegate); + }; +} // namespace ReactTestApp + +#endif // REACTTESTAPP_JNI_COMPONENTSREGISTRY_H_ diff --git a/android/app/src/main/jni/OnLoad.cpp b/android/app/src/main/jni/OnLoad.cpp new file mode 100644 index 000000000..70630d0e8 --- /dev/null +++ b/android/app/src/main/jni/OnLoad.cpp @@ -0,0 +1,15 @@ +#include + +#include "ComponentsRegistry.h" +#include "TurboModuleManagerDelegate.h" + +using ReactTestApp::ComponentsRegistry; +using ReactTestApp::TurboModuleManagerDelegate; + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) +{ + return facebook::jni::initialize(vm, [] { + TurboModuleManagerDelegate::registerNatives(); + ComponentsRegistry::registerNatives(); + }); +} diff --git a/android/app/src/main/jni/TurboModuleManagerDelegate.cpp b/android/app/src/main/jni/TurboModuleManagerDelegate.cpp new file mode 100644 index 000000000..4f645ade5 --- /dev/null +++ b/android/app/src/main/jni/TurboModuleManagerDelegate.cpp @@ -0,0 +1,48 @@ +#include "TurboModuleManagerDelegate.h" + +#include +#include + +using facebook::react::CallInvoker; +using facebook::react::JavaTurboModule; +using facebook::react::TurboModule; +using ReactTestApp::TurboModuleManagerDelegate; + +void TurboModuleManagerDelegate::registerNatives() +{ + registerHybrid({ + makeNativeMethod("initHybrid", TurboModuleManagerDelegate::initHybrid), + makeNativeMethod("canCreateTurboModule", TurboModuleManagerDelegate::canCreateTurboModule), + }); +} + +std::shared_ptr TurboModuleManagerDelegate::getTurboModule(StringRef, + SharedCallInvoker) +{ + return nullptr; +} + +std::shared_ptr TurboModuleManagerDelegate::getTurboModule( + StringRef name, const JavaTurboModule::InitParams ¶ms) +{ + // Try autolinked module providers first + auto rncli_module = rncli_ModuleProvider(name, params); + if (rncli_module != nullptr) { + return rncli_module; + } + + return rncore_ModuleProvider(name, params); +} + +facebook::jni::local_ref +TurboModuleManagerDelegate::initHybrid( + facebook::jni::alias_ref) +{ + return makeCxxInstance(); +} + +bool TurboModuleManagerDelegate::canCreateTurboModule(StringRef name) +{ + return getTurboModule(name, nullptr) != nullptr || + getTurboModule(name, {.moduleName = name}) != nullptr; +} diff --git a/android/app/src/main/jni/TurboModuleManagerDelegate.h b/android/app/src/main/jni/TurboModuleManagerDelegate.h new file mode 100644 index 000000000..52f451fdc --- /dev/null +++ b/android/app/src/main/jni/TurboModuleManagerDelegate.h @@ -0,0 +1,52 @@ +#ifndef REACTTESTAPP_JNI_TURBOMODULEMANAGERDELEGATE_H_ +#define REACTTESTAPP_JNI_TURBOMODULEMANAGERDELEGATE_H_ + +#include +#include + +#include + +#include + +namespace ReactTestApp +{ + class TurboModuleManagerDelegate + : public facebook::jni::HybridClass + { + // Signatures changed in 0.70 to avoid unnecessary string copies; see + // https://github.com/facebook/react-native/commit/3337add547c60b84816ef5dad82f4ead2e8742ef +#if __has_include() + using SharedCallInvoker = const std::shared_ptr &; + using StringRef = const std::string &; +#else + using SharedCallInvoker = const std::shared_ptr; + using StringRef = const std::string; +#endif + + public: + static constexpr auto kJavaDescriptor = + "Lcom/microsoft/reacttestapp/turbomodule/TurboModuleManagerDelegate;"; + + static void registerNatives(); + + std::shared_ptr + getTurboModule(StringRef name, SharedCallInvoker jsInvoker) override; + + std::shared_ptr + getTurboModule(StringRef name, // + const facebook::react::JavaTurboModule::InitParams ¶ms) override; + + private: + static facebook::jni::local_ref + initHybrid(facebook::jni::alias_ref); + + /** + * Test-only method. Allows user to verify whether a TurboModule can be + * created by instances of this class. + */ + bool canCreateTurboModule(StringRef name); + }; +} // namespace ReactTestApp + +#endif // REACTTESTAPP_JNI_TURBOMODULEMANAGERDELEGATE_H_ diff --git a/android/app/src/no-turbomodule/java/com/microsoft/reacttestapp/compat/ReactNativeHostCompat.kt b/android/app/src/no-turbomodule/java/com/microsoft/reacttestapp/compat/ReactNativeHostCompat.kt new file mode 100644 index 000000000..d288ecae9 --- /dev/null +++ b/android/app/src/no-turbomodule/java/com/microsoft/reacttestapp/compat/ReactNativeHostCompat.kt @@ -0,0 +1,5 @@ +package com.microsoft.reacttestapp.compat + +import com.facebook.react.ReactNativeHost + +typealias ReactNativeHostCompat = ReactNativeHost diff --git a/android/app/src/no-turbomodule/java/com/microsoft/reacttestapp/fabric/ComponentsRegistry.kt b/android/app/src/no-turbomodule/java/com/microsoft/reacttestapp/fabric/ComponentsRegistry.kt new file mode 100644 index 000000000..497a30b7a --- /dev/null +++ b/android/app/src/no-turbomodule/java/com/microsoft/reacttestapp/fabric/ComponentsRegistry.kt @@ -0,0 +1,13 @@ +package com.microsoft.reacttestapp.fabric + +import com.facebook.react.fabric.ComponentFactory + +class ComponentsRegistry private constructor() { + companion object { + fun register( + @Suppress("UNUSED_PARAMETER") componentFactory: ComponentFactory + ): ComponentsRegistry { + return ComponentsRegistry() + } + } +} diff --git a/android/app/src/turbomodule/java/com/microsoft/reacttestapp/compat/ReactNativeHostCompat.kt b/android/app/src/turbomodule/java/com/microsoft/reacttestapp/compat/ReactNativeHostCompat.kt new file mode 100644 index 000000000..0f1d2cb44 --- /dev/null +++ b/android/app/src/turbomodule/java/com/microsoft/reacttestapp/compat/ReactNativeHostCompat.kt @@ -0,0 +1,10 @@ +package com.microsoft.reacttestapp.compat + +import android.app.Application +import com.facebook.react.ReactNativeHost +import com.microsoft.reacttestapp.turbomodule.TurboModuleManagerDelegate + +abstract class ReactNativeHostCompat(application: Application) : ReactNativeHost(application) { + override fun getReactPackageTurboModuleManagerDelegateBuilder() = + TurboModuleManagerDelegate.Builder() +} diff --git a/android/app/src/turbomodule/java/com/microsoft/reacttestapp/fabric/ComponentsRegistry.kt b/android/app/src/turbomodule/java/com/microsoft/reacttestapp/fabric/ComponentsRegistry.kt new file mode 100644 index 000000000..b467fe846 --- /dev/null +++ b/android/app/src/turbomodule/java/com/microsoft/reacttestapp/fabric/ComponentsRegistry.kt @@ -0,0 +1,36 @@ +package com.microsoft.reacttestapp.fabric + +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.fabric.ComponentFactory +import com.facebook.soloader.SoLoader + +/** + * The corresponding C++ implementation is in `android/app/src/main/jni/ComponentsRegistry.cpp` + */ +@DoNotStrip +class ComponentsRegistry @DoNotStrip private constructor( + componentFactory: ComponentFactory +) { + companion object { + @DoNotStrip + fun register(componentFactory: ComponentFactory): ComponentsRegistry { + return ComponentsRegistry(componentFactory) + } + + init { + SoLoader.loadLibrary("fabricjni") + SoLoader.loadLibrary("reacttestapp_appmodules") + } + } + + @DoNotStrip + private val mHybridData: HybridData + + @DoNotStrip + private external fun initHybrid(componentFactory: ComponentFactory): HybridData + + init { + mHybridData = initHybrid(componentFactory) + } +} diff --git a/android/app/src/turbomodule/java/com/microsoft/reacttestapp/turbomodule/TurboModuleManagerDelegate.kt b/android/app/src/turbomodule/java/com/microsoft/reacttestapp/turbomodule/TurboModuleManagerDelegate.kt new file mode 100644 index 000000000..0c843ce3d --- /dev/null +++ b/android/app/src/turbomodule/java/com/microsoft/reacttestapp/turbomodule/TurboModuleManagerDelegate.kt @@ -0,0 +1,39 @@ +package com.microsoft.reacttestapp.turbomodule + +import com.facebook.jni.HybridData +import com.facebook.react.ReactPackage +import com.facebook.react.ReactPackageTurboModuleManagerDelegate +import com.facebook.react.bridge.ReactApplicationContext + +/** + * These type aliases are here to prevent `@react-native-community/cli` from + * marking them as native modules to autolink. + * + * See also `matchClassName` in + * https://github.com/react-native-community/cli/blob/8.x/packages/platform-android/src/config/findPackageClassName.ts#L25 + */ +typealias PackagesList = List +typealias ReactTurboModuleManagerDelegate = ReactPackageTurboModuleManagerDelegate +typealias ReactTurboModuleManagerDelegateBuilder = ReactPackageTurboModuleManagerDelegate.Builder + +/** + * The corresponding C++ implementation is in `android/app/src/main/jni/TurboModuleManagerDelegate.cpp` + */ +class TurboModuleManagerDelegate protected constructor( + reactApplicationContext: ReactApplicationContext?, + packages: PackagesList? +) : ReactTurboModuleManagerDelegate(reactApplicationContext, packages) { + + external override fun initHybrid(): HybridData? + + external fun canCreateTurboModule(moduleName: String?): Boolean + + class Builder : ReactTurboModuleManagerDelegateBuilder() { + override fun build( + context: ReactApplicationContext?, + packages: PackagesList? + ): TurboModuleManagerDelegate { + return TurboModuleManagerDelegate(context, packages) + } + } +} diff --git a/android/dependencies.gradle b/android/dependencies.gradle index e5e3ac906..0bcf0be6a 100644 --- a/android/dependencies.gradle +++ b/android/dependencies.gradle @@ -3,10 +3,21 @@ static String versionOf(String spec) { } ext { + apply(from: "${buildscript.sourceFile.getParent()}/test-app-util.gradle") + compileSdkVersion = 31 minSdkVersion = 23 targetSdkVersion = 29 + // We need only set `ndkVersion` when building react-native from source. + if (hasProperty("ANDROID_NDK_VERSION")) { + ndkVersion ANDROID_NDK_VERSION + } else if (System.properties["os.arch"] == "aarch64") { + // Android NDK added support for Apple M1 in r24: + // https://github.com/android/ndk/wiki/Changelog-r24 + ndkVersion = "24.0.8215888" + } + /** * Dependabot requires a `dependencies.gradle` to evaluate Java * dependencies. It is also very particular about the formatting and cannot @@ -32,6 +43,6 @@ ext { moshiKotlinCodegen : "com.squareup.moshi:moshi-kotlin-codegen:1.13.0", ] - androidPluginVersion = "7.1.3" + androidPluginVersion = "7.2.1" kotlinVersion = versionOf(libraries.kotlinStdlibJdk7) } diff --git a/android/test-app-util.gradle b/android/test-app-util.gradle index 6fc60b76a..7422d710a 100644 --- a/android/test-app-util.gradle +++ b/android/test-app-util.gradle @@ -7,7 +7,7 @@ import java.security.MessageDigest ext.manifest = null ext.buildReactNativeFromSource = { baseDir -> - def reactNativePath = findNodeModulesPath(baseDir, "react-native") + def reactNativePath = findNodeModulesPath("react-native", baseDir) return !file("${reactNativePath}/android").exists() } @@ -49,7 +49,7 @@ ext.findFile = { fileName -> * The search begins at the given base directory (a File object). The returned * path is a string. */ -ext.findNodeModulesPath = { baseDir, packageName -> +ext.findNodeModulesPath = { packageName, baseDir -> def basePath = baseDir.toPath().normalize() // Node's module resolution algorithm searches up to the root directory, @@ -96,7 +96,7 @@ ext.getApplicationId = { } ext.getFlipperRecommendedVersion = { baseDir -> - def reactNativePath = findNodeModulesPath(baseDir, "react-native") + def reactNativePath = findNodeModulesPath("react-native", baseDir) def props = new Properties() file("${reactNativePath}/template/android/gradle.properties").withInputStream { props.load(it) @@ -134,9 +134,9 @@ ext.getManifest = { return manifest } -ext.getReactNativeVersionNumber = { baseDir -> - def reactNativePath = findNodeModulesPath(baseDir, "react-native") - def packageJson = file("${reactNativePath}/package.json") +ext.getPackageVersionNumber = { packageName, baseDir -> + def packagePath = findNodeModulesPath(packageName, baseDir) + def packageJson = file("${packagePath}/package.json") def manifest = new JsonSlurper().parseText(packageJson.text) def (major, minor, patch) = manifest["version"].findAll(/\d+/) return (major as int) * 10000 + (minor as int) * 100 + (patch as int) @@ -217,10 +217,19 @@ ext.getVersionName = { return "1.0" } -ext.isFabricEnabled = { baseDir -> - return project.hasProperty("USE_FABRIC") && - project.getProperty("USE_FABRIC") == "1" && - getReactNativeVersionNumber(baseDir) >= 6800 +ext.isFabricEnabled = { project -> + return isNewArchitectureEnabled(project) || + (isPropertySet(project, "USE_FABRIC", "1") && + getPackageVersionNumber("react-native", project.rootDir) >= 6800) +} + +ext.isNewArchitectureEnabled = { project -> + return isPropertySet(project, "newArchEnabled", "true") && + getPackageVersionNumber("react-native", project.rootDir) >= 6800 +} + +ext.isPropertySet = { project, propertyName, value -> + return project.hasProperty(propertyName) && project.getProperty(propertyName) == value } ext.validateManifest = { baseDir -> diff --git a/example/App.js b/example/App.js index e57ccd876..41a0ba788 100644 --- a/example/App.js +++ b/example/App.js @@ -1,6 +1,5 @@ import React from "react"; import { - SafeAreaView, ScrollView, StatusBar, StyleSheet, @@ -8,7 +7,7 @@ import { useColorScheme, View, } from "react-native"; - +import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; import { Colors, DebugInstructions, @@ -53,35 +52,37 @@ const App = () => { }; return ( - - - -
- + + + -
- Edit App.js to change this - screen and then come back to see your edits. -
-
- -
-
- -
-
- Read the docs to discover what to do next: -
- -
- - +
+ +
+ Edit App.js to change this + screen and then come back to see your edits. +
+
+ +
+
+ +
+
+ Read the docs to discover what to do next: +
+ +
+ + + ); }; diff --git a/example/android/build.gradle b/example/android/build.gradle index 0c6c69da1..2ed70f198 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,6 +1,7 @@ buildscript { def androidTestAppDir = "../node_modules/react-native-test-app/android" apply(from: "${androidTestAppDir}/dependencies.gradle") + apply(from: "${androidTestAppDir}/test-app-util.gradle") repositories { mavenCentral() @@ -8,8 +9,12 @@ buildscript { } dependencies { - classpath "com.android.tools.build:gradle:$androidPluginVersion" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + classpath("com.android.tools.build:gradle:${androidPluginVersion}") + + if (isNewArchitectureEnabled(project)) { + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("de.undercouch:gradle-download-task:5.1.0") + } } } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 7a3711fb3..3cd810b88 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -34,5 +34,9 @@ android.enableJetifier=true # Enable Fabric at runtime. #USE_FABRIC=1 +# Enable new architecture, i.e. Fabric + TurboModule - implies USE_FABRIC=1. +# Note that this is incompatible with web debugging. +#newArchEnabled=true + # Uncomment the line below if building react-native from source #ANDROID_NDK_VERSION=21.4.7075529 diff --git a/example/ios/Podfile b/example/ios/Podfile index 505269503..cea072bcf 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -4,7 +4,13 @@ workspace 'Example.xcworkspace' use_flipper! false if ENV['DISABLE_FLIPPER'] -use_test_app! :fabric_enabled => false, :hermes_enabled => false do |target| +options = { + :fabric_enabled => false, + :hermes_enabled => false, + :turbomodule_enabled => false, +} + +use_test_app! options do |target| target.tests do pod 'Example-Tests', :path => '..' end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d77421b5b..c9a255dcf 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -285,6 +285,12 @@ PODS: - React-jsinspector (0.68.2) - React-logger (0.68.2): - glog + - react-native-safe-area-context (4.3.1): + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React + - ReactCommon/turbomodule/core - React-perflogger (0.68.2) - React-RCTActionSheet (0.68.2): - React-Core/RCTActionSheetHeaders (= 0.68.2) @@ -403,6 +409,7 @@ DEPENDENCIES: - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) @@ -476,6 +483,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsinspector" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" + react-native-safe-area-context: + :path: "../node_modules/react-native-safe-area-context" React-perflogger: :path: "../node_modules/react-native/ReactCommon/reactperflogger" React-RCTActionSheet: @@ -540,6 +549,7 @@ SPEC CHECKSUMS: React-jsiexecutor: b7b553412f2ec768fe6c8f27cd6bafdb9d8719e6 React-jsinspector: c5989c77cb89ae6a69561095a61cce56a44ae8e8 React-logger: a0833912d93b36b791b7a521672d8ee89107aff1 + react-native-safe-area-context: 6c12e3859b6f27b25de4fee8201cfb858432d8de React-perflogger: a18b4f0bd933b8b24ecf9f3c54f9bf65180f3fe6 React-RCTActionSheet: 547fe42fdb4b6089598d79f8e1d855d7c23e2162 React-RCTAnimation: bc9440a1c37b06ae9ebbb532d244f607805c6034 @@ -558,6 +568,6 @@ SPEC CHECKSUMS: Yoga: 99652481fcd320aefa4a7ef90095b95acd181952 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: bcdb8c6a896b4276dbcf172c76964cd24abde5ce +PODFILE CHECKSUM: 2b1a64c7bbe2717d38b70f5573c321acc909ea09 COCOAPODS: 1.11.3 diff --git a/example/package.json b/example/package.json index 42c00df4f..790926a71 100644 --- a/example/package.json +++ b/example/package.json @@ -31,6 +31,7 @@ "react": "17.0.2", "react-native": "^0.68.2", "react-native-macos": "^0.68.3", + "react-native-safe-area-context": "^4.3.1", "react-native-test-app": "workspace:.", "react-native-windows": "^0.68.8" }, diff --git a/ios/ReactTestApp.xcodeproj/project.pbxproj b/ios/ReactTestApp.xcodeproj/project.pbxproj index 14626796d..7a03b0a44 100644 --- a/ios/ReactTestApp.xcodeproj/project.pbxproj +++ b/ios/ReactTestApp.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1914199A234B2DD800D856AE /* RCTDevSupport+UIScene.m in Sources */ = {isa = PBXBuildFile; fileRef = 19141999234B2DD800D856AE /* RCTDevSupport+UIScene.m */; }; 192DD201240FCAF5004E9CEB /* Manifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 192DD200240FCAF5004E9CEB /* Manifest.swift */; }; + 193B614D27F5CD7D00080064 /* React+TurboModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = 193B614B27F5CD7D00080064 /* React+TurboModule.mm */; }; 1963A06227C82E730013D276 /* React+Fabric.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1963A06127C82E730013D276 /* React+Fabric.mm */; }; 196C22622490CB7600449D3C /* React+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 196C22602490CB7600449D3C /* React+Compatibility.m */; }; 196C7215232F1788006556ED /* ReactInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196C7214232F1788006556ED /* ReactInstance.swift */; }; @@ -48,6 +49,8 @@ 192F052624AD3CC500A48456 /* ReactTestApp.release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReactTestApp.release.xcconfig; sourceTree = ""; }; 192F052724AD3CC500A48456 /* ReactTestApp.common.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReactTestApp.common.xcconfig; sourceTree = ""; }; 192F052824AD3CC500A48456 /* ReactTestApp.debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReactTestApp.debug.xcconfig; sourceTree = ""; }; + 193B614B27F5CD7D00080064 /* React+TurboModule.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "React+TurboModule.mm"; sourceTree = ""; }; + 193B614C27F5CD7D00080064 /* React+TurboModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "React+TurboModule.h"; sourceTree = ""; }; 1963A06027C82E730013D276 /* React+Fabric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "React+Fabric.h"; sourceTree = ""; }; 1963A06127C82E730013D276 /* React+Fabric.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "React+Fabric.mm"; sourceTree = ""; }; 196C22602490CB7600449D3C /* React+Compatibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "React+Compatibility.m"; sourceTree = ""; }; @@ -137,6 +140,8 @@ 196C22602490CB7600449D3C /* React+Compatibility.m */, 1963A06027C82E730013D276 /* React+Fabric.h */, 1963A06127C82E730013D276 /* React+Fabric.mm */, + 193B614C27F5CD7D00080064 /* React+TurboModule.h */, + 193B614B27F5CD7D00080064 /* React+TurboModule.mm */, 1988282224105BCC005057FF /* UIViewController+ReactTestApp.h */, 1988284424105BEC005057FF /* UIViewController+ReactTestApp.m */, 19ECD0DB232ED427003D8557 /* Assets.xcassets */, @@ -329,6 +334,7 @@ 1914199A234B2DD800D856AE /* RCTDevSupport+UIScene.m in Sources */, 196C22622490CB7600449D3C /* React+Compatibility.m in Sources */, 1963A06227C82E730013D276 /* React+Fabric.mm in Sources */, + 193B614D27F5CD7D00080064 /* React+TurboModule.mm in Sources */, 196C7215232F1788006556ED /* ReactInstance.swift in Sources */, 19ECD0D8232ED425003D8557 /* SceneDelegate.swift in Sources */, 19A624A4258C95F000032776 /* Session.swift in Sources */, diff --git a/ios/ReactTestApp/React+TurboModule.h b/ios/ReactTestApp/React+TurboModule.h new file mode 100644 index 000000000..d9237703f --- /dev/null +++ b/ios/ReactTestApp/React+TurboModule.h @@ -0,0 +1,13 @@ +#if USE_TURBOMODULE + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RTATurboModuleManagerDelegate : NSObject +- (instancetype)initWithBridgeDelegate:(id)bridgeDelegate; +@end + +NS_ASSUME_NONNULL_END + +#endif // USE_TURBOMODULE diff --git a/ios/ReactTestApp/React+TurboModule.mm b/ios/ReactTestApp/React+TurboModule.mm new file mode 100644 index 000000000..ee0709764 --- /dev/null +++ b/ios/ReactTestApp/React+TurboModule.mm @@ -0,0 +1,80 @@ +#import "React+TurboModule.h" + +#if USE_TURBOMODULE + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@interface RTATurboModuleManagerDelegate () +@end + +@implementation RTATurboModuleManagerDelegate { + __weak id _bridgeDelegate; + RCTTurboModuleManager *_turboModuleManager; +} + +- (instancetype)initWithBridgeDelegate:(id)bridgeDelegate +{ + if (self = [super init]) { + _bridgeDelegate = bridgeDelegate; + } + return self; +} + +// MARK: - RCTBridgeDelegate details + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge +{ + return [_bridgeDelegate sourceURLForBridge:bridge]; +} + +- (NSArray> *)extraModulesForBridge:(RCTBridge *)bridge +{ + return [_bridgeDelegate extraModulesForBridge:bridge]; +} + +// MARK: - RCTCxxBridgeDelegate details + +- (std::unique_ptr)jsExecutorFactoryForBridge: + (RCTBridge *)bridge +{ + if (_turboModuleManager == nil) { + _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge + delegate:self + jsInvoker:bridge.jsCallInvoker]; + } + return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager); +} + +// MARK: - RCTTurboModuleManagerDelegate details + +- (Class)getModuleClassFromName:(const char *)name +{ + return RCTCoreModulesClassProvider(name); +} + +- (std::shared_ptr) + getTurboModule:(const std::string &)name + jsInvoker:(std::shared_ptr)jsInvoker +{ + return nullptr; +} + +- (id)getModuleInstanceFromClass:(Class)moduleClass +{ + return RCTAppSetupDefaultModuleFromClass(moduleClass); +} + +@end + +#endif // USE_TURBOMODULE diff --git a/ios/ReactTestApp/ReactInstance.swift b/ios/ReactTestApp/ReactInstance.swift index 740a40309..5a92d773d 100644 --- a/ios/ReactTestApp/ReactInstance.swift +++ b/ios/ReactTestApp/ReactInstance.swift @@ -18,6 +18,10 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { private(set) var bridge: RCTBridge? private var bundleRoot: String? + #if USE_TURBOMODULE + private lazy var turboModuleManagerDelegate = RTATurboModuleManagerDelegate(bridgeDelegate: self) + #endif + override init() { #if DEBUG remoteBundleURL = ReactInstance.jsBundleURL() @@ -25,8 +29,9 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { super.init() - // Turbo Modules is incompatible with remote JS debugging - RCTEnableTurboModule(false) + #if USE_TURBOMODULE + RCTEnableTurboModule(true) + #endif RCTSetFatalHandler { (error: Error?) in guard let error = error else { @@ -114,10 +119,17 @@ final class ReactInstance: NSObject, RCTBridgeDelegate { object: nil ) - guard let bridge = RCTBridge(delegate: self, launchOptions: nil) else { - assertionFailure("Failed to instantiate RCTBridge") - return - } + #if USE_TURBOMODULE + guard let bridge = RCTBridge(delegate: turboModuleManagerDelegate, launchOptions: nil) else { + assertionFailure("Failed to instantiate RCTBridge with TurboModule") + return + } + #else + guard let bridge = RCTBridge(delegate: self, launchOptions: nil) else { + assertionFailure("Failed to instantiate RCTBridge") + return + } + #endif // USE_TURBOMODULE surfacePresenterBridgeAdapter = RTACreateSurfacePresenterBridgeAdapter(bridge) self.bridge = bridge diff --git a/ios/ReactTestApp/ReactTestApp-Bridging-Header.h b/ios/ReactTestApp/ReactTestApp-Bridging-Header.h index 97ae52443..eca59367f 100644 --- a/ios/ReactTestApp/ReactTestApp-Bridging-Header.h +++ b/ios/ReactTestApp/ReactTestApp-Bridging-Header.h @@ -27,6 +27,7 @@ #import "React+Compatibility.h" #import "React+Fabric.h" +#import "React+TurboModule.h" #import "UIViewController+ReactTestApp.h" // Generated by `validate-manifest.js` diff --git a/ios/pod_helpers.rb b/ios/pod_helpers.rb index dbc022604..44b0407e0 100644 --- a/ios/pod_helpers.rb +++ b/ios/pod_helpers.rb @@ -1,3 +1,9 @@ +def fabric_enabled?(options, react_native_version) + return true if new_architecture_enabled?(options, react_native_version) + + react_native_version >= 6800 && options[:fabric_enabled] +end + def find_file(file_name, current_dir) return if current_dir.expand_path.to_s == '/' @@ -7,6 +13,10 @@ def find_file(file_name, current_dir) find_file(file_name, current_dir.parent) end +def new_architecture_enabled?(options, react_native_version) + react_native_version >= 6800 && ENV.fetch('RCT_NEW_ARCH_ENABLED', options[:turbomodule_enabled]) +end + def resolve_module(request) @module_cache ||= {} return @module_cache[request] if @module_cache.key?(request) diff --git a/ios/test_app.rb b/ios/test_app.rb index a5a5c778c..68fe2db40 100644 --- a/ios/test_app.rb +++ b/ios/test_app.rb @@ -255,7 +255,8 @@ def make_project!(xcodeproj, project_root, target_platform, options) build_settings['PRODUCT_BUILD_NUMBER'] = build_number || '1' supports_flipper = target_platform == :ios && flipper_enabled? - use_fabric = options[:fabric_enabled] && rn_version >= 6800 + use_fabric = fabric_enabled?(options, rn_version) + use_turbomodule = new_architecture_enabled?(options, rn_version) app_project = Xcodeproj::Project.open(xcodeproj_dst) app_project.native_targets.each do |target| @@ -271,6 +272,11 @@ def make_project!(xcodeproj, project_root, target_platform, options) config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'FB_SONARKIT_ENABLED=1' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'USE_FLIPPER=1' end + if use_turbomodule + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'FOLLY_NO_CONFIG=1' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'RCT_NEW_ARCH_ENABLED=1' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'USE_TURBOMODULE=1' + end build_settings.each do |setting, value| config.build_settings[setting] = value @@ -282,6 +288,7 @@ def make_project!(xcodeproj, project_root, target_platform, options) config.build_settings['OTHER_SWIFT_FLAGS'] << '-DFB_SONARKIT_ENABLED' config.build_settings['OTHER_SWIFT_FLAGS'] << '-DUSE_FLIPPER' end + config.build_settings['OTHER_SWIFT_FLAGS'] << '-DUSE_TURBOMODULE' if use_turbomodule if single_app.is_a? String config.build_settings['OTHER_SWIFT_FLAGS'] << '-DENABLE_SINGLE_APP_MODE' end @@ -312,6 +319,8 @@ def make_project!(xcodeproj, project_root, target_platform, options) :ios => config.resolve_build_setting('IPHONEOS_DEPLOYMENT_TARGET'), :macos => config.resolve_build_setting('MACOSX_DEPLOYMENT_TARGET'), }, + :use_fabric => use_fabric, + :use_turbomodule => use_turbomodule, } end @@ -323,6 +332,8 @@ def use_test_app_internal!(target_platform, options) project_target = make_project!(xcodeproj, project_root, target_platform, options) xcodeproj_dst, platforms = project_target.values_at(:xcodeproj_path, :platforms) + install! 'cocoapods', :deterministic_uuids => false if project_target[:use_turbomodule] + require_relative(autolink_script_path) begin diff --git a/ios/use_react_native-0.68.rb b/ios/use_react_native-0.68.rb index 4e651316c..6b3922971 100644 --- a/ios/use_react_native-0.68.rb +++ b/ios/use_react_native-0.68.rb @@ -2,16 +2,10 @@ require_relative('pod_helpers') -def include_react_native!(options) - fabric_enabled = options[:fabric_enabled] - react_native = options[:path] - flipper_versions = options[:rta_flipper_versions] - project_root = options[:rta_project_root] - target_platform = options[:rta_target_platform] +def use_new_architecture!(options) + new_arch_enabled = new_architecture_enabled?(options, 10_000_000) - require_relative(File.join(project_root, react_native, 'scripts', 'react_native_pods')) - - if fabric_enabled + if new_arch_enabled || options[:fabric_enabled] Pod::UI.warn( 'As of writing, Fabric is still experimental and subject to change. ' \ 'For more information, please see ' \ @@ -20,6 +14,29 @@ def include_react_native!(options) ENV['RCT_NEW_ARCH_ENABLED'] = '1' end + return unless new_arch_enabled + + Pod::UI.warn( + 'As of writing, TurboModule is still experimental and subject to change. ' \ + 'For more information, please see ' \ + 'https://reactnative.dev/docs/next/new-architecture-app-modules-ios.' + ) + # At the moment, Fabric and TurboModule code are intertwined. We need to + # enable Fabric for some code that TurboModule relies on. + options[:fabric_enabled] = true + options[:turbomodule_enabled] = true + ENV['RCT_NEW_ARCH_ENABLED'] = '1' +end + +def include_react_native!(options) + react_native = options[:path] + flipper_versions = options[:rta_flipper_versions] + project_root = options[:rta_project_root] + target_platform = options[:rta_target_platform] + + require_relative(File.join(project_root, react_native, 'scripts', 'react_native_pods')) + + use_new_architecture!(options) use_flipper!(flipper_versions) if target_platform == :ios && flipper_versions use_react_native!(options) diff --git a/package.json b/package.json index 635f48a80..dbab5b6ca 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,7 @@ "/*.{gradle,js,md,podspec,rb}", "/android/**/*.gradle", "/android/app/lint.xml", - "/android/app/src/**/compat", - "/android/app/src/{fabric,flipper,main,no-fabric}", + "/android/app/src/!(test)", "/android/support/src", "/common", "/example/.gitignore", @@ -64,7 +63,7 @@ "lint:swift": "swiftlint", "set-react-version": "node scripts/set-react-version.mjs", "test:js": "jest", - "test:rb": "bundle exec ruby -Ilib:test test/test_test_app.rb" + "test:rb": "bundle exec ruby -Ilib:test -e \"Dir.glob('test/test_*.rb').each { |file| require_relative file }\"" }, "dependencies": { "ajv": "^8.0.0", diff --git a/scripts/configure.js b/scripts/configure.js index e16fbf613..882abb73b 100755 --- a/scripts/configure.js +++ b/scripts/configure.js @@ -474,6 +474,7 @@ const getConfig = (() => { "buildscript {", ` def androidTestAppDir = "${testAppRelPath}/android"`, ' apply(from: "${androidTestAppDir}/dependencies.gradle")', + ' apply(from: "${androidTestAppDir}/test-app-util.gradle")', "", " repositories {", " mavenCentral()", @@ -481,8 +482,12 @@ const getConfig = (() => { " }", "", " dependencies {", - ` classpath "com.android.tools.build:gradle:$androidPluginVersion"`, - ` classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"`, + ' classpath("com.android.tools.build:gradle:${androidPluginVersion}")', + "", + " if (isNewArchitectureEnabled(project)) {", + ' classpath("com.facebook.react:react-native-gradle-plugin")', + ' classpath("de.undercouch:gradle-download-task:5.1.0")', + " }", " }", "}", "", diff --git a/test-app.gradle b/test-app.gradle index 7c4a282cd..cef28d4ce 100644 --- a/test-app.gradle +++ b/test-app.gradle @@ -4,21 +4,36 @@ import org.gradle.initialization.DefaultSettings import java.nio.file.Paths -private static void applySettings(Settings settings) { - def projectDir = settings.findNodeModulesPath(settings.rootDir, "react-native-test-app") +// TODO: Remove when `@react-native-community/cli` 6.0+ is required. See also +// https://github.com/react-native-community/cli/commit/fa0d09b2c9be144bbdff526bb14f171d7ddca88e +private static void patchArgumentTypeMismatchError(String testAppDir, File rootDir) { + // We need to delegate this to a separate script to avoid running out of + // Java heap space. + String[] patch = ["node", "${testAppDir}/scripts/patch-cli-platform-android.js"] + Runtime.getRuntime().exec(patch, null, rootDir).waitFor() +} +def testAppDir = buildscript.sourceFile.getParent() +apply(from: "${testAppDir}/android/test-app-util.gradle") + +patchArgumentTypeMismatchError(testAppDir, rootDir) + +def cliAndroidDir = findNodeModulesPath("@react-native-community/cli-platform-android", rootDir) +apply(from: "${cliAndroidDir}/native_modules.gradle") + +ext.applyTestAppSettings = { DefaultSettings settings -> if (settings.buildReactNativeFromSource(settings.rootDir)) { - def buildFile = Paths.get("${projectDir}/android/react-native-build.gradle") + def buildFile = Paths.get("${testAppDir}/android/react-native-build.gradle") settings.rootProject.buildFileName = settings.rootDir.toPath().relativize(buildFile) - def reactNativeDir = settings.findNodeModulesPath(settings.rootDir, "react-native") + def reactNativeDir = findNodeModulesPath("react-native", settings.rootDir) settings.include(":ReactAndroid") settings.project(":ReactAndroid") - .projectDir = new File("${reactNativeDir}/ReactAndroid") + .projectDir = file("${reactNativeDir}/ReactAndroid") settings.include(":packages:react-native-codegen:android") settings.project(":packages:react-native-codegen:android") - .projectDir = new File("${reactNativeDir}/packages/react-native-codegen/android") + .projectDir = file("${reactNativeDir}/packages/react-native-codegen/android") settings.includeBuild("${reactNativeDir}/packages/react-native-codegen/android") settings.includeBuild("${reactNativeDir}/packages/react-native-gradle-plugin") @@ -28,38 +43,37 @@ private static void applySettings(Settings settings) { settings.include(":support") settings.project(":app") - .projectDir = new File("${projectDir}/android/app") + .projectDir = file("${testAppDir}/android/app") settings.project(":support") - .projectDir = new File("${projectDir}/android/support") -} + .projectDir = file("${testAppDir}/android/support") -// TODO: Remove when `@react-native-community/cli` 6.0+ is required. See also -// https://github.com/react-native-community/cli/commit/fa0d09b2c9be144bbdff526bb14f171d7ddca88e -private static void patchArgumentTypeMismatchError(String testAppDir, File rootDir) { - // We need to delegate this to a separate script to avoid running out of - // Java heap space. - String[] patch = ["node", "${testAppDir}/scripts/patch-cli-platform-android.js"] - Runtime.getRuntime().exec(patch, null, rootDir).waitFor() -} + def reactNativeGradlePlugin = + findNodeModulesPath("react-native-gradle-plugin", settings.rootDir) + if (reactNativeGradlePlugin != null) { + settings.includeBuild(reactNativeGradlePlugin) + } -def testAppDir = buildscript.sourceFile.getParent() -apply(from: "${testAppDir}/android/test-app-util.gradle") + if (isNewArchitectureEnabled(settings)) { + def reactNativeDir = findNodeModulesPath("react-native", settings.rootDir) -patchArgumentTypeMismatchError(testAppDir, rootDir) + settings.include(":ReactAndroid") + settings.project(":ReactAndroid") + .projectDir = file("${reactNativeDir}/ReactAndroid") -def cliAndroidDir = findNodeModulesPath(rootDir, "@react-native-community/cli-platform-android") -apply(from: "${cliAndroidDir}/native_modules.gradle") + settings.include(":ReactAndroid:hermes-engine") + settings.project(":ReactAndroid:hermes-engine") + .projectDir = file("${reactNativeDir}/ReactAndroid/hermes-engine") + } -ext.applyTestAppSettings = { DefaultSettings defaultSettings -> - applySettings(defaultSettings) - applyNativeModulesSettingsGradle(defaultSettings) + applyNativeModulesSettingsGradle(settings) } ext.applyTestAppModule = { Project project -> applyNativeModulesAppBuildGradle(project) - def isRntaProject = project.projectDir.getParent() != null && - (project.rootDir == file(project.projectDir.getParent())) + def isRntaProject = + project.projectDir.getParent() != null && + project.rootDir == file(project.projectDir.getParent()) def manifestFile = findFile("app.json") if (isRntaProject && manifestFile == null) { @@ -76,8 +90,10 @@ ext.applyTestAppModule = { Project project -> } else if (resources.containsKey("android")) { androidResources = resources["android"] } else { - throw new IllegalArgumentException("The \"resources\" property of the app.json has " + - "to be either a list of paths, or must contain a nested list with the \"android\" key") + throw new IllegalArgumentException( + "The `resources` property in `app.json` has to be either a list " + + "of paths, or must contain a nested list with the `android` key" + ) } def projectRoot = manifestFile.getParent() diff --git a/test/android-test-app/test-app-util.test.js b/test/android-test-app/test-app-util.test.js index fa9847200..00073a376 100644 --- a/test/android-test-app/test-app-util.test.js +++ b/test/android-test-app/test-app-util.test.js @@ -16,8 +16,8 @@ describe("test-app-util", () => { " }", "", " dependencies {", - ' classpath "com.android.tools.build:gradle:$androidPluginVersion"', - ' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"', + ' classpath("com.android.tools.build:gradle:${androidPluginVersion}")', + ' classpath("de.undercouch:gradle-download-task:5.1.0")', " }", "}", "", @@ -39,7 +39,7 @@ describe("test-app-util", () => { * @returns */ function toVersionNumber(version) { - const [major, minor, patch] = version.split("."); + const [major, minor, patch] = version.split("-")[0].split("."); return Number(major) * 10000 + Number(minor) * 100 + Number(patch); } @@ -203,11 +203,11 @@ describe("test-app-util", () => { expect(stdout).toContain("getFlipperVersion() = null"); }); - test("getReactNativeVersionNumber() returns react-native version as a number", async () => { + test("getPackageVersionNumber() returns `react-native` version as a number", async () => { const { status, stdout } = await runGradle({ "build.gradle": [ ...buildGradle, - 'println("getReactNativeVersionNumber() = " + ext.getReactNativeVersionNumber(rootDir))', + 'println("getPackageVersionNumber() = " + ext.getPackageVersionNumber("react-native", rootDir))', ], }); @@ -216,22 +216,22 @@ describe("test-app-util", () => { const { version } = require("react-native/package.json"); expect(stdout).toContain( - `getReactNativeVersionNumber() = ${toVersionNumber(version)}` + `getPackageVersionNumber() = ${toVersionNumber(version)}` ); }); - test("getReactNativeVersionNumber() handles pre-release identifiers", async () => { + test("getPackageVersionNumber() handles pre-release identifiers", async () => { const { status, stdout } = await runGradle({ "build.gradle": [ ...buildGradle, - 'println("getReactNativeVersionNumber() = " + ext.getReactNativeVersionNumber(file("${rootDir}/pre-release-version")))', + 'println("getPackageVersionNumber() = " + ext.getPackageVersionNumber("react-native", file("${rootDir}/pre-release-version")))', ], "pre-release-version/node_modules/react-native/package.json": JSON.stringify({ name: "react-native", version: "1.2.3-053c2b4be" }), }); expect(status).toBe(0); - expect(stdout).toContain(`getReactNativeVersionNumber() = 10203`); + expect(stdout).toContain(`getPackageVersionNumber() = 10203`); }); test("getSigningConfigs() fails if `storeFile` is missing", async () => { diff --git a/test/configure/__snapshots__/gatherConfig.test.js.snap b/test/configure/__snapshots__/gatherConfig.test.js.snap index 805c3db53..7484b4912 100644 --- a/test/configure/__snapshots__/gatherConfig.test.js.snap +++ b/test/configure/__snapshots__/gatherConfig.test.js.snap @@ -72,6 +72,7 @@ Object { "android/build.gradle": "buildscript { def androidTestAppDir = \\"../../android\\" apply(from: \\"\${androidTestAppDir}/dependencies.gradle\\") + apply(from: \\"\${androidTestAppDir}/test-app-util.gradle\\") repositories { mavenCentral() @@ -79,8 +80,12 @@ Object { } dependencies { - classpath \\"com.android.tools.build:gradle:$androidPluginVersion\\" - classpath \\"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion\\" + classpath(\\"com.android.tools.build:gradle:\${androidPluginVersion}\\") + + if (isNewArchitectureEnabled(project)) { + classpath(\\"com.facebook.react:react-native-gradle-plugin\\") + classpath(\\"de.undercouch:gradle-download-task:5.1.0\\") + } } } @@ -369,6 +374,7 @@ Object { "android/build.gradle": "buildscript { def androidTestAppDir = \\"../../android\\" apply(from: \\"\${androidTestAppDir}/dependencies.gradle\\") + apply(from: \\"\${androidTestAppDir}/test-app-util.gradle\\") repositories { mavenCentral() @@ -376,8 +382,12 @@ Object { } dependencies { - classpath \\"com.android.tools.build:gradle:$androidPluginVersion\\" - classpath \\"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion\\" + classpath(\\"com.android.tools.build:gradle:\${androidPluginVersion}\\") + + if (isNewArchitectureEnabled(project)) { + classpath(\\"com.facebook.react:react-native-gradle-plugin\\") + classpath(\\"de.undercouch:gradle-download-task:5.1.0\\") + } } } @@ -515,6 +525,7 @@ Object { "android/build.gradle": "buildscript { def androidTestAppDir = \\"../../android\\" apply(from: \\"\${androidTestAppDir}/dependencies.gradle\\") + apply(from: \\"\${androidTestAppDir}/test-app-util.gradle\\") repositories { mavenCentral() @@ -522,8 +533,12 @@ Object { } dependencies { - classpath \\"com.android.tools.build:gradle:$androidPluginVersion\\" - classpath \\"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion\\" + classpath(\\"com.android.tools.build:gradle:\${androidPluginVersion}\\") + + if (isNewArchitectureEnabled(project)) { + classpath(\\"com.facebook.react:react-native-gradle-plugin\\") + classpath(\\"de.undercouch:gradle-download-task:5.1.0\\") + } } } diff --git a/test/test_pod_helpers.rb b/test/test_pod_helpers.rb new file mode 100644 index 000000000..aa37aed7e --- /dev/null +++ b/test/test_pod_helpers.rb @@ -0,0 +1,44 @@ +require('minitest/autorun') + +require_relative('../ios/pod_helpers') + +class TestPodHelpers < Minitest::Test + def test_fabric_enabled? + ENV.delete('RCT_NEW_ARCH_ENABLED') + + refute(fabric_enabled?({}, 0)) + refute(fabric_enabled?({}, 6800)) + + # Fabric is first publicly available in 0.68 + refute(fabric_enabled?({ :fabric_enabled => true }, 6799)) + assert(fabric_enabled?({ :fabric_enabled => true }, 6800)) + + # TurboModule implies Fabric + refute(fabric_enabled?({ :turbomodule_enabled => true }, 6799)) + assert(fabric_enabled?({ :turbomodule_enabled => true }, 6800)) + + # `RCT_NEW_ARCH_ENABLED` enables everything + ENV['RCT_NEW_ARCH_ENABLED'] = '1' + refute(fabric_enabled?({}, 6799)) + assert(fabric_enabled?({}, 6800)) + end + + def test_new_architecture_enabled? + ENV.delete('RCT_NEW_ARCH_ENABLED') + + refute(new_architecture_enabled?({}, 0)) + refute(new_architecture_enabled?({}, 6800)) + + # New architecture is first publicly available in 0.68 + refute(new_architecture_enabled?({ :turbomodule_enabled => true }, 6799)) + assert(new_architecture_enabled?({ :turbomodule_enabled => true }, 6800)) + + # Fabric does not imply TurboModule + refute(new_architecture_enabled?({ :fabric_enabled => true }, 6800)) + + # `RCT_NEW_ARCH_ENABLED` enables everything + ENV['RCT_NEW_ARCH_ENABLED'] = '1' + refute(new_architecture_enabled?({}, 6799)) + assert(new_architecture_enabled?({}, 6800)) + end +end diff --git a/yarn.lock b/yarn.lock index 4289150f7..fa6939eee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5514,6 +5514,7 @@ __metadata: react: 17.0.2 react-native: ^0.68.2 react-native-macos: ^0.68.3 + react-native-safe-area-context: ^4.3.1 react-native-test-app: "workspace:." react-native-windows: ^0.68.8 peerDependencies: @@ -10692,6 +10693,16 @@ fsevents@^2.3.2: languageName: node linkType: hard +"react-native-safe-area-context@npm:^4.3.1": + version: 4.3.1 + resolution: "react-native-safe-area-context@npm:4.3.1" + peerDependencies: + react: "*" + react-native: "*" + checksum: 34a4bc60448b0bc6bfa3e952b00a12754cc30ce48c07540a6618179f2c170aa2db0919ac452376322778ed34c4c56fc5d40e17c2b74157c9a134d2b766143ca3 + languageName: node + linkType: hard + "react-native-test-app@workspace:.": version: 0.0.0-use.local resolution: "react-native-test-app@workspace:."