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\.codegen"\)$)/m,
44      replaceWith: '// $1',
45    },
46    {
47      find: /(^react {[^]+?\n\})/m,
48      replaceWith: '/* $1 */',
49    },
50    {
51      find: /(\bdependsOn\("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    'android',
83    schemaOutputPath,
84    codegenOutputRoot,
85    'ReactAndroidSpec',
86    'com.facebook.fbreact.specs',
87  ]);
88}
89
90export async function renameHermesEngine(versionedReactAndroidPath: string, version: string) {
91  const abiVersion = version.replace(/\./g, '_');
92  const abiName = `abi${abiVersion}`;
93  const prebuiltHermesMkPath = path.join(
94    versionedReactAndroidPath,
95    'src',
96    'main',
97    'jni',
98    'first-party',
99    'hermes',
100    'Android.mk'
101  );
102  const versionedHermesLibName = `libhermes_${abiName}.so`;
103  await transformFileAsync(prebuiltHermesMkPath, [
104    {
105      find: /^(LOCAL_SRC_FILES\s+:=\s+jni\/\$\(TARGET_ARCH_ABI\))\/libhermes.so$/gm,
106      replaceWith: `$1/${versionedHermesLibName}`,
107    },
108  ]);
109
110  const buildGradlePath = path.join(versionedReactAndroidPath, 'build.gradle');
111  // patch prepareHermes task to rename copied library and update soname
112  // the diff is something like that:
113  //
114  // ```diff
115  // --- android/versioned-react-native/ReactAndroid/build.gradle.orig       2021-08-14 00:40:18.000000000 +0800
116  // +++ android/versioned-react-native/ReactAndroid/build.gradle    2021-08-14 00:40:58.000000000 +0800
117  // @@ -114,7 +114,7 @@
118  //      into("$thirdPartyNdkDir/folly")
119  //  }
120  //
121  // -task prepareHermes(dependsOn: createNativeDepsDirectories, type: Copy) {
122  // +task prepareHermes(dependsOn: createNativeDepsDirectories) {
123  //      def hermesPackagePath = findNodeModulePath(projectDir, "hermes-engine")
124  //      if (!hermesPackagePath) {
125  //          throw new GradleScriptException("Could not find the hermes-engine npm package", null)
126  // @@ -126,12 +126,29 @@
127  //      }
128  //
129  //      def soFiles = zipTree(hermesAAR).matching({ it.include "**/*.so" })
130  // -
131  // +    copy {
132  // +
133  //      from soFiles
134  //      from "src/main/jni/first-party/hermes/Android.mk"
135  //      into "$thirdPartyNdkDir/hermes"
136  // +
137  // +        rename '(.+).so', '$1_abi43_0_0.so'
138  // +    }
139  // +    exec {
140  // +        commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/arm64-v8a/libhermes_abi43_0_0.so")
141  // +    }
142  // +    exec {
143  // +        commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/armeabi-v7a/libhermes_abi43_0_0.so")
144  // +    }
145  // +    exec {
146  // +        commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/x86/libhermes_abi43_0_0.so")
147  // +    }
148  // +    exec {
149  // +        commandLine("patchelf", "--set-soname", "libhermes_abi43_0_0.so", "$thirdPartyNdkDir/hermes/jni/x86_64/libhermes_abi43_0_0.so")
150  // +    }
151  //  }
152  //
153  // +
154  //  task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) {
155  //      src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz")
156  //      onlyIfNewer(true)
157  // ```
158  await transformFileAsync(buildGradlePath, [
159    {
160      // reset `prepareHermes` task from Copy type to generic type then we can do both copy and exec.
161      find: /^(task prepareHermes\(dependsOn: .+), type: Copy(\).+$)/m,
162      replaceWith: '$1$2',
163    },
164    {
165      // wrap copy task and append exec tasks
166      find: /(^\s*def soFiles = zipTree\(hermesAAR\).+)\n([\s\S]+?)^\}/gm,
167      replaceWith: `\
168$1
169    copy {
170        $2
171        rename '(.+).so', '$$1_abi${abiVersion}.so'
172    }
173    exec {
174        commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/arm64-v8a/${versionedHermesLibName}")
175    }
176    exec {
177        commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/armeabi-v7a/${versionedHermesLibName}")
178    }
179    exec {
180        commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/x86/${versionedHermesLibName}")
181    }
182    exec {
183        commandLine("patchelf", "--set-soname", "${versionedHermesLibName}", "$thirdPartyNdkDir/hermes/jni/x86_64/${versionedHermesLibName}")
184    }
185}
186`,
187    },
188  ]);
189}
190