UNPKG

14.6 kBPlain TextView Raw
1/*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import org.apache.tools.ant.taskdefs.condition.Os
9
10def config = project.hasProperty("react") ? project.react : [:];
11
12def detectEntryFile(config) {
13 if (System.getenv('ENTRY_FILE')) {
14 return System.getenv('ENTRY_FILE')
15 } else if (config.entryFile) {
16 return config.entryFile
17 } else if ((new File("${projectDir}/../../index.android.js")).exists()) {
18 return "index.android.js"
19 }
20
21 return "index.js";
22}
23
24def cliPath = config.cliPath ?: "node_modules/react-native/cli.js"
25def composeSourceMapsPath = config.composeSourceMapsPath ?: "node_modules/react-native/scripts/compose-source-maps.js"
26def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
27def entryFile = detectEntryFile(config)
28def bundleCommand = config.bundleCommand ?: "bundle"
29def reactRoot = file(config.root ?: "../../")
30def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
31def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
32def enableVmCleanup = config.enableVmCleanup == null ? true : config.enableVmCleanup
33def hermesCommand = config.hermesCommand ?: "../../node_modules/hermes-engine/%OS-BIN%/hermes"
34
35def reactNativeDevServerPort() {
36 def value = project.getProperties().get("reactNativeDevServerPort")
37 return value != null ? value : "8081"
38}
39
40def reactNativeInspectorProxyPort() {
41 def value = project.getProperties().get("reactNativeInspectorProxyPort")
42 return value != null ? value : reactNativeDevServerPort()
43}
44
45def getHermesOSBin() {
46 if (Os.isFamily(Os.FAMILY_WINDOWS)) return "win64-bin";
47 if (Os.isFamily(Os.FAMILY_MAC)) return "osx-bin";
48 if (Os.isOs(null, "linux", "amd64", null)) return "linux64-bin";
49 throw new Exception("OS not recognized. Please set project.ext.react.hermesCommand " +
50 "to the path of a working Hermes compiler.");
51}
52
53// Make sure not to inspect the Hermes config unless we need it,
54// to avoid breaking any JSC-only setups.
55def getHermesCommand = {
56 // If the project specifies a Hermes command, don't second guess it.
57 if (!hermesCommand.contains("%OS-BIN%")) {
58 return hermesCommand
59 }
60
61 // Execution on Windows fails with / as separator
62 return hermesCommand
63 .replaceAll("%OS-BIN%", getHermesOSBin())
64 .replace('/' as char, File.separatorChar);
65}
66
67// Set enableHermesForVariant to a function to configure per variant,
68// or set `enableHermes` to True/False to set all of them
69def enableHermesForVariant = config.enableHermesForVariant ?: {
70 def variant -> config.enableHermes ?: false
71}
72
73android {
74 buildTypes.all {
75 resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort()
76 resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort()
77 }
78}
79
80afterEvaluate {
81 def isAndroidLibrary = plugins.hasPlugin("com.android.library")
82 def variants = isAndroidLibrary ? android.libraryVariants : android.applicationVariants
83 variants.all { def variant ->
84 // Create variant and target names
85 def targetName = variant.name.capitalize()
86 def targetPath = variant.dirName
87
88 // React js bundle directories
89 def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
90 def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
91
92 def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
93 def jsSourceMapsDir = file("$buildDir/generated/sourcemaps/react/${targetPath}")
94 def jsIntermediateSourceMapsDir = file("$buildDir/intermediates/sourcemaps/react/${targetPath}")
95 def jsPackagerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.packager.map")
96 def jsCompilerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.compiler.map")
97 def jsOutputSourceMapFile = file("$jsSourceMapsDir/${bundleAssetName}.map")
98
99 // Additional node and packager commandline arguments
100 def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
101 def extraPackagerArgs = config.extraPackagerArgs ?: []
102 def npx = Os.isFamily(Os.FAMILY_WINDOWS) ? "npx.cmd" : "npx"
103
104 def execCommand = []
105
106 if (config.cliPath || config.nodeExecutableAndArgs) {
107 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
108 execCommand.addAll(["cmd", "/c", *nodeExecutableAndArgs, cliPath])
109 } else {
110 execCommand.addAll([*nodeExecutableAndArgs, cliPath])
111 }
112 } else {
113 execCommand.addAll([npx, "react-native"])
114 }
115
116 def enableHermes = enableHermesForVariant(variant)
117
118 def currentBundleTask = tasks.create(
119 name: "bundle${targetName}JsAndAssets",
120 type: Exec) {
121 group = "react"
122 description = "bundle JS and assets for ${targetName}."
123
124 // Create dirs if they are not there (e.g. the "clean" task just ran)
125 doFirst {
126 jsBundleDir.deleteDir()
127 jsBundleDir.mkdirs()
128 resourcesDir.deleteDir()
129 resourcesDir.mkdirs()
130 jsIntermediateSourceMapsDir.deleteDir()
131 jsIntermediateSourceMapsDir.mkdirs()
132 jsSourceMapsDir.deleteDir()
133 jsSourceMapsDir.mkdirs()
134 }
135
136 // Set up inputs and outputs so gradle can cache the result
137 inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
138 outputs.dir(jsBundleDir)
139 outputs.dir(resourcesDir)
140
141 // Set up the call to the react-native cli
142 workingDir(reactRoot)
143
144 // Set up dev mode
145 def devEnabled = !(config."devDisabledIn${targetName}"
146 || targetName.toLowerCase().contains("release"))
147
148 def extraArgs = extraPackagerArgs;
149
150 if (bundleConfig) {
151 extraArgs = extraArgs.clone()
152 extraArgs.add("--config");
153 extraArgs.add(bundleConfig);
154 }
155
156 commandLine(*execCommand, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
157 "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,
158 "--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs)
159
160
161 if (enableHermes) {
162 doLast {
163 def hermesFlags;
164 def hbcTempFile = file("${jsBundleFile}.hbc")
165 exec {
166 if (targetName.toLowerCase().contains("release")) {
167 // Can't use ?: since that will also substitute valid empty lists
168 hermesFlags = config.hermesFlagsRelease
169 if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"]
170 } else {
171 hermesFlags = config.hermesFlagsDebug
172 if (hermesFlags == null) hermesFlags = []
173 }
174
175 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
176 commandLine("cmd", "/c", getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
177 } else {
178 commandLine(getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
179 }
180 }
181 ant.move(
182 file: hbcTempFile,
183 toFile: jsBundleFile
184 );
185 if (hermesFlags.contains("-output-source-map")) {
186 ant.move(
187 // Hermes will generate a source map with this exact name
188 file: "${jsBundleFile}.hbc.map",
189 tofile: jsCompilerSourceMapFile
190 );
191 exec {
192 // TODO: set task dependencies for caching
193
194 // Set up the call to the compose-source-maps script
195 workingDir(reactRoot)
196 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
197 commandLine("cmd", "/c", *nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
198 } else {
199 commandLine(*nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
200 }
201 }
202 }
203 }
204 }
205
206 enabled config."bundleIn${targetName}" != null
207 ? config."bundleIn${targetName}"
208 : config."bundleIn${variant.buildType.name.capitalize()}" != null
209 ? config."bundleIn${variant.buildType.name.capitalize()}"
210 : targetName.toLowerCase().contains("release")
211 }
212
213 // Expose a minimal interface on the application variant and the task itself:
214 variant.ext.bundleJsAndAssets = currentBundleTask
215 currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
216 currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
217
218 // registerGeneratedResFolders for Android plugin 3.x
219 if (variant.respondsTo("registerGeneratedResFolders")) {
220 variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
221 } else {
222 variant.registerResGeneratingTask(currentBundleTask)
223 }
224 variant.mergeResourcesProvider.get().dependsOn(currentBundleTask)
225
226 // packageApplication for Android plugin 3.x
227 def packageTask = variant.hasProperty("packageApplication")
228 ? variant.packageApplicationProvider.get()
229 : tasks.findByName("package${targetName}")
230 if (variant.hasProperty("packageLibrary")) {
231 packageTask = variant.packageLibrary
232 }
233
234 // pre bundle build task for Android plugin 3.2+
235 def buildPreBundleTask = tasks.findByName("build${targetName}PreBundle")
236
237 def resourcesDirConfigValue = config."resourcesDir${targetName}"
238 if (resourcesDirConfigValue) {
239 def currentCopyResTask = tasks.create(
240 name: "copy${targetName}BundledResources",
241 type: Copy) {
242 group = "react"
243 description = "copy bundled resources into custom location for ${targetName}."
244
245 from(resourcesDir)
246 into(file(resourcesDirConfigValue))
247
248 dependsOn(currentBundleTask)
249
250 enabled(currentBundleTask.enabled)
251 }
252
253 packageTask.dependsOn(currentCopyResTask)
254 if (buildPreBundleTask != null) {
255 buildPreBundleTask.dependsOn(currentCopyResTask)
256 }
257 }
258
259 def currentAssetsCopyTask = tasks.create(
260 name: "copy${targetName}BundledJs",
261 type: Copy) {
262 group = "react"
263 description = "copy bundled JS into ${targetName}."
264
265 if (config."jsBundleDir${targetName}") {
266 from(jsBundleDir)
267 into(file(config."jsBundleDir${targetName}"))
268 } else {
269 into ("$buildDir/intermediates")
270 into ("assets/${targetPath}") {
271 from(jsBundleDir)
272 }
273
274 // Workaround for Android Gradle Plugin 3.2+ new asset directory
275 into ("merged_assets/${variant.name}/merge${targetName}Assets/out") {
276 from(jsBundleDir)
277 }
278
279 // Workaround for Android Gradle Plugin 3.4+ new asset directory
280 into ("merged_assets/${variant.name}/out") {
281 from(jsBundleDir)
282 }
283 }
284
285 // mergeAssets must run first, as it clears the intermediates directory
286 dependsOn(variant.mergeAssetsProvider.get())
287
288 enabled(currentBundleTask.enabled)
289 }
290
291 packageTask.dependsOn(currentAssetsCopyTask)
292 if (buildPreBundleTask != null) {
293 buildPreBundleTask.dependsOn(currentAssetsCopyTask)
294 }
295
296 // Delete the VM related libraries that this build doesn't need.
297 // The application can manage this manually by setting 'enableVmCleanup: false'
298 //
299 // This should really be done by packaging all Hermes releated libs into
300 // two separate HermesDebug and HermesRelease AARs, but until then we'll
301 // kludge it by deleting the .so files out of the /transforms/ directory.
302 def isRelease = targetName.toLowerCase().contains("release")
303 def libDir = "$buildDir/intermediates/transforms/"
304 def vmSelectionAction = {
305 fileTree(libDir).matching {
306 if (enableHermes) {
307 // For Hermes, delete all the libjsc* files
308 include "**/libjsc*.so"
309
310 if (isRelease) {
311 // Reduce size by deleting the debugger/inspector
312 include '**/libhermes-inspector.so'
313 include '**/libhermes-executor-debug.so'
314 } else {
315 // Release libs take precedence and must be removed
316 // to allow debugging
317 include '**/libhermes-executor-release.so'
318 }
319 } else {
320 // For JSC, delete all the libhermes* files
321 include "**/libhermes*.so"
322 }
323 }.visit { details ->
324 def targetVariant = ".*/transforms/[^/]*/${targetPath}/.*"
325 def path = details.file.getAbsolutePath().replace(File.separatorChar, '/' as char)
326 if (path.matches(targetVariant) && details.file.isFile()) {
327 details.file.delete()
328 }
329 }
330 }
331
332 if (enableVmCleanup) {
333 def task = tasks.findByName("package${targetName}")
334 task.doFirst(vmSelectionAction)
335 }
336 }
337}