1import chalk from 'chalk'; 2import fs from 'fs-extra'; 3import glob from 'glob-promise'; 4import inquirer from 'inquirer'; 5import path from 'path'; 6import semver from 'semver'; 7import spawnAsync from '@expo/spawn-async'; 8 9import { JniLibNames, getJavaPackagesToRename } from './libraries'; 10import * as Directories from '../../Directories'; 11import { getListOfPackagesAsync } from '../../Packages'; 12 13const EXPO_DIR = Directories.getExpoRepositoryRootDir(); 14const ANDROID_DIR = Directories.getAndroidDir(); 15const EXPOTOOLS_DIR = Directories.getExpotoolsDir(); 16const SCRIPT_DIR = path.join(EXPOTOOLS_DIR, 'src/versioning/android'); 17 18const appPath = path.join(ANDROID_DIR, 'app'); 19const expoviewPath = path.join(ANDROID_DIR, 'expoview'); 20const versionedAbisPath = path.join(ANDROID_DIR, 'versioned-abis'); 21const versionedExpoviewAbiPath = (abiName) => path.join(versionedAbisPath, `expoview-${abiName}`); 22const expoviewBuildGradlePath = path.join(expoviewPath, 'build.gradle'); 23const appManifestPath = path.join(appPath, 'src', 'main', 'AndroidManifest.xml'); 24const templateManifestPath = path.join( 25 EXPO_DIR, 26 'template-files', 27 'android', 28 'AndroidManifest.xml' 29); 30const settingsGradlePath = path.join(ANDROID_DIR, 'settings.gradle'); 31const appBuildGradlePath = path.join(appPath, 'build.gradle'); 32const buildGradlePath = path.join(ANDROID_DIR, 'build.gradle'); 33const sdkVersionsPath = path.join(ANDROID_DIR, 'sdkVersions.json'); 34const rnActivityPath = path.join( 35 expoviewPath, 36 'src/main/java/host/exp/exponent/experience/MultipleVersionReactNativeActivity.java' 37); 38const expoviewConstantsPath = path.join( 39 expoviewPath, 40 'src/main/java/host/exp/exponent/Constants.java' 41); 42const testSuiteTestsPath = path.join( 43 appPath, 44 'src/androidTest/java/host/exp/exponent/TestSuiteTests.java' 45); 46const reactAndroidPath = path.join(ANDROID_DIR, 'ReactAndroid'); 47const reactCommonPath = path.join(ANDROID_DIR, 'ReactCommon'); 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 } 228 229 return lib; 230} 231 232function processLine(line: string, abiVersion: string) { 233 if ( 234 line.startsWith('LOCAL_MODULE') || 235 line.startsWith('LOCAL_SHARED_LIBRARIES') || 236 line.startsWith('LOCAL_STATIC_LIBRARIES') 237 ) { 238 let splitLine = line.split('='); 239 let libs = splitLine[1].split(' '); 240 for (let i = 0; i < libs.length; i++) { 241 libs[i] = renameLib(libs[i], abiVersion); 242 } 243 splitLine[1] = libs.join(' '); 244 line = splitLine.join('='); 245 } 246 247 return line; 248} 249 250async function processMkFileAsync(filename: string, abiVersion: string) { 251 let file = await fs.readFile(filename); 252 let fileString = file.toString(); 253 await fs.truncate(filename, 0); 254 let lines = fileString.split('\n'); 255 for (let i = 0; i < lines.length; i++) { 256 let line = lines[i]; 257 line = processLine(line, abiVersion); 258 await fs.appendFile(filename, `${line}\n`); 259 } 260} 261 262async function processJavaCodeAsync(libName: string, abiVersion: string) { 263 const abiName = `abi${abiVersion}`; 264 return spawnAsync( 265 `find ${versionedReactAndroidJavaPath} ${versionedExpoviewAbiPath( 266 abiName 267 )} -iname '*.java' -type f -print0 | ` + 268 `xargs -0 sed -i '' 's/"${libName}"/"${libName}_abi${abiVersion}"/g'`, 269 [], 270 { shell: true } 271 ); 272} 273 274async function updateVersionedReactNativeAsync() { 275 await fs.remove(versionedReactAndroidPath); 276 await fs.remove(versionedReactCommonPath); 277 await fs.copy(reactAndroidPath, versionedReactAndroidPath); 278 await fs.copy(reactCommonPath, versionedReactCommonPath); 279} 280 281async function renameJniLibsAsync(version: string) { 282 const abiVersion = version.replace(/\./g, '_'); 283 const abiPrefix = `abi${abiVersion}`; 284 const versionedAbiPath = path.join( 285 Directories.getAndroidDir(), 286 'versioned-abis', 287 `expoview-${abiPrefix}` 288 ); 289 290 // Update JNI methods 291 const packagesToRename = await getJavaPackagesToRename(); 292 for (const javaPackage of packagesToRename) { 293 const pathForPackage = javaPackage.replace(/\./g, '\\/'); 294 await spawnAsync( 295 `find ${versionedReactCommonPath} ${versionedReactAndroidJniPath} -type f ` + 296 `\\( -name \*.java -o -name \*.h -o -name \*.cpp -o -name \*.mk \\) -print0 | ` + 297 `xargs -0 sed -i '' 's/${pathForPackage}/abi${abiVersion}\\/${pathForPackage}/g'`, 298 [], 299 { shell: true } 300 ); 301 302 // reanimated 303 const oldJNIReanimatedPackage = 304 'versioned\\/host\\/exp\\/exponent\\/modules\\/api\\/reanimated\\/'; 305 const newJNIReanimatedPackage = 'host\\/exp\\/exponent\\/modules\\/api\\/reanimated\\/'; 306 await spawnAsync( 307 `find ${versionedAbiPath} -type f ` + 308 `\\( -name \*.java -o -name \*.h -o -name \*.cpp -o -name \*.mk \\) -print0 | ` + 309 `xargs -0 sed -i '' 's/${oldJNIReanimatedPackage}/abi${abiVersion}\\/${newJNIReanimatedPackage}/g'`, 310 [], 311 { shell: true } 312 ); 313 } 314 315 // Update LOCAL_MODULE, LOCAL_SHARED_LIBRARIES, LOCAL_STATIC_LIBRARIES fields in .mk files 316 let [reactCommonMkFiles, reactAndroidMkFiles, reanimatedMKFiles] = await Promise.all([ 317 glob(path.join(versionedReactCommonPath, '**/*.mk')), 318 glob(path.join(versionedReactAndroidJniPath, '**/*.mk')), 319 glob(path.join(versionedAbiPath, '**/*.mk')), 320 ]); 321 let filenames = [...reactCommonMkFiles, ...reactAndroidMkFiles, ...reanimatedMKFiles]; 322 await Promise.all(filenames.map((filename) => processMkFileAsync(filename, abiVersion))); 323 324 // Rename references to JNI libs in Java code 325 for (let i = 0; i < JniLibNames.length; i++) { 326 let libName = JniLibNames[i]; 327 await processJavaCodeAsync(libName, abiVersion); 328 } 329 330 // 'fbjni' is loaded without the 'lib' prefix in com.facebook.jni.Prerequisites 331 await processJavaCodeAsync('fbjni', abiVersion); 332 await processJavaCodeAsync('fb', abiVersion); 333 334 console.log('\nThese are the JNI lib names we modified:'); 335 await spawnAsync( 336 `find ${versionedReactAndroidJavaPath} ${versionedAbiPath} -name "*.java" | xargs grep -i "_abi${abiVersion}"`, 337 [], 338 { shell: true, stdio: 'inherit' } 339 ); 340 341 console.log('\nAnd here are all instances of loadLibrary:'); 342 await spawnAsync( 343 `find ${versionedReactAndroidJavaPath} ${versionedAbiPath} -name "*.java" | xargs grep -i "loadLibrary"`, 344 [], 345 { shell: true, stdio: 'inherit' } 346 ); 347 348 const { isCorrect } = await inquirer.prompt<{ isCorrect: boolean }>([ 349 { 350 type: 'confirm', 351 name: 'isCorrect', 352 message: 'Does all that look correct?', 353 default: false, 354 }, 355 ]); 356 if (!isCorrect) { 357 throw new Error('Fix JNI libs'); 358 } 359} 360 361async function copyUnimodulesAsync(version: string) { 362 const packages = await getListOfPackagesAsync(); 363 for (const pkg of packages) { 364 if ( 365 pkg.isSupportedOnPlatform('android') && 366 pkg.isIncludedInExpoClientOnPlatform('android') && 367 pkg.isVersionableOnPlatform('android') 368 ) { 369 await spawnAsync( 370 './android-copy-unimodule.sh', 371 [version, path.join(pkg.path, pkg.androidSubdirectory)], 372 { 373 shell: true, 374 cwd: SCRIPT_DIR, 375 } 376 ); 377 console.log(` ✅ Created versioned ${pkg.packageName}`); 378 } 379 } 380} 381 382async function addVersionedActivitesToManifests(version: string) { 383 const abiVersion = version.replace(/\./g, '_'); 384 const abiName = `abi${abiVersion}`; 385 const majorVersion = semver.major(version); 386 387 await transformFileAsync( 388 templateManifestPath, 389 new RegExp('<!-- ADD DEV SETTINGS HERE -->'), 390 `<!-- ADD DEV SETTINGS HERE --> 391 <!-- BEGIN_SDK_${majorVersion} --> 392 <activity android:name="${abiName}.com.facebook.react.devsupport.DevSettingsActivity"/> 393 <!-- END_SDK_${majorVersion} -->` 394 ); 395 396 await transformFileAsync( 397 templateManifestPath, 398 new RegExp('<!-- Versioned Activity for Stripe -->'), 399 `<!-- Versioned Activity for Stripe --> 400 <!-- BEGIN_SDK_${majorVersion} --> 401 <activity 402 android:exported="true" 403 android:launchMode="singleTask" 404 android:name="${abiName}.expo.modules.payments.stripe.RedirectUriReceiver" 405 android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"> 406 <intent-filter> 407 <action android:name="android.intent.action.VIEW" /> 408 <category android:name="android.intent.category.DEFAULT" /> 409 <category android:name="android.intent.category.BROWSABLE" /> 410 <data android:scheme="${abiName}.expo.modules.payments.stripe" /> 411 </intent-filter> 412 </activity> 413 <!-- END_SDK_${majorVersion} -->` 414 ); 415} 416 417async function registerNewVersionUnderSdkVersions(version: string) { 418 let fileString = await fs.readFile(sdkVersionsPath, 'utf8'); 419 let jsConfig; 420 // read the existing json config and add the new version to the sdkVersions array 421 try { 422 jsConfig = JSON.parse(fileString); 423 } catch (e) { 424 console.log('Error parsing existing sdkVersions.json file, writing a new one...', e); 425 console.log('The erroneous file contents was:', fileString); 426 jsConfig = { 427 sdkVersions: [], 428 }; 429 } 430 // apply changes 431 jsConfig.sdkVersions.push(version); 432 await fs.writeFile(sdkVersionsPath, JSON.stringify(jsConfig)); 433} 434 435async function cleanUpAsync(version: string) { 436 const abiVersion = version.replace(/\./g, '_'); 437 const abiName = `abi${abiVersion}`; 438 439 const versionedAbiSrcPath = path.join( 440 versionedExpoviewAbiPath(abiName), 441 'src/main/java', 442 abiName 443 ); 444 445 let filesToDelete: string[] = []; 446 447 // delete PrintDocumentAdapter*Callback.java 448 // their package is `android.print` and therefore they are not changed by the versioning script 449 // so we will have duplicate classes 450 const printCallbackFiles = await glob( 451 path.join(versionedAbiSrcPath, 'expo/modules/print/*Callback.java') 452 ); 453 for (const file of printCallbackFiles) { 454 const contents = await fs.readFile(file, 'utf8'); 455 if (!contents.includes(`package ${abiName}`)) { 456 filesToDelete.push(file); 457 } else { 458 console.log(`Skipping deleting ${file} because it appears to have been versioned`); 459 } 460 } 461 462 // delete versioned loader providers since we don't need them 463 filesToDelete.push(path.join(versionedAbiSrcPath, 'expo/loaders')); 464 465 console.log('Deleting the following files and directories:'); 466 console.log(filesToDelete); 467 468 for (const file of filesToDelete) { 469 await fs.remove(file); 470 } 471 472 // misc fixes for versioned code 473 const versionedExponentPackagePath = path.join( 474 versionedAbiSrcPath, 475 'host/exp/exponent/ExponentPackage.java' 476 ); 477 await transformFileAsync( 478 versionedExponentPackagePath, 479 new RegExp('// WHEN_VERSIONING_REMOVE_FROM_HERE', 'g'), 480 '/* WHEN_VERSIONING_REMOVE_FROM_HERE' 481 ); 482 await transformFileAsync( 483 versionedExponentPackagePath, 484 new RegExp('// WHEN_VERSIONING_REMOVE_TO_HERE', 'g'), 485 'WHEN_VERSIONING_REMOVE_TO_HERE */' 486 ); 487 488 await transformFileAsync( 489 path.join(versionedAbiSrcPath, 'host/exp/exponent/VersionedUtils.java'), 490 new RegExp('// DO NOT EDIT THIS COMMENT - used by versioning scripts[^,]+,[^,]+,'), 491 'null, null,' 492 ); 493 494 await transformFileAsync( 495 path.join(versionedAbiSrcPath, 'expo/modules/payments/stripe/PayFlow.java'), 496 new RegExp('// ADD BUILDCONFIG IMPORT HERE'), 497 `import ${abiName}.host.exp.expoview.BuildConfig;` 498 ); 499 500 // replace abixx_x_x...R with abixx_x_x.host.exp.expoview.R 501 await spawnAsync( 502 `find ${versionedAbiSrcPath} -iname '*.java' -type f -print0 | ` + 503 `xargs -0 sed -i '' 's/import ${abiName}\.[^;]*\.R;/import ${abiName}.host.exp.expoview.R;/g'`, 504 [], 505 { shell: true } 506 ); 507 await spawnAsync( 508 `find ${versionedAbiSrcPath} -iname '*.kt' -type f -print0 | ` + 509 `xargs -0 sed -i '' 's/import ${abiName}\\..*\\.R$/import ${abiName}.host.exp.expoview.R/g'`, 510 [], 511 { shell: true } 512 ); 513 514 // add new versioned maven to build.gradle 515 await transformFileAsync( 516 buildGradlePath, 517 new RegExp('// For old expoviews to work'), 518 `// For old expoviews to work 519 maven { 520 url "$rootDir/versioned-abis/expoview-${abiName}/maven" 521 }` 522 ); 523} 524 525async function prepareReanimatedAsync(version: string): Promise<void> { 526 const abiVersion = version.replace(/\./g, '_'); 527 const abiName = `abi${abiVersion}`; 528 const versionedExpoviewPath = versionedExpoviewAbiPath(abiName); 529 530 const buildReanimatedSO = async () => { 531 await spawnAsync(`./gradlew :expoview-${abiName}:packageNdkLibs`, [], { 532 shell: true, 533 cwd: path.join(versionedExpoviewPath, '../../'), 534 stdio: 'inherit', 535 }); 536 }; 537 538 const removeLeftoverDirectories = async () => { 539 const mainPath = path.join(versionedExpoviewPath, 'src', 'main'); 540 const toRemove = ['Common', 'JNI', 'cpp']; 541 for (let dir of toRemove) { 542 await fs.remove(path.join(mainPath, dir)); 543 } 544 }; 545 546 const removeLeftoversFromGradle = async () => { 547 await spawnAsync('./android-remove-reanimated-code-from-gradle.sh', [version], { 548 shell: true, 549 cwd: SCRIPT_DIR, 550 stdio: 'inherit', 551 }); 552 }; 553 554 await buildReanimatedSO(); 555 await removeLeftoverDirectories(); 556 await removeLeftoversFromGradle(); 557} 558 559export async function addVersionAsync(version: string) { 560 console.log(' 1/9: Updating android/versioned-react-native...'); 561 await updateVersionedReactNativeAsync(); 562 console.log(' ✅ 1/9: Finished\n\n'); 563 564 console.log(' 2/9: Creating versioned expoview package...'); 565 await spawnAsync('./android-copy-expoview.sh', [version], { 566 shell: true, 567 cwd: SCRIPT_DIR, 568 }); 569 570 console.log(' ✅ 2/9: Finished\n\n'); 571 572 console.log(' 3/9: Renaming JNI libs in android/versioned-react-native and Reanimated...'); 573 await renameJniLibsAsync(version); 574 console.log(' ✅ 3/9: Finished\n\n'); 575 576 console.log(' 4/9: prepare versioned Reanimated...'); 577 await prepareReanimatedAsync(version); 578 console.log(' ✅ 4/9: Finished\n\n'); 579 580 console.log(' 5/9: Building versioned ReactAndroid AAR...'); 581 await spawnAsync('./android-build-aar.sh', [version], { 582 shell: true, 583 cwd: SCRIPT_DIR, 584 stdio: 'inherit', 585 }); 586 console.log(' ✅ 5/9: Finished\n\n'); 587 588 console.log(' 6/9: Creating versioned unimodule packages...'); 589 await copyUnimodulesAsync(version); 590 console.log(' ✅ 6/9: Finished\n\n'); 591 592 console.log(' 7/9: Adding extra versioned activites to AndroidManifest...'); 593 await addVersionedActivitesToManifests(version); 594 console.log(' ✅ 7/9: Finished\n\n'); 595 596 console.log(' 8/9: Registering new version under sdkVersions config...'); 597 await registerNewVersionUnderSdkVersions(version); 598 console.log(' ✅ 8/9: Finished\n\n'); 599 600 console.log(' 9/9: Misc cleanup...'); 601 await cleanUpAsync(version); 602 console.log(' ✅ 9/9: Finished'); 603} 604