1 | import groovy.json.JsonSlurper
|
2 | import org.gradle.initialization.DefaultSettings
|
3 | import org.apache.tools.ant.taskdefs.condition.Os
|
4 |
|
5 | def generatedFileName = "PackageList.java"
|
6 | def generatedFilePackage = "com.facebook.react"
|
7 | def generatedFileContentsTemplate = """
|
8 | package $generatedFilePackage;
|
9 |
|
10 | import android.app.Application;
|
11 | import android.content.Context;
|
12 | import android.content.res.Resources;
|
13 |
|
14 | import com.facebook.react.ReactPackage;
|
15 | import com.facebook.react.shell.MainPackageConfig;
|
16 | import com.facebook.react.shell.MainReactPackage;
|
17 | import java.util.Arrays;
|
18 | import java.util.ArrayList;
|
19 |
|
20 | {{ packageImports }}
|
21 |
|
22 | public class PackageList {
|
23 | private Application application;
|
24 | private ReactNativeHost reactNativeHost;
|
25 | private MainPackageConfig mConfig;
|
26 |
|
27 | public PackageList(ReactNativeHost reactNativeHost) {
|
28 | this(reactNativeHost, null);
|
29 | }
|
30 |
|
31 | public PackageList(Application application) {
|
32 | this(application, null);
|
33 | }
|
34 |
|
35 | public PackageList(ReactNativeHost reactNativeHost, MainPackageConfig config) {
|
36 | this.reactNativeHost = reactNativeHost;
|
37 | mConfig = config;
|
38 | }
|
39 |
|
40 | public PackageList(Application application, MainPackageConfig config) {
|
41 | this.reactNativeHost = null;
|
42 | this.application = application;
|
43 | mConfig = config;
|
44 | }
|
45 |
|
46 | private ReactNativeHost getReactNativeHost() {
|
47 | return this.reactNativeHost;
|
48 | }
|
49 |
|
50 | private Resources getResources() {
|
51 | return this.getApplication().getResources();
|
52 | }
|
53 |
|
54 | private Application getApplication() {
|
55 | if (this.reactNativeHost == null) return this.application;
|
56 | return this.reactNativeHost.getApplication();
|
57 | }
|
58 |
|
59 | private Context getApplicationContext() {
|
60 | return this.getApplication().getApplicationContext();
|
61 | }
|
62 |
|
63 | public ArrayList<ReactPackage> getPackages() {
|
64 | return new ArrayList<>(Arrays.<ReactPackage>asList(
|
65 | new MainReactPackage(mConfig){{ packageClassInstances }}
|
66 | ));
|
67 | }
|
68 | }
|
69 | """
|
70 |
|
71 | class ReactNativeModules {
|
72 | private Logger logger
|
73 | private String packageName
|
74 | private File root
|
75 | private ArrayList<HashMap<String, String>> reactNativeModules
|
76 | private HashMap<String, ArrayList> reactNativeModulesBuildVariants
|
77 |
|
78 | private static String LOG_PREFIX = ":ReactNative:"
|
79 |
|
80 | ReactNativeModules(Logger logger, File root) {
|
81 | this.logger = logger
|
82 | this.root = root
|
83 |
|
84 | def (nativeModules, reactNativeModulesBuildVariants, packageName) = this.getReactNativeConfig()
|
85 | this.reactNativeModules = nativeModules
|
86 | this.reactNativeModulesBuildVariants = reactNativeModulesBuildVariants
|
87 | this.packageName = packageName
|
88 | }
|
89 |
|
90 | |
91 |
|
92 |
|
93 | void addReactNativeModuleProjects(DefaultSettings defaultSettings) {
|
94 | reactNativeModules.forEach { reactNativeModule ->
|
95 | String nameCleansed = reactNativeModule["nameCleansed"]
|
96 | String androidSourceDir = reactNativeModule["androidSourceDir"]
|
97 | defaultSettings.include(":${nameCleansed}")
|
98 | defaultSettings.project(":${nameCleansed}").projectDir = new File("${androidSourceDir}")
|
99 | }
|
100 | }
|
101 |
|
102 | |
103 |
|
104 |
|
105 | void addReactNativeModuleDependencies(Project appProject) {
|
106 | reactNativeModules.forEach { reactNativeModule ->
|
107 | def nameCleansed = reactNativeModule["nameCleansed"]
|
108 | def dependencyConfiguration = reactNativeModule["dependencyConfiguration"]
|
109 | appProject.dependencies {
|
110 | if (reactNativeModulesBuildVariants.containsKey(nameCleansed)) {
|
111 | reactNativeModulesBuildVariants
|
112 | .get(nameCleansed)
|
113 | .forEach { buildVariant ->
|
114 | if(dependencyConfiguration != null) {
|
115 | "${buildVariant}${dependencyConfiguration}"
|
116 | } else {
|
117 | "${buildVariant}Implementation" project(path: ":${nameCleansed}")
|
118 | }
|
119 | }
|
120 | } else {
|
121 | if(dependencyConfiguration != null) {
|
122 | "${dependencyConfiguration}"
|
123 | } else {
|
124 | implementation project(path: ":${nameCleansed}")
|
125 | }
|
126 | }
|
127 | }
|
128 | }
|
129 | }
|
130 |
|
131 | |
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | void generatePackagesFile(File outputDir, String generatedFileName, String generatedFileContentsTemplate) {
|
139 | ArrayList<HashMap<String, String>> packages = this.reactNativeModules
|
140 | String packageName = this.packageName
|
141 |
|
142 | String packageImports = ""
|
143 | String packageClassInstances = ""
|
144 |
|
145 | if (packages.size() > 0) {
|
146 | def interpolateDynamicValues = {
|
147 | it
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 | .replaceAll(~/([^.\w])(BuildConfig|R)([^\w])/, {
|
167 | wholeString, prefix, className, suffix ->
|
168 | "${prefix}${packageName}.${className}${suffix}"
|
169 | })
|
170 | }
|
171 | packageImports = packages.collect {
|
172 | "// ${it.name}\n${interpolateDynamicValues(it.packageImportPath)}"
|
173 | }.join('\n')
|
174 | packageClassInstances = ",\n " + packages.collect {
|
175 | interpolateDynamicValues(it.packageInstance)
|
176 | }.join(",\n ")
|
177 | }
|
178 |
|
179 | String generatedFileContents = generatedFileContentsTemplate
|
180 | .replace("{{ packageImports }}", packageImports)
|
181 | .replace("{{ packageClassInstances }}", packageClassInstances)
|
182 |
|
183 | outputDir.mkdirs()
|
184 | final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir)
|
185 | treeBuilder.file(generatedFileName).newWriter().withWriter { w ->
|
186 | w << generatedFileContents
|
187 | }
|
188 | }
|
189 |
|
190 | |
191 |
|
192 |
|
193 |
|
194 | String getCommandOutput(String[] command, File directory) {
|
195 | try {
|
196 | def output = ""
|
197 | def cmdProcess = Runtime.getRuntime().exec(command, null, directory)
|
198 | def bufferedReader = new BufferedReader(new InputStreamReader(cmdProcess.getInputStream()))
|
199 | def buff = ""
|
200 | def readBuffer = new StringBuffer()
|
201 | while ((buff = bufferedReader.readLine()) != null) {
|
202 | readBuffer.append(buff)
|
203 | }
|
204 | output = readBuffer.toString()
|
205 | if (!output) {
|
206 | this.logger.error("${LOG_PREFIX}Unexpected empty result of running '${command}' command.")
|
207 | def bufferedErrorReader = new BufferedReader(new InputStreamReader(cmdProcess.getErrorStream()))
|
208 | def errBuff = ""
|
209 | def readErrorBuffer = new StringBuffer()
|
210 | while ((errBuff = bufferedErrorReader.readLine()) != null) {
|
211 | readErrorBuffer.append(errBuff)
|
212 | }
|
213 | throw new Exception(readErrorBuffer.toString())
|
214 | }
|
215 | return output
|
216 | } catch (Exception exception) {
|
217 | this.logger.error("${LOG_PREFIX}Running '${command}' command failed.")
|
218 | throw exception
|
219 | }
|
220 | }
|
221 |
|
222 | |
223 |
|
224 |
|
225 | ArrayList<HashMap<String, String>> getReactNativeConfig() {
|
226 | if (this.reactNativeModules != null) return this.reactNativeModules
|
227 |
|
228 | ArrayList<HashMap<String, String>> reactNativeModules = new ArrayList<HashMap<String, String>>()
|
229 | HashMap<String, ArrayList> reactNativeModulesBuildVariants = new HashMap<String, ArrayList>()
|
230 |
|
231 | |
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 | def cliResolveScript = "try {console.log(require('@react-native-community/cli').bin);} catch (e) {console.log(require('react-native/cli').bin);}"
|
240 | String[] nodeCommand = ["node", "-e", cliResolveScript]
|
241 | def cliPath = this.getCommandOutput(nodeCommand, this.root)
|
242 |
|
243 | String[] reactNativeConfigCommand = ["node", cliPath, "config"]
|
244 | def reactNativeConfigOutput = this.getCommandOutput(reactNativeConfigCommand, this.root)
|
245 |
|
246 | def json
|
247 | try {
|
248 | json = new JsonSlurper().parseText(reactNativeConfigOutput)
|
249 | } catch (Exception exception) {
|
250 | throw new Exception("Calling `${reactNativeConfigCommand}` finished with an exception. Error message: ${exception.toString()}. Output: ${reactNativeConfigOutput}");
|
251 | }
|
252 | def dependencies = json["dependencies"]
|
253 | def project = json["project"]["android"]
|
254 |
|
255 | if (project == null) {
|
256 | throw new Exception("React Native CLI failed to determine Android project configuration. This is likely due to misconfiguration. Config output:\n${json.toMapString()}")
|
257 | }
|
258 |
|
259 | def engine = new groovy.text.SimpleTemplateEngine()
|
260 |
|
261 | dependencies.each { name, value ->
|
262 | def platformsConfig = value["platforms"];
|
263 | def androidConfig = platformsConfig["android"]
|
264 |
|
265 | if (androidConfig != null && androidConfig["sourceDir"] != null) {
|
266 | this.logger.info("${LOG_PREFIX}Automatically adding native module '${name}'")
|
267 |
|
268 | HashMap reactNativeModuleConfig = new HashMap<String, String>()
|
269 | def nameCleansed = name.replaceAll('[~*!\'()]+', '_').replaceAll('^@([\\w-.]+)/', '$1_')
|
270 | reactNativeModuleConfig.put("name", name)
|
271 | reactNativeModuleConfig.put("nameCleansed", nameCleansed)
|
272 | reactNativeModuleConfig.put("androidSourceDir", androidConfig["sourceDir"])
|
273 | reactNativeModuleConfig.put("packageInstance", androidConfig["packageInstance"])
|
274 | reactNativeModuleConfig.put("packageImportPath", androidConfig["packageImportPath"])
|
275 | if (androidConfig["buildTypes"] && !androidConfig["buildTypes"].isEmpty()) {
|
276 | reactNativeModulesBuildVariants.put(nameCleansed, androidConfig["buildTypes"])
|
277 | }
|
278 | if(androidConfig.containsKey("dependencyConfiguration")) {
|
279 | reactNativeModuleConfig.put("dependencyConfiguration", androidConfig["dependencyConfiguration"])
|
280 | } else if (project.containsKey("dependencyConfiguration")) {
|
281 | def bindings = ["dependencyName": nameCleansed]
|
282 | def template = engine.createTemplate(project["dependencyConfiguration"]).make(bindings)
|
283 |
|
284 | reactNativeModuleConfig.put("dependencyConfiguration", template.toString())
|
285 | }
|
286 |
|
287 | this.logger.trace("${LOG_PREFIX}'${name}': ${reactNativeModuleConfig.toMapString()}")
|
288 |
|
289 | reactNativeModules.add(reactNativeModuleConfig)
|
290 | } else {
|
291 | this.logger.info("${LOG_PREFIX}Skipping native module '${name}'")
|
292 | }
|
293 | }
|
294 |
|
295 | return [reactNativeModules, reactNativeModulesBuildVariants, json["project"]["android"]["packageName"]];
|
296 | }
|
297 | }
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 | def projectRoot = rootProject.projectDir
|
305 |
|
306 | def autoModules = new ReactNativeModules(logger, projectRoot)
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 | ext.applyNativeModulesSettingsGradle = { DefaultSettings defaultSettings, String root = null ->
|
313 | if (root != null) {
|
314 | logger.warn("${ReactNativeModules.LOG_PREFIX}Passing custom root is deprecated. CLI detects root automatically now.");
|
315 | logger.warn("${ReactNativeModules.LOG_PREFIX}Please remove second argument to `applyNativeModulesSettingsGradle`.");
|
316 | }
|
317 | autoModules.addReactNativeModuleProjects(defaultSettings)
|
318 | }
|
319 |
|
320 | ext.applyNativeModulesAppBuildGradle = { Project project, String root = null ->
|
321 | if (root != null) {
|
322 | logger.warn("${ReactNativeModules.LOG_PREFIX}Passing custom root is deprecated. CLI detects root automatically now");
|
323 | logger.warn("${ReactNativeModules.LOG_PREFIX}Please remove second argument to `applyNativeModulesAppBuildGradle`.");
|
324 | }
|
325 | autoModules.addReactNativeModuleDependencies(project)
|
326 |
|
327 | def generatedSrcDir = new File(buildDir, "generated/rncli/src/main/java")
|
328 | def generatedCodeDir = new File(generatedSrcDir, generatedFilePackage.replace('.', '/'))
|
329 |
|
330 | task generatePackageList {
|
331 | doLast {
|
332 | autoModules.generatePackagesFile(generatedCodeDir, generatedFileName, generatedFileContentsTemplate)
|
333 | }
|
334 | }
|
335 |
|
336 | preBuild.dependsOn generatePackageList
|
337 |
|
338 | android {
|
339 | sourceSets {
|
340 | main {
|
341 | java {
|
342 | srcDirs += generatedSrcDir
|
343 | }
|
344 | }
|
345 | }
|
346 | }
|
347 | }
|