Allow to opt-in to use the new Hermes on Android (#53580)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53580

Changelog: [ANDROID][ADDED] Added opt-in to use the new Hermes

Reviewed By: cortinico

Differential Revision: D81035114

fbshipit-source-id: d01e44190941d161cf641ec4e03ed487aff18dd8
This commit is contained in:
Jakub Piasecki
2025-09-04 07:42:12 -07:00
committed by Facebook GitHub Bot
parent e9cdc308b4
commit 3e9990f860
12 changed files with 149 additions and 45 deletions
+3
View File
@@ -12,3 +12,6 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# Controls whether to use Hermes from nightly builds. This will speed up builds
# but should NOT be turned on for CI or release builds.
react.internal.useHermesNightly=false
# Controls whether to use Hermes 1.0. Clean and rebuild when changing.
hermesV1Enabled=false
@@ -28,6 +28,7 @@ import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings
import com.facebook.react.utils.JdkConfiguratorUtils.configureJavaToolChains
import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk
import com.facebook.react.utils.ProjectUtils.isHermesV1Enabled
import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
import com.facebook.react.utils.findPackageJsonFile
import java.io.File
@@ -54,6 +55,10 @@ class ReactPlugin : Plugin<Project> {
project,
)
if (project.rootProject.isHermesV1Enabled != rootExtension.hermesV1Enabled.get()) {
rootExtension.hermesV1Enabled.set(project.rootProject.isHermesV1Enabled)
}
// App Only Configuration
project.pluginManager.withPlugin("com.android.application") {
// We wire the root extension with the values coming from the app (either user populated or
@@ -67,9 +72,11 @@ class ReactPlugin : Plugin<Project> {
val reactNativeDir = extension.reactNativeDir.get().asFile
val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")
val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)
val versionString = versionAndGroupStrings.first
val groupString = versionAndGroupStrings.second
configureDependencies(project, versionString, groupString)
val hermesV1Enabled =
if (project.rootProject.hasProperty("hermesV1Enabled"))
project.rootProject.findProperty("hermesV1Enabled") == "true"
else false
configureDependencies(project, versionAndGroupStrings, hermesV1Enabled)
configureRepositories(project)
}
@@ -11,6 +11,7 @@ import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
/**
* A private extension we set on the rootProject to make easier to share values at execution time
@@ -57,4 +58,6 @@ abstract class PrivateReactExtension @Inject constructor(project: Project) {
val codegenDir: DirectoryProperty =
objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen"))
val hermesV1Enabled: Property<Boolean> = objects.property(Boolean::class.java).convention(false)
}
@@ -7,12 +7,15 @@
package com.facebook.react.utils
import com.facebook.react.utils.PropertyUtils.DEFAULT_INTERNAL_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.EXCLUSIVE_ENTEPRISE_REPOSITORY
import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY
import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY_DEFAULT
import com.facebook.react.utils.PropertyUtils.INTERNAL_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_VERSION_NAME
import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO
import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_USE_HERMES_NIGHTLY
import com.facebook.react.utils.PropertyUtils.INTERNAL_VERSION_NAME
import com.facebook.react.utils.PropertyUtils.SCOPED_EXCLUSIVE_ENTEPRISE_REPOSITORY
@@ -25,6 +28,13 @@ import org.gradle.api.artifacts.repositories.MavenArtifactRepository
internal object DependencyUtils {
internal data class Coordinates(
val versionString: String,
val hermesVersionString: String,
val reactGroupString: String = DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP,
val hermesGroupString: String = DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP,
)
/**
* This method takes care of configuring the repositories{} block for both the app and all the 3rd
* party libraries which are auto-linked.
@@ -95,14 +105,15 @@ internal object DependencyUtils {
* This method takes care of configuring the resolution strategy for both the app and all the 3rd
* party libraries which are auto-linked. Specifically it takes care of:
* - Forcing the react-android/hermes-android version to the one specified in the package.json
* - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android`.
* - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android`
* - Selecting between the classic Hermes and Hermes V1
*/
fun configureDependencies(
project: Project,
versionString: String,
groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP,
coordinates: Coordinates,
hermesV1Enabled: Boolean = false,
) {
if (versionString.isBlank()) return
if (coordinates.versionString.isBlank() || coordinates.hermesVersionString.isBlank()) return
project.rootProject.allprojects { eachProject ->
eachProject.configurations.all { configuration ->
// Here we set a dependencySubstitution for both react-native and hermes-engine as those
@@ -110,53 +121,61 @@ internal object DependencyUtils {
// This allows users to import libraries that are still using
// implementation("com.facebook.react:react-native:+") and resolve the right dependency.
configuration.resolutionStrategy.dependencySubstitution {
getDependencySubstitutions(versionString, groupString).forEach { (module, dest, reason) ->
getDependencySubstitutions(coordinates, hermesV1Enabled).forEach { (module, dest, reason)
->
it.substitute(it.module(module)).using(it.module(dest)).because(reason)
}
}
configuration.resolutionStrategy.force(
"${groupString}:react-android:${versionString}",
"${coordinates.reactGroupString}:react-android:${coordinates.versionString}",
)
if (!(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean()) {
// Contributors only: The hermes-engine version is forced only if the user has
// not opted into using nightlies for local development.
configuration.resolutionStrategy.force("${groupString}:hermes-android:${versionString}")
configuration.resolutionStrategy.force(
"${coordinates.reactGroupString}:hermes-android:${coordinates.versionString}"
)
}
}
}
}
internal fun getDependencySubstitutions(
versionString: String,
groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP,
coordinates: Coordinates,
hermesV1Enabled: Boolean = false,
): List<Triple<String, String, String>> {
// TODO: T231755027 update coordinates and versioning
val dependencySubstitution = mutableListOf<Triple<String, String, String>>()
val hermesVersionString =
if (hermesV1Enabled)
"${coordinates.hermesGroupString}:hermes-android:${coordinates.versionString}"
else "${coordinates.reactGroupString}:hermes-android:${coordinates.versionString}"
dependencySubstitution.add(
Triple(
"com.facebook.react:react-native",
"${groupString}:react-android:${versionString}",
"${coordinates.reactGroupString}:react-android:${coordinates.versionString}",
"The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210.",
)
)
dependencySubstitution.add(
Triple(
"com.facebook.react:hermes-engine",
"${groupString}:hermes-android:${versionString}",
hermesVersionString,
"The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210.",
)
)
if (groupString != DEFAULT_INTERNAL_PUBLISHING_GROUP) {
if (coordinates.reactGroupString != DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP) {
dependencySubstitution.add(
Triple(
"com.facebook.react:react-android",
"${groupString}:react-android:${versionString}",
"${coordinates.reactGroupString}:react-android:${coordinates.versionString}",
"The react-android dependency was modified to use the correct Maven group.",
)
)
dependencySubstitution.add(
Triple(
"com.facebook.react:hermes-android",
"${groupString}:hermes-android:${versionString}",
hermesVersionString,
"The hermes-android dependency was modified to use the correct Maven group.",
)
)
@@ -164,10 +183,14 @@ internal object DependencyUtils {
return dependencySubstitution
}
fun readVersionAndGroupStrings(propertiesFile: File): Pair<String, String> {
fun readVersionAndGroupStrings(propertiesFile: File): Coordinates {
val reactAndroidProperties = Properties()
propertiesFile.inputStream().use { reactAndroidProperties.load(it) }
val versionStringFromFile = (reactAndroidProperties[INTERNAL_VERSION_NAME] as? String).orEmpty()
// TODO: T231755027 update HERMES_VERSION_NAME in gradle.properties to point to the correct
// hermes version
val hermesVersionStringFromFile =
(reactAndroidProperties[INTERNAL_HERMES_VERSION_NAME] as? String).orEmpty()
// If on a nightly, we need to fetch the -SNAPSHOT artifact from Sonatype.
val versionString =
if (versionStringFromFile.startsWith("0.0.0") || "-nightly-" in versionStringFromFile) {
@@ -176,10 +199,18 @@ internal object DependencyUtils {
versionStringFromFile
}
// Returns Maven group for repos using different group for Maven artifacts
val groupString =
reactAndroidProperties[INTERNAL_PUBLISHING_GROUP] as? String
?: DEFAULT_INTERNAL_PUBLISHING_GROUP
return Pair(versionString, groupString)
val reactGroupString =
reactAndroidProperties[INTERNAL_REACT_PUBLISHING_GROUP] as? String
?: DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP
val hermesGroupString =
reactAndroidProperties[INTERNAL_HERMES_PUBLISHING_GROUP] as? String
?: DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP
return Coordinates(
versionString,
hermesVersionStringFromFile,
reactGroupString,
hermesGroupString,
)
}
fun Project.mavenRepoFromUrl(
@@ -13,9 +13,11 @@ import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat
import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat
import com.facebook.react.utils.PropertyUtils.EDGE_TO_EDGE_ENABLED
import com.facebook.react.utils.PropertyUtils.HERMES_ENABLED
import com.facebook.react.utils.PropertyUtils.HERMES_V1_ENABLED
import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES
import com.facebook.react.utils.PropertyUtils.SCOPED_EDGE_TO_EDGE_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_V1_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES
import com.facebook.react.utils.PropertyUtils.SCOPED_USE_THIRD_PARTY_JSC
import com.facebook.react.utils.PropertyUtils.USE_THIRD_PARTY_JSC
@@ -68,6 +70,13 @@ internal object ProjectUtils {
(project.hasProperty(SCOPED_USE_THIRD_PARTY_JSC) &&
project.property(SCOPED_USE_THIRD_PARTY_JSC).toString().toBoolean())
internal val Project.isHermesV1Enabled: Boolean
get() =
(project.hasProperty(HERMES_V1_ENABLED) &&
project.property(HERMES_V1_ENABLED).toString().toBoolean()) ||
(project.hasProperty(SCOPED_HERMES_V1_ENABLED) &&
project.property(SCOPED_HERMES_V1_ENABLED).toString().toBoolean())
internal fun Project.needsCodegenFromPackageJson(rootProperty: DirectoryProperty): Boolean {
val parsedPackageJson = readPackageJsonFile(this, rootProperty)
return needsCodegenFromPackageJson(parsedPackageJson)
@@ -18,6 +18,10 @@ object PropertyUtils {
const val HERMES_ENABLED = "hermesEnabled"
const val SCOPED_HERMES_ENABLED = "react.hermesEnabled"
/** Public property that toggles Hermes V1 */
const val HERMES_V1_ENABLED = "hermesV1Enabled"
const val SCOPED_HERMES_V1_ENABLED = "react.hermesV1Enabled"
/** Public property that toggles edge-to-edge */
const val EDGE_TO_EDGE_ENABLED = "edgeToEdgeEnabled"
const val SCOPED_EDGE_TO_EDGE_ENABLED = "react.edgeToEdgeEnabled"
@@ -68,9 +72,13 @@ object PropertyUtils {
const val INTERNAL_USE_HERMES_NIGHTLY = "react.internal.useHermesNightly"
/** Internal property used to override the publishing group for the React Native artifacts. */
const val INTERNAL_PUBLISHING_GROUP = "react.internal.publishingGroup"
const val DEFAULT_INTERNAL_PUBLISHING_GROUP = "com.facebook.react"
const val INTERNAL_REACT_PUBLISHING_GROUP = "react.internal.publishingGroup"
const val INTERNAL_HERMES_PUBLISHING_GROUP = "react.internal.hermesPublishingGroup"
const val DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP = "com.facebook.react"
const val DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP = "com.facebook.hermes"
/** Internal property used to control the version name of React Native */
const val INTERNAL_VERSION_NAME = "VERSION_NAME"
/** Internal property used to control the version name of Hermes Engine */
const val INTERNAL_HERMES_VERSION_NAME = "HERMES_VERSION_NAME"
}
@@ -271,11 +271,13 @@ class DependencyUtilsTest {
.isEqualTo(2)
}
// TODO: T236767053
@Test
fun configureDependencies_withEmptyVersion_doesNothing() {
val project = createProject()
configureDependencies(project, "")
configureDependencies(project, DependencyUtils.Coordinates("", ""))
assertThat(project.configurations.first().resolutionStrategy.forcedModules.isEmpty()).isTrue()
}
@@ -284,7 +286,7 @@ class DependencyUtilsTest {
fun configureDependencies_withVersionString_appliesResolutionStrategy() {
val project = createProject()
configureDependencies(project, "1.2.3")
configureDependencies(project, DependencyUtils.Coordinates("1.2.3", "1.2.3"))
val forcedModules = project.configurations.first().resolutionStrategy.forcedModules
assertThat(forcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" })
@@ -301,7 +303,7 @@ class DependencyUtilsTest {
appProject.plugins.apply("com.android.application")
libProject.plugins.apply("com.android.library")
configureDependencies(appProject, "1.2.3")
configureDependencies(appProject, DependencyUtils.Coordinates("1.2.3", "1.2.3"))
val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules
val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules
@@ -323,7 +325,10 @@ class DependencyUtilsTest {
appProject.plugins.apply("com.android.application")
libProject.plugins.apply("com.android.library")
configureDependencies(appProject, "1.2.3", "io.github.test")
configureDependencies(
appProject,
DependencyUtils.Coordinates("1.2.3", "1.2.3", "io.github.test"),
)
val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules
val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules
@@ -339,7 +344,8 @@ class DependencyUtilsTest {
@Test
fun getDependencySubstitutions_withDefaultGroup_substitutesCorrectly() {
val dependencySubstitutions = getDependencySubstitutions("0.42.0")
val dependencySubstitutions =
getDependencySubstitutions(DependencyUtils.Coordinates("0.42.0", "0.42.0"))
assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first)
assertThat("com.facebook.react:react-android:0.42.0")
@@ -359,7 +365,10 @@ class DependencyUtilsTest {
@Test
fun getDependencySubstitutions_withCustomGroup_substitutesCorrectly() {
val dependencySubstitutions = getDependencySubstitutions("0.42.0", "io.github.test")
val dependencySubstitutions =
getDependencySubstitutions(
DependencyUtils.Coordinates("0.42.0", "0.42.0", "io.github.test")
)
assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first)
assertThat("io.github.test:react-android:0.42.0").isEqualTo(dependencySubstitutions[0].second)
@@ -396,7 +405,7 @@ class DependencyUtilsTest {
)
}
val versionString = readVersionAndGroupStrings(propertiesFile).first
val versionString = readVersionAndGroupStrings(propertiesFile).versionString
assertThat(versionString).isEqualTo("1000.0.0")
}
@@ -414,7 +423,7 @@ class DependencyUtilsTest {
)
}
val versionString = readVersionAndGroupStrings(propertiesFile).first
val versionString = readVersionAndGroupStrings(propertiesFile).versionString
assertThat(versionString).isEqualTo("0.0.0-20221101-2019-cfe811ab1-SNAPSHOT")
}
@@ -431,7 +440,7 @@ class DependencyUtilsTest {
)
}
val versionString = readVersionAndGroupStrings(propertiesFile).first
val versionString = readVersionAndGroupStrings(propertiesFile).versionString
assertThat(versionString).isEqualTo("")
}
@@ -448,7 +457,7 @@ class DependencyUtilsTest {
)
}
val versionString = readVersionAndGroupStrings(propertiesFile).first
val versionString = readVersionAndGroupStrings(propertiesFile).versionString
assertThat(versionString).isEqualTo("")
}
@@ -465,7 +474,7 @@ class DependencyUtilsTest {
)
}
val groupString = readVersionAndGroupStrings(propertiesFile).second
val groupString = readVersionAndGroupStrings(propertiesFile).reactGroupString
assertThat(groupString).isEqualTo("io.github.test")
}
@@ -482,7 +491,7 @@ class DependencyUtilsTest {
)
}
val groupString = readVersionAndGroupStrings(propertiesFile).second
val groupString = readVersionAndGroupStrings(propertiesFile).reactGroupString
assertThat(groupString).isEqualTo("com.facebook.react")
}
@@ -39,6 +39,9 @@ val downloadsDir =
val thirdPartyNdkDir = File("$buildDir/third-party-ndk")
val reactNativeRootDir = projectDir.parent
val hermesV1Enabled =
rootProject.extensions.getByType(PrivateReactExtension::class.java).hermesV1Enabled.get()
// We put the publishing version from gradle.properties inside ext. so other
// subprojects can access it as well.
extra["publishing_version"] = project.findProperty("VERSION_NAME")?.toString()!!
@@ -565,6 +568,10 @@ android {
"-DCMAKE_POLICY_DEFAULT_CMP0069=NEW",
)
if (hermesV1Enabled) {
arguments("-DHERMES_V1_ENABLED=1")
}
targets(
"reactnative",
"jsi",
@@ -1,5 +1,7 @@
VERSION_NAME=1000.0.0
HERMES_VERSION_NAME=1000.0.0
react.internal.publishingGroup=com.facebook.react
react.internal.hermesPublishingGroup=com.facebook.hermes
android.useAndroidX=true
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.tasks.internal.*
import de.undercouch.gradle.tasks.download.Download
import org.apache.tools.ant.taskdefs.condition.Os
@@ -50,6 +51,8 @@ fun getSDKManagerPath(): String {
}
}
val hermesV1Enabled =
rootProject.extensions.getByType(PrivateReactExtension::class.java).hermesV1Enabled.get()
val reactNativeRootDir = project(":packages:react-native:ReactAndroid").projectDir.parent
val customDownloadDir = System.getenv("REACT_NATIVE_DOWNLOADS_DIR")
val downloadsDir =
@@ -79,12 +82,21 @@ val hermesBuildOutputFileTree =
fileTree(hermesBuildDir.toString())
.include("**/*.cmake", "**/*.marks", "**/compiler_depends.ts", "**/Makefile", "**/link.txt")
var hermesVersion = "main"
val hermesVersionFile = File(reactNativeRootDir, "sdks/.hermesversion")
val hermesVersionProvider: Provider<String> =
providers.provider {
var hermesVersion = if (hermesV1Enabled) "250829098.0.0-stable" else "main"
val hermesVersionFile =
File(
reactNativeRootDir,
if (hermesV1Enabled) "sdks/.hermesv1version" else "\"sdks/.hermesversion\"",
)
if (hermesVersionFile.exists()) {
hermesVersion = hermesVersionFile.readText()
}
if (hermesVersionFile.exists()) {
hermesVersion = hermesVersionFile.readText()
}
hermesVersion
}
val ndkBuildJobs = Runtime.getRuntime().availableProcessors().toString()
val prefabHeadersDir = File("$buildDir/prefab-headers")
@@ -95,7 +107,11 @@ val jsiDir = File(reactNativeRootDir, "ReactCommon/jsi")
val downloadHermesDest = File(downloadsDir, "hermes.tar.gz")
val downloadHermes by
tasks.registering(Download::class) {
src("https://github.com/facebook/hermes/tarball/${hermesVersion}")
src(
providers.provider {
"https://github.com/facebook/hermes/tarball/${hermesVersionProvider.get()}"
}
)
onlyIfModified(true)
overwrite(true)
quiet(true)
@@ -151,6 +167,7 @@ val configureBuildForHermes by
"-B",
hermesBuildDir.toString(),
"-DJSI_DIR=" + jsiDir.absolutePath,
"-DCMAKE_BUILD_TYPE=Release",
)
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
cmakeCommandLine = cmakeCommandLine + "-GNMake Makefiles"
@@ -295,7 +312,11 @@ android {
// Therefore we're passing as build type Release, to provide a faster build.
// This has the (unlucky) side effect of letting AGP call the build
// tasks `configureCMakeRelease` while is actually building the debug flavor.
arguments("-DCMAKE_BUILD_TYPE=Release")
arguments(
"-DCMAKE_BUILD_TYPE=Release",
// For debug builds, explicitly enable the Hermes Debugger.
"-DHERMES_ENABLE_DEBUGGER=True",
)
}
}
}
@@ -22,6 +22,8 @@ file(TO_CMAKE_PATH "${REACT_ANDROID_DIR}" REACT_ANDROID_DIR)
file(TO_CMAKE_PATH "${REACT_BUILD_DIR}" REACT_BUILD_DIR)
file(TO_CMAKE_PATH "${REACT_COMMON_DIR}" REACT_COMMON_DIR)
set(HERMES_V1_ENABLED OFF CACHE BOOL "Build with support for Hermes v1")
# If you have ccache installed, we're going to honor it.
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
@@ -155,6 +155,8 @@ function updateTestFiles(
async function updateGradleFile(version /*: string */) /*: Promise<void> */ {
const contents = await fs.readFile(GRADLE_FILE_PATH, 'utf-8');
// TODO: T231755027 set HERMES_VERSION_NAME
return fs.writeFile(
GRADLE_FILE_PATH,
contents.replace(/^VERSION_NAME=.*/, `VERSION_NAME=${version}`),