1import spawnAsync from '@expo/spawn-async'; 2import fs from 'fs-extra'; 3import path from 'path'; 4 5import { copyFileWithTransformsAsync, transformFileAsync } from '../../Transforms'; 6import { searchFilesAsync } from '../../Utils'; 7import { 8 codegenTransforms, 9 hermesTransforms, 10 reactNativeTransforms, 11} from './reactNativeTransforms'; 12 13export async function updateVersionedReactNativeAsync( 14 reactNativeSubmoduleRoot: string, 15 androidDir: string, 16 sdkVersion: string 17): Promise<void> { 18 const abiVersion = `abi${sdkVersion.replace(/\./g, '_')}`; 19 const versionedReactNativeDir = path.join(androidDir, 'versioned-react-native'); 20 await Promise.all([ 21 fs.remove(path.join(versionedReactNativeDir, 'ReactAndroid')), 22 fs.remove(path.join(versionedReactNativeDir, 'ReactCommon')), 23 fs.remove(path.join(versionedReactNativeDir, 'codegen')), 24 fs.remove(path.join(versionedReactNativeDir, 'sdks')), 25 ]); 26 27 await fs.mkdirp(path.join(versionedReactNativeDir, 'sdks')); 28 await fs.copy( 29 path.join(androidDir, 'sdks/.hermesversion'), 30 path.join(versionedReactNativeDir, 'sdks/.hermesversion') 31 ); 32 33 // Run and version codegen 34 const codegenOutputRoot = path.join(versionedReactNativeDir, 'codegen'); 35 const tmpCodegenOutputRoot = path.join(versionedReactNativeDir, 'codegen-tmp'); 36 try { 37 await runReactNativeCodegenAndroidAsync(reactNativeSubmoduleRoot, tmpCodegenOutputRoot); 38 await versionCodegenDirectoryAsync(tmpCodegenOutputRoot, codegenOutputRoot, abiVersion); 39 } finally { 40 await fs.remove(tmpCodegenOutputRoot); 41 } 42 43 // Copy and version ReactAndroid and ReactCommon 44 await versionReactNativeAsync(androidDir, versionedReactNativeDir, abiVersion); 45 46 await versionHermesAsync(versionedReactNativeDir, abiVersion); 47} 48 49async function versionHermesAsync(versionedReactNativeDir: string, abiVersion: string) { 50 await spawnAsync('./gradlew', [':ReactAndroid:hermes-engine:unzipHermes'], { 51 shell: true, 52 cwd: versionedReactNativeDir, 53 stdio: 'inherit', 54 }); 55 await transformFileAsync( 56 path.join(versionedReactNativeDir, 'sdks/hermes/API/hermes/CMakeLists.txt'), 57 hermesTransforms(abiVersion) 58 ); 59} 60 61async function versionReactNativeAsync( 62 androidDir: string, 63 versionedReactNativeDir: string, 64 abiVersion: string 65) { 66 const files = await searchFilesAsync(androidDir, ['./ReactAndroid/**', './ReactCommon/**']); 67 for (const file of files) { 68 if ((file.match(/\/build\//) && !file.match(/src.*\/build\//)) || file.match(/\/\.cxx\//)) { 69 files.delete(file); 70 } 71 } 72 73 const transforms = reactNativeTransforms(versionedReactNativeDir, abiVersion); 74 for (const sourceFile of files) { 75 await copyFileWithTransformsAsync({ 76 sourceFile, 77 targetDirectory: versionedReactNativeDir, 78 sourceDirectory: androidDir, 79 transforms, 80 }); 81 } 82} 83 84async function versionCodegenDirectoryAsync( 85 tmpCodegenDir: string, 86 codegenDir: string, 87 abiVersion: string 88) { 89 const files = await searchFilesAsync(tmpCodegenDir, ['**']); 90 const transforms = codegenTransforms(abiVersion); 91 for (const sourceFile of files) { 92 await copyFileWithTransformsAsync({ 93 sourceFile, 94 targetDirectory: codegenDir, 95 sourceDirectory: tmpCodegenDir, 96 transforms, 97 }); 98 } 99} 100 101async function runReactNativeCodegenAndroidAsync( 102 reactNativeSubmoduleRoot: string, 103 tmpCodegenOutputRoot: string 104) { 105 await fs.remove(tmpCodegenOutputRoot); 106 await fs.ensureDir(tmpCodegenOutputRoot); 107 108 // generate schema.json from js & flow types 109 const genSchemaScript = path.join( 110 reactNativeSubmoduleRoot, 111 'packages', 112 'react-native-codegen', 113 'lib', 114 'cli', 115 'combine', 116 'combine-js-to-schema-cli.js' 117 ); 118 const schemaOutputPath = path.join(tmpCodegenOutputRoot, 'schema.json'); 119 const jsSourceRoot = path.join(reactNativeSubmoduleRoot, 'Libraries'); 120 await spawnAsync('yarn', ['node', genSchemaScript, schemaOutputPath, jsSourceRoot]); 121 122 // generate code from schema.json 123 const genCodeScript = path.join(reactNativeSubmoduleRoot, 'scripts', 'generate-specs-cli.js'); 124 await spawnAsync('yarn', [ 125 'node', 126 genCodeScript, 127 '--platform', 128 'android', 129 '--schemaPath', 130 schemaOutputPath, 131 '--outputDir', 132 tmpCodegenOutputRoot, 133 '--libraryName', 134 'rncore', 135 '--javaPackageName', 136 'com.facebook.fbreact.specs', 137 ]); 138} 139 140export async function renameHermesEngine(versionedReactAndroidPath: string, version: string) { 141 const abiVersion = version.replace(/\./g, '_'); 142 const abiName = `abi${abiVersion}`; 143 const prebuiltHermesMkPath = path.join( 144 versionedReactAndroidPath, 145 'src', 146 'main', 147 'jni', 148 'first-party', 149 'hermes', 150 'Android.mk' 151 ); 152 const versionedHermesLibName = `libhermes_${abiName}.so`; 153 await transformFileAsync(prebuiltHermesMkPath, [ 154 { 155 find: /^(LOCAL_SRC_FILES\s+:=\s+jni\/\$\(TARGET_ARCH_ABI\))\/libhermes.so$/gm, 156 replaceWith: `$1/${versionedHermesLibName}`, 157 }, 158 ]); 159 160 const buildGradlePath = path.join(versionedReactAndroidPath, 'build.gradle'); 161 // patch prepareHermes task to rename copied library and update soname 162 // the diff is something like that: 163 // 164 // ```diff 165 // --- android/versioned-react-native/ReactAndroid/build.gradle.orig 2021-08-14 00:40:18.000000000 +0800 166 // +++ android/versioned-react-native/ReactAndroid/build.gradle 2021-08-14 00:40:58.000000000 +0800 167 // @@ -114,7 +114,7 @@ 168 // into("$thirdPartyNdkDir/folly") 169 // } 170 // 171 // -task prepareHermes(dependsOn: createNativeDepsDirectories, type: Copy) { 172 // +task prepareHermes(dependsOn: createNativeDepsDirectories) { 173 // def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine") 174 // if (!hermesPackagePath) { 175 // throw new GradleScriptException("Could not find the hermes-engine npm package", null) 176 // @@ -126,12 +126,29 @@ 177 // } 178 // 179 // def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" }) 180 // - 181 // + copy { 182 // + 183 // from soFiles 184 // from "src/main/jni/first-party/hermes/Android.mk" 185 // into "$thirdPartyNdkDir/hermes" 186 // + 187 // + rename '(.+).so', '$1_abi43_0_0.so' 188 // + } 189 // + exec { 190 // + commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/arm64-v8a/libhermes_abi43_0_0.so") 191 // + } 192 // + exec { 193 // + commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/armeabi-v7a/libhermes_abi43_0_0.so") 194 // + } 195 // + exec { 196 // + commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/x86/libhermes_abi43_0_0.so") 197 // + } 198 // + exec { 199 // + commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/x86_64/libhermes_abi43_0_0.so") 200 // + } 201 // } 202 // 203 // + 204 // task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { 205 // src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") 206 // onlyIfNewer(true) 207 // ``` 208 await transformFileAsync(buildGradlePath, [ 209 { 210 // reset `prepareHermes` task from Copy type to generic type then we can do both copy and exec. 211 find: /^(task prepareHermes\(dependsOn: .+), type: Copy(\).+$)/m, 212 replaceWith: '$1$2', 213 }, 214 { 215 // wrap copy task and append exec tasks 216 find: /(^\s*def soFiles = zipTree\(hermesAAR\).+)\n([\s\S]+?)^\}/gm, 217 replaceWith: `\ 218$1 219 copy { 220 $2 221 rename '(.+).so', '$$1_abi${abiVersion}.so' 222 } 223 exec { 224 commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/arm64-v8a/${versionedHermesLibName}") 225 } 226 exec { 227 commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/armeabi-v7a/${versionedHermesLibName}") 228 } 229 exec { 230 commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/x86/${versionedHermesLibName}") 231 } 232 exec { 233 commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/x86_64/${versionedHermesLibName}") 234 } 235} 236`, 237 }, 238 ]); 239} 240