@callstack/react-native-brownfield 3.12.0 → 3.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/gradle-plugin/brownfield/build.gradle.kts +134 -0
  3. package/gradle-plugin/brownfield/config/detekt/detekt.yml +936 -0
  4. package/gradle-plugin/brownfield/gradle/libs.versions.toml +19 -0
  5. package/gradle-plugin/brownfield/gradle/wrapper/gradle-wrapper.jar +0 -0
  6. package/gradle-plugin/brownfield/gradle/wrapper/gradle-wrapper.properties +7 -0
  7. package/gradle-plugin/brownfield/gradle.properties +14 -0
  8. package/gradle-plugin/brownfield/gradlew +249 -0
  9. package/gradle-plugin/brownfield/gradlew.bat +92 -0
  10. package/gradle-plugin/brownfield/settings.gradle +7 -0
  11. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/artifacts/ArtifactsResolver.kt +132 -0
  12. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/exceptions/NameSpaceNotFound.kt +4 -0
  13. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/exceptions/TaskNotFound.kt +4 -0
  14. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/ExpoPublishingHelper.kt +432 -0
  15. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/utils/BrownfieldPrimitives.kt +31 -0
  16. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/utils/ExpoProjectionPrimitives.kt +22 -0
  17. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/utils/LocalMavenUtils.kt +83 -0
  18. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/utils/ReflectionUtils.kt +79 -0
  19. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/expo/utils/VersionMediatingDependencySet.kt +100 -0
  20. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/CustomDependencyResolver.kt +50 -0
  21. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/ProjectConfigurations.kt +107 -0
  22. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RClassTransformer.kt +131 -0
  23. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNBrownfieldPlugin.kt +171 -0
  24. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt +149 -0
  25. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/AssetTaskProcessor.kt +29 -0
  26. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ExplodeTaskProvider.kt +63 -0
  27. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/JNILibsProcessor.kt +165 -0
  28. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ManifestTaskProcessor.kt +74 -0
  29. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/MergeProcessor.kt +55 -0
  30. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ProguardProcessor.kt +61 -0
  31. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/ResourceTaskProcessor.kt +26 -0
  32. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantHelper.kt +61 -0
  33. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantPackagesProperty.kt +21 -0
  34. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantTaskProvider.kt +103 -0
  35. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/BaseProject.kt +17 -0
  36. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/BundleTaskProvider.kt +46 -0
  37. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/Constants.kt +58 -0
  38. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ExplodeAarTask.kt +72 -0
  39. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/GradleILogger.kt +59 -0
  40. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/Logging.kt +24 -0
  41. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/shared/ModuleInfo.kt +22 -0
  42. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/AndroidArchiveLibrary.kt +77 -0
  43. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/DirectoryManager.kt +20 -0
  44. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Extension.kt +44 -0
  45. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/StringExtensions.kt +5 -0
  46. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/StringMatcher.kt +21 -0
  47. package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/utils/Utils.kt +59 -0
  48. package/ios/Expo/ExpoHostRuntime.swift +3 -2
  49. package/ios/ReactNativeBrownfield.swift +1 -1
  50. package/ios/Vanilla/ReactNativeHostRuntime.swift +5 -2
  51. package/ios/swiftpm/Sources/BrownfieldBundleSupport/BrownfieldBundleURLResolver.swift +11 -5
  52. package/ios/swiftpm/Tests/BrownfieldBundleSupportTests/BrownfieldBundleURLResolverTests.swift +17 -5
  53. package/lib/commonjs/expo-config-plugin/android/utils/constants.js +1 -1
  54. package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js +2 -1
  55. package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
  56. package/lib/commonjs/index.js.map +1 -1
  57. package/lib/module/expo-config-plugin/android/utils/constants.js +1 -1
  58. package/lib/module/expo-config-plugin/ios/xcodeHelpers.js +2 -1
  59. package/lib/module/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
  60. package/lib/module/index.js.map +1 -1
  61. package/lib/typescript/commonjs/src/expo-config-plugin/android/utils/constants.d.ts +2 -2
  62. package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/src/index.d.ts +1 -0
  64. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  65. package/lib/typescript/module/src/expo-config-plugin/android/utils/constants.d.ts +2 -2
  66. package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
  67. package/lib/typescript/module/src/index.d.ts +1 -0
  68. package/lib/typescript/module/src/index.d.ts.map +1 -1
  69. package/package.json +6 -4
  70. package/scripts/sync-gradle-plugin-source.js +58 -0
  71. package/src/expo-config-plugin/android/utils/constants.ts +1 -1
  72. package/src/expo-config-plugin/ios/xcodeHelpers.ts +14 -0
  73. package/src/index.ts +2 -0
