mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
add download/extract hermes prebuilts (#51216)
Summary: When running the prebuild script: `node scripts/ios-prebuild.js` The script will now try to resolve and download a prebuilt version of hermes: 1. Hermes artifacts will be extracted to the `./build/artifacts/hermes` folder to ensure that Package.swift can find a version to link against. 2. The script checks the environment variable `HERMES_ENGINE_TARBALL_PATH` and tries do expand the tarball into the artifacts folder from 1 3. If not found, the script reads the hermes version from either the hidden environment-variable `HERMES_VERSION` and tries to download a release-tarball or a nightly tarball for this version. If the version does not exist, the script will fail. Also added some extra logging features to the script. bypass-github-export-checks ## Changelog: [IOS] [ADDED] - Added downloading of hermes artifacts when pre-building for iOS. Pull Request resolved: https://github.com/facebook/react-native/pull/51216 Test Plan: 1. Delete the `packages/react-native/.build` folder 2. Run the build script (provide a valid HERMES_ENGINE_TARBALL_PATH or a valid Hermes version (or nightly version) 3. Verify that the script successfully exits 4. Build the Package.swift in Xcode and verify that it finds and links the relevant Hermes files, verifying is done by the build succeeding. Reviewed By: NickGerleman Differential Revision: D74565936 Pulled By: cipolleschi fbshipit-source-id: 5bd231999da24cfcce446150ac0fc1b5a4b6a4ae
This commit is contained in:
committed by
Facebook GitHub Bot
parent
d6371bd6ac
commit
41d2b5de0a
+12
-7
@@ -17,6 +17,15 @@ const {execSync} = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const REACT_NATIVE_PACKAGE_ROOT_FOLDER = path.join(__dirname, '..');
|
||||
const packageJsonPath = path.join(
|
||||
REACT_NATIVE_PACKAGE_ROOT_FOLDER,
|
||||
'package.json',
|
||||
);
|
||||
|
||||
// $FlowIgnore[unsupported-syntax]
|
||||
const {version: currentVersion} = require(packageJsonPath);
|
||||
|
||||
async function main() {
|
||||
console.log('Prebuilding React Native iOS...');
|
||||
console.log('');
|
||||
@@ -93,6 +102,9 @@ async function main() {
|
||||
});
|
||||
};
|
||||
|
||||
// HERMES ARTIFACTS
|
||||
await prepareHermesArtifactsAsync(currentVersion, 'release');
|
||||
|
||||
// CODEGEN
|
||||
console.log('Running codegen...');
|
||||
const codegenPath = path.join(root, '.build/codegen');
|
||||
@@ -102,13 +114,6 @@ async function main() {
|
||||
console.log(command);
|
||||
execSync(command, {stdio: 'inherit'});
|
||||
|
||||
// HERMES ARTIFACTS
|
||||
console.log('Download hermes...');
|
||||
// Temporary hardcoded hermes version to make the script work
|
||||
// We will make it right in a future diff.
|
||||
// TODO: T223708709
|
||||
await prepareHermesArtifactsAsync('0.80.0-rc.0', 'debug');
|
||||
|
||||
// LINKING
|
||||
link('Libraries/WebSocket/', 'React');
|
||||
link('React/Base', 'React');
|
||||
|
||||
+289
-47
@@ -12,75 +12,317 @@ const {execSync} = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Downloads hermes artifacts from the specified version and build type. If you want to specify a specific
|
||||
* version of hermes, use the HERMES_VERSION environment variable. The path to the artifacts will be inside
|
||||
* the .build/artifacts/hermes folder, but this can be overridden by setting the HERMES_ENGINE_TARBALL_PATH
|
||||
* environment variable. If this varuable is set, the script will use the local tarball instead of downloading it.
|
||||
*/
|
||||
async function prepareHermesArtifactsAsync(
|
||||
version /*:string*/,
|
||||
buildType /*:string*/,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
) /*: Promise<string> */ {
|
||||
// Check if the Hermes artifacts are already downloaded
|
||||
hermesLog(`Preparing Hermes...`);
|
||||
|
||||
// See if the user has set the HERMES_ENGINE_TARBALL_PATH environment variable
|
||||
let localPath = process.env.HERMES_ENGINE_TARBALL_PATH ?? '';
|
||||
|
||||
// Create artifacts folder
|
||||
const artifactsPath /*: string*/ = path.resolve(
|
||||
process.cwd(),
|
||||
'.build',
|
||||
'artifacts',
|
||||
'hermes',
|
||||
);
|
||||
if (fs.existsSync(artifactsPath)) {
|
||||
return artifactsPath;
|
||||
|
||||
// Ensure that the artifacts folder exists
|
||||
fs.mkdirSync(artifactsPath, {recursive: true});
|
||||
|
||||
// Path for keeping track of the current version in the artifacts folder
|
||||
const versionFilePath = path.join(artifactsPath, 'version.txt');
|
||||
|
||||
// Only check if the artifacts folder exists if we are not using a local tarball
|
||||
if (!localPath) {
|
||||
// Resolve the version from the environment variable or use the default version
|
||||
const resolvedVersion = process.env.HERMES_VERSION ?? version;
|
||||
|
||||
// Check if the Hermes artifacts are already downloaded
|
||||
if (
|
||||
checkExistingVersion(
|
||||
versionFilePath,
|
||||
resolvedVersion,
|
||||
buildType,
|
||||
artifactsPath,
|
||||
)
|
||||
) {
|
||||
return artifactsPath;
|
||||
}
|
||||
|
||||
const sourceType = hermesSourceType(resolvedVersion, buildType);
|
||||
localPath = resolveSourceFromSourceType(
|
||||
sourceType,
|
||||
resolvedVersion,
|
||||
buildType,
|
||||
artifactsPath,
|
||||
);
|
||||
} else {
|
||||
hermesLog('Using local tarball, skipping artifacts folder check');
|
||||
// Delete version.txt if it exists
|
||||
if (fs.existsSync(versionFilePath)) {
|
||||
fs.unlinkSync(versionFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Download the Hermes artifacts
|
||||
const url = getHermesArtifactsUrl(version, buildType);
|
||||
console.log(`Downloading Hermes artifacts from ${url}...`);
|
||||
// Extract the tar.gz
|
||||
execSync(`tar -xzf "${localPath}" -C "${artifactsPath}"`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
// Delete the tarball after extraction
|
||||
if (!process.env.HERMES_ENGINE_TARBALL_PATH) {
|
||||
fs.unlinkSync(localPath);
|
||||
}
|
||||
|
||||
// download the file pointed to by the URL and store it in the ./.build/artifacts folder on disk
|
||||
await downloadAndExtract(url, artifactsPath);
|
||||
return artifactsPath;
|
||||
}
|
||||
|
||||
async function downloadAndExtract(url /*:string*/, targetFolder /*:string*/) {
|
||||
const buildDir = path.resolve('.build', 'artifacts');
|
||||
const tarballPath = path.join(buildDir, 'artifact.tar.gz');
|
||||
/*::
|
||||
type HermesEngineSourceType =
|
||||
| 'local_prebuilt_tarball'
|
||||
| 'download_prebuild_tarball'
|
||||
| 'download_prebuilt_nightly_tarball'
|
||||
*/
|
||||
|
||||
// Ensure build directory exists
|
||||
fs.mkdirSync(targetFolder, {recursive: true});
|
||||
const HermesEngineSourceTypes = {
|
||||
LOCAL_PREBUILT_TARBALL: 'local_prebuilt_tarball',
|
||||
DOWNLOAD_PREBUILD_TARBALL: 'download_prebuild_tarball',
|
||||
DOWNLOAD_PREBUILT_NIGHTLY_TARBALL: 'download_prebuilt_nightly_tarball',
|
||||
};
|
||||
|
||||
console.log(`Downloading file from ${url} to ${tarballPath}...`);
|
||||
/**
|
||||
* Checks if the Hermes artifacts are already downloaded and up to date with the specified version.
|
||||
* Returns true if the artifacts are up to date, false otherwise.
|
||||
*/
|
||||
function checkExistingVersion(
|
||||
versionFilePath /*: string */,
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
artifactsPath /*: string */,
|
||||
) {
|
||||
const resolvedVersion = `${version}-${buildType}`;
|
||||
const hermesXCFramework = path.join(
|
||||
artifactsPath,
|
||||
'destroot',
|
||||
'Libraries',
|
||||
'Frameworks',
|
||||
'universal',
|
||||
'hermes.xcframework',
|
||||
);
|
||||
|
||||
try {
|
||||
// Download the file using curl via execSync
|
||||
execSync(`curl -L "${url}" -o "${tarballPath}"`, {stdio: 'inherit'});
|
||||
|
||||
console.log('Download complete. Extracting...');
|
||||
|
||||
// Extract the tar.gz using execSync
|
||||
execSync(`tar -xzf "${tarballPath}" -C "${targetFolder}"`, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
// Delete the tarball after extraction
|
||||
fs.unlinkSync(tarballPath);
|
||||
|
||||
console.log('Download and extraction complete.');
|
||||
} catch (error) {
|
||||
if (fs.existsSync(tarballPath)) {
|
||||
fs.unlinkSync(tarballPath);
|
||||
if (fs.existsSync(versionFilePath) && fs.existsSync(hermesXCFramework)) {
|
||||
const versionFileContent = fs.readFileSync(versionFilePath, 'utf8');
|
||||
if (versionFileContent.trim() === resolvedVersion) {
|
||||
hermesLog(
|
||||
`Hermes artifacts already downloaded and up to date: ${artifactsPath}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
throw new Error(`Failed to download or extract: ${error.message}`);
|
||||
}
|
||||
// If the version file does not exist or the version does not match, delete the artifacts folder
|
||||
fs.rmSync(artifactsPath, {recursive: true, force: true});
|
||||
hermesLog(
|
||||
`Hermes artifacts folder already exists, but version does not match. Deleting: ${artifactsPath}`,
|
||||
);
|
||||
// Lets create the version.txt file
|
||||
fs.mkdirSync(artifactsPath, {recursive: true});
|
||||
fs.writeFileSync(versionFilePath, resolvedVersion, 'utf8');
|
||||
hermesLog(
|
||||
`Hermes artifacts folder created: ${artifactsPath} with version: ${resolvedVersion}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
function hermesEngineTarballEnvvarDefined() /*: boolean */ {
|
||||
return !!process.env.HERMES_ENGINE_TARBALL_PATH;
|
||||
}
|
||||
|
||||
function getTarballUrl(
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
) /*: string */ {
|
||||
const mavenRepoUrl = 'https://repo1.maven.org/maven2';
|
||||
const namespace = 'com/facebook/react';
|
||||
return `${mavenRepoUrl}/${namespace}/react-native-artifacts/${version}/react-native-artifacts-${version}-hermes-ios-${buildType}.tar.gz`;
|
||||
}
|
||||
|
||||
function getNightlyTarballUrl(
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
) /*: string */ {
|
||||
const params = `r=snapshots&g=com.facebook.react&a=react-native-artifacts&c=hermes-ios-${buildType}&e=tar.gz&v=${version}-SNAPSHOT`;
|
||||
return resolveUrlRedirects(
|
||||
`https://oss.sonatype.org/service/local/artifact/maven/redirect?${params}`,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveUrlRedirects(url /*: string */) /*: string */ {
|
||||
// Synchronously resolve the final URL after redirects using curl
|
||||
try {
|
||||
return execSync(`curl -Ls -o /dev/null -w '%{url_effective}' "${url}"`)
|
||||
.toString()
|
||||
.trim();
|
||||
} catch (e) {
|
||||
hermesLog(`Failed to resolve URL redirects\n${e}`, 'error');
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
function getHermesArtifactsUrl(
|
||||
version /*:string*/,
|
||||
buildType /*:string*/,
|
||||
) /*:string*/ {
|
||||
// Define the URL for the Hermes artifacts
|
||||
// The URL format is:
|
||||
// https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/<version>/react-native-artifacts-<version>-hermes-ios-<buildType>.tar.gz
|
||||
// where <version> is the version of React Native and <buildType> is the build type (e.g., "debug" or "release")
|
||||
// The Maven repository URL and namespace
|
||||
// are hardcoded for simplicity, but they could be parameterized if needed
|
||||
const maven_repo_url = 'https://repo1.maven.org/maven2';
|
||||
const namespace = 'com/facebook/react';
|
||||
return `${maven_repo_url}/${namespace}/react-native-artifacts/${version}/react-native-artifacts-${version}-hermes-ios-${buildType}.tar.gz`;
|
||||
function hermesArtifactExists(tarballUrl /*: string */) /*: boolean */ {
|
||||
try {
|
||||
const code = execSync(
|
||||
`curl -o /dev/null --silent -Iw '%{http_code}' -L "${tarballUrl}"`,
|
||||
)
|
||||
.toString()
|
||||
.trim();
|
||||
return code === '200';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hermesSourceType(
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
) /*: HermesEngineSourceType */ {
|
||||
if (hermesEngineTarballEnvvarDefined()) {
|
||||
hermesLog('Using local prebuild tarball');
|
||||
return HermesEngineSourceTypes.LOCAL_PREBUILT_TARBALL;
|
||||
}
|
||||
if (hermesArtifactExists(getTarballUrl(version, buildType))) {
|
||||
hermesLog(`Using download prebuild ${buildType} tarball`);
|
||||
return HermesEngineSourceTypes.DOWNLOAD_PREBUILD_TARBALL;
|
||||
}
|
||||
if (
|
||||
hermesArtifactExists(
|
||||
getNightlyTarballUrl(version, buildType).replace(/\\/g, ''),
|
||||
)
|
||||
) {
|
||||
hermesLog('Using download prebuild nightly tarball');
|
||||
return HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL;
|
||||
}
|
||||
hermesLog(
|
||||
'Using download prebuild nightly tarball - this is a fallback and might not work.',
|
||||
);
|
||||
return HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL;
|
||||
}
|
||||
|
||||
function resolveSourceFromSourceType(
|
||||
sourceType /*: HermesEngineSourceType */,
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
artifactsPath /*: string*/,
|
||||
) /*: string */ {
|
||||
switch (sourceType) {
|
||||
case HermesEngineSourceTypes.LOCAL_PREBUILT_TARBALL:
|
||||
return localPrebuiltTarball();
|
||||
case HermesEngineSourceTypes.DOWNLOAD_PREBUILD_TARBALL:
|
||||
return downloadPrebuildTarball(version, buildType, artifactsPath);
|
||||
case HermesEngineSourceTypes.DOWNLOAD_PREBUILT_NIGHTLY_TARBALL:
|
||||
return downloadPrebuiltNightlyTarball(version, buildType, artifactsPath);
|
||||
default:
|
||||
abort(
|
||||
`[Hermes] Unsupported or invalid source type provided: ${sourceType}`,
|
||||
);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function localPrebuiltTarball() /*: string */ {
|
||||
const tarballPath = process.env.HERMES_ENGINE_TARBALL_PATH;
|
||||
if (tarballPath && fs.existsSync(tarballPath)) {
|
||||
hermesLog(
|
||||
`Using pre-built binary from local path defined by HERMES_ENGINE_TARBALL_PATH envvar: ${tarballPath}`,
|
||||
);
|
||||
return `file://${tarballPath}`;
|
||||
}
|
||||
abort(
|
||||
`[Hermes] HERMES_ENGINE_TARBALL_PATH is set, but points to a non-existing file: "${tarballPath ?? 'unknown'}"\nIf you don't want to use tarball, run 'unset HERMES_ENGINE_TARBALL_PATH'`,
|
||||
);
|
||||
return '';
|
||||
}
|
||||
|
||||
function downloadPrebuildTarball(
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
artifactsPath /*: string*/,
|
||||
) /*: string */ {
|
||||
const url = getTarballUrl(version, buildType);
|
||||
hermesLog(`Using release tarball from URL: ${url}`);
|
||||
return downloadStableHermes(version, buildType, artifactsPath);
|
||||
}
|
||||
|
||||
function downloadPrebuiltNightlyTarball(
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
artifactsPath /*: string*/,
|
||||
) /*: string */ {
|
||||
const url = getNightlyTarballUrl(version, buildType);
|
||||
hermesLog(`Using nightly tarball from URL: ${url}`);
|
||||
return downloadHermesTarball(url, version, buildType, artifactsPath);
|
||||
}
|
||||
|
||||
function downloadStableHermes(
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
artifactsPath /*: string */,
|
||||
) /*: string */ {
|
||||
const tarballUrl = getTarballUrl(version, buildType);
|
||||
return downloadHermesTarball(tarballUrl, version, buildType, artifactsPath);
|
||||
}
|
||||
|
||||
function downloadHermesTarball(
|
||||
tarballUrl /*: string */,
|
||||
version /*: string */,
|
||||
buildType /*: 'debug' | 'release' */,
|
||||
artifactsPath /*: string */,
|
||||
) /*: string */ {
|
||||
const destPath = buildType
|
||||
? `${artifactsPath}/hermes-ios-${version}-${buildType}.tar.gz`
|
||||
: `${artifactsPath}/hermes-ios-${version}.tar.gz`;
|
||||
if (!fs.existsSync(destPath)) {
|
||||
const tmpFile = `${artifactsPath}/hermes-ios.download`;
|
||||
try {
|
||||
fs.mkdirSync(artifactsPath, {recursive: true});
|
||||
hermesLog(`Downloading Hermes tarball from ${tarballUrl}`);
|
||||
execSync(
|
||||
`curl "${tarballUrl}" -Lo "${tmpFile}" && mv "${tmpFile}" "${destPath}"`,
|
||||
);
|
||||
} catch (e) {
|
||||
abort(`Failed to download Hermes tarball from ${tarballUrl}`);
|
||||
}
|
||||
}
|
||||
return destPath;
|
||||
}
|
||||
|
||||
function abort(message /*: string */) {
|
||||
hermesLog(message, 'error');
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
function hermesLog(
|
||||
message /*: string */,
|
||||
level /*: 'info' | 'warning' | 'error' */ = 'warning',
|
||||
) {
|
||||
// Simple log coloring for terminal output
|
||||
const prefix = '[Hermes] ';
|
||||
let colorFn = (x /*:string*/) => x;
|
||||
if (process.stdout.isTTY) {
|
||||
if (level === 'info') colorFn = x => `\x1b[32m${x}\x1b[0m`;
|
||||
else if (level === 'error') colorFn = x => `\x1b[31m${x}\x1b[0m`;
|
||||
else colorFn = x => `\x1b[33m${x}\x1b[0m`;
|
||||
}
|
||||
|
||||
console.log(colorFn(prefix + message));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
+2
-5
@@ -16,7 +16,7 @@ const fs = require('fs');
|
||||
* @param {string} folderPath - The path to the folder
|
||||
* @returns {string} The path to the created or existing folder
|
||||
*/
|
||||
function createFolderIfNotExists(folderPath /*:string*/) /*: string*/ {
|
||||
function createFolderIfNotExists(folderPath /*:string*/) /*: string */ {
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
fs.mkdirSync(folderPath, {recursive: true});
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
@@ -37,7 +37,4 @@ function throwIfOnEden() {
|
||||
throw new Error('Cannot prepare the iOS prebuilds on an Eden checkout');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createFolderIfNotExists,
|
||||
throwIfOnEden,
|
||||
};
|
||||
module.exports = {createFolderIfNotExists, throwIfOnEden};
|
||||
|
||||
Reference in New Issue
Block a user