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(); 460 461 for (const pkg of packages) { 462 const modulePath = path.join(EXPO_DIR, RELATIVE_UNIVERSAL_MODULES_PATH, pkg.packageName); 463 const podName = pkg.podspecName; 464 465 if (podName && pkg.isVersionableOnPlatform('ios') && !excludedPodNames.includes(podName)) { 466 await fs.copy(path.join(modulePath, 'ios'), path.join(versionedExpoPath, podName)); 467 await fs.move( 468 path.join(versionedExpoPath, podName, podName), 469 path.join(versionedExpoPath, podName, versionedUnimodulePods[podName]) 470 ); 471 await fs.copy( 472 path.join(modulePath, 'package.json'), 473 path.join(versionedExpoPath, podName, 'package.json') 474 ); 475 } 476 } 477 478 for (const originalUnimodulePodName of originalUnimodulePodNames) { 479 const versionedUnimodulePodName = versionedUnimodulePods[originalUnimodulePodName]; 480 481 const originalPodSpecPath = path.join( 482 versionedExpoPath, 483 originalUnimodulePodName, 484 `${originalUnimodulePodName}.podspec` 485 ); 486 const prefixedPodSpecPath = path.join( 487 versionedExpoPath, 488 originalUnimodulePodName, 489 `${versionedUnimodulePodName}.podspec` 490 ); 491 492 if (!(await fs.pathExists(originalPodSpecPath))) { 493 continue; 494 } 495 496 console.log(`Generating podspec for ${chalk.green(originalUnimodulePodName)} ...`); 497 498 await fs.move(originalPodSpecPath, prefixedPodSpecPath); 499 500 // Replaces versioned modules in the podspec eg. 'EXCore' => 'ABI28_0_0EXCore' 501 // `E` flag is required for extended syntax which allows to use `(a|b)` 502 await spawnAsync('sed', [ 503 '-Ei', 504 '--', 505 `s/'(${depsToReplace})('|\\/)/'${versionName}\\1\\2/g`, 506 prefixedPodSpecPath, 507 ]); 508 await spawnAsync('sed', [ 509 '-i', 510 '--', 511 `s/React/${versionedReactPodName}/g`, 512 prefixedPodSpecPath, 513 ]); 514 await spawnAsync('sed', [ 515 '-i', 516 '--', 517 `s/${versionName}UM${versionedReactPodName}/${versionName}UMReact/g`, 518 prefixedPodSpecPath, 519 ]); 520 await spawnAsync('sed', [ 521 '-i', 522 '--', 523 "s/'..', 'package.json'/'package.json'/g", 524 prefixedPodSpecPath, 525 ]); 526 } 527 528 console.log(`Generating podspec for ${chalk.green('ExpoKit')} ...`); 529 530 await generateExpoKitPodspecAsync( 531 versionedExpoKitPath, 532 versionedUnimodulePods, 533 versionName, 534 versionNumber 535 ); 536} 537 538/** 539 * Transforms ExpoKit.podspec, versioning Expo namespace, React pod name, replacing original ExpoKit podspecs 540 * with Expo and ExpoOptional. 541 * @param specfilePath location of ExpoKit.podspec to modify, e.g. /versioned-react-native/someversion/ 542 * @param versionedReactPodName name of the new pod (and podfile) 543 * @param universalModulesPodNames versioned names of universal modules 544 * @param versionNumber "XX.X.X" 545 */ 546async function generateExpoKitPodspecAsync( 547 specfilePath: string, 548 universalModulesPodNames: { [key: string]: string }, 549 versionName: string, 550 versionNumber: string 551): Promise<void> { 552 const versionedReactPodName = getVersionedReactPodName(versionName); 553 const versionedExpoKitPodName = getVersionedExpoKitPodName(versionName); 554 const specFilename = path.join(specfilePath, 'ExpoKit.podspec'); 555 const excludedPodNames = getExcludedPodNames(); 556 557 // rename spec to newPodName 558 const sedPattern = `s/\\(s\\.name[[:space:]]*=[[:space:]]\\)"ExpoKit"/\\1"${versionedExpoKitPodName}"/g`; 559 560 await spawnAsync('sed', ['-i', '--', sedPattern, specFilename]); 561 562 // further processing that sed can't do very well 563 await _transformFileContentsAsync(specFilename, async (fileString) => { 564 // `universalModulesPodNames` contains only versioned unimodules, 565 // so we fall back to the original name if the module is not there 566 const universalModulesDependencies = (await getListOfPackagesAsync()) 567 .filter( 568 (pkg) => 569 pkg.isIncludedInExpoClientOnPlatform('ios') && 570 pkg.podspecName && 571 !excludedPodNames.includes(pkg.podspecName) 572 ) 573 .map( 574 ({ podspecName }) => 575 `ss.dependency "${universalModulesPodNames[podspecName!] || podspecName}"` 576 ).join(` 577 `); 578 const externalDependencies = EXTERNAL_REACT_ABI_DEPENDENCIES.map( 579 (podName) => `ss.dependency "${podName}"` 580 ).join(` 581 `); 582 let subspec = `s.subspec "Expo" do |ss| 583 ss.source_files = "Core/**/*.{h,m,mm,cpp}" 584 585 ss.dependency "${versionedReactPodName}-Core" 586 ss.dependency "${versionedReactPodName}-Core/DevSupport" 587 ss.dependency "${versionedReactPodName}Common" 588 ${universalModulesDependencies} 589 ${externalDependencies} 590 end 591 592 s.subspec "ExpoOptional" do |ss| 593 ss.dependency "${versionedExpoKitPodName}/Expo" 594 ss.source_files = "Optional/**/*.{h,m,mm}" 595 end`; 596 fileString = fileString.replace( 597 /(s\.subspec ".+?"[\S\s]+?(?=end\b)end\b[\s]+)+/g, 598 `${subspec}\n` 599 ); 600 601 // correct version number 602 fileString = fileString.replace(/(?<=s.version = ").*?(?=")/g, versionNumber); 603 604 // add Reanimated V2 Folly dependency 605 fileString = fileString 606 .replace( 607 /(?=Pod::Spec.new do \|s\|)/, 608 ` 609folly_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1' 610folly_compiler_flags = folly_flags + ' ' + '-Wno-comma -Wno-shorten-64-to-32' 611folly_version = '2020.01.13.00' 612boost_compiler_flags = '-Wno-documentation'\n\n` 613 ) 614 .replace( 615 /(?= s.subspec "Expo" do \|ss\|)/g, 616 ` 617 s.pod_target_xcconfig = { 618 "USE_HEADERMAP" => "YES", 619 "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\\" " 620 } 621 s.compiler_flags = folly_compiler_flags + ' ' + boost_compiler_flags 622 s.xcconfig = { 623 "HEADER_SEARCH_PATHS" => "\\"$(PODS_ROOT)/boost-for-react-native\\" \\"$(PODS_ROOT)/glog\\" \\"$(PODS_ROOT)/Folly\\" \\"$(PODS_ROOT)/Headers/Private/${versionName}React-Core\\"", 624 "OTHER_CFLAGS" => "$(inherited)" + " " + folly_flags 625 }\n\n` 626 ); 627 628 return fileString; 629 }); 630 631 // move podspec to ${versionedExpoKitPodName}.podspec 632 await fs.move(specFilename, path.join(specfilePath, `${versionedExpoKitPodName}.podspec`)); 633} 634 635/** 636 * @param specfilePath location of React.podspec to modify, e.g. /versioned-react-native/someversion/ 637 * @param versionedReactPodName name of the new pod (and podfile) 638 */ 639async function generateReactPodspecAsync(versionedReactNativePath, versionName) { 640 const versionedReactPodName = getVersionedReactPodName(versionName); 641 const versionedYogaPodName = getVersionedYogaPodName(versionName); 642 const versionedJSIPodName = getVersionedJSIPodName(versionName); 643 const specFilename = path.join(versionedReactNativePath, 'React.podspec'); 644 645 // rename spec to newPodName 646 const sedPattern = `s/\\(s\\.name[[:space:]]*=[[:space:]]\\)"React"/\\1"${versionedReactPodName}"/g`; 647 await spawnAsync('sed', ['-i', '--', sedPattern, specFilename]); 648 649 // rename header_dir 650 await spawnAsync('sed', [ 651 '-i', 652 '--', 653 `s/^\\(.*header_dir.*\\)React\\(.*\\)$/\\1${versionedReactPodName}\\2/`, 654 specFilename, 655 ]); 656 await spawnAsync('sed', [ 657 '-i', 658 '--', 659 `s/^\\(.*header_dir.*\\)jsireact\\(.*\\)$/\\1${versionedJSIPodName}\\2/`, 660 specFilename, 661 ]); 662 663 // point source at . 664 const newPodSource = `{ :path => "." }`; 665 await spawnAsync('sed', [ 666 '-i', 667 '--', 668 `s/\\(s\\.source[[:space:]]*=[[:space:]]\\).*/\\1${newPodSource}/g`, 669 specFilename, 670 ]); 671 672 // further processing that sed can't do very well 673 await _transformFileContentsAsync(specFilename, (fileString) => { 674 // replace React/* dependency with ${versionedReactPodName}/* 675 fileString = fileString.replace( 676 /(\.dependency\s+)"React([^"]+)"/g, 677 `$1"${versionedReactPodName}$2"` 678 ); 679 680 fileString = fileString.replace('/RCTTV', `/${versionName}RCTTV`); 681 682 // namespace cpp libraries 683 const cppLibraries = getCppLibrariesToVersion(); 684 cppLibraries.forEach(({ libName }) => { 685 fileString = fileString.replace( 686 new RegExp(`([^A-Za-z0-9_])${libName}([^A-Za-z0-9_])`, 'g'), 687 `$1${getVersionedLibraryName(libName, versionName)}$2` 688 ); 689 }); 690 691 // fix wrong Yoga pod name 692 fileString = fileString.replace( 693 /^(.*dependency.*["']).*yoga.*?(["'].*)$/m, 694 `$1${versionedYogaPodName}$2` 695 ); 696 697 return fileString; 698 }); 699 700 // move podspec to ${versionedReactPodName}.podspec 701 await fs.move( 702 specFilename, 703 path.join(versionedReactNativePath, `${versionedReactPodName}.podspec`) 704 ); 705} 706 707function getCFlagsToPrefixGlobals(prefix, globals) { 708 return globals.map((val) => `-D${val}=${prefix}${val}`); 709} 710 711/** 712 * Generates `dependencies.rb` and `postinstalls.rb` files for versioned code. 713 * @param versionNumber Semver-compliant version of the SDK/ABI 714 * @param versionName Version prefix used for versioned files, e.g. ABI99_0_0 715 * @param versionedPodNames mapping from pod names to versioned pod names, e.g. React -> ReactABI99_0_0 716 * @param versionedReactPodPath path of the new react pod 717 */ 718async function generatePodfileSubscriptsAsync( 719 versionNumber: string, 720 versionName: string, 721 versionedPodNames: Record<string, string>, 722 versionedReactPodPath: string 723) { 724 if (!versionedPodNames.React) { 725 throw new Error( 726 'Tried to add generate pod dependencies, but missing a name for the versioned library.' 727 ); 728 } 729 730 const relativeReactNativePath = path.relative(IOS_DIR, getVersionedReactNativePath(versionName)); 731 const relativeExpoKitPath = path.relative(IOS_DIR, getVersionedExpoKitPath(versionName)); 732 const relativeExpoPath = path.relative(IOS_DIR, getVersionedExpoPath(versionName)); 733 734 const versionableUnimodulesPods = Object.entries( 735 await getVersionedUnimodulePodsAsync(versionName) 736 ) 737 .map(([originalUnimodulePodName, versionedUnimodulePodName]) => { 738 return `pod '${versionedUnimodulePodName}', 739 :path => './${relativeExpoPath}/${originalUnimodulePodName}', 740 :project_name => '${versionName}'`; 741 }) 742 .join('\n'); 743 744 // Add a dependency on newPodName 745 let dep = `# @generated by expotools 746 747require './${relativeReactNativePath}/react_native_pods.rb' 748 749use_react_native_${versionName}! path: './${relativeReactNativePath}' 750 751pod '${getVersionedExpoKitPodName(versionName)}', 752 :path => './${relativeExpoKitPath}', 753 :project_name => '${versionName}', 754 :subspecs => ['Expo', 'ExpoOptional'] 755 756use_pods! 'vendored/sdk${semver.major(versionNumber)}/*/*.podspec.json', '${versionName}' 757 758${versionableUnimodulesPods} 759`; 760 await fs.writeFile(path.join(versionedReactPodPath, 'dependencies.rb'), dep); 761 762 // Add postinstall. 763 // In particular, resolve conflicting globals from React by redefining them. 764 let globals = { 765 React: [ 766 // RCTNavigator 767 'kNeverRequested', 768 'kNeverProgressed', 769 // react-native-maps 770 'kSMCalloutViewRepositionDelayForUIScrollView', 771 'regionAsJSON', 772 'unionRect', 773 // jschelpers 774 'JSNoBytecodeFileFormatVersion', 775 'JSSamplingProfilerEnabled', 776 // RCTInspectorPackagerConnection 777 'RECONNECT_DELAY_MS', 778 // RCTSpringAnimation 779 'MAX_DELTA_TIME', 780 ], 781 yoga: [ 782 'gCurrentGenerationCount', 783 'gPrintSkips', 784 'gPrintChanges', 785 'layoutNodeInternal', 786 'gDepth', 787 'gPrintTree', 788 'isUndefined', 789 'gNodeInstanceCount', 790 ], 791 }; 792 let configValues = getCFlagsToPrefixGlobals( 793 versionedPodNames.React, 794 globals.React.concat(globals.yoga) 795 ); 796 const indent = ' '.repeat(3); 797 const config = `# @generated by expotools 798 799if pod_name.start_with?('${versionedPodNames.React}') || pod_name == '${versionedPodNames.ExpoKit}' 800 target_installation_result.native_target.build_configurations.each do |config| 801 config.build_settings['OTHER_CFLAGS'] = %w[ 802 ${configValues.join(`\n${indent}`)} 803 ] 804 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)'] 805 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}RCT_DEV=1' 806 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}RCT_ENABLE_INSPECTOR=0' 807 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}ENABLE_PACKAGER_CONNECTION=0' 808 # Enable Google Maps support 809 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}HAVE_GOOGLE_MAPS=1' 810 config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << '${versionName}HAVE_GOOGLE_MAPS_UTILS=1' 811 end 812end 813`; 814 await fs.writeFile(path.join(versionedReactPodPath, 'postinstalls.rb'), config); 815} 816 817/** 818 * @param transformConfig function that takes a config dict and returns a new config dict. 819 */ 820async function modifyVersionConfigAsync(configPath, transformConfig) { 821 let jsConfigFilename = `${configPath}/sdkVersions.json`; 822 await _transformFileContentsAsync(jsConfigFilename, (jsConfigContents) => { 823 let jsConfig; 824 825 // read the existing json config and add the new version to the sdkVersions array 826 try { 827 jsConfig = JSON.parse(jsConfigContents); 828 } catch (e) { 829 console.log('Error parsing existing sdkVersions.json file, writing a new one...', e); 830 console.log('The erroneous file contents was:', jsConfigContents); 831 jsConfig = { 832 sdkVersions: [], 833 }; 834 } 835 // apply changes 836 jsConfig = transformConfig(jsConfig); 837 return JSON.stringify(jsConfig); 838 }); 839 840 // convert json config to plist for iOS 841 await spawnAsync('plutil', [ 842 '-convert', 843 'xml1', 844 jsConfigFilename, 845 '-o', 846 path.join(configPath, 'EXSDKVersions.plist'), 847 ]); 848} 849 850function validateAddVersionDirectories(rootPath, newVersionPath) { 851 // Make sure the paths we want to read are available 852 let relativePathsToCheck = [ 853 RELATIVE_RN_PATH, 854 'ios/versioned-react-native', 855 'ios/Exponent', 856 'ios/Exponent/Versioned', 857 ]; 858 let isValid = true; 859 relativePathsToCheck.forEach((path) => { 860 try { 861 fs.accessSync(`${rootPath}/${path}`, fs.constants.F_OK); 862 } catch (e) { 863 console.log(`${rootPath}/${path} does not exist or is otherwise inaccessible`); 864 isValid = false; 865 } 866 }); 867 // Also, make sure the version we're about to write doesn't already exist 868 try { 869 // we want this to fail 870 fs.accessSync(newVersionPath, fs.constants.F_OK); 871 console.log(`${newVersionPath} already exists, will not overwrite`); 872 isValid = false; 873 } catch (e) {} 874 875 return isValid; 876} 877 878function validateRemoveVersionDirectories(rootPath, newVersionPath) { 879 let pathsToCheck = [ 880 `${rootPath}/ios/versioned-react-native`, 881 `${rootPath}/ios/Exponent`, 882 newVersionPath, 883 ]; 884 let isValid = true; 885 pathsToCheck.forEach((path) => { 886 try { 887 fs.accessSync(path, fs.constants.F_OK); 888 } catch (e) { 889 console.log(`${path} does not exist or is otherwise inaccessible`); 890 isValid = false; 891 } 892 }); 893 return isValid; 894} 895 896async function getConfigsFromArguments(versionNumber) { 897 let versionComponents = versionNumber.split('.'); 898 versionComponents = versionComponents.map((number) => parseInt(number, 10)); 899 let versionName = 'ABI' + versionNumber.replace(/\./g, '_'); 900 let rootPathComponents = EXPO_DIR.split('/'); 901 let versionPathComponents = path.join('ios', 'versioned-react-native', versionName).split('/'); 902 let newVersionPath = rootPathComponents.concat(versionPathComponents).join('/'); 903 904 let versionedPodNames = { 905 React: getVersionedReactPodName(versionName), 906 yoga: getVersionedYogaPodName(versionName), 907 ExpoKit: getVersionedExpoKitPodName(versionName), 908 jsireact: getVersionedJSIPodName(versionName), 909 }; 910 911 return { 912 versionName, 913 newVersionPath, 914 versionedPodNames, 915 versionComponents, 916 }; 917} 918 919async function getVersionedUnimodulePodsAsync( 920 versionName: string 921): Promise<{ [key: string]: string }> { 922 const versionedUnimodulePods = {}; 923 const packages = await getListOfPackagesAsync(); 924 const excludedPodNames = getExcludedPodNames(); 925 926 packages.forEach((pkg) => { 927 const podName = pkg.podspecName; 928 if (podName && pkg.isVersionableOnPlatform('ios') && !excludedPodNames.includes(podName)) { 929 versionedUnimodulePods[podName] = `${versionName}${podName}`; 930 } 931 }); 932 933 return versionedUnimodulePods; 934} 935 936function getVersionedReactPodName(versionName: string): string { 937 return getVersionedLibraryName('React', versionName); 938} 939 940function getVersionedYogaPodName(versionName: string): string { 941 return getVersionedLibraryName('Yoga', versionName); 942} 943 944function getVersionedJSIPodName(versionName: string): string { 945 return getVersionedLibraryName('jsiReact', versionName); 946} 947 948function getVersionedExpoKitPodName(versionName: string): string { 949 return getVersionedLibraryName('ExpoKit', versionName); 950} 951 952function getVersionedLibraryName(libraryName: string, versionName: string): string { 953 return `${versionName}${libraryName}`; 954} 955 956function getVersionedReactNativePath(versionName: string): string { 957 return path.join(VERSIONED_RN_IOS_DIR, versionName, 'ReactNative'); 958} 959 960function getVersionedExpoPath(versionName: string): string { 961 return path.join(VERSIONED_RN_IOS_DIR, versionName, 'Expo'); 962} 963 964function getVersionedExpoKitPath(versionName: string): string { 965 return path.join(getVersionedExpoPath(versionName), 'ExpoKit'); 966} 967 968function getCppLibrariesToVersion() { 969 return [ 970 { 971 libName: 'cxxreact', 972 }, 973 { 974 libName: 'jsi', 975 }, 976 { 977 libName: 'jsiexecutor', 978 customHeaderDir: 'jsireact', 979 }, 980 { 981 libName: 'jsinspector', 982 }, 983 { 984 libName: 'yoga', 985 }, 986 { 987 libName: 'fabric', 988 }, 989 { 990 libName: 'turbomodule', 991 customHeaderDir: 'ReactCommon', 992 }, 993 { 994 libName: 'callinvoker', 995 customHeaderDir: 'ReactCommon', 996 }, 997 ]; 998} 999 1000function getExcludedPodNames() { 1001 // we don't want Payments in Expo Client versions for now 1002 return ['EXPaymentsStripe']; 1003} 1004 1005export async function addVersionAsync(versionNumber: string) { 1006 let { versionName, newVersionPath, versionedPodNames } = await getConfigsFromArguments( 1007 versionNumber 1008 ); 1009 1010 // Validate the directories we need before doing anything 1011 console.log(`Validating root directory ${chalk.magenta(EXPO_DIR)} ...`); 1012 let isFilesystemReady = validateAddVersionDirectories(EXPO_DIR, newVersionPath); 1013 if (!isFilesystemReady) { 1014 throw new Error('Aborting: At least one directory we need is not available'); 1015 } 1016 1017 if (!versionedPodNames.React) { 1018 throw new Error('Missing name for versioned pod dependency.'); 1019 } 1020 1021 // Create ABIXX_0_0 directory. 1022 console.log( 1023 `Creating new ABI version ${chalk.cyan(versionNumber)} at ${chalk.magenta( 1024 path.relative(EXPO_DIR, newVersionPath) 1025 )}` 1026 ); 1027 await fs.mkdirs(newVersionPath); 1028 1029 // Generate new Podspec from the existing React.podspec 1030 console.log('Generating versioned ReactNative directory...'); 1031 await generateVersionedReactNativeAsync(versionName); 1032 1033 console.log( 1034 `Generating ${chalk.magenta( 1035 path.relative(EXPO_DIR, getVersionedExpoPath(versionName)) 1036 )} directory...` 1037 ); 1038 await generateVersionedExpoAsync(versionName, versionNumber); 1039 1040 // Namespace the new React clone 1041 console.log('Namespacing/transforming files...'); 1042 await transformReactNativeAsync(newVersionPath, versionName, versionedPodNames); 1043 1044 // Generate Ruby scripts with versioned dependencies and postinstall actions that will be evaluated in the Expo client's Podfile. 1045 console.log('Adding dependency to root Podfile...'); 1046 await generatePodfileSubscriptsAsync( 1047 versionNumber, 1048 versionName, 1049 versionedPodNames, 1050 newVersionPath 1051 ); 1052 1053 // Add the new version to the iOS config list of available versions 1054 console.log('Registering new version under sdkVersions config...'); 1055 const addVersionToConfig = (config, versionNumber) => { 1056 config.sdkVersions.push(versionNumber); 1057 return config; 1058 }; 1059 await modifyVersionConfigAsync(path.join(IOS_DIR, 'Exponent', 'Supporting'), (config) => 1060 addVersionToConfig(config, versionNumber) 1061 ); 1062 await modifyVersionConfigAsync( 1063 path.join(EXPO_DIR, 'exponent-view-template', 'ios', 'exponent-view-template', 'Supporting'), 1064 (config) => addVersionToConfig(config, versionNumber) 1065 ); 1066 1067 // Modifying kernel files 1068 console.log(`Modifying ${chalk.bold('kernel files')} to incorporate new SDK version...`); 1069 await modifyKernelFilesAsync(versionName); 1070 1071 console.log('Removing any `filename--` files from the new pod ...'); 1072 1073 try { 1074 const minusMinusFiles = await glob(path.join(newVersionPath, '**', '*--')); 1075 for (const minusMinusFile of minusMinusFiles) { 1076 await fs.remove(minusMinusFile); 1077 } 1078 } catch (error) { 1079 console.warn( 1080 "The script wasn't able to remove any possible `filename--` files created by sed. Please ensure there are no such files manually." 1081 ); 1082 } 1083 1084 console.log('Finished creating new version.'); 1085} 1086 1087async function askToReinstallPodsAsync(): Promise<boolean> { 1088 if (process.env.CI) { 1089 // If we're on the CI, let's regenerate Pods by default. 1090 return true; 1091 } 1092 const { result } = await inquirer.prompt<{ result: boolean }>([ 1093 { 1094 type: 'confirm', 1095 name: 'result', 1096 message: 'Do you want to reinstall pods?', 1097 default: true, 1098 }, 1099 ]); 1100 return result; 1101} 1102 1103export async function reinstallPodsAsync(force?: boolean, preventReinstall?: boolean) { 1104 if ( 1105 preventReinstall !== true && 1106 (force || (force !== false && (await askToReinstallPodsAsync()))) 1107 ) { 1108 await spawnAsync('pod', ['install'], { stdio: 'inherit', cwd: IOS_DIR }); 1109 console.log( 1110 'Regenerated Podfile and installed new pods. You can now try to build the project in Xcode.' 1111 ); 1112 } else { 1113 console.log( 1114 'Skipped pods regeneration. You might want to run `et ios-generate-dynamic-macros`, then `pod install` in `ios` to configure Xcode project.' 1115 ); 1116 } 1117} 1118 1119export async function removeVersionAsync(versionNumber: string) { 1120 let { newVersionPath, versionedPodNames, versionName } = await getConfigsFromArguments( 1121 versionNumber 1122 ); 1123 console.log( 1124 `Removing SDK version ${chalk.cyan(versionNumber)} from ${chalk.magenta( 1125 path.relative(EXPO_DIR, newVersionPath) 1126 )} with Pod name ${chalk.green(versionedPodNames.React)}` 1127 ); 1128 1129 // Validate the directories we need before doing anything 1130 console.log(`Validating root directory ${chalk.magenta(EXPO_DIR)} ...`); 1131 let isFilesystemReady = validateRemoveVersionDirectories(EXPO_DIR, newVersionPath); 1132 if (!isFilesystemReady) { 1133 console.log('Aborting: At least one directory we expect is not available'); 1134 return; 1135 } 1136 1137 // remove directory 1138 console.log( 1139 `Removing versioned files under ${chalk.magenta(path.relative(EXPO_DIR, newVersionPath))}...` 1140 ); 1141 await fs.remove(newVersionPath); 1142 1143 // remove dep from main podfile 1144 console.log(`Removing ${chalk.green(versionedPodNames.React)} dependency from root Podfile...`); 1145 1146 // remove from sdkVersions.json 1147 console.log('Unregistering version from sdkVersions config...'); 1148 const removeVersionFromConfig = (config, versionNumber) => { 1149 let index = config.sdkVersions.indexOf(versionNumber); 1150 if (index > -1) { 1151 // modify in place 1152 config.sdkVersions.splice(index, 1); 1153 } 1154 return config; 1155 }; 1156 await modifyVersionConfigAsync(path.join(IOS_DIR, 'Exponent', 'Supporting'), (config) => 1157 removeVersionFromConfig(config, versionNumber) 1158 ); 1159 await modifyVersionConfigAsync( 1160 path.join(EXPO_DIR, 'exponent-view-template', 'ios', 'exponent-view-template', 'Supporting'), 1161 (config) => removeVersionFromConfig(config, versionNumber) 1162 ); 1163 1164 // modify kernel files 1165 console.log('Rollbacking SDK modifications from kernel files...'); 1166 await modifyKernelFilesAsync(versionName, true); 1167 1168 await reinstallPodsAsync(); 1169} 1170 1171/** 1172 * @return an array of objects representing react native transform rules. 1173 * objects must contain 'pattern' and may optionally contain 'paths' to limit 1174 * the transform to certain file paths. 1175 * 1176 * the rules are applied in order! 1177 */ 1178function _getReactNativeTransformRules(versionPrefix, reactPodName) { 1179 const cppLibraries = getCppLibrariesToVersion().map((lib) => lib.customHeaderDir || lib.libName); 1180 const versionedLibs = [...cppLibraries, 'React', 'FBLazyVector', 'FBReactNativeSpec']; 1181 1182 return [ 1183 { 1184 // Change Obj-C symbols prefix 1185 pattern: `s/RCT/${versionPrefix}RCT/g`, 1186 }, 1187 { 1188 pattern: `s/^EX/${versionPrefix}EX/g`, 1189 // paths: 'EX', 1190 }, 1191 { 1192 pattern: `s/^UM/${versionPrefix}UM/g`, 1193 // paths: 'EX', 1194 }, 1195 { 1196 pattern: `s/\\([^\\<\\/"]\\)YG/\\1${versionPrefix}YG/g`, 1197 }, 1198 { 1199 pattern: `s/\\([\\<,]\\)YG/\\1${versionPrefix}YG/g`, 1200 }, 1201 { 1202 pattern: `s/^YG/${versionPrefix}YG/g`, 1203 }, 1204 { 1205 paths: 'Components', 1206 pattern: `s/\\([^+]\\)AIR/\\1${versionPrefix}AIR/g`, 1207 }, 1208 { 1209 pattern: `s/\\([^A-Za-z0-9_]\\)EX/\\1${versionPrefix}EX/g`, 1210 }, 1211 { 1212 pattern: `s/\\([^A-Za-z0-9_]\\)UM/\\1${versionPrefix}UM/g`, 1213 }, 1214 { 1215 pattern: `s/\\([^A-Za-z0-9_+]\\)ART/\\1${versionPrefix}ART/g`, 1216 }, 1217 { 1218 pattern: `s/ENABLE_PACKAGER_CONNECTION/${versionPrefix}ENABLE_PACKAGER_CONNECTION/g`, 1219 }, 1220 { 1221 paths: 'Components', 1222 pattern: `s/\\([^A-Za-z0-9_+]\\)SM/\\1${versionPrefix}SM/g`, 1223 }, 1224 { 1225 paths: 'Core/Api', 1226 pattern: `s/\\([^A-Za-z0-9_+]\\)RN/\\1${versionPrefix}RN/g`, 1227 }, 1228 { 1229 paths: 'Core/Api', 1230 pattern: `s/^RN/${versionPrefix}RN/g`, 1231 }, 1232 { 1233 paths: 'Core/Api', 1234 pattern: `s/HAVE_GOOGLE_MAPS/${versionPrefix}HAVE_GOOGLE_MAPS/g`, 1235 }, 1236 { 1237 paths: 'Core/Api', 1238 pattern: `s/#import "Branch/#import "${versionPrefix}Branch/g`, 1239 }, 1240 { 1241 paths: 'Core/Api', 1242 pattern: `s/#import "NSObject+RNBranch/#import "${versionPrefix}NSObject+RNBranch/g`, 1243 }, 1244 { 1245 // React will be prefixed in a moment 1246 pattern: `s/#import <${versionPrefix}RCTAnimation/#import <React/g`, 1247 }, 1248 { 1249 paths: 'Core/Api/Reanimated', 1250 pattern: `s/\\([^A-Za-z0-9_+]\\)REA/\\1${versionPrefix}REA/g`, 1251 }, 1252 { 1253 pattern: `s/^REA/${versionPrefix}REA/g`, 1254 paths: 'Core/Api/Reanimated', 1255 }, 1256 { 1257 // Prefixes all direct references to objects under `reanimated` namespace. 1258 // It must be applied before versioning `namespace reanimated` so 1259 // `using namespace reanimated::` don't get versioned twice. 1260 pattern: `s/reanimated::/${versionPrefix}reanimated::/g`, 1261 }, 1262 { 1263 // Prefixes reanimated namespace. 1264 pattern: `s/namespace reanimated/namespace ${versionPrefix}reanimated/g`, 1265 }, 1266 { 1267 // Fix imports in C++ libs in ReactCommon. 1268 // Extended syntax (-E) is required to use (a|b). 1269 flags: '-Ei', 1270 pattern: `s/([<"])(${versionedLibs.join( 1271 '|' 1272 )})\\//\\1${versionPrefix}\\2\\/${versionPrefix}/g`, 1273 }, 1274 { 1275 // Change React -> new pod name 1276 // e.g. threads and queues namespaced to com.facebook.react, 1277 // file paths beginning with the lib name, 1278 // the cpp facebook::react namespace, 1279 // iOS categories ending in +React 1280 flags: '-Ei', 1281 pattern: `s/[Rr]eact/${reactPodName}/g`, 1282 }, 1283 { 1284 // Imports from cxxreact and jsireact got prefixed twice. 1285 flags: '-Ei', 1286 pattern: `s/([<"])(${versionPrefix})(cxx|jsi)${versionPrefix}React/\\1\\2\\3react/g`, 1287 }, 1288 { 1289 // Fix imports from files like `UIView+React.*`. 1290 flags: '-Ei', 1291 pattern: `s/\\+${versionPrefix}React/\\+React/g`, 1292 }, 1293 { 1294 // Prefixes all direct references to objects under `facebook` namespace. 1295 // It must be applied before versioning `namespace facebook` so 1296 // `using namespace facebook::` don't get versioned twice. 1297 pattern: `s/facebook::/${versionPrefix}facebook::/g`, 1298 }, 1299 { 1300 // Prefixes facebook namespace. 1301 pattern: `s/namespace facebook/namespace ${versionPrefix}facebook/g`, 1302 }, 1303 { 1304 // For UMReactNativeAdapter 1305 // Fix names with 'React' substring occurring twice - only first one should be prefixed 1306 flags: '-Ei', 1307 pattern: `s/${versionPrefix}UM([[:alpha:]]*)${reactPodName}/${versionPrefix}UM\\1React/g`, 1308 }, 1309 { 1310 // For EXReactNativeAdapter 1311 pattern: `s/${versionPrefix}EX${reactPodName}/${versionPrefix}EXReact/g`, 1312 }, 1313 { 1314 // For EXConstants and EXNotifications so that when their migrators 1315 // try to access legacy storage for UUID migration, they access the proper value. 1316 pattern: `s/${versionPrefix}EXDeviceInstallUUIDKey/EXDeviceInstallUUIDKey/g`, 1317 paths: 'Expo', 1318 }, 1319 { 1320 // For EXConstants and EXNotifications so that the installation ID 1321 // stays the same between different SDK versions. (https://github.com/expo/expo/issues/11008#issuecomment-726370187) 1322 pattern: `s/${versionPrefix}EXDeviceInstallationUUIDKey/EXDeviceInstallationUUIDKey/g`, 1323 paths: 'Expo', 1324 }, 1325 { 1326 // RCTPlatform exports version of React Native 1327 pattern: `s/${reactPodName}NativeVersion/reactNativeVersion/g`, 1328 }, 1329 { 1330 pattern: `s/@"${versionPrefix}RCT"/@"RCT"/g`, 1331 }, 1332 { 1333 // Unversion EXGL_CPP imports: `<ABI37_0_0EXGL_CPP/` => `<EXGL_CPP/` 1334 pattern: `s/<${versionPrefix}EXGL_CPP\\//<EXGL_CPP\\//g`, 1335 }, 1336 { 1337 // Unprefix everything that got prefixed twice or more times. 1338 flags: '-Ei', 1339 pattern: `s/(${versionPrefix}){2,}/\\1/g`, 1340 }, 1341 ]; 1342} 1343 1344function _getTransformRulesForDirname(transformRules, dirname) { 1345 return transformRules.filter((rule) => { 1346 return ( 1347 // no paths specified, so apply rule to everything 1348 !rule.paths || 1349 // otherwise, limit this rule to paths specified 1350 dirname.indexOf(rule.paths) !== -1 1351 ); 1352 }); 1353} 1354 1355// TODO: use the one in XDL 1356function _isDirectory(dir) { 1357 try { 1358 if (fs.statSync(dir).isDirectory()) { 1359 return true; 1360 } 1361 1362 return false; 1363 } catch (e) { 1364 return false; 1365 } 1366} 1367 1368// TODO: use the one in XDL 1369async function _transformFileContentsAsync( 1370 filename: string, 1371 transform: (fileString: string) => Promise<string> | string | null 1372) { 1373 let fileString = await fs.readFile(filename, 'utf8'); 1374 let newFileString = await transform(fileString); 1375 if (newFileString !== null) { 1376 await fs.writeFile(filename, newFileString); 1377 } 1378} 1379