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