@@ -0,0 +1,432 @@
1
+ package com.callstack.react.brownfield.expo
2
+
3
+ import com.android.utils.forEach
4
+ import com.callstack.react.brownfield.expo.utils.DependencyInfo
5
+ import com.callstack.react.brownfield.expo.utils.ExpoGradleProjectProjection
6
+ import com.callstack.react.brownfield.expo.utils.LocalMavenUtils
7
+ import com.callstack.react.brownfield.expo.utils.VersionMediatingDependencySet
8
+ import com.callstack.react.brownfield.expo.utils.asExpoGradleProjectProjection
9
+ import com.callstack.react.brownfield.shared.Constants
10
+ import com.callstack.react.brownfield.shared.Logging
11
+ import groovy.json.JsonOutput
12
+ import groovy.json.JsonSlurper
13
+ import groovy.util.NodeList
14
+ import org.gradle.api.Project
15
+ import org.gradle.api.publish.PublishingExtension
16
+ import org.gradle.api.publish.maven.MavenPublication
17
+ import org.gradle.api.publish.tasks.GenerateModuleMetadata
18
+ import org.w3c.dom.Node
19
+ import java.io.File
20
+ import javax.xml.parsers.DocumentBuilderFactory
21
+
22
+ fun Node.getChildNodeByName(nodeName: String): Node? {
23
+ return childNodes
24
+ .let { childNodes ->
25
+ (0 until childNodes.length)
26
+ .map { index -> childNodes.item(index) }
27
+ .firstOrNull { node -> node.nodeName == nodeName }
28
+ }
29
+ }
30
+
31
+ open class ExpoPublishingHelper(val brownfieldAppProject: Project) {
32
+ fun configure(): List<ExpoGradleProjectProjection> {
33
+ val discoverableExpoProjects = getDiscoverableExpoProjects()
34
+
35
+ Logging.log(
36
+ "Discovered ${discoverableExpoProjects.size} discoverable Expo projects: " +
37
+ discoverableExpoProjects.joinToString(
38
+ ", ",
39
+ ) { it.name },
40
+ )
41
+
42
+ val expoTransitiveDependencies =
43
+ discoverAllExpoTransitiveDependencies(
44
+ expoProjects = discoverableExpoProjects,
45
+ )
46
+
47
+ Logging.log(
48
+ "Collected a total of ${expoTransitiveDependencies.size} unique Expo transitive " +
49
+ "dependencies for brownfield app project publishing",
50
+ )
51
+ expoTransitiveDependencies.forEach {
52
+ Logging.log(
53
+ "(*) dependency ${it.groupId}:${it.artifactId}:${it.version} (scope: ${it.scope}, " +
54
+ "${if (it.optional) "optional" else "required"})",
55
+ )
56
+ }
57
+
58
+ reconfigurePOM(expoTransitiveDependencies)
59
+ reconfigureGradleModuleJSON(expoTransitiveDependencies)
60
+
61
+ return discoverableExpoProjects
62
+ }
63
+
64
+ protected fun shouldExcludeDependency(
65
+ groupId: String,
66
+ artifactId: String,
67
+ ): Boolean {
68
+ val isRootProjectArtifact =
69
+ groupId == brownfieldAppProject.rootProject.name
70
+ val isExpoArtifact =
71
+ Constants.BROWNFIELD_EXPO_TRANSITIVE_DEPS_ARTIFACTS_BLACKLIST.any {
72
+ it.matches(
73
+ groupId = groupId,
74
+ artifactId = artifactId,
75
+ )
76
+ }
77
+
78
+ return (isRootProjectArtifact || isExpoArtifact)
79
+ }
80
+
81
+ /**
82
+ * Modifies the generated Gradle Module Metadata file to inject Expo transitive dependencies.
83
+ * @param discoveredExpoTransitiveDependencies Set of DependencyInfo
84
+ * representing Expo transitive dependencies to add.
85
+ */
86
+ @Suppress("LongMethod")
87
+ protected fun reconfigureGradleModuleJSON(discoveredExpoTransitiveDependencies: VersionMediatingDependencySet) {
88
+ val removeDependenciesFromModuleFileTask =
89
+ brownfieldAppProject.tasks.register("removeDependenciesFromModuleFile")
90
+ removeDependenciesFromModuleFileTask.configure { task ->
91
+ task.doLast {
92
+ val moduleBuildDir = brownfieldAppProject.layout.buildDirectory.get()
93
+
94
+ File("$moduleBuildDir/publications/mavenAar/module.json").run {
95
+ val json = inputStream().use { JsonSlurper().parse(it) as Map<*, *> }
96
+
97
+ discoveredExpoTransitiveDependencies.forEach { dependencyToAdd ->
98
+ @Suppress("UNCHECKED_CAST")
99
+ (json["variants"] as? List<MutableMap<String, Any>>)?.forEach { variant ->
100
+ Logging.log(
101
+ "Injecting dependency to Gradle module JSON for variant " +
102
+ "'${variant["name"]}': ${dependencyToAdd.groupId}:" +
103
+ "${dependencyToAdd.artifactId}:${dependencyToAdd.version}",
104
+ )
105
+
106
+ (variant["dependencies"] as? MutableList<MutableMap<String, Any>>)?.add(
107
+ mutableMapOf<String, Any>(
108
+ "group" to dependencyToAdd.groupId,
109
+ "module" to dependencyToAdd.artifactId,
110
+ ).apply {
111
+ dependencyToAdd.version?.let { version ->
112
+ put(
113
+ "version",
114
+ mapOf(
115
+ "requires" to version,
116
+ ),
117
+ )
118
+ }
119
+ },
120
+ )
121
+ }
122
+ }
123
+
124
+ @Suppress("UNCHECKED_CAST")
125
+ (json["variants"] as? List<MutableMap<String, Any>>)?.forEach { variant ->
126
+ (variant["dependencies"] as? MutableList<Map<String, Any>>)?.removeAll {
127
+ val group = it["group"] as String
128
+ val module = it["module"] as String
129
+
130
+ val shouldBeExcluded =
131
+ shouldExcludeDependency(
132
+ groupId = group,
133
+ artifactId = module,
134
+ )
135
+
136
+ if (shouldBeExcluded) {
137
+ Logging.log(
138
+ "Removing excluded dependency from Gradle module JSON: $group:$module",
139
+ )
140
+ }
141
+
142
+ shouldBeExcluded
143
+ }
144
+
145
+ writer().use {
146
+ it.write(
147
+ JsonOutput.prettyPrint(
148
+ JsonOutput.toJson(
149
+ json,
150
+ ),
151
+ ),
152
+ )
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ brownfieldAppProject.tasks.withType(GenerateModuleMetadata::class.java)
160
+ .configureEach {
161
+ it.finalizedBy(removeDependenciesFromModuleFileTask.get())
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Modifies the generated Maven POM file to inject Expo transitive dependencies.
167
+ * @param discoveredExpoTransitiveDependencies Set of DependencyInfo
168
+ * representing Expo transitive dependencies to add.
169
+ */
170
+ @Suppress("LongMethod")
171
+ protected fun reconfigurePOM(discoveredExpoTransitiveDependencies: VersionMediatingDependencySet) {
172
+ brownfieldAppProject.pluginManager.withPlugin("maven-publish") {
173
+ brownfieldAppProject.extensions.configure(PublishingExtension::class.java) { publishing ->
174
+ publishing.publications.withType(MavenPublication::class.java)
175
+ .configureEach { pub ->
176
+ Logging.log(
177
+ "Configuring POM for publication '${pub.name}' to include Expo transitive dependencies",
178
+ )
179
+
180
+ pub.pom.withXml {
181
+ val root = it.asNode()
182
+
183
+ // below: obtains a view of the <dependencies> node(s)
184
+ // inside the POM XML; in practice, there should be only one such node
185
+ val dependenciesNodeList =
186
+ root.get("dependencies") as NodeList
187
+ val dependenciesNode =
188
+ dependenciesNodeList.first() as groovy.util.Node
189
+
190
+ // below: inject the discovered Expo transitive dependencies
191
+ // into the POM's <dependencies> node
192
+ discoveredExpoTransitiveDependencies.forEach { dependencyToAdd ->
193
+ Logging.log(
194
+ "Injecting dependency to POM: ${dependencyToAdd.groupId}:" +
195
+ "${dependencyToAdd.artifactId}:${dependencyToAdd.version}",
196
+ )
197
+
198
+ val childTags =
199
+ mutableMapOf(
200
+ "groupId" to dependencyToAdd.groupId,
201
+ "artifactId" to dependencyToAdd.artifactId,
202
+ "scope" to dependencyToAdd.scope,
203
+ "optional" to dependencyToAdd.optional.toString(),
204
+ )
205
+
206
+ if (dependencyToAdd.version?.isNotBlank() == true) {
207
+ childTags["version"] = dependencyToAdd.version
208
+ }
209
+
210
+ dependenciesNode.appendNode("dependency").let { newDepNode ->
211
+ childTags.forEach { (tagName, tagValue) ->
212
+ newDepNode.appendNode(tagName, tagValue)
213
+ }
214
+ }
215
+ }
216
+
217
+ // below: filter out dependencies that should be excluded
218
+ dependenciesNode.children()
219
+ .filterIsInstance<groovy.util.Node>()
220
+ .filter { dependency ->
221
+ val groupId =
222
+ (dependency["groupId"] as NodeList).text()
223
+ val artifactId =
224
+ (dependency["artifactId"] as NodeList).text()
225
+
226
+ val shouldBeExcluded =
227
+ shouldExcludeDependency(
228
+ groupId = groupId,
229
+ artifactId = artifactId,
230
+ )
231
+
232
+ if (shouldBeExcluded) {
233
+ Logging.log(
234
+ "Removing excluded dependency from POM: $groupId:$artifactId",
235
+ )
236
+ }
237
+
238
+ shouldBeExcluded
239
+ }
240
+ .forEach { dependency ->
241
+ dependenciesNode.remove(dependency)
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ fun discoverAllExpoTransitiveDependencies(expoProjects: Iterable<ExpoGradleProjectProjection>): VersionMediatingDependencySet {
250
+ var discoveredExpoTransitiveDependencies = VersionMediatingDependencySet()
251
+ expoProjects.forEach { expoProj ->
252
+ val maybeTransitiveDepsForProj =
253
+ discoverExpoTransitiveDependenciesForPublication(
254
+ expoGPProjection = expoProj,
255
+ )
256
+
257
+ if (maybeTransitiveDepsForProj != null) {
258
+ discoveredExpoTransitiveDependencies.addAll(
259
+ maybeTransitiveDepsForProj,
260
+ )
261
+ }
262
+ }
263
+
264
+ discoveredExpoTransitiveDependencies =
265
+ discoveredExpoTransitiveDependencies.filter {
266
+ shouldExcludeDependency(
267
+ groupId = it.groupId,
268
+ artifactId = it.artifactId,
269
+ ).not()
270
+ }
271
+
272
+ return discoveredExpoTransitiveDependencies
273
+ }
274
+
275
+ fun discoverExpoTransitiveDependenciesForPublication(expoGPProjection: ExpoGradleProjectProjection): VersionMediatingDependencySet? {
276
+ val publication =
277
+ LocalMavenUtils.getPublishingInfo(expoGPProjection, brownfieldAppProject)
278
+ ?: error(LocalMavenUtils.publishingNotFound(expoGPProjection.name))
279
+
280
+ val pkgProjectDir = File(expoGPProjection.sourceDir)
281
+ val pkgProject =
282
+ brownfieldAppProject.rootProject.allprojects.find {
283
+ it.projectDir.canonicalFile == pkgProjectDir.canonicalFile
284
+ }
285
+ val expoPkgLocalMavenRepo = pkgProjectDir.parentFile.resolve("local-maven-repo")
286
+ val pomFile = LocalMavenUtils.getPomFile(expoPkgLocalMavenRepo, publication)
287
+
288
+ val dependencies = VersionMediatingDependencySet()
289
+ var depsDiscoverySource: String
290
+
291
+ if (pomFile.exists()) {
292
+ // firstly, try reading transitive dependencies from the POM file
293
+ val xml = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pomFile)
294
+
295
+ depsDiscoverySource = "POM file"
296
+
297
+ appendExpoTransitiveDependenciesFromMavenPOM(
298
+ xml.getElementsByTagName("dependencies"),
299
+ dependencies,
300
+ )
301
+ } else if (pkgProject != null) {
302
+ // as fallback, iterate Gradle's resolved dependencies for the Expo project
303
+ depsDiscoverySource = "Gradle resolved dependencies"
304
+
305
+ appendExpoTransitiveDependenciesFromGradle(pkgProject, dependencies)
306
+ } else {
307
+ // if no POM & no Gradle project have been resolved, there is no source for the data
308
+
309
+ Logging.log(
310
+ "WARNING: Could not discover transitive dependencies for Expo project '${expoGPProjection.name}' " +
311
+ "- no POM file found at expected location $pomFile and no Gradle project could be " +
312
+ "resolved for Expo project located at ${expoGPProjection.sourceDir}",
313
+ )
314
+
315
+ return null
316
+ }
317
+
318
+ // below: the plugin already packs Expo packages inside the brownfield AAR, so only transitive
319
+ // deps are needed in the POM; Expo packages themselves should not be declared as dependencies
320
+ dependencies.removeAll { dep ->
321
+ shouldExcludeDependency(
322
+ groupId = dep.groupId,
323
+ artifactId = dep.artifactId,
324
+ )
325
+ }
326
+
327
+ Logging.log(
328
+ "Discovered ${dependencies.size} transitive dependencies for Expo project " +
329
+ "'${expoGPProjection.name}' from $depsDiscoverySource",
330
+ )
331
+
332
+ return dependencies
333
+ }
334
+
335
+ protected fun appendExpoTransitiveDependenciesFromMavenPOM(
336
+ dependenciesNodes: org.w3c.dom.NodeList,
337
+ dependencies: VersionMediatingDependencySet,
338
+ ) {
339
+ dependenciesNodes.forEach { depNodeList ->
340
+ depNodeList.childNodes.forEach { depNode ->
341
+ /**
342
+ * below: some nodes are not dependencies, but pure text, in which case their name is '#text'
343
+ */
344
+ if (depNode.nodeName == "dependency") {
345
+ val groupId = depNode.getChildNodeByName("groupId")!!.textContent
346
+ val maybeArtifactId = depNode.getChildNodeByName("artifactId")
347
+
348
+ val artifactId = maybeArtifactId!!.textContent
349
+ val version = depNode.getChildNodeByName("version")?.textContent
350
+ val optional = depNode.getChildNodeByName("optional")?.textContent
351
+ val scope = depNode.getChildNodeByName("scope")?.textContent
352
+ val dependencyInfo =
353
+ DependencyInfo(
354
+ groupId = groupId,
355
+ artifactId = artifactId,
356
+ version = version,
357
+ scope = scope ?: "compile",
358
+ optional = optional?.toBoolean() ?: false,
359
+ )
360
+
361
+ dependencies.add(dependencyInfo)
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ protected fun appendExpoTransitiveDependenciesFromGradle(
368
+ pkgProject: Project,
369
+ dependencies: VersionMediatingDependencySet,
370
+ ) {
371
+ /**
372
+ * Not accounting for variant specific configurations as Expo packages are not
373
+ * using it. Should we face any issues/needs to account for it, we can do it here.
374
+ */
375
+ listOf("implementation", "api", "runtime").forEach {
376
+ val configuration = pkgProject.configurations.findByName(it)
377
+ configuration?.dependencies?.forEach { dep ->
378
+ if (dep.group != null) {
379
+ dependencies.add(
380
+ DependencyInfo.fromGradleDep(
381
+ groupId = dep.group!!,
382
+ artifactId = dep.name,
383
+ version = dep.version,
384
+ ),
385
+ )
386
+ }
387
+ }
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Discovers Expo projects in the current brownfield app project that are marked for publication.
393
+ * @return List of ExpoGradleProjectProjection representing the discoverable Expo projects.
394
+ */
395
+ fun getDiscoverableExpoProjects(): List<ExpoGradleProjectProjection> {
396
+ val expoExtension =
397
+ brownfieldAppProject.rootProject.gradle.extensions.findByType(Class.forName("expo.modules.plugin.ExpoGradleExtension"))
398
+ ?: error("Expo Gradle extension not found. This should never happen in an Expo project.")
399
+
400
+ // expoExtension.config
401
+ val config =
402
+ expoExtension.javaClass
403
+ .getMethod("getConfig")
404
+ .invoke(expoExtension)
405
+
406
+ // ...config.allProjects - each project is actually a data class expo.modules.plugin.configuration.GradleProject
407
+ val allProjects =
408
+ config.javaClass
409
+ .getMethod("getAllProjects")
410
+ .invoke(config) as? Iterable<*>
411
+
412
+ // ...filter { it.usePublication }
413
+ @Suppress("UNCHECKED_CAST")
414
+ return allProjects!!
415
+ .filterNotNull()
416
+ // expoInternalProject is a data class - expo.modules.plugin.configuration.GradleProject
417
+ // since Expo itself is not provided via Maven but added via local node_modules
418
+ // and this plugin supports RN Vanilla projects, it is not possible to have
419
+ // a dependency on Expo's APIs; therefore, access happens via reflection,
420
+ // which in turn is hidden behind the ReflectionUtils.wrapObjectProxy abstraction
421
+ // here provided by the asExpoGradleProjectProjection() extension fun; effectively,
422
+ // this means access is provided via a proxy exposing conformant partial interfaces,
423
+ // to which the original entities are projected
424
+ .map { expoGradleProject -> expoGradleProject.asExpoGradleProjectProjection() }
425
+ .filter { expoGradleProjectProjection ->
426
+ return@filter expoGradleProjectProjection.usePublication ||
427
+ Constants.BROWNFIELD_EXPO_TRANSITIVE_DEPS_WHITELISTED_MODULES_FOR_DISCOVERY.contains(
428
+ expoGradleProjectProjection.name,
429
+ )
430
+ }
431
+ }
432
+ }
@@ -0,0 +1,31 @@
1
+ package com.callstack.react.brownfield.expo.utils
2
+
3
+ data class BrownfieldPublishingInfo(
4
+ val groupId: String,
5
+ val artifactId: String,
6
+ val version: String,
7
+ )
8
+
9
+ data class DependencyInfo(
10
+ val groupId: String,
11
+ val artifactId: String,
12
+ val version: String?,
13
+ val scope: String,
14
+ val optional: Boolean,
15
+ ) {
16
+ companion object {
17
+ fun fromGradleDep(
18
+ groupId: String,
19
+ artifactId: String,
20
+ version: String?,
21
+ ): DependencyInfo {
22
+ return DependencyInfo(
23
+ groupId = groupId,
24
+ artifactId = artifactId,
25
+ version = version,
26
+ scope = "compile",
27
+ optional = false,
28
+ )
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,22 @@
1
+ package com.callstack.react.brownfield.expo.utils
2
+
3
+ /**
4
+ * Partial projection interface for data class expo.modules.plugin.configuration.GradleProject
5
+ * resolved via reflection.
6
+ */
7
+ interface ExpoGradleProjectProjection {
8
+ val name: String
9
+ val publication: ExpoPublication?
10
+ val usePublication: Boolean
11
+ val sourceDir: String
12
+ }
13
+
14
+ /**
15
+ * Partial projection interface for data class expo.modules.plugin.configuration.Publication
16
+ * resolved via reflection.
17
+ */
18
+ interface ExpoPublication {
19
+ val groupId: String
20
+ val artifactId: String
21
+ val version: String
22
+ }
@@ -0,0 +1,83 @@
1
+ package com.callstack.react.brownfield.expo.utils
2
+
3
+ import com.android.build.gradle.LibraryExtension
4
+ import org.gradle.api.Project
5
+ import java.io.File
6
+
7
+ object LocalMavenUtils {
8
+ fun getAarFile(
9
+ localMavenDir: File,
10
+ publication: BrownfieldPublishingInfo,
11
+ ): File {
12
+ return getFile(localMavenDir, publication, "aar")
13
+ }
14
+
15
+ fun getPomFile(
16
+ localMavenDir: File,
17
+ publication: BrownfieldPublishingInfo,
18
+ ): File {
19
+ return getFile(localMavenDir, publication, "pom")
20
+ }
21
+
22
+ private fun getFile(
23
+ localMavenDir: File,
24
+ publication: BrownfieldPublishingInfo,
25
+ extension: String,
26
+ ): File {
27
+ return localMavenDir
28
+ .resolve(
29
+ "${
30
+ publication.groupId.replace(
31
+ '.',
32
+ '/',
33
+ )
34
+ }/${publication.artifactId}/${publication.version}/" +
35
+ "${publication.artifactId}-${publication.version}.$extension",
36
+ )
37
+ }
38
+
39
+ fun getPublishingInfo(
40
+ expoGPProjection: ExpoGradleProjectProjection,
41
+ brownfieldAppProject: Project,
42
+ ): BrownfieldPublishingInfo? {
43
+ return expoGPProjection.publication?.let {
44
+ BrownfieldPublishingInfo(
45
+ groupId = it.groupId,
46
+ artifactId = it.artifactId,
47
+ version = it.version,
48
+ )
49
+ } ?: run {
50
+ val targetProject =
51
+ brownfieldAppProject.rootProject.allprojects.firstOrNull {
52
+ it.projectDir.absoluteFile.path == expoGPProjection.sourceDir
53
+ }
54
+
55
+ if (targetProject == null) {
56
+ return null
57
+ }
58
+
59
+ val targetProjectAndroidLibExt =
60
+ targetProject.extensions.getByType(LibraryExtension::class.java)
61
+
62
+ val packagePieces = targetProjectAndroidLibExt.namespace!!.split(".")
63
+ val artifactId = packagePieces.last()
64
+ // below: remove the trailing artifactId component -> leaves only the groupId components
65
+ val groupId = packagePieces.dropLast(1).joinToString(".")
66
+
67
+ (
68
+ BrownfieldPublishingInfo(
69
+ groupId = groupId,
70
+ artifactId = artifactId,
71
+ version = (
72
+ targetProjectAndroidLibExt.defaultConfig.versionName
73
+ ?: targetProject.version.toString()
74
+ ),
75
+ )
76
+ )
77
+ }
78
+ }
79
+
80
+ fun publishingNotFound(projectName: String): String {
81
+ return "Cannot configure publishing for Expo project $projectName - could not determine publishing info"
82
+ }
83
+ }
@@ -0,0 +1,79 @@
1
+ package com.callstack.react.brownfield.expo.utils
2
+
3
+ import com.callstack.react.brownfield.shared.Logging
4
+ import com.callstack.react.brownfield.utils.capitalized
5
+ import java.lang.reflect.Method
6
+ import java.lang.reflect.Proxy
7
+
8
+ object ReflectionUtils {
9
+ fun invokeMethod(
10
+ obj: Any?,
11
+ method: Method,
12
+ ): Any? {
13
+ try {
14
+ return obj?.javaClass
15
+ ?.getMethod(method.name)
16
+ ?.invoke(obj)
17
+ } catch (_: NoSuchMethodException) {
18
+ try {
19
+ return obj?.javaClass
20
+ ?.getMethod("get${method.name.capitalized()}")
21
+ ?.invoke(obj)
22
+ } catch (e: NoSuchMethodException) {
23
+ Logging.error(
24
+ "Method ${method.name} nor a getter for this name have not been found on ExpoGradleProjectProjection",
25
+ e,
26
+ )
27
+ }
28
+ }
29
+
30
+ return null
31
+ }
32
+
33
+ fun <T : Any> wrapObjectProxy(
34
+ target: Any,
35
+ projection: Class<T>,
36
+ nested: List<Class<*>> = emptyList(),
37
+ ): T {
38
+ @Suppress("UNCHECKED_CAST")
39
+ return Proxy.newProxyInstance(
40
+ projection.classLoader,
41
+ arrayOf(projection),
42
+ ) { _, method, _ ->
43
+ val value = invokeMethod(target, method)
44
+
45
+ // wrap defined nested objects
46
+ if (value != null) {
47
+ if (nested.contains(method.returnType)) {
48
+ val nestedProjection = method.returnType
49
+
50
+ return@newProxyInstance when {
51
+ value is Iterable<*> -> {
52
+ value.map { wrapObjectProxy(it!!, nestedProjection) }
53
+ }
54
+
55
+ else -> {
56
+ wrapObjectProxy(
57
+ value,
58
+ nestedProjection,
59
+ )
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ value
66
+ } as T
67
+ }
68
+ }
69
+
70
+ fun Any.asExpoGradleProjectProjection(): ExpoGradleProjectProjection {
71
+ return ReflectionUtils.wrapObjectProxy(
72
+ this,
73
+ ExpoGradleProjectProjection::class.java,
74
+ nested =
75
+ listOf(
76
+ ExpoPublication::class.java,
77
+ ),
78
+ )
79
+ }