Skip to content
Closed
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
57 changes: 39 additions & 18 deletions src/commands/codepush/lib/react-native-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,11 +348,12 @@ export function runReactNativeBundleCommand(
});
}

export function runHermesEmitBinaryCommand(
export async function runHermesEmitBinaryCommand(
bundleName: string,
outputFolder: string,
sourcemapOutput: string,
extraHermesFlags: string[]
extraHermesFlags: string[],
gradleFile: string
): Promise<void> {
const hermesArgs: string[] = [];
const envNodeArgs: string = process.env.CODE_PUSH_NODE_ARGS;
Expand All @@ -378,7 +379,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(" ")}`);

Expand Down Expand Up @@ -464,7 +465,7 @@ export function runHermesEmitBinaryCommand(
});
}

export function getAndroidHermesEnabled(gradleFile: string): boolean {
function parseBuildGradleFile(gradleFile: string) {
let buildGradlePath: string = path.join("android", "app");
if (gradleFile) {
buildGradlePath = gradleFile;
Expand All @@ -477,14 +478,28 @@ export function getAndroidHermesEnabled(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"] || []).some((line: string) => /^enableHermes\s{0,}:\s{0,}true/.test(line));
});
return g2js.parseFile(buildGradlePath).catch(() => {
throw new Error(`Unable to parse the "${buildGradlePath}" file. Please ensure it is a well-formed Gradle file.`);
});
}

async function getHermesCommandFromGradle(gradleFile: string): Promise<string> {
const buildGradle: any = await parseBuildGradleFile(gradleFile);

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 "";
}
}

export function getAndroidHermesEnabled(gradleFile: string): boolean {
return parseBuildGradleFile(gradleFile).then((buildGradle: any) => {
return Array.from(buildGradle["project.ext.react"] || []).some((line: string) => /^enableHermes\s{0,}:\s{0,}true/.test(line));
});
}

export function getiOSHermesEnabled(podFile: string): boolean {
Expand Down Expand Up @@ -529,20 +544,26 @@ function getHermesOSExe(): string {
}
}

function getHermesCommand(): string {
async function getHermesCommand(gradleFile: string): Promise<string> {
const fileExists = (file: string): boolean => {
try {
return fs.statSync(file).isFile();
} catch (e) {
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 getComposeSourceMapsPath(): string {
Expand Down
16 changes: 14 additions & 2 deletions src/commands/codepush/release-react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,25 @@ export default class CodePushReleaseReactCommand extends CodePushReleaseCommandB
if (this.os === "android") {
const isHermesEnabled = await getAndroidHermesEnabled(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
);
}
} else if (this.os === "ios") {
// Check if we have to run hermes to compile JS to Byte Code if Hermes is enabled in Podfile and we're releasing an iOS build
const isHermesEnabled = await getiOSHermesEnabled(this.podFile);
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"));
Expand Down
70 changes: 70 additions & 0 deletions test/commands/codepush/release-react-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,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(<CommandResult>{ 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
Expand Down