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 let lines = fileString.split('\n'); 260 for (let i = 0; i < lines.length; i++) { 261 let line = lines[i]; 262 line = processLine(line, abiVersion); 263 await fs.appendFile(filename, `${line}\n`); 264 } 265} 266 267async function processCMake(filePath: string, abiVersion: string) { 268 const libNameToReplace = new Set<string>(); 269 for (const libName of JniLibNames) { 270 if (libName.startsWith('lib')) { 271 // in CMake we don't use the lib prefix 272 libNameToReplace.add(libName.slice(3)); 273 } else { 274 libNameToReplace.add(libName); 275 } 276 } 277 278 libNameToReplace.delete('fb'); 279 libNameToReplace.delete('fbjni'); // we use the prebuilt binary which is part of the `com.facebook.fbjni:fbjni` 280 libNameToReplace.delete('jsi'); // jsi is a special case which only replace libName but not header include name 281 282 const transforms = Array.from(libNameToReplace).map(libName => ( 283 { 284 find: new RegExp(`${libName}([^/]*$)`, 'mg'), 285 replaceWith: `${libName}_abi${abiVersion}$1`, 286 } 287 )); 288 289 // to only replace jsi libName 290 transforms.push({ 291 find: new RegExp(`(\ 292\\s+find_library\\( 293\\s+JSI_LIB 294\\s+)jsi$`, 'mg'), 295 replaceWith: `$1jsi_abi${abiVersion}`, 296 }); 297 298 await transformFileMultiReplacerAsync(filePath, transforms); 299} 300 301async function processJavaCodeAsync(libName: string, abiVersion: string) { 302 const abiName = `abi${abiVersion}`; 303 return spawnAsync( 304 `find ${versionedReactAndroidJavaPath} ${versionedExpoviewAbiPath( 305 abiName 306 )} -iname '*.java' -type f -print0 | ` + 307 `xargs -0 sed -i '' 's/"${libName}"/"${libName}_abi${abiVersion}"/g'`, 308 [], 309 { shell: true } 310 ); 311} 312 313async function ensureToolsInstalledAsync() { 314 try { 315 await spawnAsync('patchelf', ['-h'], { ignoreStdio: true }); 316 } catch (e) { 317 throw new Error('patchelf not found.'); 318 } 319} 320 321async function renameJniLibsAsync(version: string) { 322 const abiVersion = version.replace(/\./g, '_'); 323 const abiPrefix = `abi${abiVersion}`; 324 const versionedAbiPath = path.join( 325 Directories.getAndroidDir(), 326 'versioned-abis', 327 `expoview-${abiPrefix}` 328 ); 329 330 // Update JNI methods 331 const packagesToRename = await getJavaPackagesToRename(); 332 const codegenOutputRoot = path.join(ANDROID_DIR, 'versioned-react-native', 'codegen'); 333 for (const javaPackage of packagesToRename) { 334 const pathForPackage = javaPackage.replace(/\./g, '\\/'); 335 await spawnAsync( 336 `find ${versionedReactCommonPath} ${versionedReactAndroidJniPath} ${codegenOutputRoot} -type f ` + 337 `\\( -name \*.java -o -name \*.h -o -name \*.cpp -o -name \*.mk \\) -print0 | ` + 338 `xargs -0 sed -i '' 's/${pathForPackage}/abi${abiVersion}\\/${pathForPackage}/g'`, 339 [], 340 { shell: true } 341 ); 342 343 // reanimated 344 const oldJNIReanimatedPackage = 345 'versioned\\/host\\/exp\\/exponent\\/modules\\/api\\/reanimated\\/'; 346 const newJNIReanimatedPackage = 'host\\/exp\\/exponent\\/modules\\/api\\/reanimated\\/'; 347 await spawnAsync( 348 `find ${versionedAbiPath} -type f ` + 349 `\\( -name \*.java -o -name \*.h -o -name \*.cpp -o -name \*.mk \\) -print0 | ` + 350 `xargs -0 sed -i '' 's/${oldJNIReanimatedPackage}/abi${abiVersion}\\/${newJNIReanimatedPackage}/g'`, 351 [], 352 { shell: true } 353 ); 354 } 355 356 // Update LOCAL_MODULE, LOCAL_SHARED_LIBRARIES, LOCAL_STATIC_LIBRARIES fields in .mk files 357 let [ 358 reactCommonMkFiles, 359 reactAndroidMkFiles, 360 versionedAbiMKFiles, 361 reactAndroidPrebuiltMk, 362 codegenMkFiles, 363 ] = await Promise.all([ 364 glob(path.join(versionedReactCommonPath, '**/*.mk')), 365 glob(path.join(versionedReactAndroidJniPath, '**/*.mk')), 366 glob(path.join(versionedAbiPath, '**/*.mk')), 367 path.join(versionedReactAndroidPath, 'Android-prebuilt.mk'), 368 glob(path.join(codegenOutputRoot, '**/*.mk')), 369 ]); 370 let filenames = [ 371 ...reactCommonMkFiles, 372 ...reactAndroidMkFiles, 373 ...versionedAbiMKFiles, 374 reactAndroidPrebuiltMk, 375 ...codegenMkFiles, 376 ]; 377 await Promise.all(filenames.map((filename) => processMkFileAsync(filename, abiVersion))); 378 379 // Rename references to JNI libs in CMake 380 const cmakesFiles = await glob(path.join(versionedAbiPath, '**/CMakeLists.txt')); 381 await Promise.all(cmakesFiles.map((file) => processCMake(file, abiVersion))); 382 383 // Rename references to JNI libs in Java code 384 for (let i = 0; i < JniLibNames.length; i++) { 385 let libName = JniLibNames[i]; 386 await processJavaCodeAsync(libName, abiVersion); 387 } 388 389 // 'fbjni' is loaded without the 'lib' prefix in com.facebook.jni.Prerequisites 390 await processJavaCodeAsync('fbjni', abiVersion); 391 await processJavaCodeAsync('fb', abiVersion); 392 393 console.log('\nThese are the JNI lib names we modified:'); 394 await spawnAsync( 395 `find ${versionedReactAndroidJavaPath} ${versionedAbiPath} -name "*.java" | xargs grep -i "_abi${abiVersion}"`, 396 [], 397 { shell: true, stdio: 'inherit' } 398 ); 399 400 console.log('\nAnd here are all instances of loadLibrary:'); 401 await spawnAsync( 402 `find ${versionedReactAndroidJavaPath} ${versionedAbiPath} -name "*.java" | xargs grep -i "loadLibrary"`, 403 [], 404 { shell: true, stdio: 'inherit' } 405 ); 406 407 const { isCorrect } = await inquirer.prompt<{ isCorrect: boolean }>([ 408 { 409 type: 'confirm', 410 name: 'isCorrect', 411 message: 'Does all that look correct?', 412 default: false, 413 }, 414 ]); 415 if (!isCorrect) { 416 throw new Error('Fix JNI libs'); 417 } 418} 419 420async function copyExpoModulesAsync(version: string) { 421 const packages = await getListOfPackagesAsync(); 422 for (const pkg of packages) { 423 if ( 424 pkg.isSupportedOnPlatform('android') && 425 pkg.isIncludedInExpoClientOnPlatform('android') && 426 pkg.isVersionableOnPlatform('android') 427 ) { 428 await spawnAsync( 429 './android-copy-expo-module.sh', 430 [pkg.packageName, version, path.join(pkg.path, pkg.androidSubdirectory)], 431 { 432 shell: true, 433 cwd: SCRIPT_DIR, 434 } 435 ); 436 console.log(` ✅ Created versioned ${pkg.packageName}`); 437 } 438 } 439} 440 441async function addVersionedActivitesToManifests(version: string) { 442 const abiVersion = version.replace(/\./g, '_'); 443 const abiName = `abi${abiVersion}`; 444 const majorVersion = semver.major(version); 445 446 await transformFileAsync( 447 templateManifestPath, 448 new RegExp('<!-- ADD DEV SETTINGS HERE -->'), 449 `<!-- ADD DEV SETTINGS HERE --> 450 <!-- BEGIN_SDK_${majorVersion} --> 451 <activity android:name="${abiName}.com.facebook.react.devsupport.DevSettingsActivity"/> 452 <!-- END_SDK_${majorVersion} -->` 453 ); 454} 455 456async function registerNewVersionUnderSdkVersions(version: string) { 457 let fileString = await fs.readFile(sdkVersionsPath, 'utf8'); 458 let jsConfig; 459 // read the existing json config and add the new version to the sdkVersions array 460 try { 461 jsConfig = JSON.parse(fileString); 462 } catch (e) { 463 console.log('Error parsing existing sdkVersions.json file, writing a new one...', e); 464 console.log('The erroneous file contents was:', fileString); 465 jsConfig = { 466 sdkVersions: [], 467 }; 468 } 469 // apply changes 470 jsConfig.sdkVersions.push(version); 471 await fs.writeFile(sdkVersionsPath, JSON.stringify(jsConfig)); 472} 473 474async function cleanUpAsync(version: string) { 475 const abiVersion = version.replace(/\./g, '_'); 476 const abiName = `abi${abiVersion}`; 477 478 const versionedAbiSrcPath = path.join( 479 versionedExpoviewAbiPath(abiName), 480 'src/main/java', 481 abiName 482 ); 483 484 let filesToDelete: string[] = []; 485 486 // delete PrintDocumentAdapter*Callback.kt 487 // their package is `android.print` and therefore they are not changed by the versioning script 488 // so we will have duplicate classes 489 const printCallbackFiles = await glob( 490 path.join(versionedAbiSrcPath, 'expo/modules/print/*Callback.kt') 491 ); 492 for (const file of printCallbackFiles) { 493 const contents = await fs.readFile(file, 'utf8'); 494 if (!contents.includes(`package ${abiName}`)) { 495 filesToDelete.push(file); 496 } else { 497 console.log(`Skipping deleting ${file} because it appears to have been versioned`); 498 } 499 } 500 501 // delete versioned loader providers since we don't need them 502 filesToDelete.push(path.join(versionedAbiSrcPath, 'expo/loaders')); 503 504 console.log('Deleting the following files and directories:'); 505 console.log(filesToDelete); 506 507 for (const file of filesToDelete) { 508 await fs.remove(file); 509 } 510 511 // misc fixes for versioned code 512 const versionedExponentPackagePath = path.join( 513 versionedAbiSrcPath, 514 'host/exp/exponent/ExponentPackage.kt' 515 ); 516 await transformFileAsync( 517 versionedExponentPackagePath, 518 new RegExp('// WHEN_VERSIONING_REMOVE_FROM_HERE', 'g'), 519 '/* WHEN_VERSIONING_REMOVE_FROM_HERE' 520 ); 521 await transformFileAsync( 522 versionedExponentPackagePath, 523 new RegExp('// WHEN_VERSIONING_REMOVE_TO_HERE', 'g'), 524 'WHEN_VERSIONING_REMOVE_TO_HERE */' 525 ); 526 527 await transformFileAsync( 528 path.join(versionedAbiSrcPath, 'host/exp/exponent/VersionedUtils.kt'), 529 new RegExp('// DO NOT EDIT THIS COMMENT - used by versioning scripts[^,]+,[^,]+,'), 530 'null, null,' 531 ); 532 533 // replace abixx_x_x...R with abixx_x_x.host.exp.expoview.R 534 await spawnAsync( 535 `find ${versionedAbiSrcPath} -iname '*.java' -type f -print0 | ` + 536 `xargs -0 sed -i '' 's/import ${abiName}\.[^;]*\.R;/import ${abiName}.host.exp.expoview.R;/g'`, 537 [], 538 { shell: true } 539 ); 540 await spawnAsync( 541 `find ${versionedAbiSrcPath} -iname '*.kt' -type f -print0 | ` + 542 `xargs -0 sed -i '' 's/import ${abiName}\\..*\\.R$/import ${abiName}.host.exp.expoview.R/g'`, 543 [], 544 { shell: true } 545 ); 546 547 // add new versioned maven to build.gradle 548 await transformFileAsync( 549 buildGradlePath, 550 new RegExp('// For old expoviews to work'), 551 `// For old expoviews to work 552 maven { 553 url "$rootDir/versioned-abis/expoview-${abiName}/maven" 554 }` 555 ); 556} 557 558async function prepareReanimatedAsync(version: string): Promise<void> { 559 const abiVersion = version.replace(/\./g, '_'); 560 const abiName = `abi${abiVersion}`; 561 const versionedExpoviewPath = versionedExpoviewAbiPath(abiName); 562 563 const buildReanimatedSO = async () => { 564 await spawnAsync(`./gradlew :expoview-${abiName}:packageNdkLibs`, [], { 565 shell: true, 566 cwd: path.join(versionedExpoviewPath, '../../'), 567 stdio: 'inherit', 568 }); 569 }; 570 571 const removeLeftoverDirectories = async () => { 572 const mainPath = path.join(versionedExpoviewPath, 'src', 'main'); 573 const toRemove = ['Common', 'JNI', 'cpp']; 574 for (let dir of toRemove) { 575 await fs.remove(path.join(mainPath, dir)); 576 } 577 }; 578 579 const removeLeftoversFromGradle = async () => { 580 await spawnAsync('./android-remove-reanimated-code-from-gradle.sh', [version], { 581 shell: true, 582 cwd: SCRIPT_DIR, 583 stdio: 'inherit', 584 }); 585 }; 586 587 await buildReanimatedSO(); 588 await removeLeftoverDirectories(); 589 await removeLeftoversFromGradle(); 590} 591 592async function exportReactNdks() { 593 const versionedRN = path.join(versionedReactAndroidPath, '..'); 594 await spawnAsync(`./gradlew :ReactAndroid:packageReactNdkLibs`, [], { 595 shell: true, 596 cwd: versionedRN, 597 stdio: 'inherit', 598 }); 599} 600 601async function exportReactNdksIfNeeded() { 602 const ndksPath = path.join(versionedReactAndroidPath, 'build', 'react-ndk', 'exported'); 603 const exists = await fs.pathExists(ndksPath); 604 if (!exists) { 605 await exportReactNdks(); 606 return; 607 } 608 609 const exportedSO = await glob(path.join(ndksPath, '**/*.so')); 610 if (exportedSO.length === 0) { 611 await exportReactNdks(); 612 } 613} 614 615export async function addVersionAsync(version: string) { 616 await ensureToolsInstalledAsync(); 617 618 console.log(' 1/11: Updating android/versioned-react-native...'); 619 await updateVersionedReactNativeAsync( 620 Directories.getReactNativeSubmoduleDir(), 621 ANDROID_DIR, 622 path.join(ANDROID_DIR, 'versioned-react-native') 623 ); 624 console.log(' ✅ 1/11: Finished\n\n'); 625 626 console.log(' 2/11: Creating versioned expoview package...'); 627 await spawnAsync('./android-copy-expoview.sh', [version], { 628 shell: true, 629 cwd: SCRIPT_DIR, 630 }); 631 632 console.log(' ✅ 2/11: Finished\n\n'); 633 634 console.log(' 3/11: Renaming JNI libs in android/versioned-react-native and Reanimated...'); 635 await renameJniLibsAsync(version); 636 console.log(' ✅ 3/11: Finished\n\n'); 637 638 console.log(' 4/11: Renaming libhermes.so...'); 639 await renameHermesEngine(versionedReactAndroidPath, version); 640 console.log(' ✅ 4/11: Finished\n\n'); 641 642 console.log(' 5/11: Building versioned ReactAndroid AAR...'); 643 await spawnAsync('./android-build-aar.sh', [version], { 644 shell: true, 645 cwd: SCRIPT_DIR, 646 stdio: 'inherit', 647 }); 648 console.log(' ✅ 5/11: Finished\n\n'); 649 650 console.log(' 6/11: Exporting react ndks if needed...'); 651 await exportReactNdksIfNeeded(); 652 console.log(' ✅ 6/11: Finished\n\n'); 653 654 console.log(' 7/11: prepare versioned Reanimated...'); 655 await prepareReanimatedAsync(version); 656 console.log(' ✅ 7/11: Finished\n\n'); 657 658 console.log(' 8/11: Creating versioned expo-modules packages...'); 659 await copyExpoModulesAsync(version); 660 console.log(' ✅ 8/11: Finished\n\n'); 661 662 console.log(' 9/11: Adding extra versioned activites to AndroidManifest...'); 663 await addVersionedActivitesToManifests(version); 664 console.log(' ✅ 9/11: Finished\n\n'); 665 666 console.log(' 10/11: Registering new version under sdkVersions config...'); 667 await registerNewVersionUnderSdkVersions(version); 668 console.log(' ✅ 10/11: Finished\n\n'); 669 670 console.log(' 11/11: Misc cleanup...'); 671 await cleanUpAsync(version); 672 console.log(' ✅ 11/11: Finished'); 673} 674