Skip to content

Commit 6c41757

Browse files
authored
Merge pull request #10 from codemagic-ci-cd/rn-84
Fix release-react Hermes support for React Native 0.84
2 parents 9d9bed3 + c08132c commit 6c41757

3 files changed

Lines changed: 432 additions & 36 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,13 +440,15 @@ _NOTE: This parameter can be set using either --outputDir or -o_
440440

441441
#### Use Hermes parameter
442442

443-
This parameter enforces the use of the Hermes compiler. If not specified, the automatic checks will be performed, inspecting the `build.gradle` and `Podfile` for the Hermes flag.
443+
This parameter forces the use of the Hermes compiler. If not specified, automatic checks will inspect Android `gradle.properties`, legacy Android `build.gradle` `project.ext.react.enableHermes` settings, iOS `Podfile` `hermes_enabled` settings, and React Native 0.70+ default Hermes behavior.
444+
445+
Explicit `hermesEnabled=false` in `android/gradle.properties` or `hermes_enabled: false` / `:hermes_enabled => false` in the `Podfile` disables automatic Hermes compilation for that platform.
444446

445447
_NOTE: This parameter can be set using either --useHermes or -h_
446448

447449
#### Podfile parameter (iOS only)
448450

449-
The Podfile path will be used for Hermes automatic check. Not used if `--useHermes` is specified.
451+
The Podfile path will be used for the iOS Hermes automatic check. Not used if `--useHermes` is specified.
450452

451453
_NOTE: This parameter can be set using either --podFile or --pod_
452454

script/react-native-utils.ts

Lines changed: 126 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,20 @@ import { coerce, compare, valid } from "semver";
66
import { fileDoesNotExistOrIsDirectory } from "./utils/file-utils";
77

88
const g2js = require("gradle-to-js/lib/parser");
9+
const REACT_NATIVE_HERMES_DEFAULT_VERSION = "0.70.0";
910

1011
export function isValidVersion(version: string): boolean {
1112
return !!valid(version) || /^\d+\.\d+$/.test(version);
1213
}
1314

