@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.
- package/CHANGELOG.md +7 -0
- package/GraniteScreen.podspec +25 -0
- package/LICENSE +202 -0
- package/android/CMakeLists.txt +62 -0
- package/android/build.gradle +63 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/BundleEvaluator.cpp +27 -0
- package/android/src/main/cpp/BundleEvaluator.h +17 -0
- package/android/src/main/cpp/onLoad.cpp +6 -0
- package/android/src/main/kotlin/run/granite/BundleEvaluator.kt +50 -0
- package/android/src/main/kotlin/run/granite/BundleLoader.kt +40 -0
- package/android/src/main/kotlin/run/granite/DefaultBundleLoader.kt +20 -0
- package/android/src/main/kotlin/run/granite/DefaultErrorView.kt +58 -0
- package/android/src/main/kotlin/run/granite/DefaultLoadingView.kt +51 -0
- package/android/src/main/kotlin/run/granite/GraniteReactDelegate.kt +76 -0
- package/android/src/main/kotlin/run/granite/GraniteReactDelegateImpl.kt +448 -0
- package/android/src/main/kotlin/run/granite/GraniteReactHost.kt +113 -0
- package/android/src/main/kotlin/run/granite/ReactHostFactory.kt +106 -0
- package/gradle-plugin/LICENSE +201 -0
- package/gradle-plugin/README.md +578 -0
- package/gradle-plugin/build.gradle.kts +97 -0
- package/gradle-plugin/gradle/libs.versions.toml +17 -0
- package/gradle-plugin/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/gradle-plugin/gradle.properties +12 -0
- package/gradle-plugin/gradlew +248 -0
- package/gradle-plugin/gradlew.bat +93 -0
- package/gradle-plugin/settings.gradle.kts +1 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteExtension.kt +225 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/GranitePlugin.kt +784 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootExtension.kt +107 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootProjectPlugin.kt +290 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/BuildConfigConfigurator.kt +69 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyConfigurator.kt +232 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyCoordinates.kt +29 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DevServerResourceConfigurator.kt +101 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/JniPackagingConfigurator.kt +160 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/NdkConfigurator.kt +135 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/RepositoryConfigurator.kt +148 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/ResourceConfigurator.kt +56 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CMakeGenerator.kt +105 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CppAutolinkingGenerator.kt +152 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/EntryPointGenerator.kt +100 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AndroidDependencyConfig.kt +23 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AutolinkingConfig.kt +89 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/CMakeEntry.kt +47 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/NativeModule.kt +177 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AssetPackagingTask.kt +194 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AutolinkingTask.kt +431 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/BundleTask.kt +275 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenArtifactsTask.kt +218 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenSchemaTask.kt +186 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/AutolinkingParser.kt +128 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ConflictDetector.kt +121 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/JdkValidator.kt +73 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/NodeExecutableFinder.kt +43 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ReactNativeVersionReader.kt +329 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/TaskDependencyValidator.kt +198 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteExtensionTest.kt +191 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/GranitePluginTest.kt +156 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteRootProjectPluginTest.kt +87 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/BuildConfigConfiguratorTest.kt +115 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DependencyConfiguratorTest.kt +338 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DevServerResourceConfiguratorTest.kt +205 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/ResourceConfiguratorTest.kt +131 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/fixtures/NativeModuleFixtures.kt +67 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CMakeGeneratorTest.kt +71 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CppAutolinkingGeneratorTest.kt +344 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/EntryPointGeneratorTest.kt +40 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/AutolinkingConfigTest.kt +350 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/CMakeEntryTest.kt +200 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/NativeModuleTest.kt +562 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AssetPackagingTaskTest.kt +318 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AutolinkingTaskTest.kt +89 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/BundleTaskTest.kt +68 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/CodegenTasksTest.kt +410 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/AutolinkingParserTest.kt +335 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ConflictDetectorTest.kt +75 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/JdkValidatorTest.kt +88 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ReactNativeVersionReaderTest.kt +585 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskDependencyValidatorTest.kt +123 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskTestUtils.kt +88 -0
- package/gradle-plugin/src/test/resources/fixtures/sample-rn-config.json +45 -0
- package/ios/BundleLoader/BundleEvaluator.h +16 -0
- package/ios/BundleLoader/BundleEvaluator.mm +76 -0
- package/ios/BundleLoader/BundleLoadable.swift +91 -0
- package/ios/GraniteBundleLoaderTypes.swift +7 -0
- package/ios/GraniteScreen.h +12 -0
- package/ios/ReactNativeHosting/DefaultViews.swift +138 -0
- package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.h +24 -0
- package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.mm +22 -0
- package/ios/ReactNativeHosting/GraniteHostingHelper.swift +103 -0
- package/ios/ReactNativeHosting/GraniteNativeFactory.swift +35 -0
- package/ios/ReactNativeHosting/GraniteNativeFactoryDelegateImpl.swift +30 -0
- package/ios/ReactNativeHosting/GraniteNativeFactoryImpl.swift +24 -0
- package/ios/ReactNativeHosting/GraniteReactHost.swift +39 -0
- package/ios/ReactNativeHosting/GraniteScreen-Bridging-Header.h +12 -0
- package/package.json +59 -0
- package/react-native.config.js +8 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
package run.granite.gradle.tasks
|
|
2
|
+
|
|
3
|
+
import org.gradle.api.DefaultTask
|
|
4
|
+
import org.gradle.api.file.DirectoryProperty
|
|
5
|
+
import org.gradle.api.file.RegularFileProperty
|
|
6
|
+
import org.gradle.api.provider.Property
|
|
7
|
+
import org.gradle.api.tasks.*
|
|
8
|
+
import org.gradle.process.ExecOperations
|
|
9
|
+
import run.granite.gradle.utils.NodeExecutableFinder
|
|
10
|
+
import java.io.ByteArrayOutputStream
|
|
11
|
+
import java.io.File
|
|
12
|
+
import javax.inject.Inject
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gradle task for React Native Codegen artifact generation.
|
|
16
|
+
*
|
|
17
|
+
* This task:
|
|
18
|
+
* 1. Reads the schema.json generated by CodegenSchemaTask
|
|
19
|
+
* 2. Executes React Native codegen to generate native code
|
|
20
|
+
* 3. Outputs Java/Kotlin and C++ source files for TurboModules and Fabric components
|
|
21
|
+
*
|
|
22
|
+
* Generated artifacts include:
|
|
23
|
+
* - Java/Kotlin interfaces for TurboModules
|
|
24
|
+
* - JNI C++ bindings
|
|
25
|
+
* - Fabric component descriptors
|
|
26
|
+
*
|
|
27
|
+
* Inputs:
|
|
28
|
+
* - schema.json file
|
|
29
|
+
* - React Native directory (for codegen binary)
|
|
30
|
+
* - Package name for generated Java code
|
|
31
|
+
*
|
|
32
|
+
* Outputs:
|
|
33
|
+
* - Java sources in build/generated/codegen/java/
|
|
34
|
+
* - C++ sources in build/generated/codegen/jni/
|
|
35
|
+
*/
|
|
36
|
+
@CacheableTask
|
|
37
|
+
abstract class CodegenArtifactsTask @Inject constructor(
|
|
38
|
+
private val execOperations: ExecOperations,
|
|
39
|
+
) : DefaultTask() {
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Schema file generated by CodegenSchemaTask.
|
|
43
|
+
*/
|
|
44
|
+
@get:InputFile
|
|
45
|
+
@get:PathSensitive(PathSensitivity.RELATIVE)
|
|
46
|
+
abstract val schemaFile: RegularFileProperty
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* React Native directory containing codegen tools.
|
|
50
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
51
|
+
*/
|
|
52
|
+
@get:Internal
|
|
53
|
+
abstract val reactNativeDir: DirectoryProperty
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Node modules directory.
|
|
57
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
58
|
+
*/
|
|
59
|
+
@get:Internal
|
|
60
|
+
abstract val nodeModulesDir: DirectoryProperty
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Package name for generated Java code.
|
|
64
|
+
*/
|
|
65
|
+
@get:Input
|
|
66
|
+
abstract val packageName: Property<String>
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Library name for generated code.
|
|
70
|
+
*/
|
|
71
|
+
@get:Input
|
|
72
|
+
abstract val libraryName: Property<String>
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Output directory for generated Java sources.
|
|
76
|
+
*/
|
|
77
|
+
@get:OutputDirectory
|
|
78
|
+
abstract val javaOutputDir: DirectoryProperty
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Output directory for generated C++ JNI sources.
|
|
82
|
+
*/
|
|
83
|
+
@get:OutputDirectory
|
|
84
|
+
abstract val jniOutputDir: DirectoryProperty
|
|
85
|
+
|
|
86
|
+
init {
|
|
87
|
+
group = "granite"
|
|
88
|
+
description = "Generates React Native Codegen artifacts (Java and C++)"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@TaskAction
|
|
92
|
+
fun execute() {
|
|
93
|
+
logger.lifecycle("Generating React Native Codegen artifacts...")
|
|
94
|
+
|
|
95
|
+
val schemaFileResolved = schemaFile.get().asFile
|
|
96
|
+
|
|
97
|
+
if (!schemaFileResolved.exists()) {
|
|
98
|
+
error(
|
|
99
|
+
"""
|
|
100
|
+
|Schema file not found: ${schemaFileResolved.absolutePath}
|
|
101
|
+
|
|
|
102
|
+
|The schema should be generated by CodegenSchemaTask.
|
|
103
|
+
|Ensure CodegenSchemaTask runs before CodegenArtifactsTask.
|
|
104
|
+
""".trimMargin(),
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Execute codegen binary
|
|
109
|
+
executeCodegen()
|
|
110
|
+
|
|
111
|
+
logger.lifecycle("Codegen artifacts generated successfully")
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Executes the React Native codegen binary.
|
|
116
|
+
*
|
|
117
|
+
* Implementation details in corresponding task.
|
|
118
|
+
*/
|
|
119
|
+
private fun executeCodegen() {
|
|
120
|
+
val codegenBinary = findCodegenBinary()
|
|
121
|
+
val nodeExecutable = NodeExecutableFinder.findNodeExecutable()
|
|
122
|
+
|
|
123
|
+
if (!codegenBinary.exists()) {
|
|
124
|
+
logger.warn(
|
|
125
|
+
"""
|
|
126
|
+
|React Native codegen binary not found: ${codegenBinary.absolutePath}
|
|
127
|
+
|
|
|
128
|
+
|Skipping codegen generation. This is expected if your library has no TurboModules or Fabric components.
|
|
129
|
+
""".trimMargin(),
|
|
130
|
+
)
|
|
131
|
+
// Create empty output directories
|
|
132
|
+
javaOutputDir.get().asFile.mkdirs()
|
|
133
|
+
jniOutputDir.get().asFile.mkdirs()
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Prepare output directories
|
|
138
|
+
val javaOutputDirFile = javaOutputDir.get().asFile
|
|
139
|
+
val jniOutputDirFile = jniOutputDir.get().asFile
|
|
140
|
+
|
|
141
|
+
javaOutputDirFile.mkdirs()
|
|
142
|
+
jniOutputDirFile.mkdirs()
|
|
143
|
+
|
|
144
|
+
// Execute codegen
|
|
145
|
+
// Command: node path/to/codegen/lib/cli/combine/combine-js-to-schema-cli.js schema-file output-dir
|
|
146
|
+
val command = listOf(
|
|
147
|
+
nodeExecutable.absolutePath,
|
|
148
|
+
codegenBinary.absolutePath,
|
|
149
|
+
"--schema", schemaFile.get().asFile.absolutePath,
|
|
150
|
+
"--javaPackageName", packageName.get(),
|
|
151
|
+
"--libraryName", libraryName.get(),
|
|
152
|
+
"--outputDir", javaOutputDirFile.absolutePath,
|
|
153
|
+
"--jniOutputDir", jniOutputDirFile.absolutePath,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
logger.debug("Executing codegen: ${command.joinToString(" ")}")
|
|
157
|
+
|
|
158
|
+
val stdout = ByteArrayOutputStream()
|
|
159
|
+
val stderr = ByteArrayOutputStream()
|
|
160
|
+
|
|
161
|
+
val result = execOperations.exec {
|
|
162
|
+
commandLine = command
|
|
163
|
+
standardOutput = stdout
|
|
164
|
+
errorOutput = stderr
|
|
165
|
+
isIgnoreExitValue = true
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (result.exitValue != 0) {
|
|
169
|
+
logger.warn(
|
|
170
|
+
"""
|
|
171
|
+
|Codegen execution completed with warnings (exit code: ${result.exitValue})
|
|
172
|
+
|
|
|
173
|
+
|This is expected if your library has no TurboModules or Fabric components.
|
|
174
|
+
|
|
|
175
|
+
|Error output:
|
|
176
|
+
|$stderr
|
|
177
|
+
""".trimMargin(),
|
|
178
|
+
)
|
|
179
|
+
} else {
|
|
180
|
+
logger.debug("Codegen output: $stdout")
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Verify output was generated
|
|
184
|
+
if (javaOutputDirFile.listFiles()?.isEmpty() != false &&
|
|
185
|
+
jniOutputDirFile.listFiles()?.isEmpty() != false
|
|
186
|
+
) {
|
|
187
|
+
logger.lifecycle("No codegen artifacts generated (no TurboModules/Fabric components found)")
|
|
188
|
+
} else {
|
|
189
|
+
logger.lifecycle("Generated Java sources: ${javaOutputDirFile.absolutePath}")
|
|
190
|
+
logger.lifecycle("Generated C++ sources: ${jniOutputDirFile.absolutePath}")
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Finds the React Native codegen binary.
|
|
196
|
+
*/
|
|
197
|
+
private fun findCodegenBinary(): File {
|
|
198
|
+
// Try multiple possible locations
|
|
199
|
+
val possiblePaths = listOf(
|
|
200
|
+
"react-native-codegen/lib/cli/combine/combine-js-to-schema-cli.js",
|
|
201
|
+
"@react-native/codegen/lib/cli/combine/combine-js-to-schema-cli.js",
|
|
202
|
+
"react-native/node_modules/@react-native/codegen/lib/cli/combine/combine-js-to-schema-cli.js",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
val nodeModules = nodeModulesDir.get().asFile
|
|
206
|
+
|
|
207
|
+
for (path in possiblePaths) {
|
|
208
|
+
val codegenFile = nodeModules.resolve(path)
|
|
209
|
+
if (codegenFile.exists()) {
|
|
210
|
+
logger.debug("Found codegen binary: ${codegenFile.absolutePath}")
|
|
211
|
+
return codegenFile
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Return a placeholder file (will be checked for existence later)
|
|
216
|
+
return nodeModules.resolve(possiblePaths.first())
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
package run.granite.gradle.tasks
|
|
2
|
+
|
|
3
|
+
import com.google.gson.Gson
|
|
4
|
+
import com.google.gson.JsonArray
|
|
5
|
+
import com.google.gson.JsonObject
|
|
6
|
+
import org.gradle.api.DefaultTask
|
|
7
|
+
import org.gradle.api.file.DirectoryProperty
|
|
8
|
+
import org.gradle.api.file.RegularFileProperty
|
|
9
|
+
import org.gradle.api.provider.ListProperty
|
|
10
|
+
import org.gradle.api.tasks.*
|
|
11
|
+
import java.io.File
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Gradle task for React Native Codegen schema generation.
|
|
15
|
+
*
|
|
16
|
+
* This task:
|
|
17
|
+
* 1. Scans JavaScript source files for TurboModule and Fabric component specifications
|
|
18
|
+
* 2. Extracts TypeScript/Flow type definitions
|
|
19
|
+
* 3. Generates a unified JSON schema file for codegen
|
|
20
|
+
*
|
|
21
|
+
* The schema is used by CodegenArtifactsTask to generate native code (Java/C++).
|
|
22
|
+
*
|
|
23
|
+
* Inputs:
|
|
24
|
+
* - JavaScript source directories
|
|
25
|
+
* - React Native directory (for codegen tools)
|
|
26
|
+
*
|
|
27
|
+
* Outputs:
|
|
28
|
+
* - schema.json file in build/generated/codegen/schema/
|
|
29
|
+
*/
|
|
30
|
+
@CacheableTask
|
|
31
|
+
abstract class CodegenSchemaTask : DefaultTask() {
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* JavaScript source directories to scan for specs.
|
|
35
|
+
*/
|
|
36
|
+
@get:InputFiles
|
|
37
|
+
@get:PathSensitive(PathSensitivity.RELATIVE)
|
|
38
|
+
abstract val jsSourceDirs: ListProperty<File>
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* React Native directory containing codegen tools.
|
|
42
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
43
|
+
*/
|
|
44
|
+
@get:Internal
|
|
45
|
+
abstract val reactNativeDir: DirectoryProperty
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Node modules directory.
|
|
49
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
50
|
+
*/
|
|
51
|
+
@get:Internal
|
|
52
|
+
abstract val nodeModulesDir: DirectoryProperty
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Output directory for schema file.
|
|
56
|
+
*/
|
|
57
|
+
@get:OutputDirectory
|
|
58
|
+
abstract val outputDir: DirectoryProperty
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generated schema.json file.
|
|
62
|
+
*/
|
|
63
|
+
@get:OutputFile
|
|
64
|
+
abstract val schemaFile: RegularFileProperty
|
|
65
|
+
|
|
66
|
+
init {
|
|
67
|
+
group = "granite"
|
|
68
|
+
description = "Scans JavaScript sources and generates React Native Codegen schema"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@TaskAction
|
|
72
|
+
fun execute() {
|
|
73
|
+
logger.lifecycle("Generating React Native Codegen schema...")
|
|
74
|
+
|
|
75
|
+
// Scan JavaScript files for specs
|
|
76
|
+
val specFiles = scanForSpecFiles()
|
|
77
|
+
|
|
78
|
+
if (specFiles.isEmpty()) {
|
|
79
|
+
logger.warn("No React Native spec files found in source directories")
|
|
80
|
+
// Generate empty schema
|
|
81
|
+
generateEmptySchema()
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
logger.lifecycle("Found ${specFiles.size} spec files")
|
|
86
|
+
|
|
87
|
+
// Generate schema from specs
|
|
88
|
+
generateSchema(specFiles)
|
|
89
|
+
|
|
90
|
+
logger.lifecycle("Codegen schema generated successfully")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Scans JavaScript source directories for spec files.
|
|
95
|
+
*
|
|
96
|
+
* Spec files are identified by patterns:
|
|
97
|
+
* - Native*.js, Native*.ts, Native*.jsx, Native*.tsx (TurboModules)
|
|
98
|
+
* - *NativeComponent.js, *NativeComponent.ts, etc. (Fabric components)
|
|
99
|
+
*
|
|
100
|
+
* Implementation details in corresponding task.
|
|
101
|
+
*/
|
|
102
|
+
private fun scanForSpecFiles(): List<File> {
|
|
103
|
+
val specFiles = mutableListOf<File>()
|
|
104
|
+
|
|
105
|
+
for (sourceDir in jsSourceDirs.get()) {
|
|
106
|
+
if (!sourceDir.exists() || !sourceDir.isDirectory) {
|
|
107
|
+
logger.warn("JavaScript source directory not found: ${sourceDir.absolutePath}")
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
sourceDir.walk()
|
|
112
|
+
.filter { it.isFile }
|
|
113
|
+
.filter { file ->
|
|
114
|
+
val name = file.name
|
|
115
|
+
// TurboModule specs
|
|
116
|
+
(
|
|
117
|
+
name.startsWith("Native") && (
|
|
118
|
+
name.endsWith(".js") || name.endsWith(".ts") ||
|
|
119
|
+
name.endsWith(".jsx") || name.endsWith(".tsx")
|
|
120
|
+
)
|
|
121
|
+
) ||
|
|
122
|
+
// Fabric component specs
|
|
123
|
+
(
|
|
124
|
+
name.contains("NativeComponent") && (
|
|
125
|
+
name.endsWith(".js") || name.endsWith(".ts") ||
|
|
126
|
+
name.endsWith(".jsx") || name.endsWith(".tsx")
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
.forEach { specFiles.add(it) }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return specFiles
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generates JSON schema from spec files.
|
|
138
|
+
*
|
|
139
|
+
* Uses React Native's codegen tools to parse TypeScript/Flow specs.
|
|
140
|
+
*/
|
|
141
|
+
private fun generateSchema(specFiles: List<File>) {
|
|
142
|
+
val outputFile = schemaFile.get().asFile
|
|
143
|
+
outputFile.parentFile.mkdirs()
|
|
144
|
+
|
|
145
|
+
// For a basic implementation, we'll generate a schema structure
|
|
146
|
+
// In production, this should invoke React Native's codegen parser
|
|
147
|
+
val schema = JsonObject()
|
|
148
|
+
schema.addProperty("version", "0.81.0")
|
|
149
|
+
|
|
150
|
+
val modules = JsonObject()
|
|
151
|
+
|
|
152
|
+
for (specFile in specFiles) {
|
|
153
|
+
val moduleName = specFile.nameWithoutExtension
|
|
154
|
+
val moduleSpec = JsonObject()
|
|
155
|
+
moduleSpec.addProperty("type", "NativeModule")
|
|
156
|
+
moduleSpec.addProperty("spec", specFile.absolutePath)
|
|
157
|
+
|
|
158
|
+
modules.add(moduleName, moduleSpec)
|
|
159
|
+
|
|
160
|
+
logger.debug("Added module to schema: $moduleName")
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
schema.add("modules", modules)
|
|
164
|
+
|
|
165
|
+
// Write schema to file
|
|
166
|
+
outputFile.writeText(Gson().toJson(schema))
|
|
167
|
+
|
|
168
|
+
logger.lifecycle("Schema written to: ${outputFile.absolutePath}")
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generates an empty schema when no spec files are found.
|
|
173
|
+
*/
|
|
174
|
+
private fun generateEmptySchema() {
|
|
175
|
+
val outputFile = schemaFile.get().asFile
|
|
176
|
+
outputFile.parentFile.mkdirs()
|
|
177
|
+
|
|
178
|
+
val emptySchema = JsonObject()
|
|
179
|
+
emptySchema.addProperty("version", "0.81.0")
|
|
180
|
+
emptySchema.add("modules", JsonObject())
|
|
181
|
+
|
|
182
|
+
outputFile.writeText(Gson().toJson(emptySchema))
|
|
183
|
+
|
|
184
|
+
logger.debug("Empty schema written to: ${outputFile.absolutePath}")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
package run.granite.gradle.utils
|
|
2
|
+
|
|
3
|
+
import com.google.gson.Gson
|
|
4
|
+
import com.google.gson.JsonSyntaxException
|
|
5
|
+
import run.granite.gradle.models.*
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parses react-native config JSON output into typed AutolinkingConfig models.
|
|
9
|
+
*
|
|
10
|
+
* Implements fail-fast error handling:
|
|
11
|
+
* - Throws IllegalArgumentException for malformed JSON with descriptive messages
|
|
12
|
+
* - Follows error format: "[Context] Failed: [Reason]. [Remediation]"
|
|
13
|
+
*/
|
|
14
|
+
object AutolinkingParser {
|
|
15
|
+
private val gson = Gson()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parses react-native config JSON string into AutolinkingConfig.
|
|
19
|
+
*
|
|
20
|
+
* @param json JSON string from react-native config command output
|
|
21
|
+
* @return Parsed AutolinkingConfig with validated models
|
|
22
|
+
* @throws IllegalArgumentException if JSON is malformed or missing required fields
|
|
23
|
+
*/
|
|
24
|
+
fun parse(json: String): AutolinkingConfig {
|
|
25
|
+
try {
|
|
26
|
+
// Parse JSON into intermediate map structure
|
|
27
|
+
@Suppress("UNCHECKED_CAST")
|
|
28
|
+
val root = gson.fromJson(json, Map::class.java) as Map<String, Any>
|
|
29
|
+
|
|
30
|
+
// Extract project info
|
|
31
|
+
val projectMap = root["project"] as? Map<String, Any>
|
|
32
|
+
?: throw IllegalArgumentException(
|
|
33
|
+
"react-native config: Failed to parse JSON - missing 'project' field. " +
|
|
34
|
+
"Ensure react-native CLI is properly configured.",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
val project = parseProjectInfo(projectMap)
|
|
38
|
+
|
|
39
|
+
// Extract dependencies
|
|
40
|
+
@Suppress("UNCHECKED_CAST")
|
|
41
|
+
val dependenciesMap = root["dependencies"] as? Map<String, Any> ?: emptyMap()
|
|
42
|
+
val dependencies = parseDependencies(dependenciesMap as Map<String, Map<String, Any>>)
|
|
43
|
+
|
|
44
|
+
return AutolinkingConfig(
|
|
45
|
+
project = project,
|
|
46
|
+
dependencies = dependencies,
|
|
47
|
+
)
|
|
48
|
+
} catch (e: JsonSyntaxException) {
|
|
49
|
+
throw IllegalArgumentException(
|
|
50
|
+
"react-native config: Failed to parse JSON - syntax error. " +
|
|
51
|
+
"Verify command output is valid JSON. Error: ${e.message}",
|
|
52
|
+
e,
|
|
53
|
+
)
|
|
54
|
+
} catch (e: ClassCastException) {
|
|
55
|
+
throw IllegalArgumentException(
|
|
56
|
+
"react-native config: Failed to parse JSON - unexpected structure. " +
|
|
57
|
+
"Ensure react-native CLI version is compatible. Error: ${e.message}",
|
|
58
|
+
e,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private fun parseProjectInfo(map: Map<String, Any>): ProjectInfo {
|
|
64
|
+
@Suppress("UNCHECKED_CAST")
|
|
65
|
+
val androidMap = map["android"] as? Map<String, Any>
|
|
66
|
+
|
|
67
|
+
return ProjectInfo(
|
|
68
|
+
name = map["name"] as? String,
|
|
69
|
+
version = map["version"] as? String,
|
|
70
|
+
ios = map["ios"] as? Map<String, Any>,
|
|
71
|
+
android = androidMap?.let { parseAndroidProjectConfig(it) },
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private fun parseAndroidProjectConfig(map: Map<String, Any>): AndroidProjectConfig = AndroidProjectConfig(
|
|
76
|
+
sourceDir = map["sourceDir"] as? String,
|
|
77
|
+
manifestPath = map["manifestPath"] as? String,
|
|
78
|
+
packageName = map["packageName"] as? String,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
private fun parseDependencies(map: Map<String, Map<String, Any>>): Map<String, DependencyConfig> = map.mapValues { (name, depMap) ->
|
|
82
|
+
parseDependencyConfig(name, depMap)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private fun parseDependencyConfig(name: String, map: Map<String, Any>): DependencyConfig {
|
|
86
|
+
@Suppress("UNCHECKED_CAST")
|
|
87
|
+
val platformsMap = map["platforms"] as? Map<String, Any>
|
|
88
|
+
|
|
89
|
+
return DependencyConfig(
|
|
90
|
+
name = name,
|
|
91
|
+
root = map["root"] as? String ?: "",
|
|
92
|
+
platforms = platformsMap?.let { parsePlatformConfig(it) },
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private fun parsePlatformConfig(map: Map<String, Any>): PlatformConfig {
|
|
97
|
+
@Suppress("UNCHECKED_CAST")
|
|
98
|
+
val androidMap = map["android"] as? Map<String, Any>
|
|
99
|
+
|
|
100
|
+
return PlatformConfig(
|
|
101
|
+
ios = map["ios"] as? Map<String, Any>,
|
|
102
|
+
android = androidMap?.let { parseAndroidDependencyConfig(it) },
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private fun parseAndroidDependencyConfig(map: Map<String, Any>): AndroidDependencyConfig {
|
|
107
|
+
@Suppress("UNCHECKED_CAST")
|
|
108
|
+
val componentDescriptorsList = map["componentDescriptors"] as? List<String>
|
|
109
|
+
|
|
110
|
+
@Suppress("UNCHECKED_CAST")
|
|
111
|
+
val buildTypesList = map["buildTypes"] as? List<String>
|
|
112
|
+
|
|
113
|
+
return AndroidDependencyConfig(
|
|
114
|
+
sourceDir = map["sourceDir"] as? String,
|
|
115
|
+
packageImportPath = map["packageImportPath"] as? String,
|
|
116
|
+
packageInstance = map["packageInstance"] as? String,
|
|
117
|
+
dependencyConfiguration = map["dependencyConfiguration"] as? String,
|
|
118
|
+
buildTypes = buildTypesList,
|
|
119
|
+
libraryName = map["libraryName"] as? String,
|
|
120
|
+
componentDescriptors = componentDescriptorsList,
|
|
121
|
+
cmakeListsPath = map["cmakeListsPath"] as? String,
|
|
122
|
+
cxxModuleCMakeListsPath = map["cxxModuleCMakeListsPath"] as? String,
|
|
123
|
+
cxxModuleCMakeListsModuleName = map["cxxModuleCMakeListsModuleName"] as? String,
|
|
124
|
+
cxxModuleHeaderName = map["cxxModuleHeaderName"] as? String,
|
|
125
|
+
isPureCxxDependency = map["isPureCxxDependency"] as? Boolean,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
package run.granite.gradle.utils
|
|
2
|
+
|
|
3
|
+
import org.gradle.api.Project
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detects conflicts with the standard React Native Gradle Plugin.
|
|
7
|
+
*
|
|
8
|
+
* The Granite plugin cannot coexist with the unpatched React Native Gradle Plugin
|
|
9
|
+
* because both configure React Native build processes (codegen, autolinking, bundling).
|
|
10
|
+
* Only one library module per dependency tree should use the Granite plugin.
|
|
11
|
+
*/
|
|
12
|
+
object ConflictDetector {
|
|
13
|
+
|
|
14
|
+
private const val REACT_NATIVE_PLUGIN_ID = "com.facebook.react"
|
|
15
|
+
private const val REACT_NATIVE_APP_PLUGIN_ID = "com.facebook.react.application"
|
|
16
|
+
private const val REACT_NATIVE_LIBRARY_PLUGIN_ID = "com.facebook.react.library"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates that the React Native Gradle Plugin is not applied to the same project.
|
|
20
|
+
*
|
|
21
|
+
* @param project The Gradle project
|
|
22
|
+
* @throws IllegalStateException if conflicting plugin is detected
|
|
23
|
+
*/
|
|
24
|
+
fun validateNoConflicts(project: Project) {
|
|
25
|
+
// Check if React Native plugin is applied
|
|
26
|
+
val hasReactPlugin = project.pluginManager.hasPlugin(REACT_NATIVE_PLUGIN_ID)
|
|
27
|
+
val hasReactAppPlugin = project.pluginManager.hasPlugin(REACT_NATIVE_APP_PLUGIN_ID)
|
|
28
|
+
val hasReactLibraryPlugin = project.pluginManager.hasPlugin(REACT_NATIVE_LIBRARY_PLUGIN_ID)
|
|
29
|
+
|
|
30
|
+
if (hasReactPlugin || hasReactAppPlugin || hasReactLibraryPlugin) {
|
|
31
|
+
val detectedPlugin = when {
|
|
32
|
+
hasReactPlugin -> REACT_NATIVE_PLUGIN_ID
|
|
33
|
+
hasReactAppPlugin -> REACT_NATIVE_APP_PLUGIN_ID
|
|
34
|
+
else -> REACT_NATIVE_LIBRARY_PLUGIN_ID
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
error(
|
|
38
|
+
"""
|
|
39
|
+
|Granite plugin cannot coexist with the React Native Gradle Plugin.
|
|
40
|
+
|
|
|
41
|
+
|Detected conflicting plugin: $detectedPlugin
|
|
42
|
+
|Project: ${project.path}
|
|
43
|
+
|
|
|
44
|
+
|The Granite plugin provides its own implementation of React Native build tasks
|
|
45
|
+
|(codegen, autolinking, bundling) and cannot work alongside the standard plugin.
|
|
46
|
+
|
|
|
47
|
+
|Solutions:
|
|
48
|
+
| 1. Remove the React Native plugin from your library module:
|
|
49
|
+
| Remove: id("$detectedPlugin")
|
|
50
|
+
| Keep: id("run.granite.library")
|
|
51
|
+
|
|
|
52
|
+
| 2. If you need both plugins in your project:
|
|
53
|
+
| - Use Granite plugin ONLY in the library module that packages React Native
|
|
54
|
+
| - Application modules should NOT use either plugin
|
|
55
|
+
| - Only ONE library module per dependency tree can use Granite plugin
|
|
56
|
+
|
|
|
57
|
+
|Example (library module build.gradle.kts):
|
|
58
|
+
| plugins {
|
|
59
|
+
| id("com.android.library")
|
|
60
|
+
| id("run.granite.library") // Use Granite, not React Native plugin
|
|
61
|
+
| }
|
|
62
|
+
|
|
|
63
|
+
|Example (app module build.gradle.kts):
|
|
64
|
+
| dependencies {
|
|
65
|
+
| implementation(project(":your-library-module"))
|
|
66
|
+
| // No React Native plugin needed - library provides everything
|
|
67
|
+
| }
|
|
68
|
+
""".trimMargin(),
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Warn if multiple library modules might be using Granite plugin
|
|
73
|
+
// (This is detected by checking if dependencies include other Granite-enabled modules)
|
|
74
|
+
detectMultipleGraniteModules(project)
|
|
75
|
+
|
|
76
|
+
project.logger.lifecycle("Granite plugin: Conflict detection passed")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Detects if multiple library modules in the dependency tree are using Granite plugin.
|
|
81
|
+
*
|
|
82
|
+
* This is a warning, not an error, because it may be intentional in some cases.
|
|
83
|
+
*/
|
|
84
|
+
private fun detectMultipleGraniteModules(project: Project) {
|
|
85
|
+
// Check if any dependencies also apply the Granite plugin
|
|
86
|
+
val graniteModuleCount = project.rootProject.subprojects.count { subproject ->
|
|
87
|
+
subproject.pluginManager.hasPlugin("run.granite.library")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (graniteModuleCount > 1) {
|
|
91
|
+
project.logger.warn(
|
|
92
|
+
"""
|
|
93
|
+
|
|
|
94
|
+
|⚠️ WARNING: Multiple modules in this project are using the Granite plugin.
|
|
95
|
+
|
|
|
96
|
+
|Detected $graniteModuleCount modules with 'run.granite.library' plugin applied.
|
|
97
|
+
|
|
|
98
|
+
|Best practice: Only ONE library module per dependency tree should use Granite plugin.
|
|
99
|
+
|Multiple Granite modules can cause:
|
|
100
|
+
| - Duplicate React Native initialization
|
|
101
|
+
| - Conflicting native module registration
|
|
102
|
+
| - Increased AAR size
|
|
103
|
+
| - Build performance issues
|
|
104
|
+
|
|
|
105
|
+
|If this is intentional, ensure the modules are not in the same dependency tree.
|
|
106
|
+
|
|
|
107
|
+
""".trimMargin(),
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Checks if a project has the React Native plugin applied.
|
|
114
|
+
*
|
|
115
|
+
* @param project The Gradle project to check
|
|
116
|
+
* @return true if React Native plugin is applied, false otherwise
|
|
117
|
+
*/
|
|
118
|
+
fun hasReactNativePlugin(project: Project): Boolean = project.pluginManager.hasPlugin(REACT_NATIVE_PLUGIN_ID) ||
|
|
119
|
+
project.pluginManager.hasPlugin(REACT_NATIVE_APP_PLUGIN_ID) ||
|
|
120
|
+
project.pluginManager.hasPlugin(REACT_NATIVE_LIBRARY_PLUGIN_ID)
|
|
121
|
+
}
|