@react-native-native/cli 0.1.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/bin/nativ.js +14 -0
- package/commands/build-rust.js +38 -0
- package/commands/build.js +8 -0
- package/commands/doctor.js +18 -0
- package/commands/setup-compose.js +8 -0
- package/commands/setup-kotlin.js +8 -0
- package/commands/setup-rust.js +15 -0
- package/commands/setup.js +21 -0
- package/package.json +34 -0
- package/scripts/doctor.js +436 -0
- package/scripts/project-config.js +58 -0
- package/scripts/setup-compose.js +467 -0
- package/scripts/setup-kotlin.js +314 -0
- package/scripts/setup-rust.js +123 -0
- package/scripts/setup.js +107 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setup-kotlin.js — Sets up the Kotlin/Compose dev hot-reload toolchain.
|
|
5
|
+
*
|
|
6
|
+
* Downloads the non-embeddable Kotlin compiler from Maven, locates the
|
|
7
|
+
* Compose compiler plugin and AAR JARs from the Gradle cache, and builds
|
|
8
|
+
* the pre-transform stubs needed for standalone kotlinc compilation.
|
|
9
|
+
*
|
|
10
|
+
* Run: npx @react-native-native/cli setup-kotlin
|
|
11
|
+
* Or: node packages/cli/scripts/setup-kotlin.js
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { execSync } = require('child_process');
|
|
17
|
+
const https = require('https');
|
|
18
|
+
|
|
19
|
+
// ─── Config ────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const projectRoot = process.cwd();
|
|
22
|
+
|
|
23
|
+
// Read Kotlin version from nativ.config.json (written by setup-kotlin)
|
|
24
|
+
function readKotlinVersion() {
|
|
25
|
+
try {
|
|
26
|
+
const config = JSON.parse(fs.readFileSync(path.join(projectRoot, '.nativ/nativ.config.json'), 'utf8'));
|
|
27
|
+
return config.kotlin?.version;
|
|
28
|
+
} catch {}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const KOTLIN_VERSION = readKotlinVersion() || '2.0.21';
|
|
33
|
+
const COMPOSE_VERSION = '1.7.0';
|
|
34
|
+
const COMPOSE_MATERIAL3_VERSION = '1.3.0';
|
|
35
|
+
const outDir = path.join(projectRoot, '.nativ/compose-pretransform');
|
|
36
|
+
const composeJarsDir = path.join(projectRoot, '.nativ/compose-jars');
|
|
37
|
+
const gradleCache = path.join(process.env.HOME || '', '.gradle/caches/modules-2/files-2.1');
|
|
38
|
+
|
|
39
|
+
// ─── Helpers ───────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function download(url, dest) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
console.log(` Downloading ${path.basename(dest)}...`);
|
|
44
|
+
const file = fs.createWriteStream(dest);
|
|
45
|
+
const request = (url.startsWith('https') ? https : require('http')).get(url, (response) => {
|
|
46
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
47
|
+
file.close();
|
|
48
|
+
fs.unlinkSync(dest);
|
|
49
|
+
return download(response.headers.location, dest).then(resolve, reject);
|
|
50
|
+
}
|
|
51
|
+
response.pipe(file);
|
|
52
|
+
file.on('finish', () => { file.close(); resolve(); });
|
|
53
|
+
});
|
|
54
|
+
request.on('error', (err) => { fs.unlinkSync(dest); reject(err); });
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function findInGradleCache(group, artifact, nameFilter, version) {
|
|
59
|
+
const dir = path.join(gradleCache, group, artifact);
|
|
60
|
+
if (!fs.existsSync(dir)) return null;
|
|
61
|
+
try {
|
|
62
|
+
// If version specified, look in that version's directory first
|
|
63
|
+
if (version) {
|
|
64
|
+
const versionDir = path.join(dir, version);
|
|
65
|
+
if (fs.existsSync(versionDir)) {
|
|
66
|
+
const result = execSync(
|
|
67
|
+
`find "${versionDir}" -name "${nameFilter || '*.jar'}" -not -name "*sources*" -not -name "*javadoc*" 2>/dev/null | head -1`,
|
|
68
|
+
{ encoding: 'utf8' }
|
|
69
|
+
).trim();
|
|
70
|
+
if (result && fs.existsSync(result)) return result;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Fall back to latest version
|
|
74
|
+
const result = execSync(
|
|
75
|
+
`find "${dir}" -name "${nameFilter || '*.jar'}" -not -name "*sources*" -not -name "*javadoc*" 2>/dev/null | sort -V | tail -1`,
|
|
76
|
+
{ encoding: 'utf8' }
|
|
77
|
+
).trim();
|
|
78
|
+
return result && fs.existsSync(result) ? result : null;
|
|
79
|
+
} catch { return null; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function findAarClassesJar(group, artifact) {
|
|
83
|
+
const dir = path.join(gradleCache, group, artifact);
|
|
84
|
+
if (!fs.existsSync(dir)) return null;
|
|
85
|
+
try {
|
|
86
|
+
// Find the AAR, extract classes.jar from it
|
|
87
|
+
const aar = execSync(
|
|
88
|
+
`find "${dir}" -name "*.aar" 2>/dev/null | sort -V | tail -1`,
|
|
89
|
+
{ encoding: 'utf8' }
|
|
90
|
+
).trim();
|
|
91
|
+
if (!aar) return null;
|
|
92
|
+
|
|
93
|
+
// Check if already extracted
|
|
94
|
+
const name = `${artifact.replace(/\//g, '-')}-${path.basename(path.dirname(path.dirname(aar)))}.jar`;
|
|
95
|
+
const dest = path.join(composeJarsDir, name);
|
|
96
|
+
if (fs.existsSync(dest)) return dest;
|
|
97
|
+
|
|
98
|
+
// Extract classes.jar from AAR (it's a zip)
|
|
99
|
+
const tmpDir = path.join(outDir, '_tmp_aar');
|
|
100
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
101
|
+
execSync(`unzip -o -q "${aar}" classes.jar -d "${tmpDir}" 2>/dev/null`, { stdio: 'pipe' });
|
|
102
|
+
const classesJar = path.join(tmpDir, 'classes.jar');
|
|
103
|
+
if (fs.existsSync(classesJar)) {
|
|
104
|
+
fs.copyFileSync(classesJar, dest);
|
|
105
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
106
|
+
return dest;
|
|
107
|
+
}
|
|
108
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
109
|
+
return null;
|
|
110
|
+
} catch { return null; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── Step 1: Non-embeddable Kotlin compiler ────────────────────────────
|
|
114
|
+
|
|
115
|
+
async function setupKotlinCompiler() {
|
|
116
|
+
const compilerJar = path.join(outDir, `kotlin-compiler-${KOTLIN_VERSION}.jar`);
|
|
117
|
+
if (fs.existsSync(compilerJar)) {
|
|
118
|
+
console.log(`✓ kotlin-compiler-${KOTLIN_VERSION}.jar (already exists)`);
|
|
119
|
+
return compilerJar;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
123
|
+
const url = `https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-compiler/${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.jar`;
|
|
124
|
+
await download(url, compilerJar);
|
|
125
|
+
const size = fs.statSync(compilerJar).size;
|
|
126
|
+
console.log(`✓ kotlin-compiler-${KOTLIN_VERSION}.jar (${(size / 1024 / 1024).toFixed(1)}MB)`);
|
|
127
|
+
return compilerJar;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Step 2: Compose compiler plugin ───────────────────────────────────
|
|
131
|
+
|
|
132
|
+
function setupComposePlugin() {
|
|
133
|
+
const plugin = findInGradleCache(
|
|
134
|
+
'org.jetbrains.kotlin',
|
|
135
|
+
'kotlin-compose-compiler-plugin',
|
|
136
|
+
'*.jar'
|
|
137
|
+
);
|
|
138
|
+
if (plugin) {
|
|
139
|
+
console.log(`✓ Compose compiler plugin (from Gradle cache)`);
|
|
140
|
+
return plugin;
|
|
141
|
+
}
|
|
142
|
+
console.warn('⚠ Compose compiler plugin not found in Gradle cache.');
|
|
143
|
+
console.warn(' Run an Android build first: npx expo run:android');
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── Step 3: Compose AAR classes.jar ───────────────────────────────────
|
|
148
|
+
|
|
149
|
+
function setupComposeJars() {
|
|
150
|
+
fs.mkdirSync(composeJarsDir, { recursive: true });
|
|
151
|
+
|
|
152
|
+
const aars = [
|
|
153
|
+
['androidx.compose.runtime', `runtime-android`],
|
|
154
|
+
['androidx.compose.runtime', `runtime-saveable-android`],
|
|
155
|
+
['androidx.compose.ui', `ui-android`],
|
|
156
|
+
['androidx.compose.foundation', `foundation-android`],
|
|
157
|
+
['androidx.compose.foundation', `foundation-layout-android`],
|
|
158
|
+
['androidx.compose.material3', `material3-android`],
|
|
159
|
+
['androidx.lifecycle', 'lifecycle-common'],
|
|
160
|
+
['androidx.lifecycle', 'lifecycle-runtime'],
|
|
161
|
+
['androidx.savedstate', 'savedstate'],
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
let found = 0;
|
|
165
|
+
for (const [group, artifact] of aars) {
|
|
166
|
+
const jar = findAarClassesJar(group, artifact);
|
|
167
|
+
if (jar) {
|
|
168
|
+
found++;
|
|
169
|
+
} else {
|
|
170
|
+
// Try plain JAR (some are not AARs)
|
|
171
|
+
const plainJar = findInGradleCache(group, artifact);
|
|
172
|
+
if (plainJar) {
|
|
173
|
+
const dest = path.join(composeJarsDir, `${artifact}.jar`);
|
|
174
|
+
if (!fs.existsSync(dest)) fs.copyFileSync(plainJar, dest);
|
|
175
|
+
found++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(`✓ Compose AAR classes.jar (${found}/${aars.length} found)`);
|
|
181
|
+
if (found < aars.length) {
|
|
182
|
+
console.warn(' Some JARs missing. Run an Android build first: npx expo run:android');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── Step 4: Pre-transform stubs ───────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
function setupPretransformJar() {
|
|
189
|
+
const ptJar = path.join(outDir, `compose-pretransform-${COMPOSE_VERSION}.jar`);
|
|
190
|
+
if (fs.existsSync(ptJar)) {
|
|
191
|
+
console.log(`✓ compose-pretransform-${COMPOSE_VERSION}.jar (already exists)`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// The pretransform JAR contains stubs for inline Compose functions
|
|
196
|
+
// (remember, currentComposer) that the standalone kotlinc needs.
|
|
197
|
+
// These are minimal type-resolution stubs, not full implementations.
|
|
198
|
+
const srcDir = path.join(outDir, 'src');
|
|
199
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
200
|
+
|
|
201
|
+
// Minimal remember stub
|
|
202
|
+
fs.writeFileSync(path.join(srcDir, 'Composables.kt'), `
|
|
203
|
+
package androidx.compose.runtime
|
|
204
|
+
|
|
205
|
+
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.PROPERTY_GETTER)
|
|
206
|
+
@Retention(AnnotationRetention.BINARY)
|
|
207
|
+
annotation class Composable
|
|
208
|
+
|
|
209
|
+
inline fun <T> remember(crossinline calculation: @Composable () -> T): T = calculation()
|
|
210
|
+
|
|
211
|
+
@Composable
|
|
212
|
+
fun currentComposer(): Any? = null
|
|
213
|
+
`);
|
|
214
|
+
|
|
215
|
+
// Try to compile with the Kotlin compiler
|
|
216
|
+
const kotlinStdlib = findInGradleCache('org.jetbrains.kotlin', 'kotlin-stdlib', null, KOTLIN_VERSION);
|
|
217
|
+
if (!kotlinStdlib) {
|
|
218
|
+
console.warn('⚠ Cannot build pretransform JAR — kotlin-stdlib not in Gradle cache');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const fullCompiler = path.join(outDir, `kotlin-compiler-${KOTLIN_VERSION}.jar`);
|
|
223
|
+
if (!fs.existsSync(fullCompiler)) {
|
|
224
|
+
console.warn('⚠ Cannot build pretransform JAR — kotlin-compiler not downloaded yet');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const jvmCp = [
|
|
230
|
+
fullCompiler,
|
|
231
|
+
kotlinStdlib,
|
|
232
|
+
findInGradleCache('org.jetbrains.kotlin', 'kotlin-script-runtime', null, KOTLIN_VERSION),
|
|
233
|
+
findInGradleCache('org.jetbrains.kotlinx', 'kotlinx-coroutines-core-jvm'),
|
|
234
|
+
findInGradleCache('org.jetbrains.intellij.deps', 'trove4j'),
|
|
235
|
+
findInGradleCache('org.jetbrains', 'annotations'),
|
|
236
|
+
].filter(Boolean);
|
|
237
|
+
const cmd = `java -cp "${jvmCp.join(':')}" org.jetbrains.kotlin.cli.jvm.K2JVMCompiler "${srcDir}/Composables.kt" -d "${ptJar}" -classpath "${kotlinStdlib}" -no-reflect -no-stdlib -jvm-target 17 2>&1`;
|
|
238
|
+
execSync(cmd, { stdio: 'pipe', encoding: 'utf8' });
|
|
239
|
+
console.log(`✓ compose-pretransform-${COMPOSE_VERSION}.jar (built)`);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
console.warn(`⚠ Failed to build pretransform JAR: ${(e.stderr || e.message || '').slice(0, 200)}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── Step 5: Non-inline wrappers ───────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
function setupWrappersJar() {
|
|
248
|
+
const wrappersJar = path.join(outDir, 'compose-wrappers.jar');
|
|
249
|
+
if (fs.existsSync(wrappersJar)) {
|
|
250
|
+
console.log(`✓ compose-wrappers.jar (already exists)`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Non-inline wrappers for Box/Column/Row/Spacer compiled WITH the Compose plugin.
|
|
255
|
+
// These delegate to the real implementations without inline body conflicts.
|
|
256
|
+
const srcDir = path.join(outDir, 'wrapper-src');
|
|
257
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
258
|
+
|
|
259
|
+
fs.writeFileSync(path.join(srcDir, 'ComposeWrappers.kt'), `
|
|
260
|
+
package com.nativfabric.compose
|
|
261
|
+
|
|
262
|
+
import androidx.compose.runtime.Composable
|
|
263
|
+
import androidx.compose.foundation.layout.BoxScope
|
|
264
|
+
import androidx.compose.foundation.layout.ColumnScope
|
|
265
|
+
import androidx.compose.foundation.layout.RowScope
|
|
266
|
+
import androidx.compose.ui.Alignment
|
|
267
|
+
import androidx.compose.ui.Modifier
|
|
268
|
+
|
|
269
|
+
@Composable
|
|
270
|
+
fun Box(
|
|
271
|
+
modifier: Modifier = Modifier,
|
|
272
|
+
contentAlignment: Alignment = Alignment.TopStart,
|
|
273
|
+
propagateMinConstraints: Boolean = false,
|
|
274
|
+
content: @Composable BoxScope.() -> Unit
|
|
275
|
+
) = androidx.compose.foundation.layout.Box(modifier, contentAlignment, propagateMinConstraints, content)
|
|
276
|
+
|
|
277
|
+
@Composable
|
|
278
|
+
fun Column(
|
|
279
|
+
modifier: Modifier = Modifier,
|
|
280
|
+
content: @Composable ColumnScope.() -> Unit
|
|
281
|
+
) = androidx.compose.foundation.layout.Column(modifier = modifier, content = content)
|
|
282
|
+
|
|
283
|
+
@Composable
|
|
284
|
+
fun Row(
|
|
285
|
+
modifier: Modifier = Modifier,
|
|
286
|
+
content: @Composable RowScope.() -> Unit
|
|
287
|
+
) = androidx.compose.foundation.layout.Row(modifier = modifier, content = content)
|
|
288
|
+
|
|
289
|
+
@Composable
|
|
290
|
+
fun Spacer(modifier: Modifier = Modifier) = androidx.compose.foundation.layout.Spacer(modifier)
|
|
291
|
+
`);
|
|
292
|
+
|
|
293
|
+
// This needs the Compose plugin + all Compose JARs to compile.
|
|
294
|
+
// Skip if deps aren't available — it'll be built on first Android build.
|
|
295
|
+
const plugin = setupComposePlugin();
|
|
296
|
+
const fullCompiler = path.join(outDir, `kotlin-compiler-${KOTLIN_VERSION}.jar`);
|
|
297
|
+
const kotlinStdlib = findInGradleCache('org.jetbrains.kotlin', 'kotlin-stdlib', null, KOTLIN_VERSION);
|
|
298
|
+
|
|
299
|
+
if (!plugin || !fs.existsSync(fullCompiler) || !kotlinStdlib) {
|
|
300
|
+
console.warn('⚠ Cannot build compose-wrappers.jar — missing deps. Run Android build first.');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const cp = [kotlinStdlib];
|
|
305
|
+
// Add android.jar for Android types
|
|
306
|
+
const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT
|
|
307
|
+
|| path.join(process.env.HOME || '', 'Library/Android/sdk');
|
|
308
|
+
const wrapperPlatformsDir = path.join(androidHome, 'platforms');
|
|
309
|
+
if (fs.existsSync(wrapperPlatformsDir)) {
|
|
310
|
+
const platforms = fs.readdirSync(wrapperPlatformsDir).sort();
|
|
311
|
+
if (platforms.length > 0) {
|
|
312
|
+
const jar = path.join(wrapperPlatformsDir, platforms[platforms.length - 1], 'android.jar');
|
|
313
|
+
if (fs.existsSync(jar)) cp.push(jar);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const localAndroidJar = path.join(process.cwd(), '.nativ/kotlin-cache/android.jar');
|
|
317
|
+
if (!cp.some(p => p.includes('android.jar')) && fs.existsSync(localAndroidJar)) cp.push(localAndroidJar);
|
|
318
|
+
|
|
319
|
+
// Add all Compose JARs to classpath
|
|
320
|
+
try {
|
|
321
|
+
const jars = fs.readdirSync(composeJarsDir).filter(f => f.endsWith('.jar')).map(f => path.join(composeJarsDir, f));
|
|
322
|
+
cp.push(...jars);
|
|
323
|
+
} catch {}
|
|
324
|
+
|
|
325
|
+
// Add pretransform JAR
|
|
326
|
+
const ptJar = path.join(outDir, `compose-pretransform-${COMPOSE_VERSION}.jar`);
|
|
327
|
+
if (fs.existsSync(ptJar)) cp.unshift(ptJar);
|
|
328
|
+
|
|
329
|
+
const jvmDeps = [
|
|
330
|
+
fullCompiler,
|
|
331
|
+
kotlinStdlib,
|
|
332
|
+
findInGradleCache('org.jetbrains.kotlin', 'kotlin-script-runtime', null, KOTLIN_VERSION),
|
|
333
|
+
findInGradleCache('org.jetbrains.kotlinx', 'kotlinx-coroutines-core-jvm'),
|
|
334
|
+
findInGradleCache('org.jetbrains.intellij.deps', 'trove4j'),
|
|
335
|
+
findInGradleCache('org.jetbrains', 'annotations'),
|
|
336
|
+
].filter(Boolean);
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const cmd = [
|
|
340
|
+
`java -cp "${jvmDeps.join(':')}" org.jetbrains.kotlin.cli.jvm.K2JVMCompiler`,
|
|
341
|
+
`"${srcDir}/ComposeWrappers.kt"`,
|
|
342
|
+
`-d "${wrappersJar}"`,
|
|
343
|
+
`-classpath "${cp.join(':')}"`,
|
|
344
|
+
`-Xplugin=${plugin}`,
|
|
345
|
+
`-no-reflect -no-stdlib -jvm-target 17`,
|
|
346
|
+
].join(' ');
|
|
347
|
+
execSync(cmd, { stdio: 'pipe', encoding: 'utf8' });
|
|
348
|
+
console.log(`✓ compose-wrappers.jar (built)`);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
console.warn(`⚠ Failed to build compose-wrappers.jar: ${(e.stderr || e.message || '').slice(0, 200)}`);
|
|
351
|
+
console.warn(' This is expected on first setup. Run an Android build, then re-run this script.');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ─── Step 6: ComposeHost JAR ───────────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
function setupComposeHostJar() {
|
|
358
|
+
const hostJar = path.join(outDir, 'compose-host.jar');
|
|
359
|
+
if (fs.existsSync(hostJar)) {
|
|
360
|
+
console.log(`✓ compose-host.jar (already exists)`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ComposeHost wraps ComposeView.setContent for hot-reloaded .dex components.
|
|
365
|
+
// Previously required a full Gradle build — now compiled standalone.
|
|
366
|
+
const srcDir = path.join(outDir, 'host-src');
|
|
367
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
368
|
+
|
|
369
|
+
fs.writeFileSync(path.join(srcDir, 'ComposeHost.kt'), `
|
|
370
|
+
package com.nativfabric
|
|
371
|
+
|
|
372
|
+
import android.view.ViewGroup
|
|
373
|
+
import android.widget.FrameLayout
|
|
374
|
+
import androidx.compose.runtime.Composable
|
|
375
|
+
import androidx.compose.ui.platform.ComposeView
|
|
376
|
+
|
|
377
|
+
object ComposeHost {
|
|
378
|
+
@JvmStatic
|
|
379
|
+
fun setContent(parent: ViewGroup, content: @Composable () -> Unit) {
|
|
380
|
+
val composeView = ComposeView(parent.context)
|
|
381
|
+
composeView.setContent { content() }
|
|
382
|
+
parent.addView(composeView, FrameLayout.LayoutParams(
|
|
383
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
384
|
+
FrameLayout.LayoutParams.MATCH_PARENT))
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
`);
|
|
388
|
+
|
|
389
|
+
const plugin = setupComposePlugin();
|
|
390
|
+
const fullCompiler = path.join(outDir, `kotlin-compiler-${KOTLIN_VERSION}.jar`);
|
|
391
|
+
const kotlinStdlib = findInGradleCache('org.jetbrains.kotlin', 'kotlin-stdlib', null, KOTLIN_VERSION);
|
|
392
|
+
|
|
393
|
+
if (!plugin || !fs.existsSync(fullCompiler) || !kotlinStdlib) {
|
|
394
|
+
console.warn('⚠ Cannot build compose-host.jar — missing deps');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const cp = [kotlinStdlib];
|
|
399
|
+
// Add android.jar for Android types (ViewGroup, FrameLayout, Context)
|
|
400
|
+
const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT
|
|
401
|
+
|| path.join(process.env.HOME || '', 'Library/Android/sdk');
|
|
402
|
+
const platformsDir = path.join(androidHome, 'platforms');
|
|
403
|
+
if (fs.existsSync(platformsDir)) {
|
|
404
|
+
const platforms = fs.readdirSync(platformsDir).sort();
|
|
405
|
+
if (platforms.length > 0) {
|
|
406
|
+
const jar = path.join(platformsDir, platforms[platforms.length - 1], 'android.jar');
|
|
407
|
+
if (fs.existsSync(jar)) cp.push(jar);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Fall back to local cache
|
|
411
|
+
const localAndroidJar = path.join(process.cwd(), '.nativ/kotlin-cache/android.jar');
|
|
412
|
+
if (!cp.some(p => p.includes('android.jar')) && fs.existsSync(localAndroidJar)) cp.push(localAndroidJar);
|
|
413
|
+
|
|
414
|
+
// Add pretransform + wrappers JARs for Compose type resolution
|
|
415
|
+
const ptJar = path.join(outDir, `compose-pretransform-${COMPOSE_VERSION}.jar`);
|
|
416
|
+
if (fs.existsSync(ptJar)) cp.unshift(ptJar);
|
|
417
|
+
const wrappersJar = path.join(outDir, 'compose-wrappers.jar');
|
|
418
|
+
if (fs.existsSync(wrappersJar)) cp.unshift(wrappersJar);
|
|
419
|
+
try {
|
|
420
|
+
const jars = fs.readdirSync(composeJarsDir).filter(f => f.endsWith('.jar')).map(f => path.join(composeJarsDir, f));
|
|
421
|
+
cp.push(...jars);
|
|
422
|
+
} catch {}
|
|
423
|
+
|
|
424
|
+
const jvmDeps = [
|
|
425
|
+
fullCompiler,
|
|
426
|
+
kotlinStdlib,
|
|
427
|
+
findInGradleCache('org.jetbrains.kotlin', 'kotlin-script-runtime', null, KOTLIN_VERSION),
|
|
428
|
+
findInGradleCache('org.jetbrains.kotlinx', 'kotlinx-coroutines-core-jvm'),
|
|
429
|
+
findInGradleCache('org.jetbrains.intellij.deps', 'trove4j'),
|
|
430
|
+
findInGradleCache('org.jetbrains', 'annotations'),
|
|
431
|
+
].filter(Boolean);
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const cmd = [
|
|
435
|
+
`java -cp "${jvmDeps.join(':')}" org.jetbrains.kotlin.cli.jvm.K2JVMCompiler`,
|
|
436
|
+
`"${srcDir}/ComposeHost.kt"`,
|
|
437
|
+
`-d "${hostJar}"`,
|
|
438
|
+
`-classpath "${cp.join(':')}"`,
|
|
439
|
+
`-Xplugin=${plugin}`,
|
|
440
|
+
`-no-reflect -jvm-target 17`,
|
|
441
|
+
].join(' ');
|
|
442
|
+
execSync(cmd, { stdio: 'pipe', encoding: 'utf8' });
|
|
443
|
+
console.log(`✓ compose-host.jar (built)`);
|
|
444
|
+
} catch (e) {
|
|
445
|
+
console.warn(`⚠ Failed to build compose-host.jar: ${(e.stderr || e.message || '').slice(0, 200)}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ─── Main ──────────────────────────────────────────────────────────────
|
|
450
|
+
|
|
451
|
+
async function main() {
|
|
452
|
+
console.log('React Native Native — Kotlin/Compose dev toolchain setup\n');
|
|
453
|
+
|
|
454
|
+
await setupKotlinCompiler();
|
|
455
|
+
setupComposePlugin();
|
|
456
|
+
setupComposeJars();
|
|
457
|
+
setupPretransformJar();
|
|
458
|
+
setupWrappersJar();
|
|
459
|
+
setupComposeHostJar();
|
|
460
|
+
|
|
461
|
+
console.log('\nDone. Compose hot-reload is ready.');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
main().catch(e => {
|
|
465
|
+
console.error('Setup failed:', e.message);
|
|
466
|
+
process.exit(1);
|
|
467
|
+
});
|