From d39004fded1913287c65843483b794c4fc0bf886 Mon Sep 17 00:00:00 2001 From: Chris Green Date: Sat, 13 Mar 2021 18:34:33 +0000 Subject: [PATCH] Use hermesCommand path in gradle file when running release-react If the hermesCommand path is set in build.gradle, this will be used instead of the default path. --- .../codepush/lib/react-native-utils.ts | 57 ++++++++++----- src/commands/codepush/release-react.ts | 8 ++- test/commands/codepush/release-react-test.ts | 70 +++++++++++++++++++ 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/commands/codepush/lib/react-native-utils.ts b/src/commands/codepush/lib/react-native-utils.ts index 3829d7d08..040d3c88d 100644 --- a/src/commands/codepush/lib/react-native-utils.ts +++ b/src/commands/codepush/lib/react-native-utils.ts @@ -333,11 +333,12 @@ export function runReactNativeBundleCommand( }); } -export function runHermesEmitBinaryCommand( +export async function runHermesEmitBinaryCommand( bundleName: string, outputFolder: string, sourcemapOutput: string, - extraHermesFlags: string[] + extraHermesFlags: string[], + gradleFile: string ): Promise { const hermesArgs: string[] = []; const envNodeArgs: string = process.env.CODE_PUSH_NODE_ARGS; @@ -363,7 +364,7 @@ export function runHermesEmitBinaryCommand( } out.text(chalk.cyan("Converting JS bundle to byte code via Hermes, running command:\n")); - const hermesCommand = getHermesCommand(); + const hermesCommand = await getHermesCommand(gradleFile); const hermesProcess = childProcess.spawn(hermesCommand, hermesArgs); out.text(`${hermesCommand} ${hermesArgs.join(" ")}`); @@ -400,7 +401,7 @@ export function runHermesEmitBinaryCommand( }); } -export function getHermesEnabled(gradleFile: string): boolean { +function parseBuildGradleFile(gradleFile: string) { let buildGradlePath: string = path.join("android", "app"); if (gradleFile) { buildGradlePath = gradleFile; @@ -413,14 +414,28 @@ export function getHermesEnabled(gradleFile: string): boolean { throw new Error(`Unable to find gradle file "${buildGradlePath}".`); } - return g2js - .parseFile(buildGradlePath) - .catch(() => { - throw new Error(`Unable to parse the "${buildGradlePath}" file. Please ensure it is a well-formed Gradle file.`); - }) - .then((buildGradle: any) => { - return Array.from(buildGradle["project.ext.react"] || []).includes("enableHermes: true"); - }); + return g2js.parseFile(buildGradlePath).catch(() => { + throw new Error(`Unable to parse the "${buildGradlePath}" file. Please ensure it is a well-formed Gradle file.`); + }); +} + +export function getHermesEnabled(gradleFile: string): boolean { + return parseBuildGradleFile(gradleFile).then((buildGradle: any) => { + return Array.from(buildGradle["project.ext.react"] || []).includes("enableHermes: true"); + }); +} + +async function getHermesCommandFromGradle(gradleFile: string): Promise { + const buildGradle: any = await parseBuildGradleFile(gradleFile); + out.text(buildGradle); + const hermesCommandProperty: any = Array.from(buildGradle["project.ext.react"] || []).find((prop: string) => + prop.trim().startsWith("hermesCommand:") + ); + if (hermesCommandProperty) { + return hermesCommandProperty.replace("hermesCommand:", "").trim().slice(1, -1); + } else { + return ""; + } } function getHermesOSBin(): string { @@ -448,7 +463,7 @@ function getHermesOSExe(): string { } } -function getHermesCommand(): string { +async function getHermesCommand(gradleFile: string): Promise { const fileExists = (file: string): boolean => { try { return fs.statSync(file).isFile(); @@ -456,12 +471,18 @@ function getHermesCommand(): string { return false; } }; - // assume if hermes-engine exists it should be used instead of hermesvm - const hermesEngine = path.join("node_modules", "hermes-engine", getHermesOSBin(), getHermesOSExe()); - if (fileExists(hermesEngine)) { - return hermesEngine; + + const gradleHermesCommand = await getHermesCommandFromGradle(gradleFile); + if (gradleHermesCommand) { + return path.join("android", "app", gradleHermesCommand.replace("%OS-BIN%", getHermesOSBin())); + } else { + // assume if hermes-engine exists it should be used instead of hermesvm + const hermesEngine = path.join("node_modules", "hermes-engine", getHermesOSBin(), getHermesOSExe()); + if (fileExists(hermesEngine)) { + return hermesEngine; + } + return path.join("node_modules", "hermesvm", getHermesOSBin(), "hermes"); } - return path.join("node_modules", "hermesvm", getHermesOSBin(), "hermes"); } function getCliPath(): string { diff --git a/src/commands/codepush/release-react.ts b/src/commands/codepush/release-react.ts index ace89df97..8ae6157e7 100644 --- a/src/commands/codepush/release-react.ts +++ b/src/commands/codepush/release-react.ts @@ -211,7 +211,13 @@ export default class CodePushReleaseReactCommand extends CodePushReleaseCommandB if (this.os === "android") { const isHermesEnabled = await getHermesEnabled(this.gradleFile); if (isHermesEnabled) { - await runHermesEmitBinaryCommand(this.bundleName, this.updateContentsPath, this.sourcemapOutput, this.extraHermesFlags); + await runHermesEmitBinaryCommand( + this.bundleName, + this.updateContentsPath, + this.sourcemapOutput, + this.extraHermesFlags, + this.gradleFile + ); } } out.text(chalk.cyan("\nReleasing update contents to CodePush:\n")); diff --git a/test/commands/codepush/release-react-test.ts b/test/commands/codepush/release-react-test.ts index 448e280d1..e282e90eb 100644 --- a/test/commands/codepush/release-react-test.ts +++ b/test/commands/codepush/release-react-test.ts @@ -938,6 +938,76 @@ describe("codepush release-react command", function () { // Assert expect(runHermesEmitBinaryCommandStub.calledOnce).is.true; }); + + it("uses hermesCommand path if set in gradle file", async function () { + const os = "Android"; + // Arrange + const args = { + ...goldenPathArgs, + // prettier-ignore + args: [ + "--target-binary-version", "1.0.0", + "--deployment-name", deployment, + "--app", app, + "--token", "c1o3d3e7", + ], + }; + const command = new CodePushReleaseReactCommand(args); + sandbox.stub(fs, "readFileSync").returns(` + { + "name": "RnCodepushAndroid", + "version": "0.0.1", + "dependencies": { + "react": "16.13.1", + "react-native": "0.63.3", + "react-native-code-push": "6.3.0" + } + } + `); + + Nock("https://api.appcenter.ms/").get(`/v0.1/apps/${app}/deployments/${deployment}`).reply(200, {}); + Nock("https://api.appcenter.ms/").get(`/v0.1/apps/${app}`).reply(200, { + os, + platform: "react-native", + }); + sandbox.stub(mkdirp, "sync"); + sandbox.stub(fileUtils, "fileDoesNotExistOrIsDirectory").returns(false); + sandbox.stub(fileUtils, "createEmptyTmpReleaseFolder"); + sandbox.stub(command, "release" as any).resolves({ succeeded: true }); + sandbox.stub(fileUtils, "removeReactTmpDir"); + sandbox.stub(ReactNativeTools, "runReactNativeBundleCommand"); + sandbox.stub(fs, "lstatSync").returns({ isDirectory: () => false } as any); + sandbox.stub(g2js, "parseFile").resolves({ + "project.ext.react": ["enableHermes: true", 'hermesCommand: "../../../hermes/is/here"'], + }); + + const childProcessStub = new events.EventEmitter() as any; + childProcessStub.stdout = { + on: () => {}, + }; + childProcessStub.stderr = { + on: () => {}, + }; + const childProcessSpawnStub = sandbox + .stub(cp, "spawn") + .onFirstCall() + .callsFake(() => { + setTimeout(() => { + childProcessStub.emit("close"); + }); + return childProcessStub as any; + }); + sandbox.stub(fs, "copyFile").yields(null); + sandbox.stub(fs, "unlink").yields(null); + + // Act + const result = await command.execute(); + + // Assert + sandbox.assert.calledWith(childProcessSpawnStub, "../hermes/is/here"); + expect(result.succeeded).to.be.true; + }); + it("project.ext.react is not defined in the app gradle file", async function () { const os = "Android"; // Arrange