Download artifacts from CI to speed up testing (#37971)

Summary:
Testing releases takes a lot of time because we have to build locally several configurations.
However, the artifacts that we build locally are also built in CI.

The goal of this PR is to implement a mechanism to download those artifacts from the CI instead of build locally, so that testing the release locally can take much less time.

As an example, the full test cycle can take more than 2 hours given that we need to repackage and rebuilt the app from the template.

My plan is to add a table with the time saved once the PR is done

### TODO:
- [x] Download Hermes tarball for RNTester iOS
- [x] Download Hermes APK for RNTester Android
- [x] Download JSC APK for RNTester Android
- [x] Download Packaged version of React Native to create a new app
- [x] Use the downloaded React Native to initialize an app from the template
- [x] Download Maven Local prebuilt in CI and use it for Template Android app

### Time Savings

| Setup | Before [s] | After [s] | Notes |
| --- | --- | --- | --- |
| iOS RNTester Hermes | 339.68 | 194.86 | Time saved by downloading Hermes rather then building it |
| iOS RNTester JSC | 129.80 | 123.35 | Not significant, expected as this workflow did not change
| Android RNTester Hermes | 1188.82 | 5.28 | Huge improvement: we download the APK rather then build |
| Android RNTester JSC | 103.10  | 6.28 | Huge improvement: we download the APK rather then build  |
| Creating the RNTestProject | 2074.82  | 191.16 | We download Maven, the packaged version of RN and Hermes instead of building from scratch |

## Changelog:
[Internal] - Speed up Release testing by downloading the CircleCI artifacts

Pull Request resolved: https://github.com/facebook/react-native/pull/37971

Test Plan: - Tested the script locally

Reviewed By: cortinico, dmytrorykun

Differential Revision: D46859120

Pulled By: cipolleschi

fbshipit-source-id: 8878ebaccf6edb801f8e9884e2bf3946380aa748
This commit is contained in:
Riccardo Cipolleschi
2023-07-21 07:15:53 -07:00
committed by Facebook GitHub Bot
parent 5705661d1f
commit f6197cd846
5 changed files with 548 additions and 147 deletions
+198
View File
@@ -0,0 +1,198 @@
#!/usr/bin/env node
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const {exec} = require('shelljs');
const util = require('util');
const asyncRequest = require('request');
const request = util.promisify(asyncRequest);
let circleCIHeaders;
let jobs;
let baseTemporaryPath;
async function initialize(circleCIToken, baseTempPath, branchName) {
console.info('Getting CircleCI information');
circleCIHeaders = {'Circle-Token': circleCIToken};
baseTemporaryPath = baseTempPath;
exec(`mkdir -p ${baseTemporaryPath}`);
const pipeline = await _getLastCircleCIPipelineID(branchName);
const packageAndReleaseWorkflow = await _getPackageAndReleaseWorkflow(
pipeline.id,
);
const testsWorkflow = await _getTestsWorkflow(pipeline.id);
const jobsPromises = [
_getCircleCIJobs(packageAndReleaseWorkflow.id),
_getCircleCIJobs(testsWorkflow.id),
];
const jobsResults = await Promise.all(jobsPromises);
jobs = jobsResults.flatMap(j => j);
}
function baseTmpPath() {
return baseTemporaryPath;
}
async function _getLastCircleCIPipelineID(branchName) {
const options = {
method: 'GET',
url: 'https://circleci.com/api/v2/project/gh/facebook/react-native/pipeline',
qs: {
branch: branchName,
},
headers: circleCIHeaders,
};
const response = await request(options);
if (response.error) {
throw new Error(error);
}
const items = JSON.parse(response.body).items;
if (!items || items.length === 0) {
throw new Error(
'No pipelines found on this branch. Make sure that the CI has run at least once, successfully',
);
}
const lastPipeline = items[0];
return {id: lastPipeline.id, number: lastPipeline.number};
}
async function _getSpecificWorkflow(pipelineId, workflowName) {
const options = {
method: 'GET',
url: `https://circleci.com/api/v2/pipeline/${pipelineId}/workflow`,
headers: circleCIHeaders,
};
const response = await request(options);
if (response.error) {
throw new Error(error);
}
const body = JSON.parse(response.body);
let workflow = body.items.find(w => w.name === workflowName);
_throwIfWorkflowNotFound(workflow, workflowName);
return workflow;
}
function _throwIfWorkflowNotFound(workflow, name) {
if (!workflow) {
throw new Error(
`Can't find a workflow named ${name}. Please check whether that workflow has started.`,
);
}
}
async function _getPackageAndReleaseWorkflow(pipelineId) {
return _getSpecificWorkflow(pipelineId, 'package_and_publish_release_dryrun');
}
async function _getTestsWorkflow(pipelineId) {
return _getSpecificWorkflow(pipelineId, 'tests');
}
async function _getCircleCIJobs(workflowId) {
const options = {
method: 'GET',
url: `https://circleci.com/api/v2/workflow/${workflowId}/job`,
headers: circleCIHeaders,
};
const response = await request(options);
if (response.error) {
throw new Error(error);
}
const body = JSON.parse(response.body);
return body.items;
}
async function _getJobsArtifacts(jobNumber) {
const options = {
method: 'GET',
url: `https://circleci.com/api/v2/project/gh/facebook/react-native/${jobNumber}/artifacts`,
headers: circleCIHeaders,
};
const response = await request(options);
if (response.error) {
throw new Error(error);
}
const body = JSON.parse(response.body);
return body.items;
}
async function _findUrlForJob(jobName, artifactPath) {
const job = jobs.find(j => j.name === jobName);
_throwIfJobIsNull(job);
_throwIfJobIsUnsuccessful(job);
const artifacts = await _getJobsArtifacts(job.job_number);
return artifacts.find(artifact => artifact.path.indexOf(artifactPath) > -1)
.url;
}
function _throwIfJobIsNull(job) {
if (!job) {
throw new Error(
`Can't find a job with name ${job.name}. Please verify that it has been executed and that all its dependencies completed successfully.`,
);
}
}
function _throwIfJobIsUnsuccessful(job) {
if (job.status !== 'success') {
throw new Error(
`The job ${job.name} status is ${job.status}. We need a 'success' status to proceed with the testing.`,
);
}
}
async function artifactURLHermesDebug() {
return _findUrlForJob('build_hermes_macos-Debug', 'hermes-ios-debug.tar.gz');
}
async function artifactURLForMavenLocal() {
return _findUrlForJob('build_and_publish_npm_package-2', 'maven-local.zip');
}
async function artifactURLForHermesRNTesterAPK(emulatorArch) {
return _findUrlForJob(
'test_android',
`rntester-apk/hermes/debug/app-hermes-${emulatorArch}-debug.apk`,
);
}
async function artifactURLForJSCRNTesterAPK(emulatorArch) {
return _findUrlForJob(
'test_android',
`rntester-apk/jsc/debug/app-jsc-${emulatorArch}-debug.apk`,
);
}
function downloadArtifact(artifactURL, destination) {
exec(`rm -rf ${destination}`);
exec(`curl ${artifactURL} -Lo ${destination}`);
}
module.exports = {
initialize,
downloadArtifact,
artifactURLForJSCRNTesterAPK,
artifactURLForHermesRNTesterAPK,
artifactURLForMavenLocal,
artifactURLHermesDebug,
baseTmpPath,
};
-5
View File
@@ -91,11 +91,6 @@ function generateiOSArtifacts(
) {
pushd(`${hermesCoreSourceFolder}`);
//Need to generate hermesc
exec(
`${hermesCoreSourceFolder}/utils/build-hermesc-xcode.sh ${hermesCoreSourceFolder}/build_host_hermesc`,
);
//Generating iOS Artifacts
exec(
`JSI_PATH=${jsiFolder} BUILD_TYPE=${buildType} ${hermesCoreSourceFolder}/utils/build-mac-framework.sh`,
+1 -3
View File
@@ -44,6 +44,7 @@ if (isPackagerRunning() === 'running') {
console.info('\n** Cleaning Gradle build artifacts **\n');
exec('./gradlew clean');
exec('rm -rf /tmp/maven-local');
exec('rm -rf /tmp/react-native-tmp');
// iOS
console.info('\n** Nuking the derived data folder **\n');
@@ -56,9 +57,6 @@ exec('rm -rf ~/Library/Caches/CocoaPods/Pods/External/hermes-engine');
console.info('\n** Removing the RNTester Pods **\n');
exec('rm -rf packages/rn-tester/Pods');
// I'm not sure we want to also remove the lock file
// exec('rm -rf packages/rn-tester/Podfile.lock');
// RNTestProject
console.info('\n** Removing the RNTestProject folder **\n');
exec('rm -rf /tmp/RNTestProject');
+186 -134
View File
@@ -16,27 +16,20 @@
* and to make it more accessible for other devs to play around with.
*/
const {exec, exit, pushd, popd, pwd, cd, cp} = require('shelljs');
const updateTemplatePackage = require('../scripts/update-template-package');
const {exec, pushd, popd, pwd, cd} = require('shelljs');
const updateTemplatePackage = require('./update-template-package');
const yargs = require('yargs');
const path = require('path');
const fs = require('fs');
const {
checkPackagerRunning,
maybeLaunchAndroidEmulator,
isPackagerRunning,
launchPackagerInSeparateWindow,
setupCircleCIArtifacts,
prepareArtifacts,
} = require('./testing-utils');
const {
generateAndroidArtifacts,
generateiOSArtifacts,
} = require('./release-utils');
const {
downloadHermesSourceTarball,
expandHermesSourceTarball,
} = require('react-native/scripts/hermes/hermes-utils.js');
const argv = yargs
.option('t', {
alias: 'target',
@@ -52,105 +45,152 @@ const argv = yargs
alias: 'hermes',
type: 'boolean',
default: true,
})
.option('c', {
alias: 'circleciToken',
type: 'string',
}).argv;
/*
* see the test-local-e2e.js script for clean up process
// === RNTester === //
/**
* Start the test for RNTester on iOS.
*
* Parameters:
* - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them.
* - @onReleaseBranch whether we are on a release branch or not
*/
async function testRNTesterIOS(circleCIArtifacts, onReleaseBranch) {
console.info(
`We're going to test the ${
argv.hermes ? 'Hermes' : 'JSC'
} version of RNTester iOS with the new Architecture enabled`,
);
// command order: we ask the user to select if they want to test RN tester
// or RNTestProject
// remember that for this to be successful
// you should have run bundle install once
// in your local setup
if (argv.hermes && circleCIArtifacts != null) {
const hermesURL = await circleCIArtifacts.artifactURLHermesDebug();
const hermesPath = path.join(
circleCIArtifacts.baseTmpPath(),
'hermes-ios-debug.tar.gz',
);
// download hermes source code from manifold
circleCIArtifacts.downloadArtifact(hermesURL, hermesPath);
console.info(`Downloaded Hermes in ${hermesPath}`);
exec(
`HERMES_ENGINE_TARBALL_PATH=${hermesPath} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`,
);
} else {
exec(
`USE_HERMES=${
argv.hermes ? 1 : 0
} CI=${onReleaseBranch} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`,
);
}
// if they select RN tester, we ask if iOS or Android, and then we run the tests
// if they select RNTestProject, we run the RNTestProject test
// if everything succeeded so far, we can launch Metro and the app
// start the Metro server in a separate window
launchPackagerInSeparateWindow(pwd());
// let's check if Metro is already running, if it is let's kill it and start fresh
if (isPackagerRunning() === 'running') {
exec("lsof -i :8081 | grep LISTEN | /usr/bin/awk '{print $2}' | xargs kill");
// launch the app on iOS simulator
exec('npx react-native run-ios --scheme RNTester --simulator "iPhone 14"');
}
const onReleaseBranch = exec('git rev-parse --abbrev-ref HEAD', {
silent: true,
})
.stdout.trim()
.endsWith('-stable');
/**
* Start the test for RNTester on Android.
*
* Parameters:
* - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them.
*/
async function testRNTesterAndroid(circleCIArtifacts) {
maybeLaunchAndroidEmulator();
if (argv.target === 'RNTester') {
console.info(
`We're going to test the ${
argv.hermes ? 'Hermes' : 'JSC'
} version of RNTester Android with the new Architecture enabled`,
);
// Start the Metro server so it will be ready if the app can be built and installed successfully.
launchPackagerInSeparateWindow(pwd());
// Wait for the Android Emulator to be properly loaded and bootstrapped
exec(
"adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82'",
);
if (circleCIArtifacts != null) {
const downloadPath = path.join(
circleCIArtifacts.baseTmpPath(),
'rntester.apk',
);
const emulatorArch = exec('adb shell getprop ro.product.cpu.abi').trim();
const rntesterAPKURL = argv.hermes
? await circleCIArtifacts.artifactURLForHermesRNTesterAPK(emulatorArch)
: await circleCIArtifacts.artifactURLForJSCRNTesterAPK(emulatorArch);
console.info('Start Downloading APK');
circleCIArtifacts.downloadArtifact(rntesterAPKURL, downloadPath);
exec(`adb install ${downloadPath}`);
} else {
exec(
`../../gradlew :packages:rn-tester:android:app:${
argv.hermes ? 'installHermesDebug' : 'installJscDebug'
} --quiet`,
);
}
// launch the app
// TODO: we should find a way to make it work like for iOS, via npx react-native run-android
// currently, that fails with an error.
exec(
'adb shell am start -n com.facebook.react.uiapp/com.facebook.react.uiapp.RNTesterActivity',
);
// just to make sure that the Android up won't have troubles finding the Metro server
exec('adb reverse tcp:8081 tcp:8081');
}
/**
* Function that start testing on RNTester.
*
* Parameters:
* - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them.
* - @onReleaseBranch whether we are on a release branch or not
*/
async function testRNTester(circleCIArtifacts, onReleaseBranch) {
// FIXME: make sure that the commands retains colors
// (--ansi) doesn't always work
// see also https://github.com/shelljs/shelljs/issues/86
pushd('packages/rn-tester');
if (argv.platform === 'iOS') {
console.info(
`We're going to test the ${
argv.hermes ? 'Hermes' : 'JSC'
} version of RNTester iOS with the new Architecture enabled`,
);
// remember that for this to be successful
// you should have run bundle install once
// in your local setup - also: if I'm on release branch, I pick the
// hermes ref from the hermes ref file (see hermes-engine.podspec)
exec(
`USE_HERMES=${
argv.hermes ? 1 : 0
} CI=${onReleaseBranch} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`,
);
// if everything succeeded so far, we can launch Metro and the app
// start the Metro server in a separate window
launchPackagerInSeparateWindow(pwd());
// launch the app on iOS simulator
exec('npx react-native run-ios --scheme RNTester --simulator "iPhone 14"');
await testRNTesterIOS(circleCIArtifacts, onReleaseBranch);
} else {
// we do the android path here
maybeLaunchAndroidEmulator();
console.info(
`We're going to test the ${
argv.hermes ? 'Hermes' : 'JSC'
} version of RNTester Android with the new Architecture enabled`,
);
exec(
`../../gradlew :packages:rn-tester:android:app:${
argv.hermes ? 'installHermesDebug' : 'installJscDebug'
} --quiet`,
);
// launch the app on Android simulator
// TODO: we should find a way to make it work like for iOS, via npx react-native run-android
// currently, that fails with an error.
// if everything succeeded so far, we can launch Metro and the app
// start the Metro server in a separate window
launchPackagerInSeparateWindow(pwd());
// launch the app
exec(
'adb shell am start -n com.facebook.react.uiapp/com.facebook.react.uiapp.RNTesterActivity',
);
// just to make sure that the Android up won't have troubles finding the Metro server
exec('adb reverse tcp:8081 tcp:8081');
await testRNTesterAndroid(circleCIArtifacts);
}
popd();
} else {
}
// === RNTestProject === //
async function testRNTestProject(circleCIArtifacts) {
console.info("We're going to test a fresh new RN project");
// create the local npm package to feed the CLI
// base setup required (specular to publish-npm.js)
// we need to add the unique timestamp to avoid npm/yarn to use some local caches
const baseVersion = require('../packages/react-native/package.json').version;
// in local testing, 1000.0.0 mean we are on main, every other case means we are
// working on a release version
const buildType = baseVersion !== '1000.0.0' ? 'release' : 'dry-run';
// we need to add the unique timestamp to avoid npm/yarn to use some local caches
const dateIdentifier = new Date()
.toISOString()
.slice(0, -8)
@@ -159,62 +199,44 @@ if (argv.target === 'RNTester') {
const releaseVersion = `${baseVersion}-${dateIdentifier}`;
// this is needed to generate the Android artifacts correctly
const exitCode = exec(
`node scripts/set-rn-version.js --to-version ${releaseVersion} --build-type ${buildType}`,
).code;
if (exitCode !== 0) {
console.error(
`Failed to set the RN version. Version ${releaseVersion} is not valid for ${buildType}`,
);
process.exit(exitCode);
}
// Generate native files for Android
generateAndroidArtifacts(releaseVersion);
// Setting up generating native iOS (will be done later)
// Prepare some variables for later use
const repoRoot = pwd();
const reactNativePackagePath = `${repoRoot}/packages/react-native`;
const jsiFolder = `${reactNativePackagePath}/ReactCommon/jsi`;
const hermesCoreSourceFolder = `${reactNativePackagePath}/sdks/hermes`;
if (!fs.existsSync(hermesCoreSourceFolder)) {
console.info('The Hermes source folder is missing. Downloading...');
downloadHermesSourceTarball();
expandHermesSourceTarball();
}
// need to move the scripts inside the local hermes cloned folder
// cp sdks/hermes-engine/utils/*.sh <your_hermes_checkout>/utils/.
cp(
`${reactNativePackagePath}/sdks/hermes-engine/utils/*.sh`,
`${reactNativePackagePath}/sdks/hermes/utils/.`,
);
// for this scenario, we only need to create the debug build
// (env variable PRODUCTION defines that podspec side)
const buildTypeiOSArtifacts = 'Debug';
// the android ones get set into /private/tmp/maven-local
const localMavenPath = '/private/tmp/maven-local';
// Generate native files for iOS
const tarballOutputPath = generateiOSArtifacts(
jsiFolder,
hermesCoreSourceFolder,
buildTypeiOSArtifacts,
localMavenPath,
);
const localNodeTGZPath = `${reactNativePackagePath}/react-native-${releaseVersion}.tgz`;
const mavenLocalPath =
circleCIArtifacts != null
? path.join(circleCIArtifacts.baseTmpPath(), 'maven-local.zip')
: '/private/tmp/maven-local';
const hermesPath = await prepareArtifacts(
circleCIArtifacts,
mavenLocalPath,
localNodeTGZPath,
releaseVersion,
buildType,
reactNativePackagePath,
);
updateTemplatePackage({
'react-native': `file:${localNodeTGZPath}`,
});
// create locally the node module
exec('npm pack', {cwd: reactNativePackagePath});
exec('npm pack --pack-destination ', {cwd: reactNativePackagePath});
// node pack does not creates a version of React Native with the right name on main.
// Let's add some defensive programming checks:
if (!fs.existsSync(localNodeTGZPath)) {
const tarfile = fs
.readdirSync(reactNativePackagePath)
.find(name => name.startsWith('react-native-') && name.endsWith('.tgz'));
if (!tarfile) {
throw new Error("Couldn't find a zipped version of react-native");
}
exec(
`cp ${path.join(reactNativePackagePath, tarfile)} ${localNodeTGZPath}`,
);
}
pushd('/tmp/');
// need to avoid the pod install step - we'll do it later
@@ -227,14 +249,14 @@ if (argv.target === 'RNTester') {
// need to do this here so that Android will be properly setup either way
exec(
'echo "REACT_NATIVE_MAVEN_LOCAL_REPO=/private/tmp/maven-local" >> android/gradle.properties',
`echo "REACT_NATIVE_MAVEN_LOCAL_REPO=${mavenLocalPath}" >> android/gradle.properties`,
);
// doing the pod install here so that it's easier to play around RNTestProject
cd('ios');
exec('bundle install');
exec(
`HERMES_ENGINE_TARBALL_PATH=${tarballOutputPath} USE_HERMES=${
`HERMES_ENGINE_TARBALL_PATH=${hermesPath} USE_HERMES=${
argv.hermes ? 1 : 0
} bundle exec pod install --ansi`,
);
@@ -250,4 +272,34 @@ if (argv.target === 'RNTester') {
popd();
}
exit(0);
async function main() {
/*
* see the test-local-e2e.js script for clean up process
*/
// command order: we ask the user to select if they want to test RN tester
// or RNTestProject
// if they select RN tester, we ask if iOS or Android, and then we run the tests
// if they select RNTestProject, we run the RNTestProject test
checkPackagerRunning();
const branchName = exec('git rev-parse --abbrev-ref HEAD', {
silent: true,
}).stdout.trim();
const onReleaseBranch = branchName.endsWith('-stable');
let circleCIArtifacts = await setupCircleCIArtifacts(
argv.circleciToken,
branchName,
);
if (argv.target === 'RNTester') {
await testRNTester(circleCIArtifacts, onReleaseBranch);
} else {
await testRNTestProject(circleCIArtifacts);
}
}
main();
+163 -5
View File
@@ -9,9 +9,23 @@
'use strict';
const {exec} = require('shelljs');
const {exec, cp} = require('shelljs');
const fs = require('fs');
const os = require('os');
const {spawn} = require('node:child_process');
const path = require('path');
const circleCIArtifactsUtils = require('./circle-ci-artifacts-utils.js');
const {
generateAndroidArtifacts,
generateiOSArtifacts,
} = require('./release-utils');
const {
downloadHermesSourceTarball,
expandHermesSourceTarball,
} = require('../packages/react-native/scripts/hermes/hermes-utils.js');
/*
* Android related utils - leverages android tooling
@@ -35,12 +49,12 @@ const launchEmulator = emulatorName => {
// from docs: "When using the detached option to start a long-running process, the process will not stay running in the background after the parent exits unless it is provided with a stdio configuration that is not connected to the parent. If the parent's stdio is inherited, the child will remain attached to the controlling terminal."
// here: https://nodejs.org/api/child_process.html#optionsdetached
const cp = spawn(emulatorCommand, [`@${emulatorName}`], {
const child_process = spawn(emulatorCommand, [`@${emulatorName}`], {
detached: true,
stdio: 'ignore',
});
cp.unref();
child_process.unref();
};
function tryLaunchEmulator() {
@@ -69,7 +83,7 @@ function hasConnectedDevice() {
}
function maybeLaunchAndroidEmulator() {
if (hasConnectedDevice) {
if (hasConnectedDevice()) {
console.info('Already have a device connected. Skip launching emulator.');
return;
}
@@ -112,11 +126,155 @@ function isPackagerRunning(
// this is a very limited implementation of how this should work
function launchPackagerInSeparateWindow(folderPath) {
const command = `tell application "Terminal" to do script "cd ${folderPath} && yarn start"`;
exec(`osascript -e '${command}'`);
exec(`osascript -e '${command}' >/dev/null <<EOF`);
}
/**
* Checks if Metro is running and it kills it if that's the case
*/
function checkPackagerRunning() {
if (isPackagerRunning() === 'running') {
exec(
"lsof -i :8081 | grep LISTEN | /usr/bin/awk '{print $2}' | xargs kill",
);
}
}
// === ARTIFACTS === //
/**
* Setups the CircleCIArtifacts if a token has been passed
*
* Parameters:
* - @circleciToken a valid CircleCI Token.
* - @branchName the branch of the name we want to use to fetch the artifacts.
*/
async function setupCircleCIArtifacts(circleciToken, branchName) {
if (!circleciToken) {
return null;
}
const baseTmpPath = '/tmp/react-native-tmp';
await circleCIArtifactsUtils.initialize(
circleciToken,
baseTmpPath,
branchName,
);
return circleCIArtifactsUtils;
}
async function downloadArtifactsFromCircleCI(
circleCIArtifacts,
mavenLocalPath,
localNodeTGZPath,
) {
const mavenLocalURL = await circleCIArtifacts.artifactURLForMavenLocal();
const hermesURL = await circleCIArtifacts.artifactURLHermesDebug();
const hermesPath = path.join(
circleCIArtifacts.baseTmpPath(),
'hermes-ios-debug.tar.gz',
);
console.info('[Download] Maven Local Artifacts');
circleCIArtifacts.downloadArtifact(mavenLocalURL, mavenLocalPath);
console.info('[Download] Hermes');
circleCIArtifacts.downloadArtifact(hermesURL, hermesPath);
return hermesPath;
}
function buildArtifactsLocally(
releaseVersion,
buildType,
reactNativePackagePath,
) {
// this is needed to generate the Android artifacts correctly
const exitCode = exec(
`node scripts/set-rn-version.js --to-version ${releaseVersion} --build-type ${buildType}`,
).code;
if (exitCode !== 0) {
console.error(
`Failed to set the RN version. Version ${releaseVersion} is not valid for ${buildType}`,
);
process.exit(exitCode);
}
// Generate native files for Android
generateAndroidArtifacts(releaseVersion);
// Generate iOS Artifacts
const jsiFolder = `${reactNativePackagePath}/ReactCommon/jsi`;
const hermesCoreSourceFolder = `${reactNativePackagePath}/sdks/hermes`;
if (!fs.existsSync(hermesCoreSourceFolder)) {
console.info('The Hermes source folder is missing. Downloading...');
downloadHermesSourceTarball();
expandHermesSourceTarball();
}
// need to move the scripts inside the local hermes cloned folder
// cp sdks/hermes-engine/utils/*.sh <your_hermes_checkout>/utils/.
cp(
`${reactNativePackagePath}/sdks/hermes-engine/utils/*.sh`,
`${reactNativePackagePath}/sdks/hermes/utils/.`,
);
// for this scenario, we only need to create the debug build
// (env variable PRODUCTION defines that podspec side)
const buildTypeiOSArtifacts = 'Debug';
// the android ones get set into /private/tmp/maven-local
const localMavenPath = '/private/tmp/maven-local';
// Generate native files for iOS
const hermesPath = generateiOSArtifacts(
jsiFolder,
hermesCoreSourceFolder,
buildTypeiOSArtifacts,
localMavenPath,
);
return hermesPath;
}
/**
* It prepares the artifacts required to run a new project created from the template
*
* Parameters:
* - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them.
* - @mavenLocalPath path to the local maven repo that is needed by Android.
* - @localNodeTGZPath path where we want to store the react-native tgz.
* - @releaseVersion the version that is about to be released.
* - @buildType the type of build we want to execute if we build locally.
* - @reactNativePackagePath the path to the react native package within the repo.
*
* Returns:
* - @hermesPath the path to hermes for iOS
*/
async function prepareArtifacts(
circleCIArtifacts,
mavenLocalPath,
localNodeTGZPath,
releaseVersion,
buildType,
reactNativePackagePath,
) {
return circleCIArtifacts != null
? await downloadArtifactsFromCircleCI(
circleCIArtifacts,
mavenLocalPath,
localNodeTGZPath,
)
: buildArtifactsLocally(releaseVersion, buildType, reactNativePackagePath);
}
module.exports = {
checkPackagerRunning,
maybeLaunchAndroidEmulator,
isPackagerRunning,
launchPackagerInSeparateWindow,
setupCircleCIArtifacts,
prepareArtifacts,
};