1import spawnAsync from '@expo/spawn-async';
2import fs from 'fs-extra';
3import path from 'path';
4
5import { runReactNativeCodegenAsync } from '../../Codegen';
6import { REACT_NATIVE_SUBMODULE_DIR } from '../../Constants';
7import { copyFileWithTransformsAsync, transformFileAsync } from '../../Transforms';
8import { searchFilesAsync } from '../../Utils';
9import {
10  codegenTransforms,
11  hermesTransforms,
12  reactNativeTransforms,
13} from './reactNativeTransforms';
14
15export async function updateVersionedReactNativeAsync(
16  androidDir: string,
17  sdkVersion: string
18): Promise<void> {
19  const abiVersion = `abi${sdkVersion.replace(/\./g, '_')}`;
20  const versionedReactNativeDir = path.join(androidDir, 'versioned-react-native');
21  await Promise.all([
22    fs.remove(path.join(versionedReactNativeDir, 'ReactAndroid')),
23    fs.remove(path.join(versionedReactNativeDir, 'ReactCommon')),
24    fs.remove(path.join(versionedReactNativeDir, 'codegen')),
25    fs.remove(path.join(versionedReactNativeDir, 'sdks')),
26  ]);
27
28  await fs.mkdirp(path.join(versionedReactNativeDir, 'sdks'));
29  await fs.copy(
30    path.join(REACT_NATIVE_SUBMODULE_DIR, 'sdks/.hermesversion'),
31    path.join(versionedReactNativeDir, 'sdks/.hermesversion')
32  );
33
34  // Run and version codegen
35  const codegenOutputRoot = path.join(versionedReactNativeDir, 'codegen');
36  const tmpCodegenOutputRoot = path.join(versionedReactNativeDir, 'codegen-tmp');
37  try {
38    await runReactNativeCodegenAsync({
39      reactNativeRoot: REACT_NATIVE_SUBMODULE_DIR,
40      codegenPkgRoot: path.join(REACT_NATIVE_SUBMODULE_DIR, 'packages', 'react-native-codegen'),
41      outputDir: tmpCodegenOutputRoot,
42      name: 'rncore',
43      platform: 'android',
44      type: 'all',
45      jsSrcsDir: path.join(REACT_NATIVE_SUBMODULE_DIR, 'Libraries'),
46      javaPackageName: 'com.facebook.fbreact.specs',
47    });
48    await versionCodegenDirectoryAsync(tmpCodegenOutputRoot, codegenOutputRoot, abiVersion);
49  } finally {
50    await fs.remove(tmpCodegenOutputRoot);
51  }
52
53  // Copy and version ReactAndroid and ReactCommon
54  await versionReactNativeAsync(versionedReactNativeDir, abiVersion);
55
56  await versionHermesAsync(versionedReactNativeDir, abiVersion);
57}
58
59async function versionHermesAsync(versionedReactNativeDir: string, abiVersion: string) {
60  await spawnAsync('./gradlew', [':ReactAndroid:hermes-engine:unzipHermes'], {
61    shell: true,
62    cwd: versionedReactNativeDir,
63    stdio: 'inherit',
64  });
65  await transformFileAsync(
66    path.join(versionedReactNativeDir, 'sdks/hermes/API/hermes/CMakeLists.txt'),
67    hermesTransforms(abiVersion)
68  );
69}
70
71async function versionReactNativeAsync(versionedReactNativeDir: string, abiVersion: string) {
72  const files = await searchFilesAsync(REACT_NATIVE_SUBMODULE_DIR, [
73    './ReactAndroid/**',
74    './ReactCommon/**',
75  ]);
76  for (const file of files) {
77    if ((file.match(/\/build\//) && !file.match(/src.*\/build\//)) || file.match(/\/\.cxx\//)) {
78      files.delete(file);
79    }
80  }
81
82  const transforms = reactNativeTransforms(versionedReactNativeDir, abiVersion);
83  for (const sourceFile of files) {
84    await copyFileWithTransformsAsync({
85      sourceFile,
86      targetDirectory: versionedReactNativeDir,
87      sourceDirectory: REACT_NATIVE_SUBMODULE_DIR,
88      transforms,
89    });
90  }
91}
92
93async function versionCodegenDirectoryAsync(
94  tmpCodegenDir: string,
95  codegenDir: string,
96  abiVersion: string
97) {
98  const files = await searchFilesAsync(tmpCodegenDir, ['**']);
99  const transforms = codegenTransforms(abiVersion);
100  for (const sourceFile of files) {
101    await copyFileWithTransformsAsync({
102      sourceFile,
103      targetDirectory: codegenDir,
104      sourceDirectory: tmpCodegenDir,
105      transforms,
106    });
107  }
108}
109