react.gradle 16 KB

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