Skip to content

Commit aeac6ab

Browse files
cortinicofacebook-github-bot
authored andcommitted
Gradle: extend the algoritm to find hermesc paths.
Summary: This diff extends the Gradle algo used to search for `hermesc`. Currently we look into `node_modules/hermes-engine/%OS-BIN%/hermesc` With this change the algo will look into: - A user provided path to hermesc - Built from source version of hermesc (for users of New Architecture) - Bundled version of hermesc inside react-native - hermesc from the hermes-engine NPM package I've added tests for the new algo. I also realized our tests were broken (since they stopped running on CI), I fixed them as well. Changelog: [Android] [Changed] - Gradle: extend the algoritm to find hermesc paths Reviewed By: ShikaSD Differential Revision: D35649911 fbshipit-source-id: d4bcbe06a6bfa8d98b91c1612fc28b300de91661
1 parent 8ac8439 commit aeac6ab

File tree

9 files changed

+163
-40
lines changed

9 files changed

+163
-40
lines changed

packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt

+5-3
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,11 @@ abstract class ReactExtension @Inject constructor(project: Project) {
141141

142142
/** Hermes Config */
143143

144-
/** The command to use to invoke hermes. Default is `hermesc` for the correct OS. */
145-
val hermesCommand: Property<String> =
146-
objects.property(String::class.java).convention("node_modules/hermes-engine/%OS-BIN%/hermesc")
144+
/**
145+
* The command to use to invoke hermesc (the hermes compiler). Default is "", the plugin will
146+
* autodetect it.
147+
*/
148+
val hermesCommand: Property<String> = objects.property(String::class.java).convention("")
147149

148150
/** Toggle Hermes for the whole build. Default: false */
149151
val enableHermes: Property<Boolean> = objects.property(Boolean::class.java).convention(false)

packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/Os.kt

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ object Os {
1212
fun isWindows(): Boolean =
1313
System.getProperty("os.name")?.lowercase()?.contains("windows") ?: false
1414

15+
fun isMac(): Boolean = System.getProperty("os.name")?.lowercase()?.contains("mac") ?: false
16+
17+
fun isLinuxAmd64(): Boolean {
18+
val osNameMatch = System.getProperty("os.name")?.lowercase()?.contains("linux") ?: false
19+
val archMatch = System.getProperty("os.arch")?.lowercase()?.contains("amd64") ?: false
20+
return osNameMatch && archMatch
21+
}
22+
1523
fun String.unixifyPath() =
1624
this.replace('\\', '/').replace(":", "").let {
1725
if (!it.startsWith("/")) {

packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt

+51-16
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ package com.facebook.react.utils
1111

1212
import com.facebook.react.ReactExtension
1313
import java.io.File
14-
import org.apache.tools.ant.taskdefs.condition.Os
1514

1615
/**
1716
* Computes the entry file for React Native. The Algo follows this order:
@@ -44,13 +43,16 @@ internal fun detectedCliPath(
4443

4544
/**
4645
* Computes the `hermesc` command location. The Algo follows this order:
47-
* 1. The path provided by the `hermesCommand` config in the `reactApp` Gradle extension
48-
* 2. The file located in `node_modules/hermes-engine/%OS-BIN%/hermesc` where `%OS-BIN%` is
49-
* substituted with the correct OS arch.
50-
* 3. Fails otherwise
46+
* 1. The path provided by the `hermesCommand` config in the `react` Gradle extension
47+
* 2. The file located in `node_modules/react-native/sdks/hermes/build/bin/hermesc`. This will be
48+
* used if the user is building Hermes from source.
49+
* 3. The file located in `node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc` where `%OS-BIN%`
50+
* is substituted with the correct OS arch. This will be used if the user is using a precompiled
51+
* hermes-engine package.
52+
* 4. Fails otherwise
5153
*/
5254
internal fun detectedHermesCommand(config: ReactExtension): String =
53-
detectOSAwareHermesCommand(config.hermesCommand.get())
55+
detectOSAwareHermesCommand(config.root.get().asFile, config.hermesCommand.get())
5456

5557
private fun detectEntryFile(entryFile: File?, reactRoot: File): File =
5658
when {
@@ -112,20 +114,48 @@ private fun detectCliPath(
112114

113115
// Make sure not to inspect the Hermes config unless we need it,
114116
// to avoid breaking any JSC-only setups.
115-
private fun detectOSAwareHermesCommand(hermesCommand: String): String {
116-
// If the project specifies a Hermes command, don't second guess it.
117-
if (!hermesCommand.contains("%OS-BIN%")) {
118-
return hermesCommand
117+
internal fun detectOSAwareHermesCommand(projectRoot: File, hermesCommand: String): String {
118+
// 1. If the project specifies a Hermes command, don't second guess it.
119+
if (hermesCommand.isNotBlank()) {
120+
val osSpecificHermesCommand =
121+
if ("%OS-BIN%" in hermesCommand) {
122+
hermesCommand.replace("%OS-BIN%", getHermesOSBin())
123+
} else {
124+
hermesCommand
125+
}
126+
return osSpecificHermesCommand
127+
// Execution on Windows fails with / as separator
128+
.replace('/', File.separatorChar)
129+
}
130+
131+
// 2. If the project is building hermes-engine from source, use hermesc from there
132+
val builtHermesc = File(projectRoot, HERMESC_BUILT_FROM_SOURCE_PATH)
133+
if (builtHermesc.exists()) {
134+
return builtHermesc.absolutePath
135+
}
136+
137+
// 3. If the react-native contains a pre-built hermesc, use it.
138+
val prebuiltHermesPath =
139+
HERMESC_IN_REACT_NATIVE_PATH
140+
.replace("%OS-BIN%", getHermesOSBin())
141+
// Execution on Windows fails with / as separator
142+
.replace('/', File.separatorChar)
143+
144+
val prebuiltHermes = File(projectRoot, prebuiltHermesPath)
145+
if (prebuiltHermes.exists()) {
146+
return prebuiltHermes.absolutePath
119147
}
120148

121-
// Execution on Windows fails with / as separator
122-
return hermesCommand.replace("%OS-BIN%", getHermesOSBin()).replace('/', File.separatorChar)
149+
error(
150+
"Couldn't determine Hermesc location. " +
151+
"Please set `react.hermesCommand` to the path of the hermesc binary file. " +
152+
"node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc")
123153
}
124154

125-
private fun getHermesOSBin(): String {
126-
if (Os.isFamily(Os.FAMILY_WINDOWS)) return "win64-bin"
127-
if (Os.isFamily(Os.FAMILY_MAC)) return "osx-bin"
128-
if (Os.isOs(null, "linux", "amd64", null)) return "linux64-bin"
155+
internal fun getHermesOSBin(): String {
156+
if (Os.isWindows()) return "win64-bin"
157+
if (Os.isMac()) return "osx-bin"
158+
if (Os.isLinuxAmd64()) return "linux64-bin"
129159
error(
130160
"OS not recognized. Please set project.react.hermesCommand " +
131161
"to the path of a working Hermes compiler.")
@@ -136,3 +166,8 @@ internal fun projectPathToLibraryName(projectPath: String): String =
136166
.split(':', '-', '_', '.')
137167
.joinToString("") { token -> token.replaceFirstChar { it.uppercase() } }
138168
.plus("Spec")
169+
170+
private const val HERMESC_IN_REACT_NATIVE_PATH =
171+
"node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc"
172+
private const val HERMESC_BUILT_FROM_SOURCE_PATH =
173+
"node_modules/react-native/sdks/hermes/build/bin/hermesc"

packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTaskTest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ class GenerateCodegenArtifactsTaskTest {
9797

9898
assertEquals(
9999
listOf(
100-
"yarn",
101100
"--verbose",
102101
File(reactNativeDir, "scripts/generate-specs-cli.js").toString(),
103102
"--platform",

packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTaskTest.kt

-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ class GenerateCodegenSchemaTaskTest {
105105

106106
assertEquals(
107107
listOf(
108-
"yarn",
109108
"--verbose",
110109
File(codegenDir, "lib/cli/combine/combine-js-to-schema-cli.js").toString(),
111110
File(outputDir, "schema.json").toString(),

packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/OsTest.kt

+8-2
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,26 @@ class OsTest {
2121

2222
@Test
2323
@WithOs(OS.UNIX)
24-
fun isWindows_onUnix_returnsFalse() {
24+
fun onUnix_checksOsCorrectly() {
2525
assertFalse(Os.isWindows())
26+
assertFalse(Os.isMac())
27+
assertFalse(Os.isLinuxAmd64())
2628
}
2729

2830
@Test
2931
@WithOs(OS.MAC)
30-
fun isWindows_onMac_returnsTrue() {
32+
fun onMac_checksOsCorrectly() {
3133
assertFalse(Os.isWindows())
34+
assertTrue(Os.isMac())
35+
assertFalse(Os.isLinuxAmd64())
3236
}
3337

3438
@Test
3539
@WithOs(OS.WIN)
3640
fun isWindows_onWindows_returnsTrue() {
3741
assertTrue(Os.isWindows())
42+
assertFalse(Os.isMac())
43+
assertFalse(Os.isLinuxAmd64())
3844
}
3945

4046
@Test

packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt

+61-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
package com.facebook.react.utils
99

1010
import com.facebook.react.TestReactExtension
11+
import com.facebook.react.tests.OS
12+
import com.facebook.react.tests.OsRule
13+
import com.facebook.react.tests.WithOs
1114
import java.io.File
1215
import org.gradle.testfixtures.ProjectBuilder
1316
import org.junit.Assert.*
@@ -18,6 +21,7 @@ import org.junit.rules.TemporaryFolder
1821
class PathUtilsTest {
1922

2023
@get:Rule val tempFolder = TemporaryFolder()
24+
@get:Rule val osRule = OsRule()
2125

2226
@Test
2327
fun detectedEntryFile_withProvidedVariable() {
@@ -60,7 +64,7 @@ class PathUtilsTest {
6064
parentFile.mkdirs()
6165
writeText("<!-- nothing to see here -->")
6266
}
63-
extension.cliPath.set(project.projectDir + "/abs/fake-cli.sh")
67+
extension.cliPath.set(project.projectDir.toString() + "/abs/fake-cli.sh")
6468

6569
val actual = detectedCliPath(project.projectDir, extension)
6670

@@ -77,7 +81,7 @@ class PathUtilsTest {
7781
writeText("<!-- nothing to see here -->")
7882
}
7983
extension.cliPath.set("fake-cli.sh")
80-
extension.reactRoot.set(project.projectDir + "/react-root")
84+
extension.root.set(File(project.projectDir.toString(), "react-root"))
8185

8286
val actual = detectedCliPath(project.projectDir, extension)
8387

@@ -104,6 +108,7 @@ class PathUtilsTest {
104108
fun detectedCliPath_withCliPathFromExtensionInParentFolder() {
105109
val rootProject = ProjectBuilder.builder().build()
106110
val project = ProjectBuilder.builder().withParent(rootProject).build()
111+
project.projectDir.mkdirs()
107112
val extension = TestReactExtension(project)
108113
val expected = File(rootProject.projectDir, "cli-in-root.sh").apply { writeText("#!/bin/bash") }
109114
extension.cliPath.set("../cli-in-root.sh")
@@ -148,15 +153,11 @@ class PathUtilsTest {
148153
assertEquals(expected.toString(), actual)
149154
}
150155

151-
@Test
152-
fun detectedHermesCommand_withOSSpecificBin() {
156+
@Test(expected = IllegalStateException::class)
157+
fun detectedHermesCommand_failsIfNotFound() {
153158
val extension = TestReactExtension(ProjectBuilder.builder().build())
154159

155160
val actual = detectedHermesCommand(extension)
156-
157-
assertTrue(actual.startsWith("node_modules/hermes-engine/"))
158-
assertTrue(actual.endsWith("hermesc"))
159-
assertFalse(actual.contains("%OS-BIN%"))
160161
}
161162

162163
@Test
@@ -178,4 +179,56 @@ class PathUtilsTest {
178179
fun projectPathToLibraryName_withDotsAndUnderscores() {
179180
assertEquals("SampleAndroidAppSpec", projectPathToLibraryName("sample_android.app"))
180181
}
182+
183+
@Test
184+
fun detectOSAwareHermesCommand_withProvidedCommand() {
185+
assertEquals(
186+
"./my-home/hermes", detectOSAwareHermesCommand(tempFolder.root, "./my-home/hermes"))
187+
}
188+
189+
@Test
190+
fun detectOSAwareHermesCommand_withHermescBuiltLocally() {
191+
tempFolder.newFolder("node_modules/react-native/sdks/hermes/build/bin/")
192+
val expected = tempFolder.newFile("node_modules/react-native/sdks/hermes/build/bin/hermesc")
193+
194+
assertEquals(expected.toString(), detectOSAwareHermesCommand(tempFolder.root, ""))
195+
}
196+
197+
@Test
198+
@WithOs(OS.MAC)
199+
fun detectOSAwareHermesCommand_withBundledHermescInsideRN() {
200+
tempFolder.newFolder("node_modules/react-native/sdks/hermesc/osx-bin/")
201+
val expected = tempFolder.newFile("node_modules/react-native/sdks/hermesc/osx-bin/hermesc")
202+
203+
assertEquals(expected.toString(), detectOSAwareHermesCommand(tempFolder.root, ""))
204+
}
205+
206+
@Test(expected = IllegalStateException::class)
207+
@WithOs(OS.MAC)
208+
fun detectOSAwareHermesCommand_failsIfNotFound() {
209+
detectOSAwareHermesCommand(tempFolder.root, "")
210+
}
211+
212+
@Test
213+
@WithOs(OS.MAC)
214+
fun detectOSAwareHermesCommand_withProvidedCommand_takesPrecedence() {
215+
tempFolder.newFolder("node_modules/react-native/sdks/hermes/build/bin/")
216+
tempFolder.newFile("node_modules/react-native/sdks/hermes/build/bin/hermesc")
217+
tempFolder.newFolder("node_modules/react-native/sdks/hermesc/osx-bin/")
218+
tempFolder.newFile("node_modules/react-native/sdks/hermesc/osx-bin/hermesc")
219+
220+
assertEquals(
221+
"./my-home/hermes", detectOSAwareHermesCommand(tempFolder.root, "./my-home/hermes"))
222+
}
223+
224+
@Test
225+
@WithOs(OS.MAC)
226+
fun detectOSAwareHermesCommand_withoutProvidedCommand_builtHermescTakesPrecedence() {
227+
tempFolder.newFolder("node_modules/react-native/sdks/hermes/build/bin/")
228+
val expected = tempFolder.newFile("node_modules/react-native/sdks/hermes/build/bin/hermesc")
229+
tempFolder.newFolder("node_modules/react-native/sdks/hermesc/osx-bin/")
230+
tempFolder.newFile("node_modules/react-native/sdks/hermesc/osx-bin/hermesc")
231+
232+
assertEquals(expected.toString(), detectOSAwareHermesCommand(tempFolder.root, ""))
233+
}
181234
}

packages/rn-tester/android/app/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ react {
8383
root = rootDir
8484
inputExcludes = ["android/**", "./**", ".gradle/**"]
8585
composeSourceMapsPath = "$rootDir/scripts/compose-source-maps.js"
86-
hermesCommand = "$rootDir/node_modules/hermes-engine/%OS-BIN%/hermesc"
86+
hermesCommand = "$rootDir/sdks/hermes/build/bin/hermesc"
8787
enableHermesForVariant { def v -> v.name.contains("hermes") }
8888

8989
// Codegen Configs

react.gradle

+29-8
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def reactRoot = file(config.root ?: "../../")
3030
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
3131
def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
3232
def enableVmCleanup = config.enableVmCleanup == null ? true : config.enableVmCleanup
33-
def hermesCommand = config.hermesCommand ?: "../../node_modules/hermes-engine/%OS-BIN%/hermesc"
33+
def hermesCommand = config.hermesCommand
3434

3535
/**
3636
* Detects CLI location in a similar fashion to the React Native CLI
@@ -90,15 +90,36 @@ def getHermesOSBin() {
9090
// Make sure not to inspect the Hermes config unless we need it,
9191
// to avoid breaking any JSC-only setups.
9292
def getHermesCommand = {
93-
// If the project specifies a Hermes command, don't second guess it.
94-
if (!hermesCommand.contains("%OS-BIN%")) {
95-
return hermesCommand
93+
// 1. If the project specifies a Hermes command, don't second guess it.
94+
if (config.hermesCommand?.trim()) {
95+
if (hermesCommand.contains("%OS-BIN%")) {
96+
return hermesCommand
97+
.replaceAll("%OS-BIN%", getHermesOSBin())
98+
.replace('/' as char, File.separatorChar)
99+
} else {
100+
return hermesCommand
101+
.replace('/' as char, File.separatorChar)
102+
}
103+
}
104+
105+
// 2. If the project is building hermes-engine from source, use hermesc from there
106+
def builtHermesc = new File(reactRoot, "node_modules/react-native/sdks/hermes/build/bin/hermesc")
107+
if (builtHermesc.exists()) {
108+
return builtHermesc.getAbsolutePath()
109+
}
110+
111+
// 3. If the react-native contains a pre-built hermesc, use it.
112+
def prebuiltHermesPath = "node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc"
113+
.replaceAll("%OS-BIN%", getHermesOSBin())
114+
.replace('/' as char, File.separatorChar);
115+
def prebuiltHermes = new File(reactRoot, prebuiltHermesPath)
116+
if (prebuiltHermes.exists()) {
117+
return prebuiltHermes.getAbsolutePath()
96118
}
97119

98-
// Execution on Windows fails with / as separator
99-
return hermesCommand
100-
.replaceAll("%OS-BIN%", getHermesOSBin())
101-
.replace('/' as char, File.separatorChar);
120+
throw new Exception("Couldn't determine Hermesc location. " +
121+
"Please set `project.ext.react.hermesCommand` to the path of the hermesc binary file. " +
122+
"node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc");
102123
}
103124

104125
// Set enableHermesForVariant to a function to configure per variant,

0 commit comments

Comments
 (0)