1import chalk from 'chalk'; 2import fs from 'fs-extra'; 3import glob from 'glob-promise'; 4import path from 'path'; 5 6import { IOS_DIR } from '../../Constants'; 7import logger from '../../Logger'; 8import { copyFileWithTransformsAsync } from '../../Transforms'; 9import { FileTransforms } from '../../Transforms.types'; 10import { searchFilesAsync } from '../../Utils'; 11import vendoredModulesTransforms from './transforms/vendoredModulesTransforms'; 12 13const IOS_VENDORED_DIR = path.join(IOS_DIR, 'vendored'); 14 15/** 16 * Versions iOS vendored modules. 17 */ 18export async function versionVendoredModulesAsync( 19 sdkNumber: number, 20 filterModules: string[] | null 21): Promise<void> { 22 const prefix = `ABI${sdkNumber}_0_0`; 23 const config = vendoredModulesTransforms(prefix); 24 const baseTransforms = baseTransformsFactory(prefix); 25 const unversionedDir = path.join(IOS_VENDORED_DIR, 'unversioned'); 26 const versionedDir = vendoredDirectoryForSDK(sdkNumber); 27 let vendoredModuleNames = await getVendoredModuleNamesAsync(unversionedDir); 28 if (filterModules) { 29 vendoredModuleNames = vendoredModuleNames.filter((name) => filterModules.includes(name)); 30 } 31 32 for (const name of vendoredModuleNames) { 33 logger.info(' Versioning vendored module %s', chalk.green(name)); 34 35 const moduleConfig = config[name]; 36 const sourceDirectory = path.join(unversionedDir, name); 37 const targetDirectory = path.join(versionedDir, name); 38 const files = await searchFilesAsync(sourceDirectory, '**'); 39 40 await fs.remove(targetDirectory); 41 42 for (const sourceFile of files) { 43 await copyFileWithTransformsAsync({ 44 sourceFile, 45 sourceDirectory, 46 targetDirectory, 47 transforms: { 48 path: [...baseTransforms.path, ...(moduleConfig?.path ?? [])], 49 content: [...baseTransforms.content, ...(moduleConfig?.content ?? [])], 50 }, 51 }); 52 } 53 } 54} 55 56/** 57 * Gets the library name of each vendored module in a specific directory. 58 */ 59async function getVendoredModuleNamesAsync(directory: string): Promise<string[]> { 60 const vendoredPodspecPaths = await glob(`**/*.podspec.json`, { 61 cwd: directory, 62 nodir: true, 63 realpath: true, 64 }); 65 66 const podspecPattern = new RegExp(`${directory}/(.*)/.*podspec.json`, 'i'); 67 68 return vendoredPodspecPaths.reduce((result, podspecPath) => { 69 const moduleName = podspecPath.match(podspecPattern); 70 if (moduleName) { 71 result.push(moduleName[1]); 72 } 73 return result; 74 }, [] as string[]); 75} 76 77/** 78 * Removes the directory with vendored modules for given SDK number. 79 */ 80export async function removeVersionedVendoredModulesAsync(sdkNumber: number): Promise<void> { 81 const versionedDir = vendoredDirectoryForSDK(sdkNumber); 82 await fs.remove(versionedDir); 83} 84 85/** 86 * Generates base transforms to apply for all vendored modules. 87 */ 88function baseTransformsFactory(prefix: string): Required<FileTransforms> { 89 return { 90 path: [ 91 { 92 find: /([^/]+\.podspec\.json)$\b/, 93 replaceWith: `${prefix}$1`, 94 }, 95 { 96 find: /\b(RCT|RNC|RNG|RNR|REA|RNS)([^/]*\.)(h|m|mm)/, 97 replaceWith: `${prefix}$1$2$3`, 98 }, 99 ], 100 content: [ 101 { 102 paths: '*.podspec.json', 103 find: /"name": "([\w-]+)"/, 104 replaceWith: `"name": "${prefix}$1"`, 105 }, 106 { 107 // Prefixes `React` with word-boundary and also in `insertReactSubview`, `removeReactSubview`. 108 find: /(\b|insert|remove)(React)/g, 109 replaceWith: `$1${prefix}$2`, 110 }, 111 { 112 find: /\b(RCT|RNC|RNG|RNR|REA|RNS)(\w+)\b/g, 113 replaceWith: `${prefix}$1$2`, 114 }, 115 { 116 find: /facebook::/g, 117 replaceWith: `${prefix}facebook::`, 118 }, 119 { 120 find: /react::/g, 121 replaceWith: `${prefix}React::`, 122 }, 123 { 124 find: /using namespace (facebook|react)/g, 125 replaceWith: (_, p1) => { 126 return `using namespace ${prefix}${p1 === 'react' ? 'React' : p1}`; 127 }, 128 }, 129 { 130 // Objective-C only, see the comment in the rule below. 131 paths: '*.{h,m,mm}', 132 find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi, 133 replaceWith: `${prefix}R$1`, 134 }, 135 { 136 // Swift translates uppercased letters at the beginning of the method name to lowercased letters, we must comply with it. 137 paths: '*.swift', 138 find: /r(eactTag|eactSubviews|eactSuperview|eactViewController|eactSetFrame|eactAddControllerToClosestParent|eactZIndex)/gi, 139 replaceWith: (_, p1) => `${prefix.toLowerCase()}R${p1}`, 140 }, 141 { 142 find: /<jsi\/(.*)\.h>/, 143 replaceWith: `<${prefix}jsi/${prefix}$1.h>`, 144 }, 145 { 146 find: /(JSCExecutorFactory|HermesExecutorFactory)\.h/g, 147 replaceWith: `${prefix}$1.h`, 148 }, 149 { 150 find: /viewForReactTag/g, 151 replaceWith: `viewFor${prefix}ReactTag`, 152 }, 153 { 154 find: /isReactRootView/g, 155 replaceWith: `is${prefix}ReactRootView`, 156 }, 157 { 158 // Prefix only unindented `@objc` (notice `^` and `m` flag in the pattern). Method names shouldn't get prefixed. 159 paths: '*.swift', 160 find: /^@objc\(([^)]+)\)/gm, 161 replaceWith: `@objc(${prefix}$1)`, 162 }, 163 { 164 paths: '*.podspec.json', 165 find: new RegExp(`${prefix}React-${prefix}RCT`, 'g'), 166 replaceWith: `${prefix}React-RCT`, 167 }, 168 { 169 paths: '*.podspec.json', 170 find: new RegExp(`${prefix}React-Core\\/${prefix}RCT`, 'g'), 171 replaceWith: `${prefix}React-Core/RCT`, 172 }, 173 ], 174 }; 175} 176 177/** 178 * Returns the vendored directory for given SDK number. 179 */ 180function vendoredDirectoryForSDK(sdkNumber: number): string { 181 return path.join(IOS_VENDORED_DIR, `sdk${sdkNumber}`); 182} 183