@granite-js/screen 1.0.0

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 (99) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/GraniteScreen.podspec +25 -0
  3. package/LICENSE +202 -0
  4. package/android/CMakeLists.txt +62 -0
  5. package/android/build.gradle +63 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/BundleEvaluator.cpp +27 -0
  8. package/android/src/main/cpp/BundleEvaluator.h +17 -0
  9. package/android/src/main/cpp/onLoad.cpp +6 -0
  10. package/android/src/main/kotlin/run/granite/BundleEvaluator.kt +50 -0
  11. package/android/src/main/kotlin/run/granite/BundleLoader.kt +40 -0
  12. package/android/src/main/kotlin/run/granite/DefaultBundleLoader.kt +20 -0
  13. package/android/src/main/kotlin/run/granite/DefaultErrorView.kt +58 -0
  14. package/android/src/main/kotlin/run/granite/DefaultLoadingView.kt +51 -0
  15. package/android/src/main/kotlin/run/granite/GraniteReactDelegate.kt +76 -0
  16. package/android/src/main/kotlin/run/granite/GraniteReactDelegateImpl.kt +448 -0
  17. package/android/src/main/kotlin/run/granite/GraniteReactHost.kt +113 -0
  18. package/android/src/main/kotlin/run/granite/ReactHostFactory.kt +106 -0
  19. package/gradle-plugin/LICENSE +201 -0
  20. package/gradle-plugin/README.md +578 -0
  21. package/gradle-plugin/build.gradle.kts +97 -0
  22. package/gradle-plugin/gradle/libs.versions.toml +17 -0
  23. package/gradle-plugin/gradle/wrapper/gradle-wrapper.jar +0 -0
  24. package/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +7 -0
  25. package/gradle-plugin/gradle.properties +12 -0
  26. package/gradle-plugin/gradlew +248 -0
  27. package/gradle-plugin/gradlew.bat +93 -0
  28. package/gradle-plugin/settings.gradle.kts +1 -0
  29. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteExtension.kt +225 -0
  30. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GranitePlugin.kt +784 -0
  31. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootExtension.kt +107 -0
  32. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootProjectPlugin.kt +290 -0
  33. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/BuildConfigConfigurator.kt +69 -0
  34. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyConfigurator.kt +232 -0
  35. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyCoordinates.kt +29 -0
  36. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DevServerResourceConfigurator.kt +101 -0
  37. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/JniPackagingConfigurator.kt +160 -0
  38. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/NdkConfigurator.kt +135 -0
  39. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/RepositoryConfigurator.kt +148 -0
  40. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/ResourceConfigurator.kt +56 -0
  41. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CMakeGenerator.kt +105 -0
  42. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CppAutolinkingGenerator.kt +152 -0
  43. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/EntryPointGenerator.kt +100 -0
  44. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AndroidDependencyConfig.kt +23 -0
  45. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AutolinkingConfig.kt +89 -0
  46. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/CMakeEntry.kt +47 -0
  47. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/NativeModule.kt +177 -0
  48. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AssetPackagingTask.kt +194 -0
  49. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AutolinkingTask.kt +431 -0
  50. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/BundleTask.kt +275 -0
  51. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenArtifactsTask.kt +218 -0
  52. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenSchemaTask.kt +186 -0
  53. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/AutolinkingParser.kt +128 -0
  54. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ConflictDetector.kt +121 -0
  55. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/JdkValidator.kt +73 -0
  56. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/NodeExecutableFinder.kt +43 -0
  57. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ReactNativeVersionReader.kt +329 -0
  58. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/TaskDependencyValidator.kt +198 -0
  59. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteExtensionTest.kt +191 -0
  60. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GranitePluginTest.kt +156 -0
  61. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteRootProjectPluginTest.kt +87 -0
  62. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/BuildConfigConfiguratorTest.kt +115 -0
  63. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DependencyConfiguratorTest.kt +338 -0
  64. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DevServerResourceConfiguratorTest.kt +205 -0
  65. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/ResourceConfiguratorTest.kt +131 -0
  66. package/gradle-plugin/src/test/kotlin/run/granite/gradle/fixtures/NativeModuleFixtures.kt +67 -0
  67. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CMakeGeneratorTest.kt +71 -0
  68. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CppAutolinkingGeneratorTest.kt +344 -0
  69. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/EntryPointGeneratorTest.kt +40 -0
  70. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/AutolinkingConfigTest.kt +350 -0
  71. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/CMakeEntryTest.kt +200 -0
  72. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/NativeModuleTest.kt +562 -0
  73. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AssetPackagingTaskTest.kt +318 -0
  74. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AutolinkingTaskTest.kt +89 -0
  75. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/BundleTaskTest.kt +68 -0
  76. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/CodegenTasksTest.kt +410 -0
  77. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/AutolinkingParserTest.kt +335 -0
  78. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ConflictDetectorTest.kt +75 -0
  79. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/JdkValidatorTest.kt +88 -0
  80. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ReactNativeVersionReaderTest.kt +585 -0
  81. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskDependencyValidatorTest.kt +123 -0
  82. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskTestUtils.kt +88 -0
  83. package/gradle-plugin/src/test/resources/fixtures/sample-rn-config.json +45 -0
  84. package/ios/BundleLoader/BundleEvaluator.h +16 -0
  85. package/ios/BundleLoader/BundleEvaluator.mm +76 -0
  86. package/ios/BundleLoader/BundleLoadable.swift +91 -0
  87. package/ios/GraniteBundleLoaderTypes.swift +7 -0
  88. package/ios/GraniteScreen.h +12 -0
  89. package/ios/ReactNativeHosting/DefaultViews.swift +138 -0
  90. package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.h +24 -0
  91. package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.mm +22 -0
  92. package/ios/ReactNativeHosting/GraniteHostingHelper.swift +103 -0
  93. package/ios/ReactNativeHosting/GraniteNativeFactory.swift +35 -0
  94. package/ios/ReactNativeHosting/GraniteNativeFactoryDelegateImpl.swift +30 -0
  95. package/ios/ReactNativeHosting/GraniteNativeFactoryImpl.swift +24 -0
  96. package/ios/ReactNativeHosting/GraniteReactHost.swift +39 -0
  97. package/ios/ReactNativeHosting/GraniteScreen-Bridging-Header.h +12 -0
  98. package/package.json +59 -0
  99. package/react-native.config.js +8 -0