1415
export async function runHermesEmitBinaryCommand(
15-
bundleName: string,
16-
outputFolder: string,
17-
sourcemapOutput: string,
18-
extraHermesFlags: string[],
19-
gradleFile: string
16+
bundleName: string,
17+
outputFolder: string,
18+
sourcemapOutput: string,
19+
extraHermesFlags: string[],
20+
gradleFile: string
2021
): Promise<void> {
2122
const hermesArgs: string[] = [];
22-
const envNodeArgs: string = process.env.CODE_PUSH_NODE_ARGS;
23-
24-
if (typeof envNodeArgs !== "undefined") {
25-
Array.prototype.push.apply(hermesArgs, envNodeArgs.trim().split(/\s+/));
26-
}
2723

2824
Array.prototype.push.apply(hermesArgs, [
2925
"-emit-binary",
@@ -43,6 +39,8 @@ export async function runHermesEmitBinaryCommand(
4339
console.log(`${hermesCommand} ${hermesArgs.join(" ")}`);
4440

4541
return new Promise<void>((resolve, reject) => {
42+
let hermesProcessError: Error = null;
43+
4644
hermesProcess.stdout.on("data", (data: Buffer) => {
4745
console.log(data.toString().trim());
4846
});
@@ -51,23 +49,38 @@ export async function runHermesEmitBinaryCommand(
5149
console.error(data.toString().trim());
5250
});
5351

52+
hermesProcess.on("error", (err: Error) => {
53+
hermesProcessError = err;
54+
reject(new Error(`Failed to run Hermes compiler "${hermesCommand}": ${err.message}`));
55+
});
56+
5457
hermesProcess.on("close", (exitCode: number, signal: string) => {
58+
if (hermesProcessError) {
59+
return;
60+
}
61+
5562
if (exitCode !== 0) {
5663
reject(new Error(`"hermes" command failed (exitCode=${exitCode}, signal=${signal}).`));
64+
return;
5765
}
66+
5867
// Copy HBC bundle to overwrite JS bundle
5968
const source = path.join(outputFolder, bundleName + ".hbc");
6069
const destination = path.join(outputFolder, bundleName);
6170
fs.copyFile(source, destination, (err) => {
6271
if (err) {
6372
console.error(err);
6473
reject(new Error(`Copying file ${source} to ${destination} failed. "hermes" previously exited with code ${exitCode}.`));
74+
return;
6575
}
76+
6677
fs.unlink(source, (err) => {
6778
if (err) {
6879
console.error(err);
6980
reject(err);
81+
return;
7082
}
83+
7184
resolve(null as void);
7285
});
7386
});
@@ -125,11 +138,8 @@ export async function runHermesEmitBinaryCommand(
125138
}
126139

127140
function parseBuildGradleFile(gradleFile: string) {
128-
let buildGradlePath: string = path.join("android", "app");
129-
if (gradleFile) {
130-
buildGradlePath = gradleFile;
131-
}
132-
if (fs.lstatSync(buildGradlePath).isDirectory()) {
141+
let buildGradlePath: string = getBuildGradlePath(gradleFile);
142+
if (fs.existsSync(buildGradlePath) && fs.lstatSync(buildGradlePath).isDirectory()) {
133143
buildGradlePath = path.join(buildGradlePath, "build.gradle");
134144
}
135145

@@ -142,6 +152,19 @@ function parseBuildGradleFile(gradleFile: string) {
142152
});
143153
}
144154

155+
function getBuildGradlePath(gradleFile: string): string {
156+
let buildGradlePath: string = path.join("android", "app");
157+
if (gradleFile) {
158+
buildGradlePath = gradleFile;
159+
}
160+
161+
if (fs.existsSync(buildGradlePath) && fs.lstatSync(buildGradlePath).isDirectory()) {
162+
return path.join(buildGradlePath, "build.gradle");
163+
}
164+
165+
return buildGradlePath;
166+
}
167+
145168
async function getHermesCommandFromGradle(gradleFile: string): Promise<string> {
146169
const buildGradle: any = await parseBuildGradleFile(gradleFile);
147170
const hermesCommandProperty: any = Array.from(buildGradle["project.ext.react"] || []).find((prop: string) =>
@@ -154,10 +177,43 @@ async function getHermesCommandFromGradle(gradleFile: string): Promise<string> {
154177
}
155178
}
156179

157-
export function getAndroidHermesEnabled(gradleFile: string): boolean {
158-
return parseBuildGradleFile(gradleFile).then((buildGradle: any) => {
159-
return Array.from(buildGradle["project.ext.react"] || []).some((line: string) => /^enableHermes\s{0,}:\s{0,}true/.test(line));
160-
});
180+
function getLegacyAndroidHermesEnabled(buildGradle: any): boolean | null {
181+
const hermesEnabledProperty = Array.from(buildGradle["project.ext.react"] || []).find((line: string) =>
182+
/^enableHermes\s*:\s*(true|false)\b/.test(line)
183+
) as string;
184+
185+
if (hermesEnabledProperty) {
186+
return /:\s*true\b/.test(hermesEnabledProperty);
187+
}
188+
189+
return null;
190+
}
191+
192+
export async function getAndroidHermesEnabled(gradleFile: string): Promise<boolean> {
193+
const gradlePropertiesPath = path.join("android", "gradle.properties");
194+
if (fs.existsSync(gradlePropertiesPath)) {
195+
const gradlePropertiesContents = fs.readFileSync(gradlePropertiesPath).toString();
196+
const hermesEnabledMatch = gradlePropertiesContents.match(/^\s*hermesEnabled\s*=\s*(true|false)\s*$/im);
197+
if (hermesEnabledMatch) {
198+
return hermesEnabledMatch[1].toLowerCase() === "true";
199+
}
200+
}
201+
202+
const buildGradlePath = getBuildGradlePath(gradleFile);
203+
if (fs.existsSync(buildGradlePath) && fs.readFileSync(buildGradlePath).toString().indexOf("enableHermes") !== -1) {
204+
const buildGradle: any = await parseBuildGradleFile(gradleFile);
205+
const legacyHermesEnabled = getLegacyAndroidHermesEnabled(buildGradle);
206+
if (legacyHermesEnabled !== null) {
207+
return legacyHermesEnabled;
208+
}
209+
}
210+
211+
const reactNativeVersion = coerce(getReactNativeVersion());
212+
if (reactNativeVersion && compare(reactNativeVersion.version, REACT_NATIVE_HERMES_DEFAULT_VERSION) >= 0) {
213+
return true;
214+
}
215+
216+
return false;
161217
}
162218

163219
export function getiOSHermesEnabled(podFile: string): boolean {
@@ -166,12 +222,26 @@ export function getiOSHermesEnabled(podFile: string): boolean {
166222
podPath = podFile;
167223
}
168224
if (fileDoesNotExistOrIsDirectory(podPath)) {
169-
throw new Error(`Unable to find Podfile file "${podPath}".`);
225+
if (podFile) {
226+
throw new Error(`Unable to find Podfile file "${podPath}".`);
227+
}
228+
229+
const reactNativeVersion = coerce(getReactNativeVersion());
230+
return !!reactNativeVersion && compare(reactNativeVersion.version, REACT_NATIVE_HERMES_DEFAULT_VERSION) >= 0;
170231
}
171232

172233
try {
173234
const podFileContents = fs.readFileSync(podPath).toString();
174-
return /([^#\n]*:?hermes_enabled(\s+|\n+)?(=>|:)(\s+|\n+)?true)/.test(podFileContents);
235+
if (/^[^#\n]*:?\bhermes_enabled\b\s*(=>|:)\s*false\b/im.test(podFileContents)) {
236+
return false;
237+
}
238+
239+
if (/^[^#\n]*:?\bhermes_enabled\b\s*(=>|:)\s*true\b/im.test(podFileContents)) {
240+
return true;
241+
}
242+
243+
const reactNativeVersion = coerce(getReactNativeVersion());
244+
return !!reactNativeVersion && compare(reactNativeVersion.version, REACT_NATIVE_HERMES_DEFAULT_VERSION) >= 0;
175245
} catch (error) {
176246
throw error;
177247
}
@@ -192,7 +262,8 @@ function getHermesOSBin(): string {
192262
}
193263

194264
function getHermesOSExe(): string {
195-
const react63orAbove = compare(coerce(getReactNativeVersion()).version, "0.63.0") !== -1;
265+
const reactNativeVersion = coerce(getReactNativeVersion());
266+
const react63orAbove = !!reactNativeVersion && compare(reactNativeVersion.version, "0.63.0") !== -1;
196267
const hermesExecutableName = react63orAbove ? "hermesc" : "hermes";
197268
switch (process.platform) {
198269
case "win32":
@@ -210,23 +281,34 @@ async function getHermesCommand(gradleFile: string): Promise<string> {
210281
return false;
211282
}
212283
};
284+
285+
const buildGradlePath = getBuildGradlePath(gradleFile);
286+
287+
if (fs.existsSync(buildGradlePath) && fs.readFileSync(buildGradlePath).toString().indexOf("hermesCommand") !== -1) {
288+
const gradleHermesCommand = await getHermesCommandFromGradle(gradleFile);
289+
if (gradleHermesCommand) {
290+
return path.join("android", "app", gradleHermesCommand.replace("%OS-BIN%", getHermesOSBin()));
291+
}
292+
}
293+
294+
const hermesCompilerExe = process.platform === "win32" ? "hermesc.exe" : "hermesc";
295+
const hermesCompiler = path.join("node_modules", "hermes-compiler", "hermesc", getHermesOSBin(), hermesCompilerExe);
296+
if (fileExists(hermesCompiler)) {
297+
return hermesCompiler;
298+
}
299+
213300
// Hermes is bundled with react-native since 0.69
214301
const bundledHermesEngine = path.join(getReactNativePackagePath(), "sdks", "hermesc", getHermesOSBin(), getHermesOSExe());
215302
if (fileExists(bundledHermesEngine)) {
216303
return bundledHermesEngine;
217304
}
218305

219-
const gradleHermesCommand = await getHermesCommandFromGradle(gradleFile);
220-
if (gradleHermesCommand) {
221-
return path.join("android", "app", gradleHermesCommand.replace("%OS-BIN%", getHermesOSBin()));
222-
} else {
223-
// assume if hermes-engine exists it should be used instead of hermesvm
224-
const hermesEngine = path.join("node_modules", "hermes-engine", getHermesOSBin(), getHermesOSExe());
225-
if (fileExists(hermesEngine)) {
226-
return hermesEngine;
227-
}
228-
return path.join("node_modules", "hermesvm", getHermesOSBin(), "hermes");
306+
// assume if hermes-engine exists it should be used instead of hermesvm
307+
const hermesEngine = path.join("node_modules", "hermes-engine", getHermesOSBin(), getHermesOSExe());
308+
if (fileExists(hermesEngine)) {
309+
return hermesEngine;
229310
}
311+
return path.join("node_modules", "hermesvm", getHermesOSBin(), "hermes");
230312
}
231313

232314
function getComposeSourceMapsPath(): string {
@@ -240,7 +322,7 @@ function getComposeSourceMapsPath(): string {
240322

241323
function getReactNativePackagePath(): string {
242324
const result = childProcess.spawnSync("node", ["--print", "require.resolve('react-native/package.json')"]);
243-
const packagePath = path.dirname(result.stdout.toString());
325+
const packagePath = path.dirname(result.stdout.toString().trim());
244326
if (result.status === 0 && directoryExistsSync(packagePath)) {
245327
return packagePath;
246328
}
@@ -276,8 +358,18 @@ export function getReactNativeVersion(): string {
276358
throw new Error(`The "package.json" file in the CWD does not have the "name" field set.`);
277359
}
278360

361+
try {
362+
const reactNativePackageJsonFilename = path.join(getReactNativePackagePath(), "package.json");
363+
const reactNativePackageJson = JSON.parse(fs.readFileSync(reactNativePackageJsonFilename, "utf-8"));
364+
if (reactNativePackageJson.version) {
365+
return reactNativePackageJson.version;
366+
}
367+
} catch (error) {
368+
// Fall back to the app package.json dependency range below.
369+
}
370+
279371
return (
280372
(projectPackageJson.dependencies && projectPackageJson.dependencies["react-native"]) ||
281373
(projectPackageJson.devDependencies && projectPackageJson.devDependencies["react-native"])
282374
);
283-
}
375+
}

0 commit comments

Comments
 (0)