Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ abstract class BundleHermesCTask : DefaultTask() {
runCommand(bundleCommand)

if (hermesEnabled.get()) {
val detectedHermesCommand = detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get())
val detectedHermesCommand =
detectOSAwareHermesCommand(
root.get().asFile,
hermesCommand.get(),
reactNativeDir.get().asFile,
)
val bytecodeFile = File("${bundleFile}.hbc")
val outputSourceMap = resolveOutputSourceMap(bundleAssetFilename)
val compilerSourceMap = resolveCompilerSourceMap(bundleAssetFilename)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ internal fun detectedCliFile(config: ReactExtension): File =
* 4. Fails otherwise
*/
internal fun detectedHermesCommand(config: ReactExtension): String =
detectOSAwareHermesCommand(config.root.get().asFile, config.hermesCommand.get())
detectOSAwareHermesCommand(
config.root.get().asFile,
config.hermesCommand.get(),
config.reactNativeDir.get().asFile,
)

private fun detectEntryFile(
entryFile: File?,
Expand Down Expand Up @@ -120,16 +124,20 @@ private fun detectCliFile(
/**
* Computes the `hermesc` command location. The Algo follows this order:
* 1. The path provided by the `hermesCommand` config in the `react` Gradle extension
* 2. The file located in `node_modules/react-native/sdks/hermes/build/bin/hermesc`. This will be
* 2. The file located in
* `node_modules/react-native/ReactAndroid/hermes-engine/build/hermes/bin/hermesc`. This will be
* used if the user is building Hermes from source.
* 3. The file located in `node_modules/hermes-compiler/%OS-BIN%/hermesc` where `%OS-BIN%` is
* substituted with the correct OS arch. This is used when Hermes V1 is consumed as a prebuilt
* 3. The built from source hermesc located under the configured `reactNativeDir`.
* 4. The file located in `node_modules/hermes-compiler/hermesc/%OS-BIN%/hermesc` where `%OS-BIN%` is
* substituted with the correct OS arch. This is used when Hermes is consumed as a prebuilt
* package via the `hermes-compiler` npm package.
* 4. Fails otherwise
* 5. The prebuilt `hermes-compiler` package located next to the configured `reactNativeDir`.
* 6. Fails otherwise
*/
internal fun detectOSAwareHermesCommand(
projectRoot: File,
hermesCommand: String,
reactNativeDir: File? = null,
): String { // 1. If the project specifies a Hermes command, don't second guess it.
if (hermesCommand.isNotBlank()) {
val osSpecificHermesCommand =
Expand All @@ -144,31 +152,52 @@ internal fun detectOSAwareHermesCommand(
}

// 2. If the project is building hermes-engine from source, use hermesc from there
val builtHermesc =
getBuiltHermescFile(projectRoot, System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR"))
if (builtHermesc.exists()) {
return builtHermesc.cliPath(projectRoot)
}
val builtHermescCandidates =
listOfNotNull(
getBuiltHermescFile(projectRoot, System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR")),
reactNativeDir?.let { getBuiltHermescFileFromReactNativeDir(it) },
)
builtHermescCandidates.firstOrNull { it.exists() }?.let { return it.cliPath(projectRoot) }

val prebuiltHermesCandidates = getHermesCompilerCandidates(projectRoot, reactNativeDir)
prebuiltHermesCandidates.firstOrNull { it.exists() }?.let { return it.cliPath(projectRoot) }

error(
buildString {
appendLine("Couldn't determine Hermesc location.")
appendLine("Checked:")
(builtHermescCandidates + prebuiltHermesCandidates).forEach { appendLine("- ${it.path}") }
append(
"Please set `react.hermesCommand` to the path of the hermesc binary file, " +
"or make sure the `hermes-compiler` package is installed and resolvable from " +
"your project root or configured `reactNativeDir`."
)
}
)
}

// 3. Use hermes-compiler from npm
private fun getHermesCompilerCandidates(projectRoot: File, reactNativeDir: File?): List<File> {
val prebuiltHermesPath =
HERMES_COMPILER_NPM_DIR.plus(getHermesCBin())
.replace("%OS-BIN%", getHermesOSBin())
// Execution on Windows fails with / as separator
.replace('/', File.separatorChar)

val prebuiltHermes = File(projectRoot, prebuiltHermesPath)
if (prebuiltHermes.exists()) {
return prebuiltHermes.cliPath(projectRoot)
}

error(
"Couldn't determine Hermesc location. " +
"Please set `react.hermesCommand` to the path of the hermesc binary file. " +
"node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc"
)
return listOfNotNull(
File(projectRoot, prebuiltHermesPath),
reactNativeDir?.parentFile?.let { nodeModulesDir ->
File(nodeModulesDir, prebuiltHermesPath.removePrefix("node_modules${File.separator}"))
},
)
.distinctBy { it.absolutePath }
}

private fun getBuiltHermescFileFromReactNativeDir(reactNativeDir: File): File =
File(
reactNativeDir,
"ReactAndroid/hermes-engine/build/hermes/bin/${getHermesCBin()}",
)

/**
* Gets the location where Hermesc should be. If nothing is specified, built hermesc is assumed to
* be inside [HERMESC_BUILT_FROM_SOURCE_DIR]. Otherwise user can specify an override with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.facebook.react.tests.OsRule
import com.facebook.react.tests.WithOs
import java.io.File
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.gradle.process.ProcessExecutionException
import org.gradle.testfixtures.ProjectBuilder
import org.junit.Assume.assumeTrue
Expand Down Expand Up @@ -163,12 +164,68 @@ class PathUtilsTest {
assertThat(detectOSAwareHermesCommand(tempFolder.root, "")).isEqualTo(expected.toString())
}

@Test
@WithOs(OS.MAC)
fun detectOSAwareHermesCommand_withHoistedHermescFromNPM() {
val projectRoot = tempFolder.newFolder("apps", "mobile")
val reactNativeDir = tempFolder.newFolder("node_modules", "react-native")
tempFolder.newFolder("node_modules/hermes-compiler/hermesc/osx-bin/")
val expected = tempFolder.newFile("node_modules/hermes-compiler/hermesc/osx-bin/hermesc")

assertThat(detectOSAwareHermesCommand(projectRoot, "", reactNativeDir))
.isEqualTo(expected.toString())
}

@Test
@WithOs(OS.MAC)
fun detectOSAwareHermesCommand_withLocalAndHoistedHermescFromNPM_prefersLocal() {
val projectRoot = tempFolder.newFolder("apps", "mobile")
val reactNativeDir = tempFolder.newFolder("node_modules", "react-native")
File(projectRoot, "node_modules/hermes-compiler/hermesc/osx-bin/").mkdirs()
val expected = File(projectRoot, "node_modules/hermes-compiler/hermesc/osx-bin/hermesc")
expected.createNewFile()
tempFolder.newFolder("node_modules/hermes-compiler/hermesc/osx-bin/")
tempFolder.newFile("node_modules/hermes-compiler/hermesc/osx-bin/hermesc")

assertThat(detectOSAwareHermesCommand(projectRoot, "", reactNativeDir))
.isEqualTo(expected.toString())
}

@Test
fun detectedHermesCommand_usesConfiguredReactNativeDirForHoistedHermescFromNPM() {
val project = ProjectBuilder.builder().build()
val extension = TestReactExtension(project)
val projectRoot = tempFolder.newFolder("apps", "mobile")
val reactNativeDir = tempFolder.newFolder("node_modules", "react-native")
extension.root.set(projectRoot)
extension.reactNativeDir.set(reactNativeDir)
tempFolder.newFolder("node_modules/hermes-compiler/hermesc/${getHermesOSBin()}/")
val expected = tempFolder.newFile("node_modules/hermes-compiler/hermesc/${getHermesOSBin()}/hermesc")

assertThat(detectedHermesCommand(extension)).isEqualTo(expected.toString())
}

@Test(expected = IllegalStateException::class)
@WithOs(OS.MAC)
fun detectOSAwareHermesCommand_failsIfNotFound() {
detectOSAwareHermesCommand(tempFolder.root, "")
}

@Test
@WithOs(OS.MAC)
fun detectOSAwareHermesCommand_whenHermescNotFound_reportsCheckedLocations() {
val projectRoot = tempFolder.newFolder("apps", "mobile")
val reactNativeDir = tempFolder.newFolder("node_modules", "react-native")

assertThatThrownBy { detectOSAwareHermesCommand(projectRoot, "", reactNativeDir) }
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("Couldn't determine Hermesc location.")
.hasMessageContaining("Checked:")
.hasMessageContaining("node_modules/hermes-compiler/hermesc/osx-bin/hermesc")
.hasMessageContaining("configured `reactNativeDir`")
.hasMessageNotContaining("node_modules/react-native/sdks/hermesc")
}

@Test
@WithOs(OS.MAC)
fun detectOSAwareHermesCommand_withProvidedCommand_takesPrecedence() {
Expand Down
Loading