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