1import spawnAsync from '@expo/spawn-async';
2import fs from 'fs-extra';
3import path from 'path';
4
5import { transformFileAsync } from '../../Transforms';
6
7export async function updateVersionedReactNativeAsync(
8  reactNativeRoot: string,
9  androidDir: string,
10  versionedReactNativeRoot: string
11): Promise<void> {
12  // Clone whole directories
13  const copyDirs = ['ReactAndroid', 'ReactCommon'];
14  await Promise.all(
15    copyDirs.map((subdir) => fs.remove(path.join(versionedReactNativeRoot, subdir)))
16  );
17  await Promise.all(
18    copyDirs.map((subdir) =>
19      fs.copy(path.join(androidDir, subdir), path.join(versionedReactNativeRoot, subdir))
20    )
21  );
22
23  // Run codegen
24  const codegenOutputRoot = path.join(versionedReactNativeRoot, 'codegen');
25  await fs.remove(codegenOutputRoot);
26  await runReactNativeCodegenAndroidAsync(reactNativeRoot, codegenOutputRoot);
27
28  // Patch ReactAndroid/build.gradle for codegen
29  const buildGradlePath = path.join(versionedReactNativeRoot, 'ReactAndroid', 'build.gradle');
30  await transformFileAsync(buildGradlePath, [
31    // Update codegen folder to our customized folder
32    {
33      find: /"REACT_GENERATED_SRC_DIR=.+?",/,
34      replaceWith: `"REACT_GENERATED_SRC_DIR=${versionedReactNativeRoot}",`,
35    },
36    // Add generated java to sourceSets
37    {
38      find: /(\bsrcDirs = \["src\/main\/java",.+)(])/,
39      replaceWith: `$1, "${codegenOutputRoot}/java"$2`,
40    },
41    // Disable codegen plugin
42    {
43      find: /(\bid\("com\.facebook\.react"\)$)/m,
44      replaceWith: '// $1',
45    },
46    {
47      find: /(^react {[^]+?\n\})/m,
48      replaceWith: '/* $1 */',
49    },
50    {
51      find: /(\bpreBuild\.dependsOn\("generateCodegenArtifactsFromSchema"\))/,
52      replaceWith: '// $1',
53    },
54  ]);
55}
56
57async function runReactNativeCodegenAndroidAsync(
58  reactNativeRoot: string,
59  codegenOutputRoot: string
60) {
61  await fs.ensureDir(codegenOutputRoot);
62
63  // generate schema.json from js & flow types
64  const genSchemaScript = path.join(
65    reactNativeRoot,
66    'packages',
67    'react-native-codegen',
68    'lib',
69    'cli',
70    'combine',
71    'combine-js-to-schema-cli.js'
72  );
73  const schemaOutputPath = path.join(codegenOutputRoot, 'schema.json');
74  const jsSourceRoot = path.join(reactNativeRoot, 'Libraries');
75  await spawnAsync('yarn', ['node', genSchemaScript, schemaOutputPath, jsSourceRoot]);
76
77  // generate code from schema.json
78  const genCodeScript = path.join(reactNativeRoot, 'scripts', 'generate-specs-cli.js');
79  await spawnAsync('yarn', [
80    'node',
81    genCodeScript,
82    '--platform',
83    'android',
84    '--schemaPath',
85    schemaOutputPath,
86    '--outputDir',
87    codegenOutputRoot,
88    '--libraryName',
89    'rncore',
90    '--javaPackageName',
91    'com.facebook.fbreact.specs',
92  ]);
93}
94
95export async function renameHermesEngine(versionedReactAndroidPath: string, version: string) {
96  const abiVersion = version.replace(/\./g, '_');
97  const abiName = `abi${abiVersion}`;
98  const prebuiltHermesMkPath = path.join(
99    versionedReactAndroidPath,
100    'src',
101    'main',
102    'jni',
103    'first-party',
104    'hermes',
105    'Android.mk'
106  );
107  const versionedHermesLibName = `libhermes_${abiName}.so`;
108  await transformFileAsync(prebuiltHermesMkPath, [
109    {
110      find: /^(LOCAL_SRC_FILES\s+:=\s+jni\/\$\(TARGET_ARCH_ABI\))\/libhermes.so$/gm,
111      replaceWith: `$1/${versionedHermesLibName}`,
112    },
113  ]);
114
115  const buildGradlePath = path.join(versionedReactAndroidPath, 'build.gradle');
116  // patch prepareHermes task to rename copied library and update soname
117  // the diff is something like that:
118  //
119  // ```diff
120  // --- android/versioned-react-native/ReactAndroid/build.gradle.orig       2021-08-14 00:40:18.000000000 +0800
121  // +++ android/versioned-react-native/ReactAndroid/build.gradle    2021-08-14 00:40:58.000000000 +0800
122  // @@ -114,7 +114,7 @@
123  //      into("$thirdPartyNdkDir/folly")
124  //  }
125  //
126  // -task prepareHermes(dependsOn: createNativeDepsDirectories, type: Copy) {
127  // +task prepareHermes(dependsOn: createNativeDepsDirectories) {
128  //      def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine")
129  //      if (!hermesPackagePath) {
130  //          throw new GradleScriptException("Could not find the hermes-engine npm package", null)
131  // @@ -126,12 +126,29 @@
132  //      }
133  //
134  //      def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
135  // -
136  // +    copy {
137  // +
138  //      from soFiles
139  //      from "src/main/jni/first-party/hermes/Android.mk"
140  //      into "$thirdPartyNdkDir/hermes"
141  // +
142  // +        rename '(.+).so', '$1_abi43_0_0.so'
143  // +    }
144  // +    exec {
145  // +        commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/arm64-v8a/libhermes_abi43_0_0.so")
146  // +    }
147  // +    exec {
148  // +        commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/armeabi-v7a/libhermes_abi43_0_0.so")
149  // +    }
150  // +    exec {
151  // +        commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/x86/libhermes_abi43_0_0.so")
152  // +    }
153  // +    exec {
154  // +        commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/x86_64/libhermes_abi43_0_0.so")
155  // +    }
156  //  }
157  //
158  // +
159  //  task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) {
160  //      src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
161  //      onlyIfNewer(true)
162  // ```
163  await transformFileAsync(buildGradlePath, [
164    {
165      // reset `prepareHermes` task from Copy type to generic type then we can do both copy and exec.
166      find: /^(task prepareHermes\(dependsOn: .+), type: Copy(\).+$)/m,
167      replaceWith: '$1$2',
168    },
169    {
170      // wrap copy task and append exec tasks
171      find: /(^\s*def soFiles = zipTree\(hermesAAR\).+)\n([\s\S]+?)^\}/gm,
172      replaceWith: `\
173$1
174    copy {
175        $2
176        rename '(.+).so', '$$1_abi${abiVersion}.so'
177    }
178    exec {
179        commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/arm64-v8a/${versionedHermesLibName}")
180    }
181    exec {
182        commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/armeabi-v7a/${versionedHermesLibName}")
183    }
184    exec {
185        commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/x86/${versionedHermesLibName}")
186    }
187    exec {
188        commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/x86_64/${versionedHermesLibName}")
189    }
190}
191`,
192    },
193  ]);
194}
195