@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,431 @@
|
|
|
1
|
+
package run.granite.gradle.tasks
|
|
2
|
+
|
|
3
|
+
import com.google.gson.Gson
|
|
4
|
+
import com.google.gson.JsonObject
|
|
5
|
+
import org.gradle.api.DefaultTask
|
|
6
|
+
import org.gradle.api.file.DirectoryProperty
|
|
7
|
+
import org.gradle.api.file.RegularFileProperty
|
|
8
|
+
import org.gradle.api.provider.Property
|
|
9
|
+
import org.gradle.api.tasks.*
|
|
10
|
+
import org.gradle.process.ExecOperations
|
|
11
|
+
import run.granite.gradle.generators.CMakeGenerator
|
|
12
|
+
import run.granite.gradle.generators.CppAutolinkingGenerator
|
|
13
|
+
import run.granite.gradle.generators.EntryPointGenerator
|
|
14
|
+
import run.granite.gradle.models.AutolinkingConfig
|
|
15
|
+
import run.granite.gradle.models.NativeModule
|
|
16
|
+
import run.granite.gradle.utils.AutolinkingParser
|
|
17
|
+
import run.granite.gradle.utils.NodeExecutableFinder
|
|
18
|
+
import java.io.ByteArrayOutputStream
|
|
19
|
+
import java.io.File
|
|
20
|
+
import javax.inject.Inject
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Gradle task for React Native autolinking.
|
|
24
|
+
*
|
|
25
|
+
* This task:
|
|
26
|
+
* 1. Executes `react-native config` command to discover native modules
|
|
27
|
+
* 2. Parses the autolinking configuration JSON
|
|
28
|
+
* 3. Generates PackageList.kt file with all discovered React Native packages
|
|
29
|
+
*
|
|
30
|
+
* The generated PackageList.kt is used by the React Native initialization code
|
|
31
|
+
* to automatically register all native modules without manual configuration.
|
|
32
|
+
*
|
|
33
|
+
* Inputs:
|
|
34
|
+
* - React Native CLI path
|
|
35
|
+
* - Node modules directory
|
|
36
|
+
* - Project root directory
|
|
37
|
+
*
|
|
38
|
+
* Outputs:
|
|
39
|
+
* - PackageList.kt file in build/generated/autolinking/
|
|
40
|
+
*/
|
|
41
|
+
@CacheableTask
|
|
42
|
+
abstract class AutolinkingTask @Inject constructor(
|
|
43
|
+
private val execOperations: ExecOperations,
|
|
44
|
+
) : DefaultTask() {
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* React Native directory containing the CLI.
|
|
48
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
49
|
+
*/
|
|
50
|
+
@get:Internal
|
|
51
|
+
abstract val reactNativeDir: DirectoryProperty
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Node modules directory for dependency resolution.
|
|
55
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
56
|
+
*/
|
|
57
|
+
@get:Internal
|
|
58
|
+
abstract val nodeModulesDir: DirectoryProperty
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Project root directory.
|
|
62
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
63
|
+
*/
|
|
64
|
+
@get:Internal
|
|
65
|
+
abstract val projectDir: DirectoryProperty
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Output directory for generated PackageList.kt.
|
|
69
|
+
*/
|
|
70
|
+
@get:OutputDirectory
|
|
71
|
+
abstract val outputDir: DirectoryProperty
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generated PackageList.kt file.
|
|
75
|
+
*/
|
|
76
|
+
@get:OutputFile
|
|
77
|
+
abstract val packageListFile: RegularFileProperty
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Package name for the generated PackageList class.
|
|
81
|
+
*/
|
|
82
|
+
@get:Input
|
|
83
|
+
abstract val packageName: Property<String>
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Output directory for generated JNI autolinking files.
|
|
87
|
+
*/
|
|
88
|
+
@get:OutputDirectory
|
|
89
|
+
abstract val jniOutputDir: DirectoryProperty
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generated autolinking.h header file.
|
|
93
|
+
*/
|
|
94
|
+
@get:OutputFile
|
|
95
|
+
abstract val autolinkingHeaderFile: RegularFileProperty
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generated autolinking.cpp implementation file.
|
|
99
|
+
*/
|
|
100
|
+
@get:OutputFile
|
|
101
|
+
abstract val autolinkingCppFile: RegularFileProperty
|
|
102
|
+
|
|
103
|
+
init {
|
|
104
|
+
group = "granite"
|
|
105
|
+
description = "Executes React Native autolinking and generates PackageList.kt"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@TaskAction
|
|
109
|
+
fun execute() {
|
|
110
|
+
logger.lifecycle("Running React Native autolinking...")
|
|
111
|
+
|
|
112
|
+
// Execute react-native config once and reuse results
|
|
113
|
+
val configJson = executeReactNativeConfig()
|
|
114
|
+
|
|
115
|
+
// Parse configuration using AutolinkingParser
|
|
116
|
+
val autolinkingConfig = try {
|
|
117
|
+
AutolinkingParser.parse(configJson)
|
|
118
|
+
} catch (e: IllegalArgumentException) {
|
|
119
|
+
// Fail-fast with descriptive error message
|
|
120
|
+
throw IllegalArgumentException(
|
|
121
|
+
"react-native config: Failed to parse output. ${e.message}",
|
|
122
|
+
e,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
val modules = autolinkingConfig.androidDependencies()
|
|
127
|
+
val javaModules = autolinkingConfig.javaModules()
|
|
128
|
+
|
|
129
|
+
// Generate all autolinking files in a single task execution
|
|
130
|
+
generatePackageList(javaModules)
|
|
131
|
+
generateJniAutolinking(modules, autolinkingConfig)
|
|
132
|
+
|
|
133
|
+
logger.lifecycle("Autolinking complete. Found ${modules.size} native modules (${javaModules.size} Java, ${autolinkingConfig.cxxModules().size} C++, ${autolinkingConfig.fabricModules().size} Fabric).")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Executes `react-native config` command and returns the JSON output.
|
|
138
|
+
*
|
|
139
|
+
* Implementation details in corresponding task.
|
|
140
|
+
*/
|
|
141
|
+
private fun executeReactNativeConfig(): String {
|
|
142
|
+
val cliPath = reactNativeDir.get().file("cli.js").asFile
|
|
143
|
+
val nodeExecutable = NodeExecutableFinder.findNodeExecutable()
|
|
144
|
+
|
|
145
|
+
if (!cliPath.exists()) {
|
|
146
|
+
error(
|
|
147
|
+
"""
|
|
148
|
+
|React Native CLI not found: ${cliPath.absolutePath}
|
|
149
|
+
|
|
|
150
|
+
|Solution: Run 'npm install' or 'yarn install' to install React Native
|
|
151
|
+
""".trimMargin(),
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Execute: node path/to/react-native/cli.js config
|
|
156
|
+
val command = listOf(
|
|
157
|
+
nodeExecutable.absolutePath,
|
|
158
|
+
cliPath.absolutePath,
|
|
159
|
+
"config",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
val stdout = ByteArrayOutputStream()
|
|
163
|
+
val stderr = ByteArrayOutputStream()
|
|
164
|
+
|
|
165
|
+
val result = execOperations.exec {
|
|
166
|
+
workingDir = projectDir.get().asFile
|
|
167
|
+
commandLine = command
|
|
168
|
+
standardOutput = stdout
|
|
169
|
+
errorOutput = stderr
|
|
170
|
+
isIgnoreExitValue = true
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (result.exitValue != 0) {
|
|
174
|
+
error(
|
|
175
|
+
"""
|
|
176
|
+
|Failed to execute react-native config command.
|
|
177
|
+
|
|
|
178
|
+
|Command: ${command.joinToString(" ")}
|
|
179
|
+
|Exit code: ${result.exitValue}
|
|
180
|
+
|Error output:
|
|
181
|
+
|$stderr
|
|
182
|
+
""".trimMargin(),
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return stdout.toString()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Interpolates dynamic values (BuildConfig, R) in package instances.
|
|
191
|
+
* Replaces non-FQDN references with fully qualified package names.
|
|
192
|
+
*/
|
|
193
|
+
private fun interpolateDynamicValues(input: String, pkgName: String): String = input.replace(Regex("([^.\\w])(BuildConfig|R)(\\W)")) { match ->
|
|
194
|
+
val (prefix, className, suffix) = match.destructured
|
|
195
|
+
"${prefix}$pkgName.${className}$suffix"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Composes package import statements from native modules.
|
|
200
|
+
* Filters out pure C++ dependencies.
|
|
201
|
+
*/
|
|
202
|
+
private fun composePackageImports(modules: List<run.granite.gradle.models.NativeModule>): String = modules
|
|
203
|
+
.filter { !it.isPureCxxDependency }
|
|
204
|
+
.mapNotNull { module ->
|
|
205
|
+
module.packageImportPath?.let { importPath ->
|
|
206
|
+
val cleanImportPath = importPath
|
|
207
|
+
.removePrefix("import ")
|
|
208
|
+
.removeSuffix(";")
|
|
209
|
+
.trim()
|
|
210
|
+
interpolateDynamicValues("import $cleanImportPath;", packageName.get())
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
.joinToString("\n")
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Composes package instance code for getPackages() method.
|
|
217
|
+
* Filters out pure C++ dependencies.
|
|
218
|
+
*/
|
|
219
|
+
private fun composePackageInstance(modules: List<run.granite.gradle.models.NativeModule>): String = modules
|
|
220
|
+
.filter { !it.isPureCxxDependency }
|
|
221
|
+
.mapIndexed { index, module ->
|
|
222
|
+
val instanceCode = interpolateDynamicValues(
|
|
223
|
+
module.packageInstance ?: "new UnknownPackage()",
|
|
224
|
+
packageName.get(),
|
|
225
|
+
)
|
|
226
|
+
if (index == 0) ",\n $instanceCode" else ",\n $instanceCode"
|
|
227
|
+
}
|
|
228
|
+
.joinToString("")
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generates PackageList.java file from discovered Java packages.
|
|
232
|
+
*/
|
|
233
|
+
private fun generatePackageList(modules: List<run.granite.gradle.models.NativeModule>) {
|
|
234
|
+
val outputFile = packageListFile.get().asFile
|
|
235
|
+
outputFile.parentFile.mkdirs()
|
|
236
|
+
|
|
237
|
+
val packageImports = composePackageImports(modules)
|
|
238
|
+
val packageInstances = composePackageInstance(modules)
|
|
239
|
+
|
|
240
|
+
val packageListContent = """
|
|
241
|
+
// Generated by Granite Gradle Plugin - DO NOT EDIT
|
|
242
|
+
// This file is automatically generated during the build process
|
|
243
|
+
package com.facebook.react;
|
|
244
|
+
|
|
245
|
+
import android.app.Application;
|
|
246
|
+
import android.content.Context;
|
|
247
|
+
import android.content.res.Resources;
|
|
248
|
+
|
|
249
|
+
import com.facebook.react.ReactPackage;
|
|
250
|
+
import com.facebook.react.ReactNativeHost;
|
|
251
|
+
import com.facebook.react.shell.MainPackageConfig;
|
|
252
|
+
import com.facebook.react.shell.MainReactPackage;
|
|
253
|
+
import java.util.Arrays;
|
|
254
|
+
import java.util.ArrayList;
|
|
255
|
+
|
|
256
|
+
$packageImports
|
|
257
|
+
|
|
258
|
+
@SuppressWarnings("deprecation")
|
|
259
|
+
public class PackageList {
|
|
260
|
+
private Application application;
|
|
261
|
+
private ReactNativeHost reactNativeHost;
|
|
262
|
+
private MainPackageConfig mConfig;
|
|
263
|
+
|
|
264
|
+
public PackageList(ReactNativeHost reactNativeHost) {
|
|
265
|
+
this(reactNativeHost, null);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public PackageList(Application application) {
|
|
269
|
+
this(application, null);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public PackageList(ReactNativeHost reactNativeHost, MainPackageConfig config) {
|
|
273
|
+
this.reactNativeHost = reactNativeHost;
|
|
274
|
+
mConfig = config;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
public PackageList(Application application, MainPackageConfig config) {
|
|
278
|
+
this.reactNativeHost = null;
|
|
279
|
+
this.application = application;
|
|
280
|
+
mConfig = config;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private ReactNativeHost getReactNativeHost() {
|
|
284
|
+
return this.reactNativeHost;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private Resources getResources() {
|
|
288
|
+
return this.getApplication().getResources();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private Application getApplication() {
|
|
292
|
+
if (this.reactNativeHost == null) return this.application;
|
|
293
|
+
return this.reactNativeHost.getApplication();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private Context getApplicationContext() {
|
|
297
|
+
return this.getApplication().getApplicationContext();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public ArrayList<ReactPackage> getPackages() {
|
|
301
|
+
return new ArrayList<>(Arrays.<ReactPackage>asList(
|
|
302
|
+
new MainReactPackage(mConfig)$packageInstances
|
|
303
|
+
));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
""".trimIndent()
|
|
307
|
+
|
|
308
|
+
outputFile.writeText(packageListContent)
|
|
309
|
+
logger.lifecycle("Generated PackageList.java: ${outputFile.absolutePath}")
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Generates JNI autolinking files (autolinking.h, autolinking.cpp, Android-autolinking.cmake)
|
|
314
|
+
* and Entry Point (ReactNativeApplicationEntryPoint.java).
|
|
315
|
+
*/
|
|
316
|
+
private fun generateJniAutolinking(
|
|
317
|
+
modules: List<NativeModule>,
|
|
318
|
+
config: AutolinkingConfig,
|
|
319
|
+
) {
|
|
320
|
+
val jniDir = jniOutputDir.get().asFile
|
|
321
|
+
val javaOutputDir = outputDir.get().asFile
|
|
322
|
+
val projectRoot = projectDir.get().asFile
|
|
323
|
+
|
|
324
|
+
// Generate autolinking.h (header with forward declarations)
|
|
325
|
+
generateAutolinkingHeader(modules)
|
|
326
|
+
|
|
327
|
+
// Generate autolinking.cpp using CppAutolinkingGenerator
|
|
328
|
+
CppAutolinkingGenerator.generateToFile(modules, jniDir)
|
|
329
|
+
logger.lifecycle("Generated autolinking.cpp with ${modules.filter { it.needsCppAutolinking }.size} modules")
|
|
330
|
+
|
|
331
|
+
// Generate Android-autolinking.cmake using CMakeGenerator
|
|
332
|
+
val cmakeModules = modules.filter { it.hasCMakeConfiguration }
|
|
333
|
+
if (cmakeModules.isNotEmpty()) {
|
|
334
|
+
CMakeGenerator.generateToFile(modules, projectRoot, jniDir)
|
|
335
|
+
logger.lifecycle("Generated Android-autolinking.cmake with ${cmakeModules.size} CMake modules")
|
|
336
|
+
} else {
|
|
337
|
+
logger.lifecycle("Skipped Android-autolinking.cmake generation - no CMake modules found")
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Generate ReactNativeApplicationEntryPoint.java using EntryPointGenerator
|
|
341
|
+
val packageName = config.project.android?.packageName
|
|
342
|
+
try {
|
|
343
|
+
EntryPointGenerator.generateToFile(packageName, javaOutputDir)
|
|
344
|
+
logger.lifecycle("Generated ReactNativeApplicationEntryPoint.java")
|
|
345
|
+
} catch (e: IllegalStateException) {
|
|
346
|
+
// Package name validation error
|
|
347
|
+
logger.warn("Skipped entry point generation: ${e.message}")
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
logger.lifecycle("Generated all autolinking files in: ${jniDir.absolutePath}")
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Generates autolinking.h header file with forward declarations.
|
|
355
|
+
*/
|
|
356
|
+
private fun generateAutolinkingHeader(modules: List<run.granite.gradle.models.NativeModule>) {
|
|
357
|
+
val headerFile = autolinkingHeaderFile.get().asFile
|
|
358
|
+
headerFile.parentFile.mkdirs()
|
|
359
|
+
|
|
360
|
+
val cxxModules = modules.filter { it.hasCxxImplementation }
|
|
361
|
+
val javaModules = modules.filter { it.hasJavaImplementation }
|
|
362
|
+
|
|
363
|
+
val headerContent = buildString {
|
|
364
|
+
appendLine("// Generated by Granite Gradle Plugin - DO NOT EDIT")
|
|
365
|
+
appendLine("// This file is automatically generated during the build process")
|
|
366
|
+
appendLine()
|
|
367
|
+
appendLine("#pragma once")
|
|
368
|
+
appendLine()
|
|
369
|
+
appendLine("#include <memory>")
|
|
370
|
+
appendLine("#include <string>")
|
|
371
|
+
appendLine()
|
|
372
|
+
appendLine("#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>")
|
|
373
|
+
appendLine("#include <react/bridging/Bridging.h>")
|
|
374
|
+
appendLine("#include <ReactCommon/CallInvokerHolder.h>")
|
|
375
|
+
appendLine("#include <ReactCommon/TurboModule.h>")
|
|
376
|
+
appendLine("#include <ReactCommon/JavaTurboModule.h>")
|
|
377
|
+
appendLine()
|
|
378
|
+
appendLine("namespace facebook::react {")
|
|
379
|
+
appendLine()
|
|
380
|
+
|
|
381
|
+
// Forward declarations for C++ TurboModule providers
|
|
382
|
+
if (cxxModules.isNotEmpty()) {
|
|
383
|
+
appendLine("// Forward declarations for C++ TurboModule providers")
|
|
384
|
+
cxxModules.forEach { module ->
|
|
385
|
+
module.cxxModuleHeaderName?.let { headerName ->
|
|
386
|
+
appendLine("class $headerName;")
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
appendLine()
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Forward declarations for Java TurboModule providers
|
|
393
|
+
if (javaModules.isNotEmpty()) {
|
|
394
|
+
appendLine("// Forward declarations for Java TurboModule providers")
|
|
395
|
+
javaModules.forEach { module ->
|
|
396
|
+
module.libraryName?.let { libraryName ->
|
|
397
|
+
appendLine("std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(")
|
|
398
|
+
appendLine(" const std::string& moduleName,")
|
|
399
|
+
appendLine(" const JavaTurboModule::InitParams& params);")
|
|
400
|
+
appendLine()
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
appendLine("/**")
|
|
406
|
+
appendLine(" * Registers Fabric component providers from autolinked libraries.")
|
|
407
|
+
appendLine(" */")
|
|
408
|
+
appendLine("void autolinking_registerProviders(")
|
|
409
|
+
appendLine(" std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);")
|
|
410
|
+
appendLine()
|
|
411
|
+
appendLine("/**")
|
|
412
|
+
appendLine(" * Provides C++ TurboModule from autolinked libraries.")
|
|
413
|
+
appendLine(" */")
|
|
414
|
+
appendLine("std::shared_ptr<TurboModule> autolinking_cxxModuleProvider(")
|
|
415
|
+
appendLine(" const std::string& name,")
|
|
416
|
+
appendLine(" const std::shared_ptr<CallInvoker>& jsInvoker);")
|
|
417
|
+
appendLine()
|
|
418
|
+
appendLine("/**")
|
|
419
|
+
appendLine(" * Provides Java TurboModule from autolinked libraries.")
|
|
420
|
+
appendLine(" */")
|
|
421
|
+
appendLine("std::shared_ptr<TurboModule> autolinking_ModuleProvider(")
|
|
422
|
+
appendLine(" const std::string& name,")
|
|
423
|
+
appendLine(" const JavaTurboModule::InitParams& params);")
|
|
424
|
+
appendLine()
|
|
425
|
+
appendLine("} // namespace facebook::react")
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
headerFile.writeText(headerContent)
|
|
429
|
+
logger.debug("Generated autolinking.h: ${headerFile.absolutePath}")
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
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 generating React Native JavaScript bundles.
|
|
16
|
+
*
|
|
17
|
+
* This task:
|
|
18
|
+
* 1. Executes Metro bundler to bundle JavaScript code
|
|
19
|
+
* 2. Compiles the bundle to Hermes bytecode (always enabled)
|
|
20
|
+
* 3. Handles dev vs release build configurations
|
|
21
|
+
* 4. Generates source maps for debugging
|
|
22
|
+
*/
|
|
23
|
+
@CacheableTask
|
|
24
|
+
abstract class BundleTask @Inject constructor(
|
|
25
|
+
private val execOperations: ExecOperations,
|
|
26
|
+
) : DefaultTask() {
|
|
27
|
+
|
|
28
|
+
@get:InputFile
|
|
29
|
+
@get:PathSensitive(PathSensitivity.RELATIVE)
|
|
30
|
+
abstract val entryFile: RegularFileProperty
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* React Native directory.
|
|
34
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
35
|
+
*/
|
|
36
|
+
@get:Internal
|
|
37
|
+
abstract val reactNativeDir: DirectoryProperty
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Node modules directory.
|
|
41
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
42
|
+
*/
|
|
43
|
+
@get:Internal
|
|
44
|
+
abstract val nodeModulesDir: DirectoryProperty
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Project root directory.
|
|
48
|
+
* Marked as Internal to avoid task output overlaps with other modules.
|
|
49
|
+
*/
|
|
50
|
+
@get:Internal
|
|
51
|
+
abstract val projectDir: DirectoryProperty
|
|
52
|
+
|
|
53
|
+
@get:OutputFile
|
|
54
|
+
abstract val bundleFile: RegularFileProperty
|
|
55
|
+
|
|
56
|
+
@get:OutputFile
|
|
57
|
+
abstract val sourceMapFile: RegularFileProperty
|
|
58
|
+
|
|
59
|
+
@get:Input
|
|
60
|
+
abstract val bundleAssetName: Property<String>
|
|
61
|
+
|
|
62
|
+
@get:Input
|
|
63
|
+
abstract val devMode: Property<Boolean>
|
|
64
|
+
|
|
65
|
+
@get:Input
|
|
66
|
+
abstract val variantName: Property<String>
|
|
67
|
+
|
|
68
|
+
init {
|
|
69
|
+
group = "granite"
|
|
70
|
+
description = "Generates React Native JavaScript bundle"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@TaskAction
|
|
74
|
+
fun execute() {
|
|
75
|
+
val variant = variantName.get()
|
|
76
|
+
logger.lifecycle("Bundling JavaScript for variant: $variant")
|
|
77
|
+
|
|
78
|
+
// Validate entry file exists at execution time
|
|
79
|
+
// This allows flexibility for projects with JS in separate repos during development
|
|
80
|
+
val entryFileResolved = entryFile.get().asFile
|
|
81
|
+
if (!entryFileResolved.exists()) {
|
|
82
|
+
error(
|
|
83
|
+
"""
|
|
84
|
+
|JavaScript entry file not found: ${entryFileResolved.absolutePath}
|
|
85
|
+
|
|
|
86
|
+
|The entry file is required for bundle generation but was not found.
|
|
87
|
+
|This typically occurs when:
|
|
88
|
+
| 1. JavaScript source files are in a separate repository
|
|
89
|
+
| 2. The entry file path is incorrectly configured
|
|
90
|
+
| 3. Build is running before JS files are available
|
|
91
|
+
|
|
|
92
|
+
|Solutions:
|
|
93
|
+
| 1. Ensure JavaScript files are available before running release builds
|
|
94
|
+
| 2. Configure the correct entry file path in build.gradle.kts:
|
|
95
|
+
| granite {
|
|
96
|
+
| entryFile.set("path/to/index.js")
|
|
97
|
+
| }
|
|
98
|
+
| 3. For development, use Metro dev server instead of bundling
|
|
99
|
+
|
|
|
100
|
+
|Variant: $variant
|
|
101
|
+
""".trimMargin(),
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
val jsBundleFile = generateJavaScriptBundle()
|
|
106
|
+
|
|
107
|
+
// Always compile to Hermes bytecode (JSC not supported)
|
|
108
|
+
compileToHermes(jsBundleFile)
|
|
109
|
+
|
|
110
|
+
logger.lifecycle("Bundle generation complete for variant: $variant")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private fun generateJavaScriptBundle(): File {
|
|
114
|
+
logger.lifecycle("Running Metro bundler...")
|
|
115
|
+
|
|
116
|
+
val cliPath = reactNativeDir.get().file("cli.js").asFile
|
|
117
|
+
val nodeExecutable = NodeExecutableFinder.findNodeExecutable()
|
|
118
|
+
|
|
119
|
+
if (!cliPath.exists()) {
|
|
120
|
+
error("React Native CLI not found: ${cliPath.absolutePath}")
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
val tempJsBundle = File(bundleFile.get().asFile.parentFile, "temp_${bundleAssetName.get()}")
|
|
124
|
+
tempJsBundle.parentFile.mkdirs()
|
|
125
|
+
|
|
126
|
+
val command = buildList {
|
|
127
|
+
add(nodeExecutable.absolutePath)
|
|
128
|
+
add(cliPath.absolutePath)
|
|
129
|
+
add("bundle")
|
|
130
|
+
add("--platform")
|
|
131
|
+
add("android")
|
|
132
|
+
add("--entry-file")
|
|
133
|
+
add(entryFile.get().asFile.absolutePath)
|
|
134
|
+
add("--bundle-output")
|
|
135
|
+
add(tempJsBundle.absolutePath)
|
|
136
|
+
add("--sourcemap-output")
|
|
137
|
+
add(sourceMapFile.get().asFile.absolutePath)
|
|
138
|
+
add("--assets-dest")
|
|
139
|
+
add(bundleFile.get().asFile.parentFile.absolutePath)
|
|
140
|
+
|
|
141
|
+
if (devMode.get()) {
|
|
142
|
+
add("--dev")
|
|
143
|
+
add("true")
|
|
144
|
+
} else {
|
|
145
|
+
add("--dev")
|
|
146
|
+
add("false")
|
|
147
|
+
add("--minify")
|
|
148
|
+
add("true")
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
val stdout = ByteArrayOutputStream()
|
|
153
|
+
val stderr = ByteArrayOutputStream()
|
|
154
|
+
|
|
155
|
+
val result = execOperations.exec {
|
|
156
|
+
workingDir = projectDir.get().asFile
|
|
157
|
+
commandLine = command
|
|
158
|
+
standardOutput = stdout
|
|
159
|
+
errorOutput = stderr
|
|
160
|
+
isIgnoreExitValue = true
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (result.exitValue != 0) {
|
|
164
|
+
error("Failed to bundle JavaScript. Exit code: ${result.exitValue}\n$stderr")
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
logger.lifecycle("Metro bundling complete: ${tempJsBundle.absolutePath}")
|
|
168
|
+
return tempJsBundle
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private fun compileToHermes(jsBundleFile: File) {
|
|
172
|
+
logger.lifecycle("Compiling to Hermes bytecode...")
|
|
173
|
+
|
|
174
|
+
val hermesExecutable = findHermesCompiler()
|
|
175
|
+
|
|
176
|
+
val command = listOf(
|
|
177
|
+
hermesExecutable.absolutePath,
|
|
178
|
+
"-emit-binary",
|
|
179
|
+
"-out",
|
|
180
|
+
bundleFile.get().asFile.absolutePath,
|
|
181
|
+
jsBundleFile.absolutePath,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
val stdout = ByteArrayOutputStream()
|
|
185
|
+
val stderr = ByteArrayOutputStream()
|
|
186
|
+
|
|
187
|
+
val result = execOperations.exec {
|
|
188
|
+
commandLine = command
|
|
189
|
+
standardOutput = stdout
|
|
190
|
+
errorOutput = stderr
|
|
191
|
+
isIgnoreExitValue = true
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (result.exitValue != 0) {
|
|
195
|
+
error("Failed to compile to Hermes bytecode. Exit code: ${result.exitValue}\n$stderr")
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
jsBundleFile.delete()
|
|
199
|
+
|
|
200
|
+
logger.lifecycle("Hermes compilation complete: ${bundleFile.get().asFile.absolutePath}")
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private fun findHermesCompiler(): File {
|
|
204
|
+
// Determine OS-specific binary directory
|
|
205
|
+
val osBinDir = when {
|
|
206
|
+
System.getProperty("os.name").startsWith("Windows") -> "win64-bin"
|
|
207
|
+
System.getProperty("os.name").startsWith("Mac") -> "osx-bin"
|
|
208
|
+
else -> "linux64-bin"
|
|
209
|
+
}
|
|
210
|
+
val hermescName = if (osBinDir == "win64-bin") "hermesc.exe" else "hermesc"
|
|
211
|
+
|
|
212
|
+
val nodeModules = nodeModulesDir.get().asFile
|
|
213
|
+
|
|
214
|
+
// 1. Check known paths in hermes-engine package
|
|
215
|
+
val hermesEnginePaths = listOf(
|
|
216
|
+
File(nodeModules, "hermes-engine/$osBinDir/$hermescName"),
|
|
217
|
+
File(nodeModules, "hermes-engine/bin/$hermescName"),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
for (path in hermesEnginePaths) {
|
|
221
|
+
if (path.exists() && path.canExecute()) {
|
|
222
|
+
return path
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 2. Check known paths in react-native SDKs
|
|
227
|
+
val reactNativeSdkPaths = listOf(
|
|
228
|
+
File(nodeModules, "react-native/sdks/hermesc/$osBinDir/$hermescName"),
|
|
229
|
+
File(nodeModules, "react-native/sdks/hermes/$osBinDir/$hermescName"),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
for (path in reactNativeSdkPaths) {
|
|
233
|
+
if (path.exists() && path.canExecute()) {
|
|
234
|
+
return path
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 3. Check react-native/android Maven cache (original location)
|
|
239
|
+
val reactNativeAndroid = File(reactNativeDir.get().asFile, "android")
|
|
240
|
+
val hermesEngine = File(reactNativeAndroid, "com/facebook/react/hermes-engine")
|
|
241
|
+
|
|
242
|
+
if (hermesEngine.exists()) {
|
|
243
|
+
val hermescExecutables = hermesEngine.walk()
|
|
244
|
+
.filter { it.name == hermescName }
|
|
245
|
+
.filter { it.canExecute() }
|
|
246
|
+
.toList()
|
|
247
|
+
|
|
248
|
+
if (hermescExecutables.isNotEmpty()) {
|
|
249
|
+
return hermescExecutables.first()
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 4. Fallback: targeted search in specific subdirectories only
|
|
254
|
+
val targetedDirs = listOf(
|
|
255
|
+
File(nodeModules, "hermes-engine"),
|
|
256
|
+
File(nodeModules, "react-native/sdks"),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
for (dir in targetedDirs) {
|
|
260
|
+
if (dir.exists()) {
|
|
261
|
+
val found = dir.walk()
|
|
262
|
+
.maxDepth(5) // Limit depth to prevent excessive traversal
|
|
263
|
+
.filter { it.name == hermescName }
|
|
264
|
+
.filter { it.canExecute() }
|
|
265
|
+
.firstOrNull()
|
|
266
|
+
|
|
267
|
+
if (found != null) {
|
|
268
|
+
return found
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
error("Hermes compiler (hermesc) not found. Searched paths: ${hermesEnginePaths + reactNativeSdkPaths}")
|
|
274
|
+
}
|
|
275
|
+
}
|