11833af43SKudo Chienimport spawnAsync from '@expo/spawn-async'; 21833af43SKudo Chienimport assert from 'assert'; 31833af43SKudo Chienimport chalk from 'chalk'; 41833af43SKudo Chienimport fs from 'fs-extra'; 51833af43SKudo Chienimport glob from 'glob-promise'; 61833af43SKudo Chienimport path from 'path'; 71833af43SKudo Chien 85bd1be93STomasz Sapetaimport { ANDROID_DIR, ANDROID_VENDORED_DIR } from '../../Constants'; 91833af43SKudo Chienimport logger from '../../Logger'; 105ff37293SKudo Chienimport { copyFileWithTransformsAsync, transformFilesAsync } from '../../Transforms'; 111833af43SKudo Chienimport { FileTransforms } from '../../Transforms.types'; 121833af43SKudo Chienimport { searchFilesAsync } from '../../Utils'; 131833af43SKudo Chienimport { 141833af43SKudo Chien exponentPackageTransforms, 151833af43SKudo Chien vendoredModulesTransforms, 161833af43SKudo Chien} from './transforms/vendoredModulesTransforms'; 171833af43SKudo Chien 181833af43SKudo Chien/** 191833af43SKudo Chien * Versions Android vendored modules. 201833af43SKudo Chien */ 211833af43SKudo Chienexport async function versionVendoredModulesAsync( 221833af43SKudo Chien sdkNumber: number, 231833af43SKudo Chien filterModules: string[] | null 241833af43SKudo Chien): Promise<void> { 251833af43SKudo Chien const prefix = `abi${sdkNumber}_0_0`; 261833af43SKudo Chien const config = vendoredModulesTransforms(prefix); 271833af43SKudo Chien const baseTransforms = await baseTransformsFactoryAsync(prefix); 281833af43SKudo Chien const unversionedDir = path.join(ANDROID_VENDORED_DIR, 'unversioned'); 291833af43SKudo Chien const versionedDir = vendoredDirectoryForSDK(sdkNumber); 301833af43SKudo Chien let vendoredModuleNames = await getVendoredModuleNamesAsync(unversionedDir); 311833af43SKudo Chien if (filterModules) { 321833af43SKudo Chien vendoredModuleNames = vendoredModuleNames.filter((name) => filterModules.includes(name)); 331833af43SKudo Chien } 341833af43SKudo Chien 351833af43SKudo Chien for (const name of vendoredModuleNames) { 361833af43SKudo Chien logger.info(' Versioning vendored module %s', chalk.green(name)); 371833af43SKudo Chien 381833af43SKudo Chien const moduleConfig = config[name]; 391833af43SKudo Chien const sourceDirectory = path.join(unversionedDir, name); 401833af43SKudo Chien const targetDirectory = path.join(versionedDir, name); 411833af43SKudo Chien const files = await searchFilesAsync(sourceDirectory, '**'); 421833af43SKudo Chien 431833af43SKudo Chien await fs.remove(targetDirectory); 441833af43SKudo Chien 451833af43SKudo Chien for (const sourceFile of files) { 461833af43SKudo Chien await copyFileWithTransformsAsync({ 471833af43SKudo Chien sourceFile, 481833af43SKudo Chien sourceDirectory, 491833af43SKudo Chien targetDirectory, 501833af43SKudo Chien transforms: { 511833af43SKudo Chien path: [...baseTransforms.path, ...(moduleConfig?.path ?? [])], 521833af43SKudo Chien content: [...baseTransforms.content, ...(moduleConfig?.content ?? [])], 531833af43SKudo Chien }, 541833af43SKudo Chien }); 551833af43SKudo Chien } 561833af43SKudo Chien 571833af43SKudo Chien await maybePrebuildSharedLibsAsync(name, sdkNumber); 581833af43SKudo Chien await transformExponentPackageAsync(name, prefix); 591833af43SKudo Chien } 601833af43SKudo Chien} 611833af43SKudo Chien 621833af43SKudo Chien/** 631833af43SKudo Chien * Prebuild shared libraries to jniLibs and cleanup CMakeLists.txt 641833af43SKudo Chien */ 651833af43SKudo Chienasync function maybePrebuildSharedLibsAsync(module: string, sdkNumber: number) { 665bd1be93STomasz Sapeta const moduleRootDir = path.join(ANDROID_VENDORED_DIR, `sdk${sdkNumber}`, module, 'android'); 671833af43SKudo Chien const cmakeFile = path.join(moduleRootDir, 'CMakeLists.txt'); 681833af43SKudo Chien if (!fs.existsSync(cmakeFile)) { 691833af43SKudo Chien return; 701833af43SKudo Chien } 711833af43SKudo Chien 721833af43SKudo Chien logger.info(' Prebuilding shared libraries for %s', module); 731833af43SKudo Chien const gradleProject = module.replace(/\//g, '_'); 741833af43SKudo Chien await spawnAsync( 751833af43SKudo Chien './gradlew', 76a9d50702SKudo Chien [`:vendored_sdk${sdkNumber}_${gradleProject}:copyReleaseJniLibsProjectAndLocalJars`], 771833af43SKudo Chien { 781833af43SKudo Chien cwd: ANDROID_DIR, 791833af43SKudo Chien // Uncomment the following line for verbose building output 801833af43SKudo Chien // stdio: 'inherit', 811833af43SKudo Chien } 821833af43SKudo Chien ); 831833af43SKudo Chien 841833af43SKudo Chien const jniLibDir = path.join(moduleRootDir, 'src', 'main', 'jniLibs'); 851833af43SKudo Chien const buildLibDir = path.join( 861833af43SKudo Chien moduleRootDir, 871833af43SKudo Chien 'build', 881833af43SKudo Chien 'intermediates', 89a9d50702SKudo Chien 'stripped_native_libs', 901833af43SKudo Chien 'release', 91a9d50702SKudo Chien 'out', 92a9d50702SKudo Chien 'lib' 931833af43SKudo Chien ); 941833af43SKudo Chien const libFiles = await glob('**/*.so', { 951833af43SKudo Chien cwd: buildLibDir, 961833af43SKudo Chien }); 97a9d50702SKudo Chien assert(libFiles.length > 0); 981833af43SKudo Chien await Promise.all( 991833af43SKudo Chien libFiles.map(async (file) => { 1001833af43SKudo Chien const srcPath = path.join(buildLibDir, file); 1011833af43SKudo Chien const archName = path.basename(path.dirname(file)); 1021833af43SKudo Chien const dstPath = path.join(jniLibDir, archName, path.basename(file)); 1031833af43SKudo Chien await fs.ensureDir(path.dirname(dstPath)); 1041833af43SKudo Chien await fs.copy(srcPath, dstPath); 1051833af43SKudo Chien }) 1061833af43SKudo Chien ); 1071833af43SKudo Chien 1081833af43SKudo Chien // Truncate CMakeLists.txt and not to build this cxx module when building versioned Expo Go 1091833af43SKudo Chien await fs.writeFile(cmakeFile, ''); 1101833af43SKudo Chien await fs.remove(path.join(moduleRootDir, 'build')); 1111833af43SKudo Chien} 1121833af43SKudo Chien 1131833af43SKudo Chien/** 1141833af43SKudo Chien * Transform ExponentPackage.kt, e.g. add import abi prefix 1151833af43SKudo Chien */ 1161833af43SKudo Chienasync function transformExponentPackageAsync(name: string, prefix: string) { 1171833af43SKudo Chien const transforms = exponentPackageTransforms(prefix)[name] ?? null; 1185ff37293SKudo Chien const basenames = [ 1195ff37293SKudo Chien 'ExponentPackage', 1205ff37293SKudo Chien 'ExponentAsyncStorageModule', 1215ff37293SKudo Chien 'ExponentUnsignedAsyncStorageModule', 1225ff37293SKudo Chien ]; 1235ff37293SKudo Chien const files = await glob(`**/{${basenames.join(',')}}.kt`, { 1245ff37293SKudo Chien cwd: path.join(ANDROID_DIR, `versioned-abis/expoview-${prefix}`), 1255ff37293SKudo Chien nodir: true, 1265ff37293SKudo Chien absolute: true, 1275ff37293SKudo Chien }); 1285ff37293SKudo Chien await transformFilesAsync(files, transforms); 1291833af43SKudo Chien} 1301833af43SKudo Chien 1311833af43SKudo Chien/** 1321833af43SKudo Chien * Gets the library name of each vendored module in a specific directory. 1331833af43SKudo Chien */ 1341833af43SKudo Chienasync function getVendoredModuleNamesAsync(directory: string): Promise<string[]> { 1351833af43SKudo Chien const vendoredGradlePaths = await glob(`**/build.gradle`, { 1361833af43SKudo Chien cwd: directory, 1371833af43SKudo Chien nodir: true, 1381833af43SKudo Chien realpath: true, 1391833af43SKudo Chien }); 1401833af43SKudo Chien 1411833af43SKudo Chien const gradlePattern = new RegExp(`${directory}/(.*)/android/build.gradle`, 'i'); 1421833af43SKudo Chien 1431833af43SKudo Chien return vendoredGradlePaths.reduce((result, gradlePath) => { 1441833af43SKudo Chien const moduleName = gradlePath.match(gradlePattern); 1451833af43SKudo Chien if (moduleName) { 1461833af43SKudo Chien result.push(moduleName[1]); 1471833af43SKudo Chien } 1481833af43SKudo Chien return result; 1491833af43SKudo Chien }, [] as string[]); 1501833af43SKudo Chien} 1511833af43SKudo Chien 1521833af43SKudo Chien/** 1531833af43SKudo Chien * Removes the directory with vendored modules for given SDK number. 1541833af43SKudo Chien */ 1556239b2d0SKudo Chienexport async function removeVersionedVendoredModulesAsync(version: string): Promise<void> { 1566239b2d0SKudo Chien const sdkNumber = Number(version.split('.')[0]); 1571833af43SKudo Chien const versionedDir = vendoredDirectoryForSDK(sdkNumber); 1581833af43SKudo Chien await fs.remove(versionedDir); 1591833af43SKudo Chien} 1601833af43SKudo Chien 1611833af43SKudo Chien/** 1621833af43SKudo Chien * Generates base transforms to apply for all vendored modules. 1631833af43SKudo Chien */ 1641833af43SKudo Chienasync function baseTransformsFactoryAsync(prefix: string): Promise<Required<FileTransforms>> { 1651833af43SKudo Chien return { 1661833af43SKudo Chien path: [ 1671833af43SKudo Chien { 1681833af43SKudo Chien // For package renaming, src/main/java/* -> src/main/java/abiN/* 16985fe1964SKudo Chien find: /\/(java|kotlin)\//, 17085fe1964SKudo Chien replaceWith: `/$1/${prefix}/`, 1711833af43SKudo Chien }, 1721833af43SKudo Chien ], 1731833af43SKudo Chien content: [ 1741833af43SKudo Chien { 1751833af43SKudo Chien paths: '*.{java,kt}', 1761833af43SKudo Chien find: /(^package\s+)([\w.]+;?)/m, 1771833af43SKudo Chien replaceWith: `$1${prefix}.$2`, 1781833af43SKudo Chien }, 1791833af43SKudo Chien { 1801833af43SKudo Chien paths: '*.{java,kt}', 1816239b2d0SKudo Chien find: new RegExp( 1826239b2d0SKudo Chien `\\b(?<!${prefix}\\.)(com\\.facebook\\.(catalyst|csslayout|fbreact|hermes|perftest|quicklog|react|systrace|yoga|debug)\\b)`, 1836239b2d0SKudo Chien 'g' 1846239b2d0SKudo Chien ), 1851833af43SKudo Chien replaceWith: `${prefix}.$1`, 1861833af43SKudo Chien }, 1871833af43SKudo Chien { 1881833af43SKudo Chien paths: '*.{java,kt}', 189*71ea6032SKudo Chien find: /\bimport (static )?(com\.swmansion\.)/g, 190*71ea6032SKudo Chien replaceWith: `import $1${prefix}.$2`, 1916239b2d0SKudo Chien }, 1926239b2d0SKudo Chien { 1936239b2d0SKudo Chien paths: '*.{java,kt}', 1941833af43SKudo Chien find: /\b((System|SoLoader)\.loadLibrary\("[^"]*)("\);?)/g, 1951833af43SKudo Chien replaceWith: `$1_${prefix}$3`, 1961833af43SKudo Chien }, 1971833af43SKudo Chien { 1981833af43SKudo Chien paths: '*.{h,cpp}', 1996239b2d0SKudo Chien find: /(\bkJavaDescriptor\s*=\s*\n?\s*"L)(com\/)/gm, 2006239b2d0SKudo Chien replaceWith: `$1${prefix}/$2`, 2011833af43SKudo Chien }, 2021833af43SKudo Chien { 2031833af43SKudo Chien paths: 'build.gradle', 2045ff37293SKudo Chien find: /\b(compileOnly|implementation|api)\s+['"]com.facebook.react:react-(native|android):?.*['"]/gm, 2051833af43SKudo Chien replaceWith: 2061833af43SKudo Chien `implementation 'host.exp:reactandroid-${prefix}:1.0.0'` + 2071833af43SKudo Chien '\n' + 2081833af43SKudo Chien // Adding some compile time common dependencies where the versioned react-native AAR doesn't expose 2096239b2d0SKudo Chien ` compileOnly 'com.facebook.fbjni:fbjni:+'\n` + 2106239b2d0SKudo Chien ` compileOnly 'com.facebook.yoga:proguard-annotations:+'\n` + 2116239b2d0SKudo Chien ` compileOnly 'com.facebook.soloader:soloader:+'\n` + 2125ff37293SKudo Chien ` compileOnly 'com.facebook.fresco:fbcore:+'\n` + 2135ff37293SKudo Chien ` compileOnly 'com.facebook.infer.annotation:infer-annotation:+'\n` + 2146239b2d0SKudo Chien ` compileOnly 'androidx.annotation:annotation:+'\n` + 2156239b2d0SKudo Chien ` compileOnly 'com.google.code.findbugs:jsr305:+'\n` + 216*71ea6032SKudo Chien ` compileOnly 'androidx.appcompat:appcompat:+'\n` + 217*71ea6032SKudo Chien ` compileOnly 'androidx.swiperefreshlayout:swiperefreshlayout:+'\n`, 2181833af43SKudo Chien }, 2191833af43SKudo Chien { 2201833af43SKudo Chien paths: ['build.gradle', 'CMakeLists.txt'], 2211833af43SKudo Chien find: /\/react-native\//g, 222*71ea6032SKudo Chien replaceWith: '/versioned-react-native/packages/react-native/', 2231833af43SKudo Chien }, 2241833af43SKudo Chien { 2251833af43SKudo Chien paths: 'CMakeLists.txt', 2261833af43SKudo Chien find: /(^set\s*\(PACKAGE_NAME\s*['"])(\w+)(['"]\))/gm, 2271833af43SKudo Chien replaceWith: `$1$2_${prefix}$3`, 2281833af43SKudo Chien }, 2291833af43SKudo Chien { 2301833af43SKudo Chien paths: 'CMakeLists.txt', 2315ff37293SKudo Chien find: /\b(ReactAndroid::[\w-]+)\b/g, 2325ff37293SKudo Chien replaceWith: `$1_${prefix}`, 2331833af43SKudo Chien }, 2341833af43SKudo Chien { 2351833af43SKudo Chien paths: 'AndroidManifest.xml', 2361833af43SKudo Chien find: /(\bpackage=")([\w.]+)(")/, 2371833af43SKudo Chien replaceWith: `$1${prefix}.$2$3`, 2381833af43SKudo Chien }, 239*71ea6032SKudo Chien { 240*71ea6032SKudo Chien paths: 'build.gradle', 241*71ea6032SKudo Chien find: /(\bnamespace\s*=?\s*['"])([\w.]+)(['"])/, 242*71ea6032SKudo Chien replaceWith: `$1${prefix}.$2$3`, 243*71ea6032SKudo Chien }, 2441833af43SKudo Chien ], 2451833af43SKudo Chien }; 2461833af43SKudo Chien} 2471833af43SKudo Chien 2481833af43SKudo Chien/** 2491833af43SKudo Chien * Returns the vendored directory for given SDK number. 2501833af43SKudo Chien */ 2511833af43SKudo Chienfunction vendoredDirectoryForSDK(sdkNumber: number): string { 2521833af43SKudo Chien return path.join(ANDROID_VENDORED_DIR, `sdk${sdkNumber}`); 2531833af43SKudo Chien} 254