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