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