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