1import spawnAsync from '@expo/spawn-async'; 2import chalk from 'chalk'; 3import fs from 'fs-extra'; 4import glob from 'glob-promise'; 5import inquirer from 'inquirer'; 6import path from 'path'; 7import semver from 'semver'; 8 9import * as Directories from '../../Directories'; 10import { getListOfPackagesAsync } from '../../Packages'; 11import { transformFileAsync as transformFileMultiReplacerAsync } from '../../Transforms'; 12import { JniLibNames, getJavaPackagesToRename } from './libraries'; 13import { renameHermesEngine, updateVersionedReactNativeAsync } from './versionReactNative'; 14 15const EXPO_DIR = Directories.getExpoRepositoryRootDir(); 16const ANDROID_DIR = Directories.getAndroidDir(); 17const EXPOTOOLS_DIR = Directories.getExpotoolsDir(); 18const SCRIPT_DIR = path.join(EXPOTOOLS_DIR, 'src/versioning/android'); 19 20const appPath = path.join(ANDROID_DIR, 'app'); 21const expoviewPath = path.join(ANDROID_DIR, 'expoview'); 22const versionedAbisPath = path.join(ANDROID_DIR, 'versioned-abis'); 23const versionedExpoviewAbiPath = (abiName) => path.join(versionedAbisPath, `expoview-${abiName}`); 24const expoviewBuildGradlePath = path.join(expoviewPath, 'build.gradle'); 25const appManifestPath = path.join(appPath, 'src', 'main', 'AndroidManifest.xml'); 26const templateManifestPath = path.join( 27 EXPO_DIR, 28 'template-files', 29 'android', 30 'AndroidManifest.xml' 31); 32const settingsGradlePath = path.join(ANDROID_DIR, 'settings.gradle'); 33const appBuildGradlePath = path.join(appPath, 'build.gradle'); 34const buildGradlePath = path.join(ANDROID_DIR, 'build.gradle'); 35const sdkVersionsPath = path.join(ANDROID_DIR, 'sdkVersions.json'); 36const rnActivityPath = path.join( 37 expoviewPath, 38 'src/versioned/java/host/exp/exponent/experience/MultipleVersionReactNativeActivity.java' 39); 40const expoviewConstantsPath = path.join( 41 expoviewPath, 42 'src/main/java/host/exp/exponent/Constants.java' 43); 44const testSuiteTestsPath = path.join( 45 appPath, 46 'src/androidTest/java/host/exp/exponent/TestSuiteTests.kt' 47); 48const versionedReactAndroidPath = path.join(ANDROID_DIR, 'versioned-react-native/ReactAndroid'); 49const versionedReactAndroidJniPath = path.join(versionedReactAndroidPath, 'src/main'); 50const versionedReactAndroidJavaPath = path.join(versionedReactAndroidJniPath, 'java'); 51const versionedReactCommonPath = path.join(ANDROID_DIR, 'versioned-react-native/ReactCommon'); 52 53async function transformFileAsync(filePath: string, regexp: RegExp, replacement: string = '') { 54 const fileContent = await fs.readFile(filePath, 'utf8'); 55 await fs.writeFile(filePath, fileContent.replace(regexp, replacement)); 56} 57 58async function removeVersionReferencesFromFileAsync(sdkMajorVersion: string, filePath: string) { 59 console.log( 60 `Removing code surrounded by ${chalk.gray(`// BEGIN_SDK_${sdkMajorVersion}`)} and ${chalk.gray( 61 `// END_SDK_${sdkMajorVersion}` 62 )} from ${chalk.magenta(path.relative(EXPO_DIR, filePath))}...` 63 ); 64 await transformFileAsync( 65 filePath, 66 new RegExp( 67 `\\s*//\\s*BEGIN_SDK_${sdkMajorVersion}(_\d+)*\\n.*?//\\s*END_SDK_${sdkMajorVersion}(_\d+)*`, 68 'gs' 69 ), 70 '' 71 ); 72} 73 74async function removeVersionedExpoviewAsync(versionedExpoviewAbiPath: string) { 75 console.log( 76 `Removing versioned expoview at ${chalk.magenta( 77 path.relative(EXPO_DIR, versionedExpoviewAbiPath) 78 )}...` 79 ); 80 await fs.remove(versionedExpoviewAbiPath); 81} 82 83async function removeFromManifestAsync(sdkMajorVersion: string, manifestPath: string) { 84 console.log( 85 `Removing code surrounded by ${chalk.gray( 86 `<!-- BEGIN_SDK_${sdkMajorVersion} -->` 87 )} and ${chalk.gray(`<!-- END_SDK_${sdkMajorVersion} -->`)} from ${chalk.magenta( 88 path.relative(EXPO_DIR, manifestPath) 89 )}...` 90 ); 91 await transformFileAsync( 92 manifestPath, 93 new RegExp( 94 `\\s*<!--\\s*BEGIN_SDK_${sdkMajorVersion}(_\d+)*\\s*-->.*?<!--\\s*END_SDK_${sdkMajorVersion}(_\d+)*\\s*-->`, 95 'gs' 96 ), 97 '' 98 ); 99} 100 101async function removeFromSettingsGradleAsync(abiName: string, settingsGradlePath: string) { 102 console.log( 103 `Removing ${chalk.green(`expoview-${abiName}`)} from ${chalk.magenta( 104 path.relative(EXPO_DIR, settingsGradlePath) 105 )}...` 106 ); 107 await transformFileAsync(settingsGradlePath, new RegExp(`\\n\\s*"${abiName}",[^\\n]*`, 'g'), ''); 108} 109 110async function removeFromBuildGradleAsync(abiName: string, buildGradlePath: string) { 111 console.log( 112 `Removing maven repository for ${chalk.green(`expoview-${abiName}`)} from ${chalk.magenta( 113 path.relative(EXPO_DIR, buildGradlePath) 114 )}...` 115 ); 116 await transformFileAsync( 117 buildGradlePath, 118 new RegExp(`\\s*maven\\s*{\\s*url\\s*".*?/expoview-${abiName}/maven"\\s*}[^\\n]*`), 119 '' 120 ); 121} 122 123async function removeFromSdkVersionsAsync(version: string, sdkVersionsPath: string) { 124 console.log( 125 `Removing ${chalk.cyan(version)} from ${chalk.magenta( 126 path.relative(EXPO_DIR, sdkVersionsPath) 127 )}...` 128 ); 129 await transformFileAsync(sdkVersionsPath, new RegExp(`"${version}",\s*`, 'g'), ''); 130} 131 132async function removeTestSuiteTestsAsync(version: string, testsFilePath: string) { 133 console.log( 134 `Removing test-suite tests from ${chalk.magenta(path.relative(EXPO_DIR, testsFilePath))}...` 135 ); 136 await transformFileAsync( 137 testsFilePath, 138 new RegExp(`\\s*(@\\w+\\s+)*@ExpoSdkVersionTest\\("${version}"\\)[^}]+}`), 139 '' 140 ); 141} 142 143async function findAndPrintVersionReferencesInSourceFilesAsync(version: string): Promise<boolean> { 144 const pattern = new RegExp( 145 `(${version.replace(/\./g, '[._]')}|(SDK|ABI).?${semver.major(version)})`, 146 'ig' 147 ); 148 let matchesCount = 0; 149 150 const files = await glob('**/{src/**/*.@(java|kt|xml),build.gradle}', { cwd: ANDROID_DIR }); 151 152 for (const file of files) { 153 const filePath = path.join(ANDROID_DIR, file); 154 const fileContent = await fs.readFile(filePath, 'utf8'); 155 const fileLines = fileContent.split(/\r\n?|\n/g); 156 let match; 157 158 while ((match = pattern.exec(fileContent)) != null) { 159 const index = pattern.lastIndex - match[0].length; 160 const lineNumberWithMatch = fileContent.substring(0, index).split(/\r\n?|\n/g).length - 1; 161 const firstLineInContext = Math.max(0, lineNumberWithMatch - 2); 162 const lastLineInContext = Math.min(lineNumberWithMatch + 2, fileLines.length); 163 164 ++matchesCount; 165 166 console.log( 167 `Found ${chalk.bold.green(match[0])} in ${chalk.magenta( 168 path.relative(EXPO_DIR, filePath) 169 )}:` 170 ); 171 172 for (let lineIndex = firstLineInContext; lineIndex <= lastLineInContext; lineIndex++) { 173 console.log( 174 `${chalk.gray(1 + lineIndex + ':')} ${fileLines[lineIndex].replace( 175 match[0], 176 chalk.bgMagenta(match[0]) 177 )}` 178 ); 179 } 180 console.log(); 181 } 182 } 183 return matchesCount > 0; 184} 185 186export async function removeVersionAsync(version: string) { 187 const abiName = `abi${version.replace(/\./g, '_')}`; 188 const sdkMajorVersion = `${semver.major(version)}`; 189 190 console.log(`Removing SDK version ${chalk.cyan(version)} for ${chalk.blue('Android')}...`); 191 192 // Remove expoview-abi*_0_0 library 193 await removeVersionedExpoviewAsync(versionedExpoviewAbiPath(abiName)); 194 await removeFromSettingsGradleAsync(abiName, settingsGradlePath); 195 await removeFromBuildGradleAsync(abiName, buildGradlePath); 196 197 // Remove code surrounded by BEGIN_SDK_* and END_SDK_* 198 await removeVersionReferencesFromFileAsync(sdkMajorVersion, expoviewBuildGradlePath); 199 await removeVersionReferencesFromFileAsync(sdkMajorVersion, appBuildGradlePath); 200 await removeVersionReferencesFromFileAsync(sdkMajorVersion, rnActivityPath); 201 await removeVersionReferencesFromFileAsync(sdkMajorVersion, expoviewConstantsPath); 202 203 // Remove test-suite tests from the app. 204 await removeTestSuiteTestsAsync(version, testSuiteTestsPath); 205 206 // Update AndroidManifests 207 await removeFromManifestAsync(sdkMajorVersion, appManifestPath); 208 await removeFromManifestAsync(sdkMajorVersion, templateManifestPath); 209 210 // Remove SDK version from the list of supported SDKs 211 await removeFromSdkVersionsAsync(version, sdkVersionsPath); 212 213 console.log(`\nLooking for SDK references in source files...`); 214 215 if (await findAndPrintVersionReferencesInSourceFilesAsync(version)) { 216 console.log( 217 chalk.yellow(`Please review all of these references and remove them manually if possible!\n`) 218 ); 219 } 220} 221 222function renameLib(lib: string, abiVersion: string) { 223 for (let i = 0; i < JniLibNames.length; i++) { 224 if (lib.endsWith(JniLibNames[i])) { 225 return `${lib}_abi${abiVersion}`; 226 } 227 if (lib.endsWith(`${JniLibNames[i]}.so`)) { 228 const { dir, name, ext } = path.parse(lib); 229 return path.join(dir, `${name}_abi${abiVersion}${ext}`); 230 } 231 } 232 233 return lib; 234} 235 236function processLine(line: string, abiVersion: string) { 237 if ( 238 line.startsWith('LOCAL_MODULE') || 239 line.startsWith('LOCAL_SHARED_LIBRARIES') || 240 line.startsWith('LOCAL_STATIC_LIBRARIES') || 241 line.startsWith('LOCAL_SRC_FILES') 242 ) { 243 let splitLine = line.split('='); 244 let libs = splitLine[1].split(' '); 245 for (let i = 0; i < libs.length; i++) { 246 libs[i] = renameLib(libs[i], abiVersion); 247 } 248 splitLine[1] = libs.join(' '); 249 line = splitLine.join('='); 250 } 251 252 return line; 253} 254 255async function processMkFileAsync(filename: string, abiVersion: string) { 256 let file = await fs.readFile(filename); 257 let fileString = file.toString(); 258 await fs.truncate(filename, 0); 259 // Transforms multiline back to one line and makes the line based versioning easier 260 fileString = fileString.replace(/\\\n/g, ' '); 261 262 let lines = fileString.split('\n'); 263 for (let i = 0; i < lines.length; i++) { 264 let line = lines[i]; 265 line = processLine(line, abiVersion); 266 await fs.appendFile(filename, `${line}\n`); 267 } 268} 269 270async function processCMake(filePath: string, abiVersion: string) { 271 const libNameToReplace = new Set<string>(); 272 for (const libName of JniLibNames) { 273 if (libName.startsWith('lib')) { 274 // in CMake we don't use the lib prefix 275 libNameToReplace.add(libName.slice(3)); 276 } else { 277 libNameToReplace.add(libName); 278 } 279 } 280 281 libNameToReplace.delete('fb'); 282 libNameToReplace.delete('fbjni'); // we use the prebuilt binary which is part of the `com.facebook.fbjni:fbjni` 283 libNameToReplace.delete('jsi'); // jsi is a special case which only replace libName but not header include name 284 285 const transforms = Array.from(libNameToReplace).map(libName => ( 286 { 287 find: new RegExp(`${libName}([^/]*$)`, 'mg'), 288 replaceWith: `${libName}_abi${abiVersion}$1`, 289 } 290 )); 291 292 // to only replace jsi libName 293 transforms.push({ 294 find: new RegExp(`(\ 295\\s+find_library\\( 296\\s+JSI_LIB 297\\s+)jsi$`, 'mg'), 298 replaceWith: `$1jsi_abi${abiVersion}`, 299 }); 300 301 await transformFileMultiReplacerAsync(filePath, transforms); 302} 303 304async function processJavaCodeAsync(libName: string, abiVersion: string) { 305 const abiName = `abi${abiVersion}`; 306 return spawnAsync( 307 `find ${versionedReactAndroidJavaPath} ${versionedExpoviewAbiPath( 308 abiName 309 )} -iname '*.java' -type f -print0 | ` + 310 `xargs -0 sed -i '' 's/"${libName}"/"${libName}_abi${abiVersion}"/g'`, 311 [], 312 { shell: true } 313 ); 314} 315 316async function ensureToolsInstalledAsync() { 317 try { 318 await spawnAsync('patchelf', ['-h'], { ignoreStdio: true }); 319 } catch (e) { 320 throw new Error('patchelf not found.'); 321 } 322} 323 324async function renameJniLibsAsync(version: string) { 325 const abiVersion = version.replace(/\./g, '_'); 326 const abiPrefix = `abi${abiVersion}`; 327 const versionedAbiPath = path.join( 328 Directories.getAndroidDir(), 329 'versioned-abis', 330 `expoview-${abiPrefix}` 331 ); 332 333 // Update JNI methods 334 const packagesToRename = await getJavaPackagesToRename(); 335 const codegenOutputRoot = path.join(ANDROID_DIR, 'versioned-react-native', 'codegen'); 336 for (const javaPackage of packagesToRename) { 337 const pathForPackage = javaPackage.replace(/\./g, '\\/'); 338 await spawnAsync( 339 `find ${versionedReactCommonPath} ${versionedReactAndroidJniPath} ${codegenOutputRoot} -type f ` + 340 `\\( -name \*.java -o -name \*.h -o -name \*.cpp -o -name \*.mk \\) -print0 | ` + 341 `xargs -0 sed -i '' 's/${pathForPackage}/abi${abiVersion}\\/${pathForPackage}/g'`, 342 [], 343 { shell: true } 344 ); 345 346 // reanimated 347 const oldJNIReanimatedPackage = 348 'versioned\\/host\\/exp\\/exponent\\/modules\\/api\\/reanimated\\/'; 349 const newJNIReanimatedPackage = 'host\\/exp\\/exponent\\/modules\\/api\\/reanimated\\/'; 350 await spawnAsync( 351 `find ${versionedAbiPath} -type f ` + 352 `\\( -name \*.java -o -name \*.h -o -name \*.cpp -o -name \*.mk \\) -print0 | ` + 353 `xargs -0 sed -i '' 's/${oldJNIReanimatedPackage}/abi${abiVersion}\\/${newJNIReanimatedPackage}/g'`, 354 [], 355 { shell: true } 356 ); 357 } 358 359 // Update LOCAL_MODULE, LOCAL_SHARED_LIBRARIES, LOCAL_STATIC_LIBRARIES fields in .mk files 360 let [ 361 reactCommonMkFiles, 362 reactAndroidMkFiles, 363 versionedAbiMKFiles, 364 reactAndroidPrebuiltMk, 365 codegenMkFiles, 366 ] = await Promise.all([ 367 glob(path.join(versionedReactCommonPath, '**/*.mk')), 368 glob(path.join(versionedReactAndroidJniPath, '**/*.mk')), 369 glob(path.join(versionedAbiPath, '**/*.mk')), 370 path.join(versionedReactAndroidPath, 'Android-prebuilt.mk'), 371 glob(path.join(codegenOutputRoot, '**/*.mk')), 372 ]); 373 let filenames = [ 374 ...reactCommonMkFiles, 375 ...reactAndroidMkFiles, 376 ...versionedAbiMKFiles, 377 reactAndroidPrebuiltMk, 378 ...codegenMkFiles, 379 ]; 380 await Promise.all(filenames.map((filename) => processMkFileAsync(filename, abiVersion))); 381 382 // Rename references to JNI libs in CMake 383 const cmakesFiles = await glob(path.join(versionedAbiPath, '**/CMakeLists.txt')); 384 await Promise.all(cmakesFiles.map((file) => processCMake(file, abiVersion))); 385 386 // Rename references to JNI libs in Java code 387 for (let i = 0; i < JniLibNames.length; i++) { 388 let libName = JniLibNames[i]; 389 await processJavaCodeAsync(libName, abiVersion); 390 } 391 392 // 'fbjni' is loaded without the 'lib' prefix in com.facebook.jni.Prerequisites 393 await processJavaCodeAsync('fbjni', abiVersion); 394 await processJavaCodeAsync('fb', abiVersion); 395 396 console.log('\nThese are the JNI lib names we modified:'); 397 await spawnAsync( 398 `find ${versionedReactAndroidJavaPath} ${versionedAbiPath} -name "*.java" | xargs grep -i "_abi${abiVersion}"`, 399 [], 400 { shell: true, stdio: 'inherit' } 401 ); 402 403 console.log('\nAnd here are all instances of loadLibrary:'); 404 await spawnAsync( 405 `find ${versionedReactAndroidJavaPath} ${versionedAbiPath} -name "*.java" | xargs grep -i "loadLibrary"`, 406 [], 407 { shell: true, stdio: 'inherit' } 408 ); 409 410 const { isCorrect } = await inquirer.prompt<{ isCorrect: boolean }>([ 411 { 412 type: 'confirm', 413 name: 'isCorrect', 414 message: 'Does all that look correct?', 415 default: false, 416 }, 417 ]); 418 if (!isCorrect) { 419 throw new Error('Fix JNI libs'); 420 } 421} 422 423async function copyExpoModulesAsync(version: string) { 424 const packages = await getListOfPackagesAsync(); 425 for (const pkg of packages) { 426 if ( 427 pkg.isSupportedOnPlatform('android') && 428 pkg.isIncludedInExpoClientOnPlatform('android') && 429 pkg.isVersionableOnPlatform('android') 430 ) { 431 await spawnAsync( 432 './android-copy-expo-module.sh', 433 [pkg.packageName, version, path.join(pkg.path, pkg.androidSubdirectory)], 434 { 435 shell: true, 436 cwd: SCRIPT_DIR, 437 } 438 ); 439 console.log(` ✅ Created versioned ${pkg.packageName}`); 440 } 441 } 442} 443 444async function addVersionedActivitesToManifests(version: string) { 445 const abiVersion = version.replace(/\./g, '_'); 446 const abiName = `abi${abiVersion}`; 447 const majorVersion = semver.major(version); 448 449 await transformFileAsync( 450 templateManifestPath, 451 new RegExp('<!-- ADD DEV SETTINGS HERE -->'), 452 `<!-- ADD DEV SETTINGS HERE --> 453 <!-- BEGIN_SDK_${majorVersion} --> 454 <activity android:name="${abiName}.com.facebook.react.devsupport.DevSettingsActivity"/> 455 <!-- END_SDK_${majorVersion} -->` 456 ); 457} 458 459async function registerNewVersionUnderSdkVersions(version: string) { 460 let fileString = await fs.readFile(sdkVersionsPath, 'utf8'); 461 let jsConfig; 462 // read the existing json config and add the new version to the sdkVersions array 463 try { 464 jsConfig = JSON.parse(fileString); 465 } catch (e) { 466 console.log('Error parsing existing sdkVersions.json file, writing a new one...', e); 467 console.log('The erroneous file contents was:', fileString); 468 jsConfig = { 469 sdkVersions: [], 470 }; 471 } 472 // apply changes 473 jsConfig.sdkVersions.push(version); 474 await fs.writeFile(sdkVersionsPath, JSON.stringify(jsConfig)); 475} 476 477async function cleanUpAsync(version: string) { 478 const abiVersion = version.replace(/\./g, '_'); 479 const abiName = `abi${abiVersion}`; 480 481 const versionedAbiSrcPath = path.join( 482 versionedExpoviewAbiPath(abiName), 483 'src/main/java', 484 abiName 485 ); 486 487 let filesToDelete: string[] = []; 488 489 // delete PrintDocumentAdapter*Callback.kt 490 // their package is `android.print` and therefore they are not changed by the versioning script 491 // so we will have duplicate classes 492 const printCallbackFiles = await glob( 493 path.join(versionedAbiSrcPath, 'expo/modules/print/*Callback.kt') 494 ); 495 for (const file of printCallbackFiles) { 496 const contents = await fs.readFile(file, 'utf8'); 497 if (!contents.includes(`package ${abiName}`)) { 498 filesToDelete.push(file); 499 } else { 500 console.log(`Skipping deleting ${file} because it appears to have been versioned`); 501 } 502 } 503 504 // delete versioned loader providers since we don't need them 505 filesToDelete.push(path.join(versionedAbiSrcPath, 'expo/loaders')); 506 507 console.log('Deleting the following files and directories:'); 508 console.log(filesToDelete); 509 510 for (const file of filesToDelete) { 511 await fs.remove(file); 512 } 513 514 // misc fixes for versioned code 515 const versionedExponentPackagePath = path.join( 516 versionedAbiSrcPath, 517 'host/exp/exponent/ExponentPackage.kt' 518 ); 519 await transformFileAsync( 520 versionedExponentPackagePath, 521 new RegExp('// WHEN_VERSIONING_REMOVE_FROM_HERE', 'g'), 522 '/* WHEN_VERSIONING_REMOVE_FROM_HERE' 523 ); 524 await transformFileAsync( 525 versionedExponentPackagePath, 526 new RegExp('// WHEN_VERSIONING_REMOVE_TO_HERE', 'g'), 527 'WHEN_VERSIONING_REMOVE_TO_HERE */' 528 ); 529 530 await transformFileAsync( 531 path.join(versionedAbiSrcPath, 'host/exp/exponent/VersionedUtils.kt'), 532 new RegExp('// DO NOT EDIT THIS COMMENT - used by versioning scripts[^,]+,[^,]+,'), 533 'null, null,' 534 ); 535 536 // replace abixx_x_x...R with abixx_x_x.host.exp.expoview.R 537 await spawnAsync( 538 `find ${versionedAbiSrcPath} -iname '*.java' -type f -print0 | ` + 539 `xargs -0 sed -i '' 's/import ${abiName}\.[^;]*\.R;/import ${abiName}.host.exp.expoview.R;/g'`, 540 [], 541 { shell: true } 542 ); 543 await spawnAsync( 544 `find ${versionedAbiSrcPath} -iname '*.kt' -type f -print0 | ` + 545 `xargs -0 sed -i '' 's/import ${abiName}\\..*\\.R$/import ${abiName}.host.exp.expoview.R/g'`, 546 [], 547 { shell: true } 548 ); 549 550 // add new versioned maven to build.gradle 551 await transformFileAsync( 552 buildGradlePath, 553 new RegExp('// For old expoviews to work'), 554 `// For old expoviews to work 555 maven { 556 url "$rootDir/versioned-abis/expoview-${abiName}/maven" 557 }` 558 ); 559} 560 561async function prepareReanimatedAsync(version: string): Promise<void> { 562 const abiVersion = version.replace(/\./g, '_'); 563 const abiName = `abi${abiVersion}`; 564 const versionedExpoviewPath = versionedExpoviewAbiPath(abiName); 565 566 const buildReanimatedSO = async () => { 567 await spawnAsync(`./gradlew :expoview-${abiName}:packageNdkLibs`, [], { 568 shell: true, 569 cwd: path.join(versionedExpoviewPath, '../../'), 570 stdio: 'inherit', 571 }); 572 }; 573 574 const removeLeftoverDirectories = async () => { 575 const mainPath = path.join(versionedExpoviewPath, 'src', 'main'); 576 const toRemove = ['Common', 'JNI', 'cpp']; 577 for (let dir of toRemove) { 578 await fs.remove(path.join(mainPath, dir)); 579 } 580 }; 581 582 const removeLeftoversFromGradle = async () => { 583 await spawnAsync('./android-remove-reanimated-code-from-gradle.sh', [version], { 584 shell: true, 585 cwd: SCRIPT_DIR, 586 stdio: 'inherit', 587 }); 588 }; 589 590 await buildReanimatedSO(); 591 await removeLeftoverDirectories(); 592 await removeLeftoversFromGradle(); 593} 594 595async function exportReactNdks() { 596 const versionedRN = path.join(versionedReactAndroidPath, '..'); 597 await spawnAsync(`./gradlew :ReactAndroid:packageReactNdkLibs`, [], { 598 shell: true, 599 cwd: versionedRN, 600 stdio: 'inherit', 601 }); 602} 603 604async function exportReactNdksIfNeeded() { 605 const ndksPath = path.join(versionedReactAndroidPath, 'build', 'react-ndk', 'exported'); 606 const exists = await fs.pathExists(ndksPath); 607 if (!exists) { 608 await exportReactNdks(); 609 return; 610 } 611 612 const exportedSO = await glob(path.join(ndksPath, '**/*.so')); 613 if (exportedSO.length === 0) { 614 await exportReactNdks(); 615 } 616} 617 618export async function addVersionAsync(version: string) { 619 await ensureToolsInstalledAsync(); 620 621 console.log(' 1/11: Updating android/versioned-react-native...'); 622 await updateVersionedReactNativeAsync( 623 Directories.getReactNativeSubmoduleDir(), 624 ANDROID_DIR, 625 path.join(ANDROID_DIR, 'versioned-react-native') 626 ); 627 console.log(' ✅ 1/11: Finished\n\n'); 628 629 console.log(' 2/11: Creating versioned expoview package...'); 630 await spawnAsync('./android-copy-expoview.sh', [version], { 631 shell: true, 632 cwd: SCRIPT_DIR, 633 }); 634 635 console.log(' ✅ 2/11: Finished\n\n'); 636 637 console.log(' 3/11: Renaming JNI libs in android/versioned-react-native and Reanimated...'); 638 await renameJniLibsAsync(version); 639 console.log(' ✅ 3/11: Finished\n\n'); 640 641 console.log(' 4/11: Renaming libhermes.so...'); 642 await renameHermesEngine(versionedReactAndroidPath, version); 643 console.log(' ✅ 4/11: Finished\n\n'); 644 645 console.log(' 5/11: Building versioned ReactAndroid AAR...'); 646 await spawnAsync('./android-build-aar.sh', [version], { 647 shell: true, 648 cwd: SCRIPT_DIR, 649 stdio: 'inherit', 650 }); 651 console.log(' ✅ 5/11: Finished\n\n'); 652 653 console.log(' 6/11: Exporting react ndks if needed...'); 654 await exportReactNdksIfNeeded(); 655 console.log(' ✅ 6/11: Finished\n\n'); 656 657 console.log(' 7/11: prepare versioned Reanimated...'); 658 await prepareReanimatedAsync(version); 659 console.log(' ✅ 7/11: Finished\n\n'); 660 661 console.log(' 8/11: Creating versioned expo-modules packages...'); 662 await copyExpoModulesAsync(version); 663 console.log(' ✅ 8/11: Finished\n\n'); 664 665 console.log(' 9/11: Adding extra versioned activites to AndroidManifest...'); 666 await addVersionedActivitesToManifests(version); 667 console.log(' ✅ 9/11: Finished\n\n'); 668 669 console.log(' 10/11: Registering new version under sdkVersions config...'); 670 await registerNewVersionUnderSdkVersions(version); 671 console.log(' ✅ 10/11: Finished\n\n'); 672 673 console.log(' 11/11: Misc cleanup...'); 674 await cleanUpAsync(version); 675 console.log(' ✅ 11/11: Finished'); 676} 677