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