@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,73 @@
1
+ package run.granite.gradle.utils
2
+
3
+ import org.gradle.api.JavaVersion
4
+ import org.gradle.api.Project
5
+
6
+ /**
7
+ * Validates JDK version requirements for Granite plugin.
8
+ *
9
+ * The plugin requires JDK 17 or higher due to:
10
+ * - Modern Gradle features requiring Java 17
11
+ * - React Native Gradle plugin compatibility
12
+ * - Android Gradle Plugin 8.x requirements
13
+ */
14
+ object JdkValidator {
15
+
16
+ private val MINIMUM_JDK_VERSION = JavaVersion.VERSION_17
17
+
18
+ /**
19
+ * Validates that the current JDK meets minimum version requirements.
20
+ *
21
+ * @param project The Gradle project
22
+ * @throws IllegalStateException if JDK version is below minimum
23
+ */
24
+ fun validate(project: Project) {
25
+ val currentJavaVersion = JavaVersion.current()
26
+
27
+ if (currentJavaVersion < MINIMUM_JDK_VERSION) {
28
+ error(
29
+ """
30
+ |Granite plugin requires JDK 17 or higher.
31
+ |
32
+ |Current JDK version: $currentJavaVersion
33
+ |Required JDK version: $MINIMUM_JDK_VERSION or higher
34
+ |
35
+ |Solutions:
36
+ | 1. Update your JDK installation to version 17 or higher
37
+ | 2. Configure Gradle to use JDK 17+:
38
+ | - Set JAVA_HOME environment variable to JDK 17+ path
39
+ | - Or configure in gradle.properties:
40
+ | org.gradle.java.home=/path/to/jdk-17
41
+ | 3. Use Gradle toolchain to automatically download JDK 17:
42
+ | Add to build.gradle.kts:
43
+ | java {
44
+ | toolchain {
45
+ | languageVersion.set(JavaLanguageVersion.of(17))
46
+ | }
47
+ | }
48
+ |
49
+ |Project: ${project.path}
50
+ """.trimMargin(),
51
+ )
52
+ }
53
+
54
+ project.logger.lifecycle("Granite plugin: JDK validation passed (using JDK $currentJavaVersion)")
55
+ }
56
+
57
+ /**
58
+ * Gets the current JDK version.
59
+ */
60
+ fun getCurrentVersion(): JavaVersion = JavaVersion.current()
61
+
62
+ /**
63
+ * Gets the minimum required JDK version.
64
+ */
65
+ fun getMinimumVersion(): JavaVersion = MINIMUM_JDK_VERSION
66
+
67
+ /**
68
+ * Checks if the current JDK meets minimum requirements without throwing an error.
69
+ *
70
+ * @return true if current JDK >= minimum version, false otherwise
71
+ */
72
+ fun isCompatible(): Boolean = JavaVersion.current() >= MINIMUM_JDK_VERSION
73
+ }
@@ -0,0 +1,43 @@
1
+ package run.granite.gradle.utils
2
+
3
+ import java.io.File
4
+
5
+ /**
6
+ * Utility for locating the Node.js executable.
7
+ *
8
+ * Searches the PATH environment variable to find the Node.js executable.
9
+ * Looks for node.exe on Windows and node on other platforms.
10
+ */
11
+ object NodeExecutableFinder {
12
+
13
+ /**
14
+ * Finds and returns the Node.js executable.
15
+ *
16
+ * Search order:
17
+ * 1. Scan directories listed in the PATH environment variable for the node executable
18
+ * 2. If not found in PATH, return just the node name so the system can resolve it (fallback)
19
+ *
20
+ * @return a [File] pointing to the Node.js executable
21
+ */
22
+ fun findNodeExecutable(): File {
23
+ val nodeName = if (System.getProperty("os.name").startsWith("Windows")) {
24
+ "node.exe"
25
+ } else {
26
+ "node"
27
+ }
28
+
29
+ // Search for the node executable in PATH directories
30
+ val pathEnv = System.getenv("PATH") ?: ""
31
+ val pathDirs = pathEnv.split(File.pathSeparator)
32
+
33
+ for (dir in pathDirs) {
34
+ val nodeFile = File(dir, nodeName)
35
+ if (nodeFile.exists() && nodeFile.canExecute()) {
36
+ return nodeFile
37
+ }
38
+ }
39
+
40
+ // Fallback: return just the node name and let the system resolve it
41
+ return File(nodeName)
42
+ }
43
+ }
@@ -0,0 +1,329 @@
1
+ package run.granite.gradle.utils
2
+
3
+ import com.google.gson.Gson
4
+ import com.google.gson.JsonObject
5
+ import org.gradle.api.Project
6
+ import run.granite.gradle.config.DependencyCoordinates
7
+ import java.io.File
8
+ import java.util.Properties
9
+
10
+ /**
11
+ * Reads and resolves React Native version information.
12
+ *
13
+ * This utility reads version information from:
14
+ * - node_modules/react-native/package.json
15
+ * - User-specified reactNativeDir in GraniteExtension
16
+ *
17
+ * Version resolution is used to:
18
+ * - Configure compatible dependency versions (hermes-android, soloader, etc.)
19
+ * - Locate React Native codegen and bundler tools
20
+ * - Validate minimum version requirements
21
+ */
22
+ object ReactNativeVersionReader {
23
+
24
+ private const val MINIMUM_RN_VERSION = "0.81.0"
25
+ private const val INTERNAL_VERSION_NAME = "VERSION_NAME"
26
+ private const val INTERNAL_PUBLISHING_GROUP = "react.internal.publishingGroup"
27
+ private const val DEFAULT_INTERNAL_PUBLISHING_GROUP = "com.facebook.react"
28
+
29
+ // Hermes version property keys
30
+ private const val INTERNAL_HERMES_VERSION_NAME = "HERMES_VERSION_NAME"
31
+ private const val INTERNAL_HERMES_V1_VERSION_NAME = "HERMES_V1_VERSION_NAME"
32
+ private const val INTERNAL_HERMES_PUBLISHING_GROUP = "react.internal.hermesPublishingGroup"
33
+ private const val DEFAULT_HERMES_PUBLISHING_GROUP = "com.facebook.hermes"
34
+
35
+ /**
36
+ * Reads React Native version from the specified directory.
37
+ *
38
+ * @param reactNativeDir The React Native installation directory
39
+ * @return The version string (e.g., "0.81.6")
40
+ * @throws IllegalStateException if version cannot be read
41
+ */
42
+ fun readVersion(reactNativeDir: File): String {
43
+ val packageJsonFile = reactNativeDir.resolve("package.json")
44
+
45
+ if (!packageJsonFile.exists()) {
46
+ error(
47
+ """
48
+ |React Native package.json not found.
49
+ |
50
+ |Expected location: ${packageJsonFile.absolutePath}
51
+ |React Native directory: ${reactNativeDir.absolutePath}
52
+ |
53
+ |Solutions:
54
+ | 1. Run 'npm install' or 'yarn install' to install React Native
55
+ | 2. Verify React Native is listed in your package.json dependencies
56
+ | 3. Configure the React Native directory in your build.gradle.kts:
57
+ | granite {
58
+ | reactNativeDir.set(file("path/to/node_modules/react-native"))
59
+ | }
60
+ """.trimMargin(),
61
+ )
62
+ }
63
+
64
+ return try {
65
+ val packageJson = Gson().fromJson(packageJsonFile.readText(), JsonObject::class.java)
66
+ val version = packageJson.get("version")?.asString
67
+ ?: error("No 'version' field found in ${packageJsonFile.absolutePath}")
68
+
69
+ version
70
+ } catch (e: Exception) {
71
+ error(
72
+ """
73
+ |Failed to read React Native version from package.json.
74
+ |
75
+ |File: ${packageJsonFile.absolutePath}
76
+ |Error: ${e.message}
77
+ |
78
+ |Verify that the file is valid JSON and contains a 'version' field.
79
+ """.trimMargin(),
80
+ )
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Reads React Native version from the project's node_modules.
86
+ *
87
+ * @param project The Gradle project
88
+ * @return The version string
89
+ */
90
+ fun readVersion(project: Project): String {
91
+ val reactNativeDir = project.rootProject.file("node_modules/react-native")
92
+ return readVersion(reactNativeDir)
93
+ }
94
+
95
+ /**
96
+ * Reads React Native version and Maven group ID.
97
+ *
98
+ * Reads gradle.properties first, falls back to package.json if not found.
99
+ * Nightly builds automatically have the -SNAPSHOT suffix added.
100
+ *
101
+ * @param reactNativeDir React Native installation directory
102
+ * @return Pair<version, groupId> (e.g., "0.81.6" to "com.facebook.react")
103
+ * @throws IllegalStateException if version cannot be read
104
+ */
105
+ fun readVersionAndGroup(reactNativeDir: File): Pair<String, String> {
106
+ val gradlePropertiesFile = reactNativeDir.resolve("ReactAndroid/gradle.properties")
107
+
108
+ // Read gradle.properties first if it exists
109
+ if (gradlePropertiesFile.exists()) {
110
+ try {
111
+ val properties = Properties().apply {
112
+ gradlePropertiesFile.inputStream().use { load(it) }
113
+ }
114
+
115
+ val versionStringFromFile = (properties[INTERNAL_VERSION_NAME] as? String).orEmpty()
116
+
117
+ // Add -SNAPSHOT suffix for nightly builds
118
+ val versionString = if (versionStringFromFile.startsWith("0.0.0") ||
119
+ "-nightly-" in versionStringFromFile
120
+ ) {
121
+ "$versionStringFromFile-SNAPSHOT"
122
+ } else {
123
+ versionStringFromFile
124
+ }
125
+
126
+ val groupString = properties[INTERNAL_PUBLISHING_GROUP] as? String
127
+ ?: DEFAULT_INTERNAL_PUBLISHING_GROUP
128
+
129
+ if (versionString.isEmpty()) {
130
+ // Error if VERSION_NAME is empty and no package.json exists
131
+ val packageJsonFile = reactNativeDir.resolve("package.json")
132
+ if (!packageJsonFile.exists()) {
133
+ error(
134
+ """
135
+ |React Native gradle.properties does not contain VERSION_NAME.
136
+ |
137
+ |File: ${gradlePropertiesFile.absolutePath}
138
+ |
139
+ |This might indicate a corrupted React Native installation.
140
+ """.trimMargin(),
141
+ )
142
+ }
143
+ // Fallback to package.json if it exists
144
+ val version = readVersion(reactNativeDir)
145
+ return version to DEFAULT_INTERNAL_PUBLISHING_GROUP
146
+ }
147
+
148
+ return versionString to groupString
149
+ } catch (e: IllegalStateException) {
150
+ // Re-throw explicitly thrown errors
151
+ throw e
152
+ } catch (e: Exception) {
153
+ // Fallback to package.json if gradle.properties read fails
154
+ val version = readVersion(reactNativeDir)
155
+ return version to DEFAULT_INTERNAL_PUBLISHING_GROUP
156
+ }
157
+ }
158
+
159
+ // Use package.json if gradle.properties doesn't exist (legacy approach)
160
+ val version = readVersion(reactNativeDir)
161
+ return version to DEFAULT_INTERNAL_PUBLISHING_GROUP
162
+ }
163
+
164
+ /**
165
+ * Reads complete coordinate information for React Native and Hermes.
166
+ *
167
+ * - ReactAndroid/gradle.properties: RN version, React/Hermes groups
168
+ * - sdks/hermes-engine/version.properties: Hermes version, Hermes V1 version
169
+ *
170
+ * @param reactNativeDir React Native installation directory
171
+ * @return DependencyCoordinates object
172
+ * @throws IllegalStateException if version cannot be read
173
+ */
174
+ fun readCoordinates(reactNativeDir: File): DependencyCoordinates {
175
+ val gradlePropertiesFile = reactNativeDir.resolve("ReactAndroid/gradle.properties")
176
+ val hermesVersionFile = reactNativeDir.resolve("sdks/hermes-engine/version.properties")
177
+
178
+ // Read React Native version and groups
179
+ val (reactVersion, reactGroup, hermesGroup) = readReactProperties(gradlePropertiesFile, reactNativeDir)
180
+
181
+ // Read Hermes versions
182
+ val (hermesVersion, hermesV1Version) = readHermesProperties(hermesVersionFile, reactVersion)
183
+
184
+ return DependencyCoordinates(
185
+ reactVersion = reactVersion,
186
+ hermesVersion = hermesVersion,
187
+ hermesV1Version = hermesV1Version,
188
+ reactGroup = reactGroup,
189
+ hermesGroup = hermesGroup,
190
+ )
191
+ }
192
+
193
+ private fun readReactProperties(gradlePropertiesFile: File, reactNativeDir: File): Triple<String, String, String> {
194
+ if (!gradlePropertiesFile.exists()) {
195
+ // Fallback to package.json if gradle.properties doesn't exist
196
+ val version = readVersion(reactNativeDir)
197
+ return Triple(version, DEFAULT_INTERNAL_PUBLISHING_GROUP, DEFAULT_HERMES_PUBLISHING_GROUP)
198
+ }
199
+
200
+ val properties = Properties().apply {
201
+ gradlePropertiesFile.inputStream().use { load(it) }
202
+ }
203
+
204
+ val versionStringFromFile = (properties[INTERNAL_VERSION_NAME] as? String).orEmpty()
205
+ val versionString = if (versionStringFromFile.startsWith("0.0.0") ||
206
+ "-nightly-" in versionStringFromFile
207
+ ) {
208
+ "$versionStringFromFile-SNAPSHOT"
209
+ } else {
210
+ versionStringFromFile
211
+ }
212
+
213
+ if (versionString.isEmpty()) {
214
+ error("React Native gradle.properties does not contain VERSION_NAME.")
215
+ }
216
+
217
+ val reactGroup = properties[INTERNAL_PUBLISHING_GROUP] as? String
218
+ ?: DEFAULT_INTERNAL_PUBLISHING_GROUP
219
+ val hermesGroup = properties[INTERNAL_HERMES_PUBLISHING_GROUP] as? String
220
+ ?: DEFAULT_HERMES_PUBLISHING_GROUP
221
+
222
+ return Triple(versionString, reactGroup, hermesGroup)
223
+ }
224
+
225
+ private fun readHermesProperties(hermesVersionFile: File, fallbackVersion: String): Pair<String, String> {
226
+ if (!hermesVersionFile.exists()) {
227
+ // Fallback to RN version if Hermes version file doesn't exist (RN 0.83 or lower compatibility)
228
+ return fallbackVersion to fallbackVersion
229
+ }
230
+
231
+ val properties = Properties().apply {
232
+ hermesVersionFile.inputStream().use { load(it) }
233
+ }
234
+
235
+ val hermesVersionFromFile = (properties[INTERNAL_HERMES_VERSION_NAME] as? String).orEmpty()
236
+ val hermesVersion = if (hermesVersionFromFile.startsWith("0.0.0") ||
237
+ "-commitly-" in hermesVersionFromFile
238
+ ) {
239
+ "$hermesVersionFromFile-SNAPSHOT"
240
+ } else {
241
+ hermesVersionFromFile
242
+ }
243
+
244
+ val hermesV1Version = (properties[INTERNAL_HERMES_V1_VERSION_NAME] as? String).orEmpty()
245
+
246
+ // Fallback if both are empty
247
+ return if (hermesVersion.isEmpty() && hermesV1Version.isEmpty()) {
248
+ fallbackVersion to fallbackVersion
249
+ } else {
250
+ hermesVersion to hermesV1Version
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Validates that the React Native version meets minimum requirements.
256
+ *
257
+ * @param version The React Native version string
258
+ * @throws IllegalStateException if version is below minimum
259
+ */
260
+ fun validateVersion(version: String) {
261
+ if (!isVersionCompatible(version, MINIMUM_RN_VERSION)) {
262
+ error(
263
+ """
264
+ |React Native version $version is not supported.
265
+ |
266
+ |Current version: $version
267
+ |Minimum required version: $MINIMUM_RN_VERSION
268
+ |
269
+ |Solution: Update React Native to version $MINIMUM_RN_VERSION or higher:
270
+ | npm install react-native@^$MINIMUM_RN_VERSION
271
+ | # or
272
+ | yarn add react-native@^$MINIMUM_RN_VERSION
273
+ """.trimMargin(),
274
+ )
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Compares two semantic version strings.
280
+ *
281
+ * @param current The current version
282
+ * @param minimum The minimum required version
283
+ * @return true if current >= minimum, false otherwise
284
+ */
285
+ private fun isVersionCompatible(current: String, minimum: String): Boolean {
286
+ // Parse semantic versions (major.minor.patch)
287
+ val currentParts = parseVersion(current)
288
+ val minimumParts = parseVersion(minimum)
289
+
290
+ // Compare major.minor.patch
291
+ for (i in 0 until 3) {
292
+ val currentPart = currentParts.getOrNull(i) ?: 0
293
+ val minimumPart = minimumParts.getOrNull(i) ?: 0
294
+
295
+ when {
296
+ currentPart > minimumPart -> return true
297
+ currentPart < minimumPart -> return false
298
+ // Equal, continue to next part
299
+ }
300
+ }
301
+
302
+ return true // Versions are equal
303
+ }
304
+
305
+ /**
306
+ * Parses a semantic version string into major, minor, patch components.
307
+ *
308
+ * Supports version formats:
309
+ * - X.Y.Z (stable)
310
+ * - X.Y.Z-rc.N (release candidate)
311
+ * - X.Y.Z-SNAPSHOT (snapshot)
312
+ * - X.Y.Z-nightly-YYYYMMDD (nightly)
313
+ *
314
+ * @param version The version string (e.g., "0.81.6" or "0.81.6-rc.0")
315
+ * @return List of [major, minor, patch] as integers
316
+ */
317
+ private fun parseVersion(version: String): List<Int> {
318
+ // Remove pre-release suffix (e.g., "-rc.0", "-SNAPSHOT", "-nightly-20240101")
319
+ val versionCore = version.split("-")[0]
320
+
321
+ return versionCore.split(".")
322
+ .mapNotNull { it.toIntOrNull() }
323
+ }
324
+
325
+ /**
326
+ * Gets the minimum supported React Native version.
327
+ */
328
+ fun getMinimumVersion(): String = MINIMUM_RN_VERSION
329
+ }
@@ -0,0 +1,198 @@
1
+ package run.granite.gradle.utils
2
+
3
+ import org.gradle.api.Project
4
+ import org.gradle.api.Task
5
+
6
+ /**
7
+ * Validates task dependency structure for Granite plugin.
8
+ *
9
+ * Ensures that:
10
+ * - Codegen tasks complete before compilation tasks
11
+ * - Autolinking completes before codegen
12
+ * - Bundle tasks complete before per-variant packaging tasks
13
+ * - All tasks have proper input/output relationships
14
+ *
15
+ * This validator prevents build failures caused by incorrect task ordering.
16
+ */
17
+ object TaskDependencyValidator {
18
+
19
+ /**
20
+ * Validates that autolinking completes before codegen.
21
+ *
22
+ * @param project The Gradle project
23
+ * @param autolinkingTask The autolinking task
24
+ * @param codegenTask The codegen task
25
+ */
26
+ fun validateAutolinkingBeforeCodegen(
27
+ project: Project,
28
+ autolinkingTask: Task,
29
+ codegenTask: Task,
30
+ ) {
31
+ if (!codegenTask.dependsOn.contains(autolinkingTask) &&
32
+ !codegenTask.mustRunAfter.getDependencies(codegenTask).contains(autolinkingTask)
33
+ ) {
34
+ project.logger.warn(
35
+ """
36
+ |⚠️ Task dependency warning: Codegen task '${codegenTask.name}' should depend on
37
+ |autolinking task '${autolinkingTask.name}'.
38
+ |
39
+ |This may cause build failures if codegen runs before autolinking completes.
40
+ """.trimMargin(),
41
+ )
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Validates that codegen completes before compilation.
47
+ *
48
+ * @param project The Gradle project
49
+ * @param codegenTask The codegen task
50
+ * @param compileTask The compilation task
51
+ */
52
+ fun validateCodegenBeforeCompilation(
53
+ project: Project,
54
+ codegenTask: Task,
55
+ compileTask: Task,
56
+ ) {
57
+ if (!compileTask.dependsOn.contains(codegenTask) &&
58
+ !compileTask.mustRunAfter.getDependencies(compileTask).contains(codegenTask)
59
+ ) {
60
+ project.logger.warn(
61
+ """
62
+ |⚠️ Task dependency warning: Compilation task '${compileTask.name}' should depend on
63
+ |codegen task '${codegenTask.name}'.
64
+ |
65
+ |This may cause compilation errors if codegen outputs are not available.
66
+ """.trimMargin(),
67
+ )
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Validates that bundle tasks complete before packaging tasks.
73
+ *
74
+ * @param project The Gradle project
75
+ * @param bundleTask The bundle task
76
+ * @param packageTask The packaging task
77
+ */
78
+ fun validateBundleBeforePackaging(
79
+ project: Project,
80
+ bundleTask: Task,
81
+ packageTask: Task,
82
+ ) {
83
+ if (!packageTask.dependsOn.contains(bundleTask) &&
84
+ !packageTask.mustRunAfter.getDependencies(packageTask).contains(bundleTask)
85
+ ) {
86
+ project.logger.warn(
87
+ """
88
+ |⚠️ Task dependency warning: Packaging task '${packageTask.name}' should depend on
89
+ |bundle task '${bundleTask.name}'.
90
+ |
91
+ |This may cause missing bundle assets in the final AAR.
92
+ """.trimMargin(),
93
+ )
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Validates task input/output relationships.
99
+ *
100
+ * Ensures that task outputs are properly declared and consumed by dependent tasks.
101
+ *
102
+ * @param project The Gradle project
103
+ * @param task The task to validate
104
+ */
105
+ fun validateTaskInputsOutputs(project: Project, task: Task) {
106
+ val hasInputs = task.inputs.hasInputs
107
+ val hasOutputs = task.outputs.files.isEmpty.not()
108
+
109
+ if (!hasInputs && !hasOutputs) {
110
+ project.logger.warn(
111
+ """
112
+ |⚠️ Task configuration warning: Task '${task.name}' has no declared inputs or outputs.
113
+ |
114
+ |This prevents Gradle's up-to-date checking and caching from working correctly.
115
+ |Consider declaring task inputs and outputs for better build performance.
116
+ """.trimMargin(),
117
+ )
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Validates that all required tasks are registered.
123
+ *
124
+ * @param project The Gradle project
125
+ * @param requiredTaskNames List of task names that must be registered
126
+ * @return List of missing task names
127
+ */
128
+ fun validateRequiredTasks(project: Project, requiredTaskNames: List<String>): List<String> {
129
+ val missingTasks = mutableListOf<String>()
130
+
131
+ for (taskName in requiredTaskNames) {
132
+ if (project.tasks.findByName(taskName) == null) {
133
+ missingTasks.add(taskName)
134
+ }
135
+ }
136
+
137
+ if (missingTasks.isNotEmpty()) {
138
+ project.logger.warn(
139
+ """
140
+ |⚠️ Task registration warning: Required tasks are missing:
141
+ |${missingTasks.joinToString("\n") { " - $it" }}
142
+ |
143
+ |This may indicate incomplete plugin configuration.
144
+ """.trimMargin(),
145
+ )
146
+ }
147
+
148
+ return missingTasks
149
+ }
150
+
151
+ /**
152
+ * Validates that per-variant tasks only run after shared infrastructure tasks.
153
+ *
154
+ * Ensures codegen and autolinking (shared) complete before variant-specific bundling.
155
+ *
156
+ * @param project The Gradle project
157
+ * @param infrastructureTask Shared infrastructure task (codegen, autolinking)
158
+ * @param variantTask Per-variant task (bundle, package)
159
+ */
160
+ fun validateInfrastructureBeforeVariant(
161
+ project: Project,
162
+ infrastructureTask: Task,
163
+ variantTask: Task,
164
+ ) {
165
+ if (!variantTask.dependsOn.contains(infrastructureTask) &&
166
+ !variantTask.mustRunAfter.getDependencies(variantTask).contains(infrastructureTask)
167
+ ) {
168
+ project.logger.debug(
169
+ """
170
+ |Task dependency info: Variant task '${variantTask.name}' should typically depend on
171
+ |infrastructure task '${infrastructureTask.name}'.
172
+ """.trimMargin(),
173
+ )
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Logs task dependency graph for debugging.
179
+ *
180
+ * @param project The Gradle project
181
+ * @param task The task to log dependencies for
182
+ */
183
+ fun logTaskDependencies(project: Project, task: Task) {
184
+ if (project.logger.isDebugEnabled) {
185
+ val dependencies = task.dependsOn.joinToString(", ") { it.toString() }
186
+ val mustRunAfter = task.mustRunAfter.getDependencies(task)
187
+ .joinToString(", ") { it.name }
188
+
189
+ project.logger.debug(
190
+ """
191
+ |Task: ${task.name}
192
+ | Dependencies: $dependencies
193
+ | Must run after: $mustRunAfter
194
+ """.trimMargin(),
195
+ )
196
+ }
197
+ }
198
+ }