1import spawnAsync from '@expo/spawn-async'; 2import chalk from 'chalk'; 3import { TaskQueue } from 'cwait'; 4import fs from 'fs-extra'; 5import glob from 'glob-promise'; 6import inquirer from 'inquirer'; 7import path from 'path'; 8import semver from 'semver'; 9 10import { EXPO_DIR, IOS_DIR, VERSIONED_RN_IOS_DIR } from '../../Constants'; 11import { getListOfPackagesAsync } from '../../Packages'; 12import { runTransformPipelineAsync } from './transforms'; 13import { injectMacros } from './transforms/injectMacros'; 14import { kernelFilesTransforms } from './transforms/kernelFilesTransforms'; 15import { podspecTransforms } from './transforms/podspecTransforms'; 16import { postTransforms } from './transforms/postTransforms'; 17 18export { versionVendoredModulesAsync } from './versionVendoredModules'; 19 20const UNVERSIONED_PLACEHOLDER = '__UNVERSIONED__'; 21const RELATIVE_RN_PATH = './react-native-lab/react-native'; 22 23const RELATIVE_UNIVERSAL_MODULES_PATH = './packages'; 24const EXTERNAL_REACT_ABI_DEPENDENCIES = [ 25 'Amplitude', 26 'Analytics', 27 'AppAuth', 28 'FBAudienceNetwork', 29 'FBSDKCoreKit', 30 'GoogleSignIn', 31 'GoogleMaps', 32 'Google-Maps-iOS-Utils', 33 'lottie-ios', 34 'JKBigInteger2', 35 'Branch', 36 'Google-Mobile-Ads-SDK', 37 'Folly', 38]; 39 40/** 41 * Transform and rename the given react native source code files. 42 * @param filenames list of files to transform 43 * @param versionPrefix A version-specific prefix to apply to all symbols in the code, e.g. 44 * RCTSomeClass becomes {versionPrefix}RCTSomeClass 45 * @param versionedPodNames mapping from unversioned cocoapods names to versioned cocoapods names, 46 * e.g. React -> ReactABI99_0_0 47 */ 48async function namespaceReactNativeFilesAsync(filenames, versionPrefix, versionedPodNames) { 49 const reactPodName = versionedPodNames.React; 50 const transformRules = _getReactNativeTransformRules(versionPrefix, reactPodName); 51 const taskQueue = new TaskQueue(Promise, 4); // Transform up to 4 files simultaneously. 52 const transformRulesCache = {}; 53 54 const transformSingleFile = taskQueue.wrap(async (filename) => { 55 if (_isDirectory(filename)) { 56 return; 57 } 58 // protect contents of EX_UNVERSIONED macro 59 let unversionedCaptures: string[] = []; 60 await _transformFileContentsAsync(filename, (fileString) => { 61 let pattern = /EX_UNVERSIONED\((.*?)\)/g; 62 let match = pattern.exec(fileString); 63 while (match != null) { 64 unversionedCaptures.push(match[1]); 65 match = pattern.exec(fileString); 66 } 67 if (unversionedCaptures.length) { 68 return fileString.replace(pattern, UNVERSIONED_PLACEHOLDER); 69 } 70 return null; 71 }); 72 73 // rename file 74 const dirname = path.dirname(filename); 75 const basename = path.basename(filename); 76 const targetPath = path.join(dirname, `${versionPrefix}${basename}`); 77 78 // filter transformRules to patterns which apply to this dirname 79 const filteredTransformRules = 80 transformRulesCache[dirname] || _getTransformRulesForDirname(transformRules, dirname); 81 transformRulesCache[dirname] = transformRules; 82 83 // Perform sed find & replace. 84 for (const rule of filteredTransformRules) { 85 await spawnAsync('sed', [rule.flags || '-i', '--', rule.pattern, filename]); 86 } 87 88 // Rename file to be prefixed. 89 await fs.move(filename, targetPath); 90 91 // perform transforms that sed can't express 92 await _transformFileContentsAsync(targetPath, async (fileString) => { 93 // rename misc imports, e.g. Layout.h 94 fileString = fileString.replace( 95 /#(include|import)\s+"((?:[^"\/]+\/)?)([^"]+\.h)"/g, 96 (match, p1, p2, p3) => { 97 return p3.startsWith(versionPrefix) ? match : `#${p1} "${p2}${versionPrefix}${p3}"`; 98 } 99 ); 100 101 // restore EX_UNVERSIONED contents 102 if (unversionedCaptures) { 103 let index = 0; 104 do { 105 fileString = fileString.replace(UNVERSIONED_PLACEHOLDER, unversionedCaptures[index]); 106 index++; 107 } while (fileString.indexOf(UNVERSIONED_PLACEHOLDER) !== -1); 108 } 109 110 const injectedMacrosOutput = await runTransformPipelineAsync({ 111 pipeline: injectMacros(versionPrefix), 112 input: fileString, 113 targetPath, 114 }); 115 116 return await runTransformPipelineAsync({ 117 pipeline: postTransforms(versionPrefix), 118 input: injectedMacrosOutput, 119 targetPath, 120 }); 121 }); 122 // process `filename` 123 }); 124 125 await Promise.all(filenames.map(transformSingleFile)); 126} 127 128/** 129 * Transform and rename all code files we care about under `rnPath` 130 */ 131async function transformReactNativeAsync(rnPath, versionName, versionedPodNames) { 132 let filenameQueries = [`${rnPath}/**/*.[hmSc]`, `${rnPath}/**/*.mm`, `${rnPath}/**/*.cpp`]; 133 let filenames: string[] = []; 134 await Promise.all( 135 filenameQueries.map(async (query) => { 136 let queryFilenames = (await glob(query)) as string[]; 137 if (queryFilenames) { 138 filenames = filenames.concat(queryFilenames); 139 } 140 }) 141 ); 142 143 return namespaceReactNativeFilesAsync(filenames, versionName, versionedPodNames); 144} 145 146/** 147 * For all files matching the given glob query, namespace and rename them 148 * with the given version number. This utility is mainly useful for backporting 149 * small changes into an existing SDK. To create a new SDK version, use `addVersionAsync` 150 * instead. 151 * @param globQuery a string to pass to glob which matches some file paths 152 * @param versionNumber Exponent SDK version, e.g. 42.0.0 153 */ 154export async function versionReactNativeIOSFilesAsync(globQuery, versionNumber) { 155 let filenames = await glob(globQuery); 156 if (!filenames || !filenames.length) { 157 throw new Error(`No files matched the given pattern: ${globQuery}`); 158 } 159 let { versionName, versionedPodNames } = await getConfigsFromArguments(versionNumber); 160 console.log(`Versioning ${filenames.length} files with SDK version ${versionNumber}...`); 161 return namespaceReactNativeFilesAsync(filenames, versionName, versionedPodNames); 162} 163 164async function generateVersionedReactNativeAsync(versionName: string): Promise<void> { 165 const versionedReactNativePath = getVersionedReactNativePath(versionName); 166 167 await fs.mkdirs(versionedReactNativePath); 168 169 // Clone react native latest version 170 console.log(`Copying files from ${chalk.magenta(RELATIVE_RN_PATH)} ...`); 171 172 await fs.copy( 173 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'React'), 174 path.join(versionedReactNativePath, 'React') 175 ); 176 await fs.copy( 177 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'Libraries'), 178 path.join(versionedReactNativePath, 'Libraries') 179 ); 180 await fs.copy( 181 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'React.podspec'), 182 path.join(versionedReactNativePath, 'React.podspec') 183 ); 184 await fs.copy( 185 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'React-Core.podspec'), 186 path.join(versionedReactNativePath, 'React-Core.podspec') 187 ); 188 await fs.copy( 189 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'ReactCommon', 'ReactCommon.podspec'), 190 path.join(versionedReactNativePath, 'ReactCommon', 'ReactCommon.podspec') 191 ); 192 await fs.copy( 193 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'ReactCommon', 'React-Fabric.podspec'), 194 path.join(versionedReactNativePath, 'ReactCommon', 'React-Fabric.podspec') 195 ); 196 await fs.copy( 197 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'package.json'), 198 path.join(versionedReactNativePath, 'package.json') 199 ); 200 201 console.log(`Removing unnecessary ${chalk.magenta('*.js')} files ...`); 202 203 const jsFiles = (await glob(path.join(versionedReactNativePath, '**', '*.js'))) as string[]; 204 205 for (const jsFile of jsFiles) { 206 await fs.remove(jsFile); 207 } 208 209 console.log( 210 `Copying cpp libraries from ${chalk.magenta(path.join(RELATIVE_RN_PATH, 'ReactCommon'))} ...` 211 ); 212 const cppLibraries = getCppLibrariesToVersion(); 213 214 await fs.mkdirs(path.join(versionedReactNativePath, 'ReactCommon')); 215 216 for (const library of cppLibraries) { 217 await fs.copy( 218 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'ReactCommon', library.libName), 219 path.join(versionedReactNativePath, 'ReactCommon', library.libName) 220 ); 221 } 222 223 await generateAutolinkingScriptAsync(versionedReactNativePath, versionName); 224 await generateReactNativePodspecsAsync(versionedReactNativePath, versionName); 225} 226 227/** 228 * There are some kernel files that unfortunately have to call versioned code directly. 229 * This function applies the specified changes in the kernel codebase. 230 * The nature of kernel modifications is that they are temporary and at one point these have to be rollbacked. 231 * @param versionName SDK version, e.g. 21.0.0, 37.0.0, etc. 232 * @param rollback flag indicating whether to invoke rollbacking modification. 233 */ 234async function modifyKernelFilesAsync( 235 versionName: string, 236 rollback: boolean = false 237): Promise<void> { 238 const kernelFilesPath = path.join(IOS_DIR, 'Exponent/kernel'); 239 const filenameQueries = [`${kernelFilesPath}/**/EXAppViewController.m`]; 240 let filenames: string[] = []; 241 await Promise.all( 242 filenameQueries.map(async (query) => { 243 let queryFilenames = (await glob(query)) as string[]; 244 if (queryFilenames) { 245 filenames = filenames.concat(queryFilenames); 246 } 247 }) 248 ); 249 await Promise.all( 250 filenames.map(async (filename) => { 251 console.log(`Modifying ${chalk.magenta(path.relative(EXPO_DIR, filename))}:`); 252 await _transformFileContentsAsync(filename, (fileContents) => 253 runTransformPipelineAsync({ 254 pipeline: kernelFilesTransforms(versionName, rollback), 255 targetPath: filename, 256 input: fileContents, 257 }) 258 ); 259 }) 260 ); 261} 262/** 263 * - Copies `scripts/react_native_pods.rb` script into versioned ReactNative directory. 264 * - Removes pods installed from third-party-podspecs (we don't version them). 265 * - Versions `use_react_native` method and all pods it declares. 266 */ 267async function generateAutolinkingScriptAsync( 268 versionedReactNativePath: string, 269 versionName: string 270): Promise<void> { 271 const targetAutolinkPath = path.join(versionedReactNativePath, 'react_native_pods.rb'); 272 273 await fs.copy( 274 path.join(EXPO_DIR, RELATIVE_RN_PATH, 'scripts', 'react_native_pods.rb'), 275 targetAutolinkPath 276 ); 277 278 const targetSource = (await fs.readFile(targetAutolinkPath, 'utf8')) 279 .replace('def use_react_native!', `def use_react_native_${versionName}!`) 280 .replace(/(\bpod\s+([^\n]+)\/third-party-podspecs\/([^\n]+))/g, '# $1') 281 .replace(/\bpod\s+'([^\']+)'/g, `pod '${versionName}$1'`) 282 .replace(/(:path => "[^"]+")/g, `$1, :project_name => '${versionName}'`); 283 284 await fs.writeFile(targetAutolinkPath, targetSource); 285} 286 287async function generateReactNativePodspecsAsync( 288 versionedReactNativePath: string, 289 versionName: string 290): Promise<void> { 291 const podspecFiles = await glob(path.join(versionedReactNativePath, '**', '*.podspec')); 292 293 for (const podspecFile of podspecFiles) { 294 const basename = path.basename(podspecFile, '.podspec'); 295 296 if (/^react$/i.test(basename)) { 297 continue; 298 } 299 300 console.log( 301 `Generating podspec for ${chalk.green(basename)} at ${chalk.magenta( 302 path.relative(versionedReactNativePath, podspecFile) 303 )} ...` 304 ); 305 306 const podspecSource = await fs.readFile(podspecFile, 'utf8'); 307 308 const podspecOutput = await runTransformPipelineAsync({ 309 pipeline: podspecTransforms(versionName), 310 input: podspecSource, 311 targetPath: podspecFile, 312 }); 313 314 // Write transformed podspec output to the prefixed file. 315 await fs.writeFile( 316 path.join(path.dirname(podspecFile), `${versionName}${basename}.podspec`), 317 podspecOutput 318 ); 319 320 // Remove original and unprefixed podspec. 321 await fs.remove(podspecFile); 322 } 323 324 await generateReactPodspecAsync(versionedReactNativePath, versionName); 325} 326 327export async function regenerateVersionedPackageAsync( 328 versionNumber: string, 329 packageName: string 330): Promise<void> { 331 let { versionName, versionedPodNames } = await getConfigsFromArguments(versionNumber); 332 333 const versionedUnimodulePods = await getVersionedUnimodulePodsAsync(versionName); 334 const versionedExpoPath = getVersionedExpoPath(versionName); 335 const excludedPodNames = getExcludedPodNames(); 336 const packages = await getListOfPackagesAsync(); 337 const originalUnimodulePodNames = Object.keys(versionedUnimodulePods); 338 const depsToReplace = originalUnimodulePodNames.join('|'); 339 const versionedReactPodName = getVersionedReactPodName(versionName); 340 const pkg = packages.find((pkgInternal) => pkgInternal.packageName === packageName); 341 if (!pkg) { 342 throw new Error(`Package not found: ${packageName}`); 343 } 344 345 const modulePath = path.join(EXPO_DIR, RELATIVE_UNIVERSAL_MODULES_PATH, pkg.packageName); 346 const podName = pkg.podspecName; 347 348 if (!(podName && pkg.isVersionableOnPlatform('ios') && !excludedPodNames.includes(podName))) { 349 throw new Error(`No versionable pod for package: ${packageName}`); 350 } 351 352 await fs.copy(path.join(modulePath, 'ios'), path.join(versionedExpoPath, podName), { 353 overwrite: true, 354 }); 355 await fs.move( 356 path.join(versionedExpoPath, podName, podName), 357 path.join(versionedExpoPath, podName, versionedUnimodulePods[podName]) 358 ); 359 await fs.copy( 360 path.join(modulePath, 'package.json'), 361 path.join(versionedExpoPath, podName, 'package.json') 362 ); 363 364 const versionedUnimodulePodName = versionedUnimodulePods[podName]; 365 366 const originalPodSpecPath = path.join(versionedExpoPath, podName, `${podName}.podspec`); 367 const prefixedPodSpecPath = path.join( 368 versionedExpoPath, 369 podName, 370 `${versionedUnimodulePodName}.podspec` 371 ); 372 373 console.log(`Generating podspec for ${chalk.green(podName)} ...`); 374 375 await fs.move(originalPodSpecPath, prefixedPodSpecPath); 376 377 // Replaces versioned modules in the podspec eg. 'EXCore' => 'ABI28_0_0EXCore' 378 // `E` flag is required for extended syntax which allows to use `(a|b)` 379 await spawnAsync('sed', [ 380 '-Ei', 381 '--', 382 `s/'(${depsToReplace})('|\\/)/'${versionName}\\1\\2/g`, 383 prefixedPodSpecPath, 384 ]); 385 await spawnAsync('sed', ['-i', '--', `s/React/${versionedReactPodName}/g`, prefixedPodSpecPath]); 386 await spawnAsync('sed', [ 387 '-i', 388 '--', 389 `s/${versionName}UM${versionedReactPodName}/${versionName}UMReact/g`, 390 prefixedPodSpecPath, 391 ]); 392 await spawnAsync('sed', [ 393 '-i', 394 '--', 395 "s/'..', 'package.json'/'package.json'/g", 396 prefixedPodSpecPath, 397 ]); 398 399 const rnPath = path.join(versionedExpoPath, podName); 400 let filenameQueries = [`${rnPath}/**/*.[hmSc]`, `${rnPath}/**/*.mm`, `${rnPath}/**/*.cpp`]; 401 let filenames: string[] = []; 402 await Promise.all( 403 filenameQueries.map(async (query) => { 404 let queryFilenames = (await glob(query)) as string[]; 405 if (queryFilenames) { 406 filenames = filenames.concat(queryFilenames); 407 } 408 }) 409 ); 410 411 await namespaceReactNativeFilesAsync(filenames, versionName, versionedPodNames); 412 413 console.log('Removing any `filename--` files from the new pod ...'); 414 415 try { 416 const minusMinusFiles = await glob(path.join(rnPath, '**', '*--')); 417 for (const minusMinusFile of minusMinusFiles) { 418 await fs.remove(minusMinusFile); 419 } 420 } catch (error) { 421 console.warn( 422 "The script wasn't able to remove any possible `filename--` files created by sed. Please ensure there are no such files manually." 423 ); 424 } 425} 426 427/** 428 * @param versionName 429 * @param versionNumber format "XX.X.X" 430 */ 431async function generateVersionedExpoAsync( 432 versionName: string, 433 versionNumber: string 434): Promise<void> { 435 const versionedExpoPath = getVersionedExpoPath(versionName); 436 const versionedExpoKitPath = getVersionedExpoKitPath(versionName); 437 const versionedUnimodulePods = await getVersionedUnimodulePodsAsync(versionName); 438 const originalUnimodulePodNames = Object.keys(versionedUnimodulePods); 439 const depsToReplace = originalUnimodulePodNames.join('|'); 440 const versionedReactPodName = getVersionedReactPodName(versionName); 441 442 await fs.mkdirs(versionedExpoKitPath); 443 444 // Copy versioned exponent modules into the clone 445 console.log(`Copying versioned native modules into the new Pod...`); 446 447 await fs.copy(path.join(IOS_DIR, 'Exponent', 'Versioned'), versionedExpoKitPath); 448 449 await fs.copy( 450 path.join(EXPO_DIR, 'ios', 'ExpoKit.podspec'), 451 path.join(versionedExpoKitPath, 'ExpoKit.podspec') 452 ); 453 454 // Copy universal modules into the clone 455 console.log(`Copying unimodules into versioned Expo directory...`); 456 457 // some pods are optional, so those specs should be omitted from versioned code 458 const excludedPodNames = getExcludedPodNames(); 459 const packages = (await getListOfPackagesAsync()).filter((pkg) => { 460 const podName = pkg.podspecName; 461 return podName && pkg.isVersionableOnPlatform('ios') && !excludedPodNames.includes(podName); 462 }); 463 464 for (const pkg of packages) { 465 const modulePath = path.join(EXPO_DIR, RELATIVE_UNIVERSAL_MODULES_PATH, pkg.packageName); 466 const podName = pkg.podspecName!; 467 468 await fs.copy(path.join(modulePath, 'ios'), path.join(versionedExpoPath, podName)); 469 470 // We're moving away from additional and unnecessary subdirectory. 471 // The source code may not be wrapped by the directory with pod's name (see ExpoModulesCore). 472 // So, move this dir only when it exists. 473 const versionedSourcesPath = path.join(versionedExpoPath, podName, podName); 474 if (await fs.pathExists(versionedSourcesPath)) { 475 await fs.move( 476 versionedSourcesPath, 477 path.join(versionedExpoPath, podName, versionedUnimodulePods[podName]) 478 ); 479 } 480 481 await fs.copy( 482 path.join(modulePath, 'package.json'), 483 path.join(versionedExpoPath, podName, 'package.json') 484 ); 485 } 486 487 for (const originalUnimodulePodName of originalUnimodulePodNames) { 488 const versionedUnimodulePodName = versionedUnimodulePods[originalUnimodulePodName]; 489 490 const originalPodSpecPath = path.join( 491 versionedExpoPath, 492 originalUnimodulePodName, 493 `${originalUnimodulePodName}.podspec` 494 ); 495 const prefixedPodSpecPath = path.join( 496 versionedExpoPath, 497 originalUnimodulePodName, 498 `${versionedUnimodulePodName}.podspec` 499 ); 500 501 if (!(await fs.pathExists(originalPodSpecPath))) { 502 continue; 503 } 504 505 console.log(`Generating podspec for ${chalk.green(originalUnimodulePodName)} ...`); 506 507 await fs.move(originalPodSpecPath, prefixedPodSpecPath); 508 509 // Replaces versioned modules in the podspec eg. 'EXCore' => 'ABI28_0_0EXCore' 510 // `E` flag is required for extended syntax which allows to use `(a|b)` 511 await spawnAsync('sed', [ 512 '-Ei', 513 '--', 514 `s/'(${depsToReplace})('|\\/)/'${versionName}\\1\\2/g`, 515 prefixedPodSpecPath, 516 ]); 517 await spawnAsync('sed', [ 518 '-i', 519 '--', 520 `s/React/${versionedReactPodName}/g`, 521 prefixedPodSpecPath, 522 ]); 523 await spawnAsync('sed', [ 524 '-i', 525 '--', 526 `s/${versionName}UM${versionedReactPodName}/${versionName}UMReact/g`, 527 prefixedPodSpecPath, 528 ]); 529 await spawnAsync('sed', [ 530 '-i', 531 '--', 532 "s/'..', 'package.json'/'package.json'/g", 533 prefixedPodSpecPath, 534 ]); 535 } 536 537 console.log(`Generating podspec for ${chalk.green('ExpoKit')} ...`); 538 539 await generateExpoKitPodspecAsync( 540 versionedExpoKitPath, 541 versionedUnimodulePods, 542 versionName, 543 versionNumber 544 ); 545} 546 547/** 548 * Transforms ExpoKit.podspec, versioning Expo namespace, React pod name, replacing original ExpoKit podspecs 549 * with Expo and ExpoOptional. 550 * @param specfilePath location of ExpoKit.podspec to modify, e.g. /versioned-react-native/someversion/ 551 * @param versionedReactPodName name of the new pod (and podfile) 552 * @param universalModulesPodNames versioned names of universal modules 553 * @param versionNumber "XX.X.X" 554 */ 555async function generateExpoKitPodspecAsync( 556 specfilePath: string, 557 universalModulesPodNames: { [key: string]: string }, 558 versionName: string, 559 versionNumber: string 560): Promise<void> { 561 const versionedReactPodName = getVersionedReactPodName(versionName); 562 const versionedExpoKitPodName = getVersionedExpoKitPodName(versionName); 563 const specFilename = path.join(specfilePath, 'ExpoKit.podspec'); 564 const excludedPodNames = getExcludedPodNames(); 565 566 // rename spec to newPodName 567 const sedPattern = `s/\\(s\\.name[[:space:]]*=[[:space:]]\\)"ExpoKit"/\\1"${versionedExpoKitPodName}"/g`; 568 569 await spawnAsync('sed', ['-i', '--', sedPattern, specFilename]); 570 571 // further processing that sed can't do very well 572 await _transformFileContentsAsync(specFilename, async (fileString) => { 573 // `universalModulesPodNames` contains only versioned unimodules, 574 // so we fall back to the original name if the module is not there 575 const universalModulesDependencies = (await getListOfPackagesAsync()) 576 .filter( 577 (pkg) => 578 pkg.isIncludedInExpoClientOnPlatform('ios') && 579 pkg.podspecName && 580 !excludedPodNames.includes(pkg.podspecName) 581 ) 582 .map( 583 ({ podspecName }) => 584 `ss.dependency "${universalModulesPodNames[podspecName!] || podspecName}"` 585 ).join(` 586 `); 587 const externalDependencies = EXTERNAL_REACT_ABI_DEPENDENCIES.map( 588 (podName) => `ss.dependency "${podName}"` 589 ).join(` 590 `); 591 let subspec = `s.subspec "Expo" do |ss| 592 ss.source_files = "Core/**/*.{h,m,mm,cpp}" 593 594 ss.dependency "${versionedReactPodName}-Core" 595 ss.dependency "${versionedReactPodName}-Core/DevSupport" 596 ss.dependency "${versionedReactPodName}Common" 597 ${universalModulesDependencies} 598 ${externalDependencies} 599 end 600 601 s.subspec "ExpoOptional" do |ss| 602 ss.dependency "${versionedExpoKitPodName}/Expo" 603 ss.source_files = "Optional/**/*.{h,m,mm}" 604 end`; 605 fileString = fileString.replace( 606 /(s\.subspec ".+?"[\S\s]+?(?=end\b)end\b[\s]+)+/g, 607 `${subspec}\n` 608 ); 609 610 // correct version number 611 fileString = fileString.replace(/(?<=s.version = ").*?(?=")/g, versionNumber); 612 613 // add Reanimated V2 Folly dependency 614 fileString = fileString 615 .replace( 616 /(?=Pod::Spec.new do \|s\|)/, 617 ` 618folly_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1' 619folly_compiler_flags = folly_flags + ' ' + '-Wno-comma -Wno-shorten-64-to-32' 620folly_version = '2020.01.13.00' 621boost_compiler_flags = '-Wno-documentation'\n\n` 622 ) 623 .replace( 624 /(?= s.subspec "Expo" do \|ss\|)/g, 625 ` 626 s.pod_target_xcconfig = { 627 "USE_HEADERMAP" => "YES", 628 "HEADER_SEARCH_PATHS" => "\\"$(PODS_TARGET_SRCROOT)/ReactCommon\\" \\"$(PODS_TARGET_SRCROOT)\\" \\"$(PODS_ROOT)/Folly\\" \\"$(PODS_ROOT)/boost-for-react-native\\" \\"$(PODS_ROOT)/DoubleConversion\\" \\"$(PODS_ROOT)/Headers/Private/React-Core\\" " 629 } 630 s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags 631 s.xcconfig = { 632 "HEADER_SEARCH_PATHS" => "\\"$(PODS_ROOT)/boost-for-react-native\\" \\"$(PODS_ROOT)/glog\\" \\"$(PODS_ROOT)/Folly\\" \\"$(PODS_ROOT)/Headers/Private/${versionName}React-Core\\"", 633 "OTHER_CFLAGS" => "$(inherited)" + " " + folly_flags 634 }\n\n` 635 ); 636 637 return fileString; 638 }); 639 640 // move podspec to ${versionedExpoKitPodName}.podspec 641 await fs.move(specFilename, path.join(specfilePath, `${versionedExpoKitPodName}.podspec`)); 642} 643 644/** 645 * @param specfilePath location of React.podspec to modify, e.g. /versioned-react-native/someversion/ 646 * @param versionedReactPodName name of the new pod (and podfile) 647 */ 648async function generateReactPodspecAsync(versionedReactNativePath, versionName) { 649 const versionedReactPodName = getVersionedReactPodName(versionName); 650 const versionedYogaPodName = getVersionedYogaPodName(versionName); 651 const versionedJSIPodName = getVersionedJSIPodName(versionName); 652 const specFilename = path.join(versionedReactNativePath, 'React.podspec'); 653 654 // rename spec to newPodName 655 const sedPattern = `s/\\(s\\.name[[:space:]]*=[[:space:]]\\)"React"/\\1"${versionedReactPodName}"/g`; 656 await spawnAsync('sed', ['-i', '--', sedPattern, specFilename]); 657 658 // rename header_dir 659 await spawnAsync('sed', [ 660 '-i', 661 '--', 662 `s/^\\(.*header_dir.*\\)React\\(.*\\)$/\\1${versionedReactPodName}\\2/`, 663 specFilename, 664 ]); 665 await spawnAsync('sed', [ 666 '-i', 667 '--', 668 `s/^\\(.*header_dir.*\\)jsireact\\(.*\\)$/\\1${versionedJSIPodName}\\2/`, 669 specFilename, 670 ]); 671 672 // point source at . 673 const newPodSource = `{ :path => "." }`; 674 await spawnAsync('sed', [ 675 '-i', 676 '--', 677 `s/\\(s\\.source[[:space:]]*=[[:space:]]\\).*/\\1${newPodSource}/g`, 678 specFilename, 679 ]); 680 681 // further processing that sed can't do very well 682 await _transformFileContentsAsync(specFilename, (fileString) => { 683 // replace React/* dependency with ${versionedReactPodName}/* 684 fileString = fileString.replace( 685 /(\.dependency\s+)"React([^"]+)"/g, 686 `$1"${versionedReactPodName}$2"` 687 ); 688 689 fileString = fileString.replace('/RCTTV', `/${versionName}RCTTV`); 690 691 // namespace cpp libraries 692 const cppLibraries = getCppLibrariesToVersion(); 693 cppLibraries.forEach(({ libName }) => { 694 fileString = fileString.replace( 695 new RegExp(`([^A-Za-z0-9_])${libName}([^A-Za-z0-9_])`, 'g'), 696 `$1${getVersionedLibraryName(libName, versionName)}$2` 697 ); 698 }); 699 700 // fix wrong Yoga pod name 701 fileString = fileString.replace( 702 /^(.*dependency.*["']).*yoga.*?(["'].*)$/m, 703 `$1${versionedYogaPodName}$2` 704 ); 705 706 return fileString; 707 }); 708 709 // move podspec to ${versionedReactPodName}.podspec 710 await fs.move( 711 specFilename, 712 path.join(versionedReactNativePath, `${versionedReactPodName}.podspec`) 713 ); 714} 715 716function getCFlagsToPrefixGlobals(prefix, globals) { 717 return globals.map((val) => `-D${val}=${prefix}${val}`); 718} 719 720/** 721 * Generates `dependencies.rb` and `postinstalls.rb` files for versioned code. 722 * @param versionNumber Semver-compliant version of the SDK/ABI 723 * @param versionName Version prefix used for versioned files, e.g. ABI99_0_0 724 * @param versionedPodNames mapping from pod names to versioned pod names, e.g. React -> ReactABI99_0_0 725 * @param versionedReactPodPath path of the new react pod 726 */ 727async function generatePodfileSubscriptsAsync( 728 versionNumber: string, 729 versionName: string, 730 versionedPodNames: Record<string, string>, 731 versionedReactPodPath: string 732) { 733 if (!versionedPodNames.React) { 734 throw new Error( 735 'Tried to add generate pod dependencies, but missing a name for the versioned library.' 736 ); 737 } 738 739 const relativeReactNativePath = path.relative(IOS_DIR, getVersionedReactNativePath(versionName)); 740 const relativeExpoKitPath = path.relative(IOS_DIR, getVersionedExpoKitPath(versionName)); 741 const relativeExpoPath = path.relative(IOS_DIR, getVersionedExpoPath(versionName)); 742 743 const versionableUnimodulesPods = Object.entries( 744 await getVersionedUnimodulePodsAsync(versionName) 745 ) 746 .map(([originalUnimodulePodName, versionedUnimodulePodName]) => { 747 return `pod '${versionedUnimodulePodName}', 748 :path => './${relativeExpoPath}/${originalUnimodulePodName}', 749 :project_name => '${versionName}'`; 750 }) 751 .join('\n'); 752 753 // Add a dependency on newPodName 754 let dep = `# @generated by expotools 755 756require './${relativeReactNativePath}/react_native_pods.rb' 757 758use_react_native_${versionName}! path: './${relativeReactNativePath}' 759 760pod '${getVersionedExpoKitPodName(versionName)}', 761 :path => './${relativeExpoKitPath}', 762 :project_name => '${versionName}', 763 :subspecs => ['Expo', 'ExpoOptional'] 764 765use_pods! 'vendored/sdk${semver.major(versionNumber)}/*/*.podspec.json', '${versionName}' 766 767${versionableUnimodulesPods} 768`; 769 await fs.writeFile(path.join(versionedReactPodPath, 'dependencies.rb'), dep); 770 771 // Add postinstall. 772 // In particular, resolve conflicting globals from React by redefining them. 773 let globals = { 774 React: [ 775 // RCTNavigator 776 'kNeverRequested', 777 'kNeverProgressed', 778 // react-native-maps 779 'kSMCalloutViewRepositionDelayForUIScrollView', 780 'regionAsJSON', 781 'unionRect', 782 // jschelpers 783 'JSNoBytecodeFileFormatVersion', 784 'JSSamplingProfilerEnabled', 785 // RCTInspectorPackagerConnection 786 'RECONNECT_DELAY_MS', 787 // RCTSpringAnimation 788 'MAX_DELTA_TIME', 789 ], 790 yoga: [ 791 'gCurrentGenerationCount', 792 'gPrintSkips', 793 'gPrintChanges', 794 'layoutNodeInternal', 795 'gDepth', 796 'gPrintTree', 797 'isUndefined', 798 'gNodeInstanceCount', 799 ], 800 }; 801 let configValues = getCFlagsToPrefixGlobals( 802 versionedPodNames.React, 803 globals.React.concat(globals.yoga) 804 ); 805 const indent = ' '.repeat(3); 806 const config = `# @generated by expotools 807 808if pod_name.start_with?('${versionedPodNames.React}') || pod_name == '${versionedPodNames.ExpoKit}' 809 target_installation_result.native_target.build_configurations.each do |config| 810 config.build_settings['OTHER_CFLAGS'] = %w[ 811 ${configValues.join(`\n${indent}`)} 812 ] 813 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)'] 814 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}RCT_DEV=1' 815 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}RCT_ENABLE_INSPECTOR=0' 816 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}ENABLE_PACKAGER_CONNECTION=0' 817 # Enable Google Maps support 818 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}HAVE_GOOGLE_MAPS=1' 819 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}HAVE_GOOGLE_MAPS_UTILS=1' 820 end 821end 822`; 823 await fs.writeFile(path.join(versionedReactPodPath, 'postinstalls.rb'), config); 824} 825 826/** 827 * @param transformConfig function that takes a config dict and returns a new config dict. 828 */ 829async function modifyVersionConfigAsync(configPath, transformConfig) { 830 let jsConfigFilename = `${configPath}/sdkVersions.json`; 831 await _transformFileContentsAsync(jsConfigFilename, (jsConfigContents) => { 832 let jsConfig; 833 834 // read the existing json config and add the new version to the sdkVersions array 835 try { 836 jsConfig = JSON.parse(jsConfigContents); 837 } catch (e) { 838 console.log('Error parsing existing sdkVersions.json file, writing a new one...', e); 839 console.log('The erroneous file contents was:', jsConfigContents); 840 jsConfig = { 841 sdkVersions: [], 842 }; 843 } 844 // apply changes 845 jsConfig = transformConfig(jsConfig); 846 return JSON.stringify(jsConfig); 847 }); 848 849 // convert json config to plist for iOS 850 await spawnAsync('plutil', [ 851 '-convert', 852 'xml1', 853 jsConfigFilename, 854 '-o', 855 path.join(configPath, 'EXSDKVersions.plist'), 856 ]); 857} 858 859function validateAddVersionDirectories(rootPath, newVersionPath) { 860 // Make sure the paths we want to read are available 861 let relativePathsToCheck = [ 862 RELATIVE_RN_PATH, 863 'ios/versioned-react-native', 864 'ios/Exponent', 865 'ios/Exponent/Versioned', 866 ]; 867 let isValid = true; 868 relativePathsToCheck.forEach((path) => { 869 try { 870 fs.accessSync(`${rootPath}/${path}`, fs.constants.F_OK); 871 } catch (e) { 872 console.log(`${rootPath}/${path} does not exist or is otherwise inaccessible`); 873 isValid = false; 874 } 875 }); 876 // Also, make sure the version we're about to write doesn't already exist 877 try { 878 // we want this to fail 879 fs.accessSync(newVersionPath, fs.constants.F_OK); 880 console.log(`${newVersionPath} already exists, will not overwrite`); 881 isValid = false; 882 } catch (e) {} 883 884 return isValid; 885} 886 887function validateRemoveVersionDirectories(rootPath, newVersionPath) { 888 let pathsToCheck = [ 889 `${rootPath}/ios/versioned-react-native`, 890 `${rootPath}/ios/Exponent`, 891 newVersionPath, 892 ]; 893 let isValid = true; 894 pathsToCheck.forEach((path) => { 895 try { 896 fs.accessSync(path, fs.constants.F_OK); 897 } catch (e) { 898 console.log(`${path} does not exist or is otherwise inaccessible`); 899 isValid = false; 900 } 901 }); 902 return isValid; 903} 904 905async function getConfigsFromArguments(versionNumber) { 906 let versionComponents = versionNumber.split('.'); 907 versionComponents = versionComponents.map((number) => parseInt(number, 10)); 908 let versionName = 'ABI' + versionNumber.replace(/\./g, '_'); 909 let rootPathComponents = EXPO_DIR.split('/'); 910 let versionPathComponents = path.join('ios', 'versioned-react-native', versionName).split('/'); 911 let newVersionPath = rootPathComponents.concat(versionPathComponents).join('/'); 912 913 let versionedPodNames = { 914 React: getVersionedReactPodName(versionName), 915 yoga: getVersionedYogaPodName(versionName), 916 ExpoKit: getVersionedExpoKitPodName(versionName), 917 jsireact: getVersionedJSIPodName(versionName), 918 }; 919 920 return { 921 versionName, 922 newVersionPath, 923 versionedPodNames, 924 versionComponents, 925 }; 926} 927 928async function getVersionedUnimodulePodsAsync( 929 versionName: string 930): Promise<{ [key: string]: string }> { 931 const versionedUnimodulePods = {}; 932 const packages = await getListOfPackagesAsync(); 933 const excludedPodNames = getExcludedPodNames(); 934 935 packages.forEach((pkg) => { 936 const podName = pkg.podspecName; 937 if (podName && pkg.isVersionableOnPlatform('ios') && !excludedPodNames.includes(podName)) { 938 versionedUnimodulePods[podName] = `${versionName}${podName}`; 939 } 940 }); 941 942 return versionedUnimodulePods; 943} 944 945function getVersionedReactPodName(versionName: string): string { 946 return getVersionedLibraryName('React', versionName); 947} 948 949function getVersionedYogaPodName(versionName: string): string { 950 return getVersionedLibraryName('Yoga', versionName); 951} 952 953function getVersionedJSIPodName(versionName: string): string { 954 return getVersionedLibraryName('jsiReact', versionName); 955} 956 957function getVersionedExpoKitPodName(versionName: string): string { 958 return getVersionedLibraryName('ExpoKit', versionName); 959} 960 961function getVersionedLibraryName(libraryName: string, versionName: string): string { 962 return `${versionName}${libraryName}`; 963} 964 965function getVersionedReactNativePath(versionName: string): string { 966 return path.join(VERSIONED_RN_IOS_DIR, versionName, 'ReactNative'); 967} 968 969function getVersionedExpoPath(versionName: string): string { 970 return path.join(VERSIONED_RN_IOS_DIR, versionName, 'Expo'); 971} 972 973function getVersionedExpoKitPath(versionName: string): string { 974 return path.join(getVersionedExpoPath(versionName), 'ExpoKit'); 975} 976 977function getCppLibrariesToVersion() { 978 return [ 979 { 980 libName: 'cxxreact', 981 }, 982 { 983 libName: 'jsi', 984 }, 985 { 986 libName: 'jsiexecutor', 987 customHeaderDir: 'jsireact', 988 }, 989 { 990 libName: 'jsinspector', 991 }, 992 { 993 libName: 'yoga', 994 }, 995 { 996 libName: 'fabric', 997 }, 998 { 999 libName: 'turbomodule', 1000 customHeaderDir: 'ReactCommon', 1001 }, 1002 { 1003 libName: 'callinvoker', 1004 customHeaderDir: 'ReactCommon', 1005 }, 1006 ]; 1007} 1008 1009function getExcludedPodNames() { 1010 // we don't want Payments in Expo Client versions for now 1011 return ['EXPaymentsStripe']; 1012} 1013 1014export async function addVersionAsync(versionNumber: string) { 1015 let { versionName, newVersionPath, versionedPodNames } = await getConfigsFromArguments( 1016 versionNumber 1017 ); 1018 1019 // Validate the directories we need before doing anything 1020 console.log(`Validating root directory ${chalk.magenta(EXPO_DIR)} ...`); 1021 let isFilesystemReady = validateAddVersionDirectories(EXPO_DIR, newVersionPath); 1022 if (!isFilesystemReady) { 1023 throw new Error('Aborting: At least one directory we need is not available'); 1024 } 1025 1026 if (!versionedPodNames.React) { 1027 throw new Error('Missing name for versioned pod dependency.'); 1028 } 1029 1030 // Create ABIXX_0_0 directory. 1031 console.log( 1032 `Creating new ABI version ${chalk.cyan(versionNumber)} at ${chalk.magenta( 1033 path.relative(EXPO_DIR, newVersionPath) 1034 )}` 1035 ); 1036 await fs.mkdirs(newVersionPath); 1037 1038 // Generate new Podspec from the existing React.podspec 1039 console.log('Generating versioned ReactNative directory...'); 1040 await generateVersionedReactNativeAsync(versionName); 1041 1042 console.log( 1043 `Generating ${chalk.magenta( 1044 path.relative(EXPO_DIR, getVersionedExpoPath(versionName)) 1045 )} directory...` 1046 ); 1047 await generateVersionedExpoAsync(versionName, versionNumber); 1048 1049 // Namespace the new React clone 1050 console.log('Namespacing/transforming files...'); 1051 await transformReactNativeAsync(newVersionPath, versionName, versionedPodNames); 1052 1053 // Generate Ruby scripts with versioned dependencies and postinstall actions that will be evaluated in the Expo client's Podfile. 1054 console.log('Adding dependency to root Podfile...'); 1055 await generatePodfileSubscriptsAsync( 1056 versionNumber, 1057 versionName, 1058 versionedPodNames, 1059 newVersionPath 1060 ); 1061 1062 // Add the new version to the iOS config list of available versions 1063 console.log('Registering new version under sdkVersions config...'); 1064 const addVersionToConfig = (config, versionNumber) => { 1065 config.sdkVersions.push(versionNumber); 1066 return config; 1067 }; 1068 await modifyVersionConfigAsync(path.join(IOS_DIR, 'Exponent', 'Supporting'), (config) => 1069 addVersionToConfig(config, versionNumber) 1070 ); 1071 await modifyVersionConfigAsync( 1072 path.join(EXPO_DIR, 'exponent-view-template', 'ios', 'exponent-view-template', 'Supporting'), 1073 (config) => addVersionToConfig(config, versionNumber) 1074 ); 1075 1076 // Modifying kernel files 1077 console.log(`Modifying ${chalk.bold('kernel files')} to incorporate new SDK version...`); 1078 await modifyKernelFilesAsync(versionName); 1079 1080 console.log('Removing any `filename--` files from the new pod ...'); 1081 1082 try { 1083 const minusMinusFiles = await glob(path.join(newVersionPath, '**', '*--')); 1084 for (const minusMinusFile of minusMinusFiles) { 1085 await fs.remove(minusMinusFile); 1086 } 1087 } catch (error) { 1088 console.warn( 1089 "The script wasn't able to remove any possible `filename--` files created by sed. Please ensure there are no such files manually." 1090 ); 1091 } 1092 1093 console.log('Finished creating new version.'); 1094} 1095 1096async function askToReinstallPodsAsync(): Promise<boolean> { 1097 if (process.env.CI) { 1098 // If we're on the CI, let's regenerate Pods by default. 1099 return true; 1100 } 1101 const { result } = await inquirer.prompt<{ result: boolean }>([ 1102 { 1103 type: 'confirm', 1104 name: 'result', 1105 message: 'Do you want to reinstall pods?', 1106 default: true, 1107 }, 1108 ]); 1109 return result; 1110} 1111 1112export async function reinstallPodsAsync(force?: boolean, preventReinstall?: boolean) { 1113 if ( 1114 preventReinstall !== true && 1115 (force || (force !== false && (await askToReinstallPodsAsync()))) 1116 ) { 1117 await spawnAsync('pod', ['install'], { stdio: 'inherit', cwd: IOS_DIR }); 1118 console.log( 1119 'Regenerated Podfile and installed new pods. You can now try to build the project in Xcode.' 1120 ); 1121 } else { 1122 console.log( 1123 'Skipped pods regeneration. You might want to run `et ios-generate-dynamic-macros`, then `pod install` in `ios` to configure Xcode project.' 1124 ); 1125 } 1126} 1127 1128export async function removeVersionAsync(versionNumber: string) { 1129 let { newVersionPath, versionedPodNames, versionName } = await getConfigsFromArguments( 1130 versionNumber 1131 ); 1132 console.log( 1133 `Removing SDK version ${chalk.cyan(versionNumber)} from ${chalk.magenta( 1134 path.relative(EXPO_DIR, newVersionPath) 1135 )} with Pod name ${chalk.green(versionedPodNames.React)}` 1136 ); 1137 1138 // Validate the directories we need before doing anything 1139 console.log(`Validating root directory ${chalk.magenta(EXPO_DIR)} ...`); 1140 let isFilesystemReady = validateRemoveVersionDirectories(EXPO_DIR, newVersionPath); 1141 if (!isFilesystemReady) { 1142 console.log('Aborting: At least one directory we expect is not available'); 1143 return; 1144 } 1145 1146 // remove directory 1147 console.log( 1148 `Removing versioned files under ${chalk.magenta(path.relative(EXPO_DIR, newVersionPath))}...` 1149 ); 1150 await fs.remove(newVersionPath); 1151 1152 // remove dep from main podfile 1153 console.log(`Removing ${chalk.green(versionedPodNames.React)} dependency from root Podfile...`); 1154 1155 // remove from sdkVersions.json 1156 console.log('Unregistering version from sdkVersions config...'); 1157 const removeVersionFromConfig = (config, versionNumber) => { 1158 let index = config.sdkVersions.indexOf(versionNumber); 1159 if (index > -1) { 1160 // modify in place 1161 config.sdkVersions.splice(index, 1); 1162 } 1163 return config; 1164 }; 1165 await modifyVersionConfigAsync(path.join(IOS_DIR, 'Exponent', 'Supporting'), (config) => 1166 removeVersionFromConfig(config, versionNumber) 1167 ); 1168 await modifyVersionConfigAsync( 1169 path.join(EXPO_DIR, 'exponent-view-template', 'ios', 'exponent-view-template', 'Supporting'), 1170 (config) => removeVersionFromConfig(config, versionNumber) 1171 ); 1172 1173 // modify kernel files 1174 console.log('Rollbacking SDK modifications from kernel files...'); 1175 await modifyKernelFilesAsync(versionName, true); 1176 1177 await reinstallPodsAsync(); 1178} 1179 1180/** 1181 * @return an array of objects representing react native transform rules. 1182 * objects must contain 'pattern' and may optionally contain 'paths' to limit 1183 * the transform to certain file paths. 1184 * 1185 * the rules are applied in order! 1186 */ 1187function _getReactNativeTransformRules(versionPrefix, reactPodName) { 1188 const cppLibraries = getCppLibrariesToVersion().map((lib) => lib.customHeaderDir || lib.libName); 1189 const versionedLibs = [...cppLibraries, 'React', 'FBLazyVector', 'FBReactNativeSpec']; 1190 1191 return [ 1192 { 1193 // Change Obj-C symbols prefix 1194 pattern: `s/RCT/${versionPrefix}RCT/g`, 1195 }, 1196 { 1197 pattern: `s/^EX/${versionPrefix}EX/g`, 1198 // paths: 'EX', 1199 }, 1200 { 1201 pattern: `s/^UM/${versionPrefix}UM/g`, 1202 // paths: 'EX', 1203 }, 1204 { 1205 pattern: `s/\\([^\\<\\/"]\\)YG/\\1${versionPrefix}YG/g`, 1206 }, 1207 { 1208 pattern: `s/\\([\\<,]\\)YG/\\1${versionPrefix}YG/g`, 1209 }, 1210 { 1211 pattern: `s/^YG/${versionPrefix}YG/g`, 1212 }, 1213 { 1214 paths: 'Components', 1215 pattern: `s/\\([^+]\\)AIR/\\1${versionPrefix}AIR/g`, 1216 }, 1217 { 1218 pattern: `s/\\([^A-Za-z0-9_]\\)EX/\\1${versionPrefix}EX/g`, 1219 }, 1220 { 1221 pattern: `s/\\([^A-Za-z0-9_]\\)UM/\\1${versionPrefix}UM/g`, 1222 }, 1223 { 1224 pattern: `s/\\([^A-Za-z0-9_+]\\)ART/\\1${versionPrefix}ART/g`, 1225 }, 1226 { 1227 pattern: `s/ENABLE_PACKAGER_CONNECTION/${versionPrefix}ENABLE_PACKAGER_CONNECTION/g`, 1228 }, 1229 { 1230 paths: 'Components', 1231 pattern: `s/\\([^A-Za-z0-9_+]\\)SM/\\1${versionPrefix}SM/g`, 1232 }, 1233 { 1234 paths: 'Core/Api', 1235 pattern: `s/\\([^A-Za-z0-9_+]\\)RN/\\1${versionPrefix}RN/g`, 1236 }, 1237 { 1238 paths: 'Core/Api', 1239 pattern: `s/^RN/${versionPrefix}RN/g`, 1240 }, 1241 { 1242 paths: 'Core/Api', 1243 pattern: `s/HAVE_GOOGLE_MAPS/${versionPrefix}HAVE_GOOGLE_MAPS/g`, 1244 }, 1245 { 1246 paths: 'Core/Api', 1247 pattern: `s/#import "Branch/#import "${versionPrefix}Branch/g`, 1248 }, 1249 { 1250 paths: 'Core/Api', 1251 pattern: `s/#import "NSObject+RNBranch/#import "${versionPrefix}NSObject+RNBranch/g`, 1252 }, 1253 { 1254 // React will be prefixed in a moment 1255 pattern: `s/#import <${versionPrefix}RCTAnimation/#import <React/g`, 1256 }, 1257 { 1258 paths: 'Core/Api/Reanimated', 1259 pattern: `s/\\([^A-Za-z0-9_+]\\)REA/\\1${versionPrefix}REA/g`, 1260 }, 1261 { 1262 pattern: `s/^REA/${versionPrefix}REA/g`, 1263 paths: 'Core/Api/Reanimated', 1264 }, 1265 { 1266 // Prefixes all direct references to objects under `reanimated` namespace. 1267 // It must be applied before versioning `namespace reanimated` so 1268 // `using namespace reanimated::` don't get versioned twice. 1269 pattern: `s/reanimated::/${versionPrefix}reanimated::/g`, 1270 }, 1271 { 1272 // Prefixes reanimated namespace. 1273 pattern: `s/namespace reanimated/namespace ${versionPrefix}reanimated/g`, 1274 }, 1275 { 1276 // Fix imports in C++ libs in ReactCommon. 1277 // Extended syntax (-E) is required to use (a|b). 1278 flags: '-Ei', 1279 pattern: `s/([<"])(${versionedLibs.join( 1280 '|' 1281 )})\\//\\1${versionPrefix}\\2\\/${versionPrefix}/g`, 1282 }, 1283 { 1284 // Change React -> new pod name 1285 // e.g. threads and queues namespaced to com.facebook.react, 1286 // file paths beginning with the lib name, 1287 // the cpp facebook::react namespace, 1288 // iOS categories ending in +React 1289 flags: '-Ei', 1290 pattern: `s/[Rr]eact/${reactPodName}/g`, 1291 }, 1292 { 1293 // Imports from cxxreact and jsireact got prefixed twice. 1294 flags: '-Ei', 1295 pattern: `s/([<"])(${versionPrefix})(cxx|jsi)${versionPrefix}React/\\1\\2\\3react/g`, 1296 }, 1297 { 1298 // Fix imports from files like `UIView+React.*`. 1299 flags: '-Ei', 1300 pattern: `s/\\+${versionPrefix}React/\\+React/g`, 1301 }, 1302 { 1303 // Prefixes all direct references to objects under `facebook` namespace. 1304 // It must be applied before versioning `namespace facebook` so 1305 // `using namespace facebook::` don't get versioned twice. 1306 pattern: `s/facebook::/${versionPrefix}facebook::/g`, 1307 }, 1308 { 1309 // Prefixes facebook namespace. 1310 pattern: `s/namespace facebook/namespace ${versionPrefix}facebook/g`, 1311 }, 1312 { 1313 // For UMReactNativeAdapter 1314 // Fix names with 'React' substring occurring twice - only first one should be prefixed 1315 flags: '-Ei', 1316 pattern: `s/${versionPrefix}UM([[:alpha:]]*)${reactPodName}/${versionPrefix}UM\\1React/g`, 1317 }, 1318 { 1319 // For EXReactNativeAdapter 1320 pattern: `s/${versionPrefix}EX${reactPodName}/${versionPrefix}EXReact/g`, 1321 }, 1322 { 1323 // For EXConstants and EXNotifications so that when their migrators 1324 // try to access legacy storage for UUID migration, they access the proper value. 1325 pattern: `s/${versionPrefix}EXDeviceInstallUUIDKey/EXDeviceInstallUUIDKey/g`, 1326 paths: 'Expo', 1327 }, 1328 { 1329 // For EXConstants and EXNotifications so that the installation ID 1330 // stays the same between different SDK versions. (https://github.com/expo/expo/issues/11008#issuecomment-726370187) 1331 pattern: `s/${versionPrefix}EXDeviceInstallationUUIDKey/EXDeviceInstallationUUIDKey/g`, 1332 paths: 'Expo', 1333 }, 1334 { 1335 // RCTPlatform exports version of React Native 1336 pattern: `s/${reactPodName}NativeVersion/reactNativeVersion/g`, 1337 }, 1338 { 1339 pattern: `s/@"${versionPrefix}RCT"/@"RCT"/g`, 1340 }, 1341 { 1342 // Unversion EXGL_CPP imports: `<ABI37_0_0EXGL_CPP/` => `<EXGL_CPP/` 1343 pattern: `s/<${versionPrefix}EXGL_CPP\\//<EXGL_CPP\\//g`, 1344 }, 1345 { 1346 // Unprefix everything that got prefixed twice or more times. 1347 flags: '-Ei', 1348 pattern: `s/(${versionPrefix}){2,}/\\1/g`, 1349 }, 1350 { 1351 flags: '-Ei', 1352 pattern: `s/#import <(Expo|RNReanimated)/#import <${versionPrefix}\\1/g`, 1353 }, 1354 ]; 1355} 1356 1357function _getTransformRulesForDirname(transformRules, dirname) { 1358 return transformRules.filter((rule) => { 1359 return ( 1360 // no paths specified, so apply rule to everything 1361 !rule.paths || 1362 // otherwise, limit this rule to paths specified 1363 dirname.indexOf(rule.paths) !== -1 1364 ); 1365 }); 1366} 1367 1368// TODO: use the one in XDL 1369function _isDirectory(dir) { 1370 try { 1371 if (fs.statSync(dir).isDirectory()) { 1372 return true; 1373 } 1374 1375 return false; 1376 } catch (e) { 1377 return false; 1378 } 1379} 1380 1381// TODO: use the one in XDL 1382async function _transformFileContentsAsync( 1383 filename: string, 1384 transform: (fileString: string) => Promise<string> | string | null 1385) { 1386 let fileString = await fs.readFile(filename, 'utf8'); 1387 let newFileString = await transform(fileString); 1388 if (newFileString !== null) { 1389 await fs.writeFile(filename, newFileString); 1390 } 1391} 1392