@react-native-native/nativ-fabric 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.
Files changed (42) hide show
  1. package/NativFabric.podspec +41 -0
  2. package/android/build.gradle +128 -0
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/cpp/CMakeLists.txt +59 -0
  5. package/android/src/main/cpp/NativBindingsInstaller.cpp +393 -0
  6. package/android/src/main/cpp/NativRuntime.cpp +508 -0
  7. package/android/src/main/java/com/nativfabric/ComposeHost.kt +26 -0
  8. package/android/src/main/java/com/nativfabric/NativContainerPackage.kt +35 -0
  9. package/android/src/main/java/com/nativfabric/NativContainerView.kt +51 -0
  10. package/android/src/main/java/com/nativfabric/NativContainerViewManager.kt +62 -0
  11. package/android/src/main/java/com/nativfabric/NativRuntime.kt +201 -0
  12. package/android/src/main/java/com/nativfabric/NativRuntimeModule.kt +37 -0
  13. package/android/src/main/java/com/nativfabric/compose/ComposeWrappers.kt +45 -0
  14. package/app.plugin.js +159 -0
  15. package/expo-module.config.json +6 -0
  16. package/ios/NativContainerComponentView.mm +137 -0
  17. package/ios/NativRuntime.h +36 -0
  18. package/ios/NativRuntime.mm +549 -0
  19. package/metro/Nativ.h +126 -0
  20. package/metro/compilers/android-compiler.js +339 -0
  21. package/metro/compilers/dylib-compiler.js +474 -0
  22. package/metro/compilers/kotlin-compiler.js +632 -0
  23. package/metro/compilers/rust-compiler.js +722 -0
  24. package/metro/compilers/static-compiler.js +1118 -0
  25. package/metro/compilers/swift-compiler.js +363 -0
  26. package/metro/extractors/cpp-ast-extractor.js +126 -0
  27. package/metro/extractors/kotlin-extractor.js +125 -0
  28. package/metro/extractors/rust-extractor.js +118 -0
  29. package/metro/index.js +236 -0
  30. package/metro/transformer.js +649 -0
  31. package/metro/utils/bridge-generator.js +50 -0
  32. package/metro/utils/compile-commands.js +104 -0
  33. package/metro/utils/cpp-daemon.js +344 -0
  34. package/metro/utils/dts-generator.js +32 -0
  35. package/metro/utils/include-resolver.js +73 -0
  36. package/metro/utils/kotlin-daemon.js +394 -0
  37. package/metro/utils/type-mapper.js +63 -0
  38. package/package.json +43 -0
  39. package/react-native.config.js +13 -0
  40. package/src/NativContainerNativeComponent.ts +9 -0
  41. package/src/NativeNativRuntime.ts +8 -0
  42. package/src/index.ts +4 -0
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Android native compilers — builds .cpp/.rs/.kt files to arm64 .so files.
3
+ * Mirrors dylib-compiler.js (C++), rust-compiler.js (Rust), but targets Android.
4
+ */
5
+
6
+ const { execSync } = require('child_process');
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+
10
+ let _ndkPath = null;
11
+ let _resolved = false;
12
+
13
+ function resolveOnce() {
14
+ if (_resolved) return;
15
+ _resolved = true;
16
+
17
+ // Find NDK
18
+ const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
19
+ if (androidHome) {
20
+ const ndkDir = path.join(androidHome, 'ndk');
21
+ if (fs.existsSync(ndkDir)) {
22
+ // Use the latest NDK version
23
+ const versions = fs.readdirSync(ndkDir).sort();
24
+ if (versions.length > 0) {
25
+ _ndkPath = path.join(ndkDir, versions[versions.length - 1]);
26
+ }
27
+ }
28
+ }
29
+
30
+ if (_ndkPath) {
31
+ console.log(`[nativ] Android NDK: ${_ndkPath}`);
32
+ } else {
33
+ console.warn('[nativ] Android NDK not found');
34
+ }
35
+ }
36
+
37
+ // ABI → NDK clang prefix mapping
38
+ const ABI_MAP = {
39
+ 'arm64-v8a': { clang: 'aarch64-linux-android24-clang++', target: 'aarch64-linux-android24', rust: 'aarch64-linux-android' },
40
+ 'armeabi-v7a': { clang: 'armv7a-linux-androideabi24-clang++', target: 'armv7a-linux-androideabi24', rust: 'armv7-linux-androideabi' },
41
+ 'x86_64': { clang: 'x86_64-linux-android24-clang++', target: 'x86_64-linux-android24', rust: 'x86_64-linux-android' },
42
+ 'x86': { clang: 'i686-linux-android24-clang++', target: 'i686-linux-android24', rust: 'i686-linux-android' },
43
+ };
44
+
45
+ function getNdkClang(abi = 'arm64-v8a') {
46
+ if (!_ndkPath) return null;
47
+ const toolchain = path.join(_ndkPath, 'toolchains/llvm/prebuilt');
48
+ const hosts = fs.readdirSync(toolchain);
49
+ if (hosts.length === 0) return null;
50
+ const hostDir = path.join(toolchain, hosts[0], 'bin');
51
+ const clangName = ABI_MAP[abi]?.clang || ABI_MAP['arm64-v8a'].clang;
52
+ return path.join(hostDir, clangName);
53
+ }
54
+
55
+ // ─── C++/ObjC++ → .so ──────────────────────────────────────────────────
56
+
57
+ function compileAndroidCppDylib(filepath, includePaths, exports, projectRoot, { target = 'arm64-v8a' } = {}) {
58
+ resolveOnce();
59
+ const clang = getNdkClang(target);
60
+ if (!clang) return null;
61
+
62
+ const name = path.basename(filepath).replace(/\.(cpp|cc|c)$/, '');
63
+ const moduleId = name.toLowerCase();
64
+ const outputDir = path.join(projectRoot, '.nativ/dylibs', target);
65
+ fs.mkdirSync(outputDir, { recursive: true });
66
+ const soPath = path.join(outputDir, `${moduleId}.so`);
67
+
68
+ // Generate bridge (same as iOS)
69
+ const { extractCppExports } = require('../extractors/cpp-ast-extractor');
70
+ const bridgePath = path.join(outputDir, `${moduleId}_android_bridge.cpp`);
71
+ const { generateBridge } = require('../utils/bridge-generator');
72
+ const bridgeSource = generateAndroidBridge(exports, moduleId);
73
+ fs.writeFileSync(bridgePath, bridgeSource);
74
+
75
+ const cmd = [
76
+ clang,
77
+ '-shared', '-fPIC',
78
+ '-std=c++17',
79
+ '-target', ABI_MAP[target]?.target || 'aarch64-linux-android24',
80
+ '-DANDROID', '-D__ANDROID__',
81
+ `-I${path.resolve(__dirname, '..')}`, // Nativ.h
82
+ '-o', soPath,
83
+ filepath,
84
+ bridgePath,
85
+ '-llog',
86
+ ];
87
+
88
+ try {
89
+ execSync(cmd.join(' '), { stdio: 'pipe', encoding: 'utf8' });
90
+ } catch (err) {
91
+ console.error(`[nativ] Android C++ compile failed: ${name}`);
92
+ console.error((err.stderr || '').slice(0, 1000));
93
+ return null;
94
+ }
95
+
96
+ // No signing needed on Android
97
+ const size = fs.statSync(soPath).size;
98
+ console.log(`[nativ] Built ${moduleId}.so (${(size / 1024).toFixed(1)}KB)`);
99
+ return soPath;
100
+ }
101
+
102
+ function generateAndroidBridge(exports, moduleId) {
103
+ const forwardDecls = [];
104
+ const lines = [
105
+ '// Auto-generated Android bridge',
106
+ '#include <cstdlib>',
107
+ '#include <cstring>',
108
+ '#include <string>',
109
+ '#include <dlfcn.h>',
110
+ '',
111
+ '// Registry C API — resolved via dlsym (Android namespace isolation)',
112
+ 'extern "C" {',
113
+ 'typedef const char* (*NativSyncFn)(const char*);',
114
+ 'typedef void (*NativRegisterSyncFn)(const char*, const char*, NativSyncFn);',
115
+ '}',
116
+ '',
117
+ '// JSON parse helpers',
118
+ 'static double _parseNumber(const char* &p) {',
119
+ ' while (*p == \' \' || *p == \',\' || *p == \'[\') p++;',
120
+ ' char* end; double v = strtod(p, &end); p = end; return v;',
121
+ '}',
122
+ 'static std::string _parseString(const char* &p) {',
123
+ ' while (*p && *p != \'"\') p++; if (*p == \'"\') p++;',
124
+ ' std::string s; while (*p && *p != \'"\') {',
125
+ ' if (*p == \'\\\\\' && *(p+1)) { p++; s += *p; } else { s += *p; } p++; }',
126
+ ' if (*p == \'"\') p++; return s;',
127
+ '}',
128
+ '',
129
+ 'extern "C" {',
130
+ ];
131
+
132
+ for (const fn of exports) {
133
+ if (fn.async) continue;
134
+
135
+ lines.push(`static const char* nativ_android_${moduleId}_${fn.name}(const char* argsJson) {`);
136
+ lines.push(' const char* p = argsJson;');
137
+ lines.push(' while (*p && *p != \'[\') p++; if (*p == \'[\') p++;');
138
+
139
+ const argNames = [];
140
+ for (const arg of fn.args) {
141
+ const t = arg.type.replace(/const\s+/, '').replace(/\s*&\s*$/, '').trim();
142
+ if (t === 'std::string') {
143
+ lines.push(` std::string ${arg.name} = _parseString(p);`);
144
+ } else {
145
+ lines.push(` ${arg.type} ${arg.name} = (${arg.type})_parseNumber(p);`);
146
+ }
147
+ argNames.push(arg.name);
148
+ }
149
+
150
+ // Forward declaration
151
+ forwardDecls.push(`extern ${fn.ret} ${fn.name}(${fn.args.map(a => a.type + ' ' + a.name).join(', ')});`);
152
+
153
+ lines.push(` auto result = ${fn.name}(${argNames.join(', ')});`);
154
+
155
+ const retBase = fn.ret.replace(/const\s+/, '').replace(/\s*&\s*$/, '').trim();
156
+ if (retBase === 'std::string') {
157
+ lines.push(' static thread_local std::string buf;');
158
+ lines.push(' buf = "\\\"";');
159
+ lines.push(' for (char c : result) { if (c == \'"\') buf += "\\\\\\\""; else buf += c; }');
160
+ lines.push(' buf += "\\\"";');
161
+ lines.push(' return buf.c_str();');
162
+ } else {
163
+ lines.push(' static thread_local std::string buf;');
164
+ lines.push(' buf = std::to_string(result);');
165
+ lines.push(' return buf.c_str();');
166
+ }
167
+ lines.push('}');
168
+ lines.push('');
169
+ }
170
+
171
+ // Insert forward declarations after includes
172
+ const includeEnd = lines.findIndex(l => l.includes('extern "C" {'));
173
+ if (includeEnd >= 0) {
174
+ lines.splice(includeEnd, 0, ...forwardDecls, '');
175
+ }
176
+
177
+ // Init function — called by host after dlopen with the registry function pointer.
178
+ // Android linker namespaces prevent dlsym(RTLD_DEFAULT) from finding host symbols.
179
+ lines.push(`static NativRegisterSyncFn _nativ_reg = nullptr;`);
180
+ lines.push('');
181
+ lines.push(`void nativ_init(void* reg_fn) {`);
182
+ lines.push(' _nativ_reg = (NativRegisterSyncFn)reg_fn;');
183
+ for (const fn of exports) {
184
+ if (!fn.async) {
185
+ lines.push(` if (_nativ_reg) _nativ_reg("${moduleId}", "${fn.name}", nativ_android_${moduleId}_${fn.name});`);
186
+ }
187
+ }
188
+ lines.push('}');
189
+ lines.push('');
190
+ lines.push('} // extern "C"');
191
+
192
+ return lines.join('\n');
193
+ }
194
+
195
+ // ─── C++ component → .so ───────────────────────────────────────────────
196
+
197
+ function compileAndroidCppComponentDylib(filepath, includePaths, projectRoot, baseName, componentProps, { target = 'arm64-v8a' } = {}) {
198
+ resolveOnce();
199
+ const clang = getNdkClang(target);
200
+ if (!clang) return null;
201
+
202
+ const componentId = `nativ.${baseName}`;
203
+ const outputDir = path.join(projectRoot, '.nativ/dylibs', target);
204
+ fs.mkdirSync(outputDir, { recursive: true });
205
+ const soPath = path.join(outputDir, `nativ_${baseName}.so`);
206
+
207
+ const src = fs.readFileSync(filepath, 'utf8');
208
+ const compMatch = src.match(/NATIV_COMPONENT\s*\(\s*(\w+)\s*,\s*(\w+)\s*\)/);
209
+ const propsTypeName = compMatch ? compMatch[2] : null;
210
+
211
+ // Generate prop extraction
212
+ const propExtractions = (componentProps || []).map(p => {
213
+ if (p.cppType === 'std::string') {
214
+ return ` props.${p.name} = _nativ_get_string(rt, obj, "${p.jsName}", props.${p.name});`;
215
+ } else if (p.cppType === 'double' || p.cppType === 'float' || p.cppType === 'int') {
216
+ return ` props.${p.name} = _nativ_get_number(rt, obj, "${p.jsName}", props.${p.name});`;
217
+ } else if (p.cppType === 'bool') {
218
+ return ` props.${p.name} = _nativ_get_bool(rt, obj, "${p.jsName}", props.${p.name});`;
219
+ }
220
+ return '';
221
+ }).join('\n');
222
+
223
+ const relPath = path.relative(outputDir, filepath);
224
+ const bridgePath = path.join(outputDir, `nativ_${baseName}_android_bridge.cpp`);
225
+ const renderFnName = `nativ_${baseName}_render`;
226
+ const bridgeSrc = `
227
+ #include "${relPath}"
228
+
229
+ extern "C" void ${renderFnName}(void* view, float width, float height,
230
+ void* jsi_runtime, void* jsi_props) {
231
+ void* rt = jsi_runtime;
232
+ void* obj = jsi_props;
233
+ ${propsTypeName ? ` ${propsTypeName} props;
234
+ ${propExtractions}
235
+ mount(view, width, height, props);` : ' mount(view, width, height);'}
236
+ }
237
+
238
+ extern "C" {
239
+ typedef void (*NativRenderFn)(void*, float, float, void*, void*);
240
+ void nativ_register_render(const char*, NativRenderFn);
241
+ }
242
+
243
+ __attribute__((constructor))
244
+ static void register_${baseName}() {
245
+ nativ_register_render("${componentId}", ${renderFnName});
246
+ }
247
+ `;
248
+ fs.writeFileSync(bridgePath, bridgeSrc);
249
+
250
+ const cmd = [
251
+ clang,
252
+ '-shared', '-fPIC',
253
+ '-std=c++17',
254
+ '-target', ABI_MAP[target]?.target || 'aarch64-linux-android24',
255
+ '-DANDROID', '-D__ANDROID__',
256
+ `-I${path.resolve(__dirname, '..')}`,
257
+ '-o', soPath,
258
+ filepath,
259
+ bridgePath,
260
+ '-llog',
261
+ ];
262
+
263
+ try {
264
+ execSync(cmd.join(' '), { stdio: 'pipe', encoding: 'utf8' });
265
+ } catch (err) {
266
+ console.error(`[nativ] Android component compile failed: ${baseName}`);
267
+ console.error((err.stderr || '').slice(0, 1000));
268
+ return null;
269
+ }
270
+
271
+ const size = fs.statSync(soPath).size;
272
+ console.log(`[nativ] Built nativ_${baseName}.so component (${(size / 1024).toFixed(1)}KB)`);
273
+ return soPath;
274
+ }
275
+
276
+ // ─── Rust → .so ────────────────────────────────────────────────────────
277
+
278
+ function compileAndroidRustDylib(filepath, projectRoot, { target = 'arm64-v8a' } = {}) {
279
+ resolveOnce();
280
+
281
+ const { ensureRustCrate } = require('./rust-compiler');
282
+ const crate = ensureRustCrate(filepath, projectRoot);
283
+ if (!crate) return null;
284
+
285
+ const { crateDir, moduleId } = crate;
286
+ const name = path.basename(filepath, '.rs');
287
+ const outputDir = path.join(projectRoot, '.nativ/dylibs', target);
288
+ fs.mkdirSync(outputDir, { recursive: true });
289
+ const soPath = path.join(outputDir, `nativ_${moduleId}.so`);
290
+
291
+ const sharedTarget = path.join(projectRoot, '.nativ/cargo-target');
292
+ const rustTarget = ABI_MAP[target]?.rust || 'aarch64-linux-android';
293
+
294
+ // Set up NDK linker
295
+ const ndkLinker = getNdkClang(target)?.replace('clang++', 'clang');
296
+ const linkerEnvKey = `CARGO_TARGET_${rustTarget.toUpperCase().replace(/-/g, '_')}_LINKER`;
297
+
298
+ const cmd = [
299
+ 'cargo', 'build',
300
+ '--manifest-path', path.join(crateDir, 'Cargo.toml'),
301
+ `--target=${rustTarget}`,
302
+ '--lib',
303
+ ];
304
+
305
+ console.log(`[nativ] Compiling ${name}.rs for Android (${target}) via cargo...`);
306
+ try {
307
+ execSync(cmd.join(' '), {
308
+ stdio: 'pipe',
309
+ encoding: 'utf8',
310
+ env: {
311
+ ...process.env,
312
+ RUSTFLAGS: '-C link-arg=-llog',
313
+ CARGO_TARGET_DIR: sharedTarget,
314
+ [linkerEnvKey]: ndkLinker || '',
315
+ },
316
+ });
317
+ } catch (err) {
318
+ console.error(`[nativ] Android Rust compile failed: ${name}.rs`);
319
+ console.error((err.stderr || '').slice(0, 2000));
320
+ return null;
321
+ }
322
+
323
+ const builtSo = path.join(sharedTarget, `${rustTarget}/debug/libnativ_${moduleId}.so`);
324
+ if (fs.existsSync(builtSo)) {
325
+ fs.copyFileSync(builtSo, soPath);
326
+ const size = fs.statSync(soPath).size;
327
+ console.log(`[nativ] Built nativ_${moduleId}.so (${(size / 1024).toFixed(1)}KB)`);
328
+ return soPath;
329
+ }
330
+
331
+ console.error(`[nativ] Built .so not found: ${builtSo}`);
332
+ return null;
333
+ }
334
+
335
+ module.exports = {
336
+ compileAndroidCppDylib,
337
+ compileAndroidCppComponentDylib,
338
+ compileAndroidRustDylib,
339
+ };