@@ -0,0 +1,107 @@
1
+ package run.granite.gradle
2
+
3
+ import org.gradle.api.Project
4
+ import org.gradle.api.provider.Property
5
+ import run.granite.gradle.config.DependencyCoordinates
6
+ import run.granite.gradle.utils.ReactNativeVersionReader
7
+ import java.io.File
8
+
9
+ /**
10
+ * DSL configuration extension for Granite Root Project plugin.
11
+ *
12
+ * Configure the plugin in your root build.gradle.kts:
13
+ * ```
14
+ * graniteRoot {
15
+ * // Optional: Explicit version override (auto-detected from node_modules if not set)
16
+ * // reactNativeVersion.set("0.84.0")
17
+ * // hermesVersion.set("250829098.0.6")
18
+ *
19
+ * // Optional: Override Maven group (for custom Maven repositories)
20
+ * // reactGroup.set("com.facebook.react")
21
+ * // hermesGroup.set("com.facebook.hermes")
22
+ * }
23
+ * ```
24
+ */
25
+ abstract class GraniteRootExtension(private val project: Project) {
26
+
27
+ /**
28
+ * React Native version (auto-detected from node_modules if not set)
29
+ */
30
+ abstract val reactNativeVersion: Property<String>
31
+
32
+ /**
33
+ * Hermes version (auto-detected from node_modules if not set)
34
+ */
35
+ abstract val hermesVersion: Property<String>
36
+
37
+ /**
38
+ * React Native Maven group.
39
+ * Default: com.facebook.react
40
+ */
41
+ abstract val reactGroup: Property<String>
42
+
43
+ /**
44
+ * Hermes Maven group.
45
+ * Default: com.facebook.hermes
46
+ */
47
+ abstract val hermesGroup: Property<String>
48
+
49
+ /**
50
+ * Location of the node_modules directory.
51
+ * Default: rootProject.file("node_modules")
52
+ */
53
+ abstract val nodeModulesDir: Property<File>
54
+
55
+ init {
56
+ // Set default conventions
57
+ reactGroup.convention(DependencyCoordinates.DEFAULT_REACT_GROUP)
58
+ hermesGroup.convention(DependencyCoordinates.DEFAULT_HERMES_GROUP)
59
+ nodeModulesDir.convention(project.file("node_modules"))
60
+ // reactNativeVersion and hermesVersion are intentionally left without conventions
61
+ // They will be auto-detected if not explicitly set
62
+ }
63
+
64
+ /**
65
+ * Gets the resolved DependencyCoordinates.
66
+ *
67
+ * If versions are explicitly set, uses those values.
68
+ * Otherwise, auto-detects from node_modules/react-native.
69
+ *
70
+ * @return DependencyCoordinates for dependency substitution
71
+ * @throws IllegalStateException if auto-detection fails and no explicit versions set
72
+ */
73
+ internal fun getCoordinates(): DependencyCoordinates {
74
+ val reactNativeDir = nodeModulesDir.get().resolve("react-native")
75
+
76
+ // Auto-detect base coordinates if needed
77
+ val baseCoordinates = if (!reactNativeVersion.isPresent || !hermesVersion.isPresent) {
78
+ if (!reactNativeDir.exists()) {
79
+ error(
80
+ """
81
+ |React Native directory not found: ${reactNativeDir.absolutePath}
82
+ |
83
+ |Either set versions explicitly in graniteRoot { } or ensure node_modules/react-native exists.
84
+ |
85
+ |Example:
86
+ | graniteRoot {
87
+ | reactNativeVersion.set("0.84.0")
88
+ | hermesVersion.set("250829098.0.6")
89
+ | }
90
+ """.trimMargin(),
91
+ )
92
+ }
93
+ ReactNativeVersionReader.readCoordinates(reactNativeDir)
94
+ } else {
95
+ null
96
+ }
97
+
98
+ // Build final coordinates: explicit > auto-detected
99
+ return DependencyCoordinates(
100
+ reactVersion = reactNativeVersion.orNull ?: baseCoordinates!!.reactVersion,
101
+ hermesVersion = baseCoordinates?.hermesVersion ?: "",
102
+ hermesV1Version = hermesVersion.orNull ?: baseCoordinates!!.hermesV1Version,
103
+ reactGroup = reactGroup.get(),
104
+ hermesGroup = hermesGroup.get(),
105
+ )
106
+ }
107
+ }
@@ -0,0 +1,290 @@
1
+ package run.granite.gradle
2
+
3
+ import com.android.build.api.variant.ApplicationAndroidComponentsExtension
4
+ import com.android.build.api.variant.LibraryAndroidComponentsExtension
5
+ import org.gradle.api.JavaVersion
6
+ import org.gradle.api.Plugin
7
+ import org.gradle.api.Project
8
+ import org.gradle.api.artifacts.Configuration
9
+ import run.granite.gradle.config.DependencyConfigurator
10
+
11
+ /**
12
+ * Granite Root Project Plugin
13
+ *
14
+ * Plugin applied to the root project to automatically configure
15
+ * React Native dependency substitution.
16
+ *
17
+ * This plugin performs the following roles:
18
+ * - react-native → react-android dependency substitution
19
+ * - hermes-engine → hermes-android dependency substitution
20
+ * - com.facebook.react:hermes-android → com.facebook.hermes:hermes-android substitution (RN 0.84+)
21
+ * - Version forcing (force)
22
+ *
23
+ * Usage:
24
+ * ```
25
+ * // root build.gradle.kts
26
+ * plugins {
27
+ * id("run.granite.rootproject")
28
+ * }
29
+ *
30
+ * graniteRoot {
31
+ * // Optional: Explicit version override
32
+ * // reactNativeVersion.set("0.84.0")
33
+ * // hermesVersion.set("250829098.0.6")
34
+ * }
35
+ * ```
36
+ *
37
+ * @see GraniteRootExtension for configuration options
38
+ */
39
+ class GraniteRootProjectPlugin : Plugin<Project> {
40
+
41
+ companion object {
42
+ /** Plugin identifier used in build.gradle.kts: `id("run.granite.rootproject")`. */
43
+ const val PLUGIN_ID = "run.granite.rootproject"
44
+
45
+ /** Name of the DSL extension block: `graniteRoot { ... }`. */
46
+ const val EXTENSION_NAME = "graniteRoot"
47
+
48
+ // Uses Granite-specific properties (operates independently from react.internal.*)
49
+ private const val INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT =
50
+ "granite.internal.disableJavaVersionAlignment"
51
+ }
52
+
53
+ override fun apply(project: Project) {
54
+ // 1. Root project validation
55
+ validateRootProject(project)
56
+
57
+ // 2. Create extension
58
+ val extension = project.extensions.create(
59
+ EXTENSION_NAME,
60
+ GraniteRootExtension::class.java,
61
+ project,
62
+ )
63
+
64
+ // 3. Configure PrivateReactExtension for React Native Gradle Plugin compatibility
65
+ configurePrivateReactExtension(project)
66
+
67
+ // 4. Configure Java toolchains IMMEDIATELY (before afterEvaluate)
68
+ // This must run before afterEvaluate for finalizeDsl to work
69
+ configureJavaToolChains(project)
70
+
71
+ // 5. Configure dependency substitution after evaluation
72
+ project.afterEvaluate {
73
+ configureDependencySubstitution(project, extension)
74
+ }
75
+
76
+ project.logger.lifecycle("Granite Root Project plugin applied to ${project.name}")
77
+ }
78
+
79
+ private fun validateRootProject(project: Project) {
80
+ if (project != project.rootProject) {
81
+ error(
82
+ """
83
+ |Granite Root Project plugin can only be applied to the root project.
84
+ |
85
+ |Current project: ${project.path}
86
+ |Root project: ${project.rootProject.path}
87
+ |
88
+ |Solution: Move the plugin application to your root build.gradle.kts:
89
+ | plugins {
90
+ | id("run.granite.rootproject")
91
+ | }
92
+ """.trimMargin(),
93
+ )
94
+ }
95
+ }
96
+
97
+ private fun configureDependencySubstitution(project: Project, extension: GraniteRootExtension) {
98
+ val coordinates = extension.getCoordinates()
99
+ val substitutions = DependencyConfigurator.getDependencySubstitutions(coordinates)
100
+ val hermesVersion = coordinates.getEffectiveHermesVersion()
101
+
102
+ project.logger.lifecycle(
103
+ "Granite: Configuring dependency substitution " +
104
+ "(react: ${coordinates.reactVersion}, hermes: $hermesVersion)",
105
+ )
106
+
107
+ project.allprojects {
108
+ val targetProject = this
109
+
110
+ targetProject.configurations.all configBlock@{
111
+ // Filter: only Classpath configurations
112
+ if (!name.endsWith("CompileClasspath") && !name.endsWith("RuntimeClasspath")) {
113
+ return@configBlock
114
+ }
115
+ // Filter: skip AGP internal metadata configurations
116
+ if (name.contains("Metadata", ignoreCase = true)) {
117
+ return@configBlock
118
+ }
119
+ // Skip already resolved or non-resolvable configurations
120
+ if (!isCanBeResolved || state == Configuration.State.RESOLVED) {
121
+ return@configBlock
122
+ }
123
+
124
+ // Apply dependency substitution rules
125
+ resolutionStrategy.dependencySubstitution {
126
+ for ((oldCoordinate, newCoordinate, reason) in substitutions) {
127
+ substitute(module(oldCoordinate))
128
+ .using(module(newCoordinate))
129
+ .because(reason)
130
+ }
131
+ }
132
+
133
+ // Force versions for consistency
134
+ resolutionStrategy.force(
135
+ "${coordinates.reactGroup}:react-android:${coordinates.reactVersion}",
136
+ "${coordinates.hermesGroup}:hermes-android:$hermesVersion",
137
+ )
138
+ }
139
+ }
140
+
141
+ project.logger.lifecycle(
142
+ "Granite: Dependency substitution configured for all projects " +
143
+ "(${substitutions.size} rules applied)",
144
+ )
145
+ }
146
+
147
+ /**
148
+ * Configures Java 17 toolchain for all projects.
149
+ *
150
+ * Uses finalizeDsl in the same way as React Native's JdkConfiguratorUtils.kt.
151
+ * finalizeDsl runs after the android { } block evaluation but before AGP internal finalization.
152
+ *
153
+ * - Sets Java 17 for projects with com.android.application / com.android.library plugins
154
+ * - Sets jvmToolchain(17) for org.jetbrains.kotlin.android / org.jetbrains.kotlin.jvm plugins
155
+ *
156
+ * Disable: Set granite.internal.disableJavaVersionAlignment=true in gradle.properties
157
+ *
158
+ * @param project Root project
159
+ */
160
+ private fun configureJavaToolChains(project: Project) {
161
+ // Check if disabled at root project level
162
+ if (project.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) {
163
+ project.logger.lifecycle("Granite: Java version alignment disabled via property")
164
+ return
165
+ }
166
+
167
+ project.allprojects {
168
+ val targetProject = this
169
+
170
+ // Can also be disabled at project level
171
+ if (targetProject.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) {
172
+ return@allprojects
173
+ }
174
+
175
+ // Android Application plugin - set compileOptions via finalizeDsl
176
+ targetProject.pluginManager.withPlugin("com.android.application") {
177
+ targetProject.extensions
178
+ .getByType(ApplicationAndroidComponentsExtension::class.java)
179
+ .finalizeDsl { ext ->
180
+ ext.compileOptions.sourceCompatibility = JavaVersion.VERSION_17
181
+ ext.compileOptions.targetCompatibility = JavaVersion.VERSION_17
182
+ }
183
+ }
184
+
185
+ // Android Library plugin - set compileOptions via finalizeDsl
186
+ targetProject.pluginManager.withPlugin("com.android.library") {
187
+ targetProject.extensions
188
+ .getByType(LibraryAndroidComponentsExtension::class.java)
189
+ .finalizeDsl { ext ->
190
+ ext.compileOptions.sourceCompatibility = JavaVersion.VERSION_17
191
+ ext.compileOptions.targetCompatibility = JavaVersion.VERSION_17
192
+ }
193
+ }
194
+
195
+ // Kotlin Android plugin (uses reflection to avoid compile-time dependency)
196
+ targetProject.pluginManager.withPlugin("org.jetbrains.kotlin.android") {
197
+ try {
198
+ val kotlinExtension = targetProject.extensions.findByName("kotlin")
199
+ if (kotlinExtension != null) {
200
+ val jvmToolchainMethod = kotlinExtension.javaClass
201
+ .getMethod("jvmToolchain", Int::class.javaPrimitiveType)
202
+ jvmToolchainMethod.invoke(kotlinExtension, 17)
203
+ }
204
+ } catch (e: Exception) {
205
+ targetProject.logger.debug(
206
+ "Granite: Could not set jvmToolchain for ${targetProject.name}: ${e.message}",
207
+ )
208
+ }
209
+ }
210
+
211
+ // Kotlin JVM plugin configuration
212
+ targetProject.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
213
+ try {
214
+ val kotlinExtension = targetProject.extensions.findByName("kotlin")
215
+ if (kotlinExtension != null) {
216
+ val jvmToolchainMethod = kotlinExtension.javaClass
217
+ .getMethod("jvmToolchain", Int::class.javaPrimitiveType)
218
+ jvmToolchainMethod.invoke(kotlinExtension, 17)
219
+ }
220
+ } catch (e: Exception) {
221
+ targetProject.logger.debug(
222
+ "Granite: Could not set jvmToolchain for ${targetProject.name}: ${e.message}",
223
+ )
224
+ }
225
+ }
226
+ }
227
+
228
+ project.logger.lifecycle("Granite: Configured Java 17 toolchain for all projects")
229
+ }
230
+
231
+ /**
232
+ * Pre-emptively creates and configures PrivateReactExtension from the React Native Gradle Plugin.
233
+ *
234
+ * Background:
235
+ * - PrivateReactExtension is created on the rootProject to share configuration between app and library modules
236
+ * - The default convention assumes rootProject.dir/../ (standard RN app's android/ directory structure)
237
+ * - In monorepo setups where rootProject.dir IS the package.json location, ../ points to the wrong path
238
+ * - When the application module does not apply com.facebook.react, root.set() is never called
239
+ *
240
+ * Solution:
241
+ * - This plugin creates the extension first and sets root to rootProject.dir
242
+ * - ReactPlugin uses findByType to reuse an existing extension, so there is no conflict
243
+ */
244
+ private fun configurePrivateReactExtension(project: Project) {
245
+ try {
246
+ val privateReactExtensionClass = Class.forName(
247
+ "com.facebook.react.internal.PrivateReactExtension",
248
+ )
249
+
250
+ val existing = project.extensions.findByType(privateReactExtensionClass)
251
+ if (existing != null) {
252
+ // If already exists, only configure root
253
+ setRootProperty(existing, project)
254
+ project.logger.lifecycle(
255
+ "Granite: Configured existing PrivateReactExtension root to ${project.projectDir}",
256
+ )
257
+ return
258
+ }
259
+
260
+ // Create new instance
261
+ val extension = project.extensions.create(
262
+ "privateReact",
263
+ privateReactExtensionClass,
264
+ project,
265
+ )
266
+ setRootProperty(extension, project)
267
+ project.logger.lifecycle(
268
+ "Granite: Created PrivateReactExtension with root=${project.projectDir}",
269
+ )
270
+ } catch (e: Exception) {
271
+ project.logger.warn(
272
+ "Granite: Could not configure PrivateReactExtension: ${e.message}. " +
273
+ "React Native Gradle Plugin may not be available on the classpath.",
274
+ )
275
+ }
276
+ }
277
+
278
+ private fun setRootProperty(extension: Any, project: Project) {
279
+ try {
280
+ val rootProperty = extension.javaClass.getMethod("getRoot").invoke(extension)
281
+ if (rootProperty is org.gradle.api.file.DirectoryProperty) {
282
+ rootProperty.set(project.layout.projectDirectory)
283
+ }
284
+ } catch (e: Exception) {
285
+ project.logger.warn(
286
+ "Granite: Could not set PrivateReactExtension root: ${e.message}",
287
+ )
288
+ }
289
+ }
290
+ }
@@ -0,0 +1,69 @@
1
+ package run.granite.gradle.config
2
+
3
+ import com.android.build.gradle.LibraryExtension
4
+ import org.gradle.api.Project
5
+ import run.granite.gradle.GraniteExtension
6
+
7
+ /**
8
+ * Configurator for Android BuildConfig field generation.
9
+ *
10
+ * Generates BuildConfig fields to expose:
11
+ * - Feature flags (New Architecture, Hermes always enabled)
12
+ * - Development server configuration
13
+ * - React Native version information
14
+ *
15
+ * These constants are available at runtime and compile-time for conditional logic.
16
+ */
17
+ class BuildConfigConfigurator(
18
+ private val project: Project,
19
+ private val extension: GraniteExtension,
20
+ ) {
21
+
22
+ /**
23
+ * Configures BuildConfig fields for the Android library.
24
+ *
25
+ * Adds fields that are available in generated BuildConfig class:
26
+ * - IS_HERMES_ENABLED: Boolean flag for Hermes JavaScript engine (always true)
27
+ * - IS_NEW_ARCHITECTURE_ENABLED: Boolean flag for TurboModules/Fabric (always true)
28
+ * - REACT_NATIVE_VERSION: String with React Native version
29
+ */
30
+ fun configure(androidExtension: LibraryExtension) {
31
+ androidExtension.apply {
32
+ // Enable BuildConfig generation for library module
33
+ buildFeatures.buildConfig = true
34
+
35
+ // Add common BuildConfig fields
36
+ defaultConfig {
37
+ // Hermes JavaScript engine flag (always enabled)
38
+ buildConfigField(
39
+ "boolean",
40
+ "IS_HERMES_ENABLED",
41
+ "true",
42
+ )
43
+
44
+ // New Architecture (TurboModules + Fabric) flag (always enabled)
45
+ buildConfigField(
46
+ "boolean",
47
+ "IS_NEW_ARCHITECTURE_ENABLED",
48
+ "true",
49
+ )
50
+
51
+ // React Native version
52
+ buildConfigField(
53
+ "String",
54
+ "REACT_NATIVE_VERSION",
55
+ "\"${extension.reactNativeVersion.get()}\"",
56
+ )
57
+
58
+ // Bundle asset name
59
+ buildConfigField(
60
+ "String",
61
+ "BUNDLE_ASSET_NAME",
62
+ "\"${extension.bundleAssetName.get()}\"",
63
+ )
64
+ }
65
+ }
66
+
67
+ project.logger.lifecycle("BuildConfig fields configured for ${project.name}")
68
+ }
69
+ }
@@ -0,0 +1,232 @@
1
+ package run.granite.gradle.config
2
+
3
+ import com.android.build.gradle.LibraryExtension
4
+ import org.gradle.api.JavaVersion
5
+ import org.gradle.api.Project
6
+ import run.granite.gradle.GraniteExtension
7
+ import run.granite.gradle.utils.ReactNativeVersionReader
8
+
9
+ /**
10
+ * Configures React Native and related dependencies for the library module.
11
+ *
12
+ * This configurator automatically adds required dependencies:
13
+ * - react-native (implementation)
14
+ * - hermes-android (implementation)
15
+ * - soloader (implementation)
16
+ *
17
+ * Versions are resolved from the React Native installation to ensure compatibility.
18
+ */
19
+ class DependencyConfigurator(
20
+ private val project: Project,
21
+ private val extension: GraniteExtension,
22
+ ) {
23
+
24
+ companion object {
25
+ // Default Maven group (official React Native Maven group)
26
+ const val DEFAULT_GROUP = "com.facebook.react"
27
+
28
+ // Dependency coordinates
29
+ private const val REACT_NATIVE_ARTIFACT = "react-android"
30
+ private const val HERMES_ARTIFACT = "hermes-android"
31
+
32
+ private const val SOLOADER_GROUP = "com.facebook.soloader"
33
+ private const val SOLOADER_ARTIFACT = "soloader"
34
+ private const val SOLOADER_VERSION = "0.12.1" // Compatible with RN 0.84+
35
+
36
+ // Deprecated coordinates for substitution
37
+ private const val DEPRECATED_REACT_NATIVE = "com.facebook.react:react-native"
38
+ private const val DEPRECATED_HERMES_ENGINE = "com.facebook.react:hermes-engine"
39
+
40
+ // Hermes group (RN 0.84+)
41
+ private const val HERMES_GROUP = "com.facebook.hermes"
42
+
43
+ // Additional deprecated coordinate
44
+ private const val DEPRECATED_HERMES_ANDROID = "com.facebook.react:hermes-android"
45
+
46
+ /**
47
+ * Creates dependency substitution rules.
48
+ *
49
+ * @param version React Native version
50
+ * @param group React Native Maven group (default: com.facebook.react)
51
+ * @return List of triples (original coordinate, replacement coordinate, reason)
52
+ */
53
+ fun getDependencySubstitutions(
54
+ version: String,
55
+ group: String,
56
+ ): List<Triple<String, String, String>> {
57
+ val substitutions = mutableListOf<Triple<String, String, String>>()
58
+
59
+ // Base substitution rules: deprecated coordinates → new coordinates
60
+ substitutions.add(
61
+ Triple(
62
+ DEPRECATED_REACT_NATIVE,
63
+ "$group:$REACT_NATIVE_ARTIFACT:$version",
64
+ "The react-native artifact was deprecated in favor of react-android",
65
+ ),
66
+ )
67
+ substitutions.add(
68
+ Triple(
69
+ DEPRECATED_HERMES_ENGINE,
70
+ "$group:$HERMES_ARTIFACT:$version",
71
+ "The hermes-engine artifact was deprecated in favor of hermes-android",
72
+ ),
73
+ )
74
+
75
+ // Cross-group substitution: when using custom Maven group
76
+ if (group != DEFAULT_GROUP) {
77
+ substitutions.add(
78
+ Triple(
79
+ "$DEFAULT_GROUP:$REACT_NATIVE_ARTIFACT",
80
+ "$group:$REACT_NATIVE_ARTIFACT:$version",
81
+ "Modified to use correct Maven group",
82
+ ),
83
+ )
84
+ substitutions.add(
85
+ Triple(
86
+ "$DEFAULT_GROUP:$HERMES_ARTIFACT",
87
+ "$group:$HERMES_ARTIFACT:$version",
88
+ "Modified to use correct Maven group",
89
+ ),
90
+ )
91
+ }
92
+
93
+ return substitutions
94
+ }
95
+
96
+ /**
97
+ * Creates dependency substitution rules (DependencyCoordinates version).
98
+ *
99
+ * Supports the new Hermes group structure in React Native 0.84+:
100
+ * - com.facebook.react:react-native → {reactGroup}:react-android:{reactVersion}
101
+ * - com.facebook.react:hermes-engine → {hermesGroup}:hermes-android:{hermesVersion}
102
+ * - com.facebook.react:hermes-android → {hermesGroup}:hermes-android:{hermesVersion}
103
+ *
104
+ * @param coordinates Dependency coordinate information
105
+ * @return List of triples (original coordinate, replacement coordinate, reason)
106
+ */
107
+ fun getDependencySubstitutions(coordinates: DependencyCoordinates): List<Triple<String, String, String>> {
108
+ val substitutions = mutableListOf<Triple<String, String, String>>()
109
+ val hermesVersion = coordinates.getEffectiveHermesVersion()
110
+ val hermesCoordinate = "${coordinates.hermesGroup}:$HERMES_ARTIFACT:$hermesVersion"
111
+
112
+ // 1. react-native -> react-android
113
+ substitutions.add(
114
+ Triple(
115
+ DEPRECATED_REACT_NATIVE,
116
+ "${coordinates.reactGroup}:$REACT_NATIVE_ARTIFACT:${coordinates.reactVersion}",
117
+ "The react-native artifact was deprecated in favor of react-android",
118
+ ),
119
+ )
120
+
121
+ // 2. hermes-engine -> hermes-android (new group)
122
+ substitutions.add(
123
+ Triple(
124
+ DEPRECATED_HERMES_ENGINE,
125
+ hermesCoordinate,
126
+ "The hermes-engine artifact was deprecated in favor of hermes-android",
127
+ ),
128
+ )
129
+
130
+ // 3. com.facebook.react:hermes-android → com.facebook.hermes:hermes-android (NEW!)
131
+ substitutions.add(
132
+ Triple(
133
+ DEPRECATED_HERMES_ANDROID,
134
+ hermesCoordinate,
135
+ "The hermes-android artifact was moved to com.facebook.hermes publishing group",
136
+ ),
137
+ )
138
+
139
+ // 4. Custom React group substitution
140
+ if (coordinates.reactGroup != DEFAULT_GROUP) {
141
+ substitutions.add(
142
+ Triple(
143
+ "$DEFAULT_GROUP:$REACT_NATIVE_ARTIFACT",
144
+ "${coordinates.reactGroup}:$REACT_NATIVE_ARTIFACT:${coordinates.reactVersion}",
145
+ "Modified to use correct Maven group",
146
+ ),
147
+ )
148
+ }
149
+
150
+ // 5. Custom Hermes group substitution
151
+ if (coordinates.hermesGroup != HERMES_GROUP) {
152
+ substitutions.add(
153
+ Triple(
154
+ "$HERMES_GROUP:$HERMES_ARTIFACT",
155
+ hermesCoordinate,
156
+ "Modified to use correct Maven group",
157
+ ),
158
+ )
159
+ }
160
+
161
+ return substitutions
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Configures all required React Native dependencies.
167
+ *
168
+ * Called during project evaluation (afterEvaluate) to ensure configuration is complete.
169
+ */
170
+ fun configure() {
171
+ val reactNativeDir = extension.getReactNativeDirResolved()
172
+
173
+ // Read all coordinates (React + Hermes)
174
+ val coordinates = ReactNativeVersionReader.readCoordinates(reactNativeDir)
175
+ ReactNativeVersionReader.validateVersion(coordinates.reactVersion)
176
+
177
+ project.logger.lifecycle(
178
+ "Configuring React Native dependencies (version: ${coordinates.reactVersion}, " +
179
+ "hermes: ${coordinates.getEffectiveHermesVersion()}, " +
180
+ "hermesGroup: ${coordinates.hermesGroup})",
181
+ )
182
+
183
+ // Note: Dependency substitution is now handled by run.granite.rootproject plugin.
184
+ // See GraniteRootProjectPlugin for the centralized substitution logic.
185
+
186
+ // Add React Native core dependencies
187
+ addReactNativeDependencies(coordinates.reactVersion, coordinates.reactGroup)
188
+
189
+ // Add Hermes engine
190
+ addHermesDependency(coordinates)
191
+
192
+ // Add SoLoader (required for native library loading)
193
+ addSoLoaderDependency()
194
+ }
195
+
196
+ /**
197
+ * Adds React Native core dependencies.
198
+ */
199
+ private fun addReactNativeDependencies(version: String, group: String) {
200
+ project.dependencies.add(
201
+ "implementation",
202
+ "$group:$REACT_NATIVE_ARTIFACT:$version",
203
+ )
204
+
205
+ project.logger.debug("Added dependency: $group:$REACT_NATIVE_ARTIFACT:$version")
206
+ }
207
+
208
+ /**
209
+ * Adds Hermes JavaScript engine dependency (DependencyCoordinates version).
210
+ */
211
+ private fun addHermesDependency(coordinates: DependencyCoordinates) {
212
+ val hermesVersion = coordinates.getEffectiveHermesVersion()
213
+ project.dependencies.add(
214
+ "implementation",
215
+ "${coordinates.hermesGroup}:$HERMES_ARTIFACT:$hermesVersion",
216
+ )
217
+
218
+ project.logger.debug("Added dependency: ${coordinates.hermesGroup}:$HERMES_ARTIFACT:$hermesVersion")
219
+ }
220
+
221
+ /**
222
+ * Adds SoLoader dependency for native library loading.
223
+ */
224
+ private fun addSoLoaderDependency() {
225
+ project.dependencies.add(
226
+ "implementation",
227
+ "$SOLOADER_GROUP:$SOLOADER_ARTIFACT:$SOLOADER_VERSION",
228
+ )
229
+
230
+ project.logger.debug("Added dependency: $SOLOADER_GROUP:$SOLOADER_ARTIFACT:$SOLOADER_VERSION")
231
+ }
232
+ }