Skip to content

Commit

Permalink
[tools] versioning libhermes.so soname for android (expo#13918)
Browse files Browse the repository at this point in the history
# Why

for expo-go to load different hermes-engine version, versioning libhermes.so on filename is not enough, we should further update the `soname`. otherwise, android will use loaded soname directly.

# How

use [patchelf](https://github.com/NixOS/patchelf) to update soname in versioning stage.

# Test Plan

based on expo#13793 and this change, re-generate sdk-42 aar and build versioned android expo-go. load these two projects without close activity
 - load [sdk42 hermes bundle](https://expo.dev/@kudochien/sdk42) which hbc version is 74
 - load local unversioned hermes project based on react-native 0.64 (hermes-engine 0.7.2) which hbc version is 76

before the change, expo-go will crash.
after the change, expo-go could load both two projects without problems.
  • Loading branch information
Kudo authored Aug 21, 2021
1 parent d573b60 commit 7ec4045
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 31 deletions.
2 changes: 2 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Expotools is a CLI and library that contains internal Expo tooling. It is used a
## Prerequisites
Run `bundle install` in the root to install all required Ruby gems.

To support versioning react-native for Android, [patchelf](https://github.com/NixOS/patchelf) is required. On macOS, could run `brew install patchelf` to install.

## Usage
Run `expotools` or `et` from the Expo repository to run the latest version of expotools. This automatically rebuilds the code according to the latest sources.

Expand Down
45 changes: 14 additions & 31 deletions tools/src/versioning/android/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import spawnAsync from '@expo/spawn-async';
import chalk from 'chalk';
import fs from 'fs-extra';
import glob from 'glob-promise';
import inquirer from 'inquirer';
import path from 'path';
import semver from 'semver';
import spawnAsync from '@expo/spawn-async';

import { JniLibNames, getJavaPackagesToRename } from './libraries';
import * as Directories from '../../Directories';
import { getListOfPackagesAsync } from '../../Packages';
import { updateVersionedReactNativeAsync } from './versionReactNative';
import { JniLibNames, getJavaPackagesToRename } from './libraries';
import { renameHermesEngine, updateVersionedReactNativeAsync } from './versionReactNative';

const EXPO_DIR = Directories.getExpoRepositoryRootDir();
const ANDROID_DIR = Directories.getAndroidDir();
Expand Down Expand Up @@ -300,6 +300,14 @@ async function processJavaCodeAsync(libName: string, abiVersion: string) {
);
}

async function ensureToolsInstalledAsync() {
try {
await spawnAsync('patchelf', ['-h'], { ignoreStdio: true });
} catch (e) {
throw new Error('patchelf not found.');
}
}

async function renameJniLibsAsync(version: string) {
const abiVersion = version.replace(/\./g, '_');
const abiPrefix = `abi${abiVersion}`;
Expand Down Expand Up @@ -614,34 +622,9 @@ async function exportReactNdksIfNeeded() {
}
}

async function renameHermesEngine(version: string) {
const abiVersion = version.replace(/\./g, '_');
const abiName = `abi${abiVersion}`;
const prebuiltHermesMkPath = path.join(
versionedReactAndroidPath,
'src',
'main',
'jni',
'first-party',
'hermes',
'Android.mk'
);
await transformFileAsync(
prebuiltHermesMkPath,
/^(LOCAL_SRC_FILES\s+:=\s+jni\/\$\(TARGET_ARCH_ABI\))\/libhermes.so$/gm,
`$1/libhermes_${abiName}.so`
);

const buildGradlePath = path.join(versionedReactAndroidPath, 'build.gradle');
const renameTask = ` rename '(.+).so', '$$1_abi${abiVersion}.so'\n`;
await transformFileAsync(
buildGradlePath,
/(into "\$thirdPartyNdkDir\/hermes"\n)(\s*?\})/gm,
`$1${renameTask}$2`
);
}

export async function addVersionAsync(version: string) {
await ensureToolsInstalledAsync();

console.log(' 🛠 1/11: Updating android/versioned-react-native...');
await updateVersionedReactNativeAsync(
Directories.getReactNativeSubmoduleDir(),
Expand All @@ -662,7 +645,7 @@ export async function addVersionAsync(version: string) {
console.log(' ✅ 3/11: Finished\n\n');

console.log(' 🛠 4/11: Renaming libhermes.so...');
await renameHermesEngine(version);
await renameHermesEngine(versionedReactAndroidPath, version);
console.log(' ✅ 4/11: Finished\n\n');

console.log(' 🛠 5/11: Building versioned ReactAndroid AAR...');
Expand Down
101 changes: 101 additions & 0 deletions tools/src/versioning/android/versionReactNative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,104 @@ async function runReactNativeCodegenAndroidAsync(
'com.facebook.fbreact.specs',
]);
}

export async function renameHermesEngine(versionedReactAndroidPath: string, version: string) {
const abiVersion = version.replace(/\./g, '_');
const abiName = `abi${abiVersion}`;
const prebuiltHermesMkPath = path.join(
versionedReactAndroidPath,
'src',
'main',
'jni',
'first-party',
'hermes',
'Android.mk'
);
const versionedHermesLibName = `libhermes_${abiName}.so`;
await transformFileAsync(prebuiltHermesMkPath, [
{
find: /^(LOCAL_SRC_FILES\s+:=\s+jni\/\$\(TARGET_ARCH_ABI\))\/libhermes.so$/gm,
replaceWith: `$1/${versionedHermesLibName}`,
},
]);

const buildGradlePath = path.join(versionedReactAndroidPath, 'build.gradle');
// patch prepareHermes task to rename copied library and update soname
// the diff is something like that:
//
// ```diff
// --- android/versioned-react-native/ReactAndroid/build.gradle.orig 2021-08-14 00:40:18.000000000 +0800
// +++ android/versioned-react-native/ReactAndroid/build.gradle 2021-08-14 00:40:58.000000000 +0800
// @@ -114,7 +114,7 @@
// into("$thirdPartyNdkDir/folly")
// }
//
// -task prepareHermes(dependsOn: createNativeDepsDirectories, type: Copy) {
// +task prepareHermes(dependsOn: createNativeDepsDirectories) {
// def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine")
// if (!hermesPackagePath) {
// throw new GradleScriptException("Could not find the hermes-engine npm package", null)
// @@ -126,12 +126,29 @@
// }
//
// def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
// -
// + copy {
// +
// from soFiles
// from "src/main/jni/first-party/hermes/Android.mk"
// into "$thirdPartyNdkDir/hermes"
// +
// + rename '(.+).so', '$1_abi43_0_0.so'
// + }
// + exec {
// + commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/arm64-v8a/libhermes_abi43_0_0.so")
// + }
// + exec {
// + commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/armeabi-v7a/libhermes_abi43_0_0.so")
// + }
// + exec {
// + commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/x86/libhermes_abi43_0_0.so")
// + }
// + exec {
// + commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/x86_64/libhermes_abi43_0_0.so")
// + }
// }
//
// +
// task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) {
// src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
// onlyIfNewer(true)
// ```
await transformFileAsync(buildGradlePath, [
{
// reset `prepareHermes` task from Copy type to generic type then we can do both copy and exec.
find: /^(task prepareHermes\(dependsOn: .+), type: Copy(\).+$)/m,
replaceWith: '$1$2',
},
{
// wrap copy task and append exec tasks
find: /(^\s*def soFiles = zipTree\(hermesAAR\).+)\n([\s\S]+?)^\}/gm,
replaceWith: `\
$1
copy {
$2
rename '(.+).so', '$$1_abi${abiVersion}.so'
}
exec {
commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/arm64-v8a/${versionedHermesLibName}")
}
exec {
commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/armeabi-v7a/${versionedHermesLibName}")
}
exec {
commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/x86/${versionedHermesLibName}")
}
exec {
commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/x86_64/${versionedHermesLibName}")
}
}
`,
},
]);
}

0 comments on commit 7ec4045

Please sign in to comment.