@fressh/react-native-terminal 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 (73) hide show
  1. package/README.md +182 -0
  2. package/ReactNativeTerminal.podspec +82 -0
  3. package/android/CMakeLists.txt +76 -0
  4. package/android/build.gradle +121 -0
  5. package/android/fix-prefab.gradle +51 -0
  6. package/android/src/main/assets/fonts/DejaVuSansMono.ttf +0 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +180 -0
  8. package/android/src/main/java/com/margelo/nitro/fressh/HybridTerminal.kt +146 -0
  9. package/android/src/main/java/com/margelo/nitro/fressh/ReactNativeTerminalModule.kt +43 -0
  10. package/android/src/main/java/com/margelo/nitro/fressh/ReactNativeTerminalPackage.kt +32 -0
  11. package/android/src/main/jniLibs/arm64-v8a/libshim_uniffi.so +0 -0
  12. package/android/src/main/jniLibs/x86_64/libshim_uniffi.so +0 -0
  13. package/babel.config.js +12 -0
  14. package/cpp/README.md +14 -0
  15. package/cpp/generated/shim_uniffi.cpp +4246 -0
  16. package/cpp/generated/shim_uniffi.hpp +364 -0
  17. package/ios/FresshTerminalRenderABI.h +46 -0
  18. package/ios/HybridTerminal.swift +144 -0
  19. package/ios/ReactNativeTerminalUniffi.mm +77 -0
  20. package/ios/fetch-angle.sh +39 -0
  21. package/ios/fonts/DejaVuSansMono.ttf +0 -0
  22. package/libEGL.xcframework/Info.plist +44 -0
  23. package/libEGL.xcframework/ios-arm64/libEGL.framework/Info.plist +0 -0
  24. package/libEGL.xcframework/ios-arm64/libEGL.framework/libEGL +0 -0
  25. package/libEGL.xcframework/ios-arm64_x86_64-simulator/libEGL.framework/Info.plist +0 -0
  26. package/libEGL.xcframework/ios-arm64_x86_64-simulator/libEGL.framework/libEGL +0 -0
  27. package/libGLESv2.xcframework/Info.plist +44 -0
  28. package/libGLESv2.xcframework/ios-arm64/libGLESv2.framework/Info.plist +0 -0
  29. package/libGLESv2.xcframework/ios-arm64/libGLESv2.framework/libGLESv2 +0 -0
  30. package/libGLESv2.xcframework/ios-arm64_x86_64-simulator/libGLESv2.framework/Info.plist +0 -0
  31. package/libGLESv2.xcframework/ios-arm64_x86_64-simulator/libGLESv2.framework/libGLESv2 +0 -0
  32. package/nitro/Terminal.nitro.ts +40 -0
  33. package/nitrogen/generated/.gitattributes +1 -0
  34. package/nitrogen/generated/android/ReactNativeTerminal+autolinking.cmake +83 -0
  35. package/nitrogen/generated/android/ReactNativeTerminal+autolinking.gradle +27 -0
  36. package/nitrogen/generated/android/ReactNativeTerminalOnLoad.cpp +56 -0
  37. package/nitrogen/generated/android/ReactNativeTerminalOnLoad.hpp +34 -0
  38. package/nitrogen/generated/android/c++/JHybridTerminalSpec.cpp +76 -0
  39. package/nitrogen/generated/android/c++/JHybridTerminalSpec.hpp +68 -0
  40. package/nitrogen/generated/android/c++/views/JHybridTerminalStateUpdater.cpp +64 -0
  41. package/nitrogen/generated/android/c++/views/JHybridTerminalStateUpdater.hpp +49 -0
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/HybridTerminalSpec.kt +69 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/ReactNativeTerminalOnLoad.kt +35 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/views/HybridTerminalManager.kt +80 -0
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/views/HybridTerminalStateUpdater.kt +23 -0
  46. package/nitrogen/generated/ios/ReactNativeTerminal+autolinking.rb +62 -0
  47. package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Bridge.cpp +33 -0
  48. package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Bridge.hpp +57 -0
  49. package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Umbrella.hpp +43 -0
  50. package/nitrogen/generated/ios/ReactNativeTerminalAutolinking.mm +33 -0
  51. package/nitrogen/generated/ios/ReactNativeTerminalAutolinking.swift +26 -0
  52. package/nitrogen/generated/ios/c++/HybridTerminalSpecSwift.cpp +11 -0
  53. package/nitrogen/generated/ios/c++/HybridTerminalSpecSwift.hpp +96 -0
  54. package/nitrogen/generated/ios/c++/views/HybridTerminalComponent.mm +132 -0
  55. package/nitrogen/generated/ios/swift/HybridTerminalSpec.swift +57 -0
  56. package/nitrogen/generated/ios/swift/HybridTerminalSpec_cxx.swift +204 -0
  57. package/nitrogen/generated/shared/c++/HybridTerminalSpec.cpp +26 -0
  58. package/nitrogen/generated/shared/c++/HybridTerminalSpec.hpp +68 -0
  59. package/nitrogen/generated/shared/c++/views/HybridTerminalComponent.cpp +110 -0
  60. package/nitrogen/generated/shared/c++/views/HybridTerminalComponent.hpp +113 -0
  61. package/nitrogen/generated/shared/json/TerminalConfig.json +12 -0
  62. package/package.json +97 -0
  63. package/react-native.config.js +12 -0
  64. package/shim_uniffi.xcframework/Info.plist +44 -0
  65. package/shim_uniffi.xcframework/ios-arm64/libshim_uniffi.a +0 -0
  66. package/shim_uniffi.xcframework/ios-arm64_x86_64-simulator/libshim_uniffi.a +0 -0
  67. package/src/Terminal.tsx +135 -0
  68. package/src/generated/shim_uniffi-ffi.ts +320 -0
  69. package/src/generated/shim_uniffi.ts +2527 -0
  70. package/src/index.ts +52 -0
  71. package/src/ssh.ts +239 -0
  72. package/tsconfig.json +31 -0
  73. package/ubrn.config.yaml +24 -0
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # @fressh/react-native-terminal
2
+
3
+ [![npm](https://img.shields.io/npm/v/@fressh/react-native-terminal)](https://www.npmjs.com/package/@fressh/react-native-terminal)
4
+ [![npm canary](https://img.shields.io/npm/v/@fressh/react-native-terminal/canary?label=canary&color=orange)](https://www.npmjs.com/package/@fressh/react-native-terminal?activeTab=versions)
5
+
6
+ Native SSH terminal for React Native, in **one package / one `.so`**:
7
+
8
+ - **SSH** via russh (`fressh-ssh`)
9
+ - **VT engine** via `alacritty_terminal` — durable, parsed `Term` state
10
+ - **Native renderer** via Alacritty's GLES2 renderer (`fressh-render`) — no xterm.js WebView
11
+ - **Registry-owned sessions** (`fressh-core`) → tmux-style reattach, full scrollback on
12
+ re-entry, no byte replay
13
+
14
+ It renders natively on **Android (EGL/GLES2) and iOS (GLES2 via ANGLE → Metal)** and is
15
+ used in production by the [fressh](https://github.com/EthanShoeDev/fressh) app. It replaces
16
+ the earlier `@fressh/react-native-uniffi-russh` + `@fressh/react-native-xtermjs-webview`.
17
+
18
+ > **`0.1.0` — experimental.** Functionality is solid on both platforms; the **public API
19
+ > may still change** before `1.0`.
20
+
21
+ ## Requirements
22
+
23
+ - **React Native ≥ 0.85** with the **New Architecture enabled** — this is a
24
+ [Nitro](https://nitro.margelo.com) module.
25
+ - Peer deps: `react`, `react-native`, `react-native-nitro-modules`.
26
+ - **Android:** `minSdkVersion ≥ 26`. Ships prebuilt `arm64-v8a` + `x86_64` (no
27
+ `armeabi-v7a` / `x86`).
28
+ - **iOS:** deployment target **≥ 16.4**; `arm64` device + simulator.
29
+ - **Expo:** works with **CNG / `expo prebuild` + a
30
+ [dev client](https://docs.expo.dev/develop/development-builds/introduction/)** — it has
31
+ native code, so **not** Expo Go. No config plugin is needed; autolinking handles linking.
32
+
33
+ ## Install
34
+
35
+ ```sh
36
+ bun add @fressh/react-native-terminal
37
+ bun add react-native-nitro-modules # peer dep, if not already present
38
+ ```
39
+
40
+ **Bare React Native** — `cd ios && pod install` (autolinking discovers the podspec); Android
41
+ autolinks via Gradle. No manual native wiring.
42
+
43
+ **Expo** — add a dev client and prebuild:
44
+
45
+ ```sh
46
+ bun add expo-dev-client
47
+ bunx expo prebuild
48
+ ```
49
+
50
+ Enforce the build floors with `expo-build-properties` in `app.config.ts` / `app.json`:
51
+
52
+ ```ts
53
+ plugins: [
54
+ [
55
+ 'expo-build-properties',
56
+ { android: { minSdkVersion: 26 }, ios: { deploymentTarget: '16.4' } },
57
+ ],
58
+ ];
59
+ ```
60
+
61
+ ### Prerelease channels
62
+
63
+ ```sh
64
+ bun add @fressh/react-native-terminal@canary # latest CI canary (0.0.0-canary-<commit>)
65
+ bun add @fressh/react-native-terminal@rc # release-candidate line, when active
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ Connect, answer the host-key prompt, open a shell, and render it. **Bytes never cross JS** —
71
+ the native `<Terminal>` view owns rendering and input.
72
+
73
+ ```tsx
74
+ import { useEffect, useState } from 'react';
75
+ import {
76
+ Terminal,
77
+ addFresshEventListener,
78
+ connect,
79
+ respondToHostKey,
80
+ startShell,
81
+ FresshEvent_Tags,
82
+ Security,
83
+ TerminalType,
84
+ type ShellId,
85
+ } from '@fressh/react-native-terminal';
86
+
87
+ export function TerminalScreen() {
88
+ const [shellId, setShellId] = useState<ShellId | null>(null);
89
+
90
+ useEffect(() => {
91
+ // One event stream carries connect progress, host-key prompts, and close events.
92
+ const unsubscribe = addFresshEventListener((event) => {
93
+ if (event.tag === FresshEvent_Tags.HostKeyPending) {
94
+ // event.inner.info has the server key fingerprint — show it, then accept/reject.
95
+ respondToHostKey(event.inner.connectionId, true);
96
+ }
97
+ });
98
+
99
+ void (async () => {
100
+ const connectionId = await connect({
101
+ host: 'example.com',
102
+ port: 22,
103
+ username: 'me',
104
+ security: new Security.Password({ password: '…' }),
105
+ // …or key auth: new Security.Key({ privateKeyContent: pem })
106
+ });
107
+ const id = await startShell(connectionId, {
108
+ term: TerminalType.Xterm256,
109
+ cols: 80,
110
+ rows: 24,
111
+ scrollbackLines: 10_000,
112
+ });
113
+ setShellId(id);
114
+ })();
115
+
116
+ return unsubscribe;
117
+ }, []);
118
+
119
+ return shellId ? <Terminal shellId={shellId} style={{ flex: 1 }} /> : null;
120
+ }
121
+ ```
122
+
123
+ `<Terminal shellId>` reattaches to the durable `Term` that `fressh-core` keeps for that
124
+ `shellId`, so re-mounting restores full scrollback with no replay. Font size / colors are
125
+ driven by the `config` prop (`TerminalRenderConfig`). For a one-off command without a PTY,
126
+ use `runCommand(connectionId, cmd)`; generate keys with `generateKeyPair(KeyType.Ed25519)`.
127
+
128
+ The full surface is in the exported TypeScript types: `connect`, `startShell`, `runCommand`,
129
+ `sendData`, `resize`, `scroll`, the selection helpers, `generateKeyPair`,
130
+ `validatePrivateKey`, and the `FresshEvent` stream.
131
+
132
+ ---
133
+
134
+ ## Architecture — the four planes
135
+
136
+ | Plane | Path | Crosses JS? |
137
+ | ------- | ----------------------------------------------- | --------------------- |
138
+ | Control | JS `src/ssh.ts` → shim (uniffi) → `fressh-core` | yes (rare, async) |
139
+ | Event | `fressh-core` → shim → JS (one-way sink) | yes (rare) |
140
+ | Render | Nitro view ↔ `fressh-core` C-ABI | **never** |
141
+ | Data | SSH bytes → reader loop → `Term` | **never (pure Rust)** |
142
+
143
+ SSH bytes feed a durable `alacritty_terminal` `Term` entirely in Rust; the Nitro view draws
144
+ that `Term` over a C-ABI without round-tripping through JS. iOS renders the same GLES2 path
145
+ through **ANGLE** (→ Metal); ANGLE's `libEGL` / `libGLESv2` xcframeworks are vendored in the
146
+ podspec.
147
+
148
+ ## Layout
149
+
150
+ ```
151
+ react-native-terminal/
152
+ ├── rust/ # ONE cargo workspace
153
+ │ ├── fressh-ssh/ # russh wrapper
154
+ │ ├── fressh-render/ # Alacritty GLES2 renderer over Term
155
+ │ ├── fressh-core/ # runtime + registry + sessions + C-ABI
156
+ │ └── shim-uniffi/ # thin binding shim (control plane + render C-ABI)
157
+ ├── nitro/Terminal.nitro.ts # native view spec (render plane)
158
+ ├── src/ # TS public API (Terminal, ssh control plane)
159
+ ├── cpp/ android/ ios/ # hand-authored umbrella native glue
160
+ └── ReactNativeTerminal.podspec · ubrn.config.yaml · package.json
161
+ ```
162
+
163
+ ## Design & internals
164
+
165
+ Full design rationale, the renderer-extraction decisions, and the rejected alternatives live
166
+ in the design doc:
167
+ [`native-rendering-refactor.md`](https://github.com/EthanShoeDev/fressh/blob/main/docs/projects/complete/native-rendering-refactor.md).
168
+
169
+ ### Contributing (Rust toolchain)
170
+
171
+ The npm tarball ships **prebuilt** native binaries, so consumers need no Rust toolchain.
172
+ Contributors build from the repo (the Alacritty / crossfont forks are git submodules under
173
+ `rust/vendor/`):
174
+
175
+ ```sh
176
+ bun run cargo:fmt:fix # cargo fmt --all
177
+ bun run cargo:lint:fix # cargo clippy --workspace --fix -D warnings
178
+ bun run cargo:test # cargo test --workspace
179
+ ```
180
+
181
+ Lint levels are defined once in `rust/Cargo.toml` `[workspace.lints]` and inherited per-crate
182
+ via `[lints] workspace = true`.
@@ -0,0 +1,82 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ # The iOS analogue of the umbrella android/CMakeLists (§8): one pod that compiles
6
+ # BOTH planes into the app and links ONE Rust staticlib so there is a single copy
7
+ # of fressh-core's registry statics.
8
+ #
9
+ # - Control plane: the ubrn-generated JSI bindings C++ (cpp/generated/*.cpp →
10
+ # NativeShimUniffi::registerModule) + our hand-authored ios/*.mm installer
11
+ # (mirrors Android's nativeInstallRustCrate; calls registerModule directly —
12
+ # we run `ubrn generate`, NOT `ubrn build`, so ubrn does not own this build).
13
+ # - Render plane: the Nitro HybridView (nitrogen sources via add_nitrogen_files +
14
+ # our ios/HybridTerminal.swift). On iOS this is a STUB for now — GLES via
15
+ # ANGLE→Metal lands later (§5); the view compiles and draws nothing.
16
+ # - Rust: shim_uniffi.xcframework (built by `bun run build:ios` → rust/build-ios.sh),
17
+ # carrying the uniffi scaffolding + render C-ABI + the shared registry statics.
18
+
19
+ Pod::Spec.new do |s|
20
+ s.name = "ReactNativeTerminal"
21
+ s.version = package["version"]
22
+ s.summary = package["description"]
23
+ s.homepage = package["homepage"]
24
+ s.license = package["license"]
25
+ s.authors = package["author"]
26
+
27
+ s.platforms = { :ios => min_ios_version_supported }
28
+ s.source = { :git => "https://github.com/EthanShoeDev/fressh.git", :tag => "#{s.version}" }
29
+
30
+ # Hand-authored iOS sources (the .mm installer + the Nitro Swift view) and the
31
+ # ubrn-generated JSI bindings C++. add_nitrogen_files (below) APPENDS the
32
+ # nitrogen-generated specs/bridges to this list.
33
+ s.source_files = [
34
+ "ios/**/*.{h,m,mm,swift}",
35
+ "cpp/generated/**/*.{h,hpp,c,cpp}",
36
+ ]
37
+
38
+ # The single Rust staticlib (control plane + render C-ABI + fressh-core registry
39
+ # statics — ONE copy, §8), plus ANGLE's libEGL/libGLESv2 (dynamic, Metal backend
40
+ # — the GLES2 driver the renderer runs over on iOS, §2/§5). The ANGLE frameworks
41
+ # must be EMBEDDED: dyld loads them at launch and egl.rs resolves their symbols
42
+ # from the process image (Library::this()). Fetch them with `bun run angle:fetch`.
43
+ s.vendored_frameworks = [
44
+ "shim_uniffi.xcframework",
45
+ "libEGL.xcframework",
46
+ "libGLESv2.xcframework",
47
+ ]
48
+
49
+ # Expose the render C-ABI header in the module umbrella so HybridTerminal.swift can
50
+ # call fressh_terminal_* (Swift imports the module's PUBLIC headers). Setting
51
+ # public_header_files restricts the public set, so this must be additive with
52
+ # nitrogen's — which appends to it in add_nitrogen_files below.
53
+ s.public_header_files = "ios/FresshTerminalRenderABI.h"
54
+
55
+ # Bundled monospace font for the renderer — FreeType rasterizes it by file path
56
+ # (no fontconfig on mobile, §6/§8). HybridTerminal.swift resolves it from this
57
+ # bundle. The Android side ships the same DejaVuSansMono.ttf as an asset.
58
+ s.resource_bundles = {
59
+ "FresshTerminalFonts" => ["ios/fonts/*.ttf"],
60
+ }
61
+
62
+ # Brings ubrn's header-only C++ runtime (UniffiCallInvoker.h, RustArcPtr.h, …)
63
+ # onto the header path — the generated bindings #include these. Android resolves
64
+ # the same dir by walking node_modules in CMake; on iOS the pod dependency does it.
65
+ s.dependency "uniffi-bindgen-react-native"
66
+
67
+ s.pod_target_xcconfig = {
68
+ # The generated bindings + Nitro require C++20.
69
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
70
+ # `#include "shim_uniffi.hpp"` from our .mm + the generated .cpp.
71
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp/generated\"",
72
+ }
73
+
74
+ # React-Core / ReactCommon (turbomodule/core for ObjCTurboModule), Folly, etc.
75
+ install_modules_dependencies(s)
76
+
77
+ # Nitro HybridView (render plane): appends nitrogen's generated specs + bridges
78
+ # to source_files, adds the NitroModules dependency, and merges the C++ <-> Swift
79
+ # interop xcconfig. Loaded last so it extends (not overwrites) the above.
80
+ load File.join(__dir__, "nitrogen", "generated", "ios", "ReactNativeTerminal+autolinking.rb")
81
+ add_nitrogen_files(s)
82
+ end
@@ -0,0 +1,76 @@
1
+ project(ReactNativeTerminal)
2
+ cmake_minimum_required(VERSION 3.9.0)
3
+
4
+ set(PACKAGE_NAME ReactNativeTerminal)
5
+ set(CMAKE_VERBOSE_MAKEFILE ON)
6
+ set(CMAKE_CXX_STANDARD 20)
7
+
8
+ # The single package .so (§8). It contains:
9
+ # - the Nitro render JNI bridge (cpp-adapter.cpp) -> the Rust render C-ABI
10
+ # - the ubrn-generated uniffi JSI bindings (cpp/generated/shim_uniffi.cpp) +
11
+ # the install glue (cpp-adapter.cpp's nativeInstallRustCrate -> registerModule)
12
+ # ...and it LINKS the Rust cdylib `libshim_uniffi.so`, which holds the uniffi
13
+ # scaffolding + the render C-ABI + fressh-core's registry statics (ONE copy — so
14
+ # the control plane and the render plane see the same sessions).
15
+ add_library(${PACKAGE_NAME} SHARED
16
+ src/main/cpp/cpp-adapter.cpp
17
+ ../cpp/generated/shim_uniffi.cpp
18
+ )
19
+
20
+ # CRITICAL: hide all symbols by default; only the JNIEXPORT entry points (which carry
21
+ # explicit default visibility) stay exported. Without this, the React/Nitro template
22
+ # instantiations we compile in (ShadowNode, State, ConcreteState, StateWrapperImpl,
23
+ # RawPropsParser, …) are exported with default visibility and INTERPOSE over
24
+ # libreactnative.so's copies at load time. RN core then manipulates Fabric State
25
+ # objects through OUR differently-built copies, corrupting the view-state round-trip:
26
+ # props parse correctly in cloneProps but come back freed/garbage in updateViewProps,
27
+ # so NewStringUTF aborts (and ~HybridTerminalState segfaults). Other Nitro libs
28
+ # (e.g. NitroMmkv) export 0 such symbols; this hand-written umbrella must match.
29
+ target_compile_options(${PACKAGE_NAME} PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
30
+ target_link_options(${PACKAGE_NAME} PRIVATE -Wl,--exclude-libs,ALL)
31
+
32
+ # Nitrogen-generated sources + nitro/fbjni/ReactAndroid (jsi, ReactCommon) linkage
33
+ # + their include dirs (the generated uniffi cpp uses <jsi/jsi.h> and
34
+ # <ReactCommon/CallInvoker.h>, both provided here).
35
+ include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/ReactNativeTerminal+autolinking.cmake)
36
+
37
+ # Resolve uniffi-bindgen-react-native's header-only C++ runtime (UniffiCallInvoker.h
38
+ # etc.) by walking up node_modules from this dir (works from the consuming app).
39
+ execute_process(
40
+ COMMAND node -e "const fs=require('fs'),p=require('path');let d=process.cwd();while(d.length>1){const c=p.join(d,'node_modules/uniffi-bindgen-react-native/cpp/includes');if(fs.existsSync(c)){process.stdout.write(c);break;}d=p.dirname(d);}"
41
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
42
+ OUTPUT_VARIABLE UNIFFI_INCLUDES
43
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
44
+
45
+ target_include_directories(${PACKAGE_NAME} PRIVATE
46
+ ${CMAKE_SOURCE_DIR}/../cpp/generated
47
+ ${UNIFFI_INCLUDES}
48
+ )
49
+
50
+ # Required for 16KB page sizes (Android 15+).
51
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
52
+
53
+ # Import the Rust cdylib (built per-ABI by cargo-ndk into jniLibs). It carries BOTH
54
+ # the uniffi scaffolding (the ffi_* symbols the generated cpp calls) AND the render
55
+ # C-ABI (the fressh_terminal_* symbols the JNI bridge calls), so there is one shared
56
+ # registry. Linked SHARED (not a static .a) so neither plane gets its own copy.
57
+ cmake_path(SET RUST_LIB
58
+ ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libshim_uniffi.so
59
+ NORMALIZE)
60
+ add_library(shim_uniffi SHARED IMPORTED)
61
+ # The cargo-ndk cdylib has no SONAME → IMPORTED_NO_SONAME so the NEEDED entry is the
62
+ # basename (resolved by the loader from the APK), not the build-time path.
63
+ set_target_properties(shim_uniffi PROPERTIES
64
+ IMPORTED_LOCATION ${RUST_LIB}
65
+ IMPORTED_NO_SONAME TRUE)
66
+
67
+ find_library(LOG_LIB log)
68
+
69
+ # `android` provides ANativeWindow_fromSurface/_release used by cpp-adapter.
70
+ # react/jsi/fbjni/nitro come from the nitrogen autolinking include above.
71
+ target_link_libraries(
72
+ ${PACKAGE_NAME}
73
+ shim_uniffi
74
+ android
75
+ ${LOG_LIB}
76
+ )
@@ -0,0 +1,121 @@
1
+ def isNewArchitectureEnabled() {
2
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
3
+ }
4
+
5
+ def reactNativeArchitectures() {
6
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
7
+ return value ? value.split(",") : ["x86", "x86_64", "armeabi-v7a", "arm64-v8a"]
8
+ }
9
+
10
+ // Build only ABIs the app is building (so the nitro prefab has a matching
11
+ // libNitroModules.so) AND that we ship a prebuilt rust libfressh_render.so for
12
+ // (see jniLibs). Building an ABI the app skips → header-only nitro prefab →
13
+ // `undefined symbol: margelo::nitro::...` at link time.
14
+ def supportedRustAbis = ["arm64-v8a", "x86_64"]
15
+ def buildAbis = reactNativeArchitectures().findAll { supportedRustAbis.contains(it) }
16
+
17
+ apply plugin: "com.android.library"
18
+ apply plugin: "org.jetbrains.kotlin.android"
19
+ // Nitrogen-generated: adds the generated kotlin srcDir.
20
+ apply from: "../nitrogen/generated/android/ReactNativeTerminal+autolinking.gradle"
21
+ // Nitro prefab workaround: makes the react-native-nitro-modules prefab link the
22
+ // actual libNitroModules.so (not headers-only), else the native link fails with
23
+ // `undefined symbol: margelo::nitro::HybridObject::...`. Every nitro module ships
24
+ // this (react-native-nitro-modules + react-native-mmkv both apply it).
25
+ apply from: "./fix-prefab.gradle"
26
+
27
+ if (isNewArchitectureEnabled()) {
28
+ apply plugin: "com.facebook.react"
29
+ }
30
+
31
+ def getExtOrDefault(name) {
32
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["ReactNativeTerminal_" + name]
33
+ }
34
+
35
+ def getExtOrIntegerDefault(name) {
36
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativeTerminal_" + name]).toInteger()
37
+ }
38
+
39
+ android {
40
+ namespace "com.margelo.nitro.fressh"
41
+
42
+ ndkVersion getExtOrDefault("ndkVersion")
43
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
44
+
45
+ defaultConfig {
46
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
47
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
48
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
49
+
50
+ externalNativeBuild {
51
+ cmake {
52
+ cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
53
+ arguments "-DANDROID_STL=c++_shared"
54
+ abiFilters.addAll(buildAbis)
55
+ }
56
+ }
57
+ ndk {
58
+ abiFilters.addAll(buildAbis)
59
+ }
60
+ }
61
+
62
+ externalNativeBuild {
63
+ cmake {
64
+ path "CMakeLists.txt"
65
+ }
66
+ }
67
+
68
+ buildFeatures {
69
+ buildConfig true
70
+ prefab true
71
+ }
72
+
73
+ // These .so's are provided by react-native / nitro-modules at runtime; don't
74
+ // re-package them from our prefab inputs (avoids duplicate .so conflicts).
75
+ packagingOptions {
76
+ excludes = [
77
+ "META-INF",
78
+ "META-INF/**",
79
+ "**/libNitroModules.so",
80
+ "**/libc++_shared.so",
81
+ "**/libfbjni.so",
82
+ "**/libjsi.so",
83
+ "**/libreactnative.so",
84
+ ]
85
+ }
86
+
87
+ buildTypes {
88
+ release {
89
+ minifyEnabled false
90
+ }
91
+ }
92
+
93
+ lintOptions {
94
+ disable "GradleCompatible"
95
+ }
96
+
97
+ compileOptions {
98
+ sourceCompatibility JavaVersion.VERSION_1_8
99
+ targetCompatibility JavaVersion.VERSION_1_8
100
+ }
101
+ }
102
+
103
+ repositories {
104
+ mavenCentral()
105
+ google()
106
+ }
107
+
108
+ dependencies {
109
+ //noinspection GradleDynamicVersion
110
+ implementation "com.facebook.react:react-native:+"
111
+ // NitroModules core (prefab consumed by the generated autolinking CMake).
112
+ implementation project(":react-native-nitro-modules")
113
+ }
114
+
115
+ if (isNewArchitectureEnabled()) {
116
+ react {
117
+ jsRootDir = file("../src/")
118
+ libraryName = "ReactNativeTerminal"
119
+ codegenJavaPackageName = "com.margelo.nitro.fressh"
120
+ }
121
+ }
@@ -0,0 +1,51 @@
1
+ tasks.configureEach { task ->
2
+ // Make sure that we generate our prefab publication file only after having built the native library
3
+ // so that not a header publication file, but a full configuration publication will be generated, which
4
+ // will include the .so file
5
+
6
+ def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/
7
+ def matcher = task.name =~ prefabConfigurePattern
8
+ if (matcher.matches()) {
9
+ def variantName = matcher[0][1]
10
+ task.outputs.upToDateWhen { false }
11
+ task.dependsOn("externalNativeBuild${variantName}")
12
+ }
13
+ }
14
+
15
+ afterEvaluate {
16
+ def abis = reactNativeArchitectures()
17
+ rootProject.allprojects.each { proj ->
18
+ if (proj === rootProject) return
19
+
20
+ def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config ->
21
+ config.dependencies.any { dep ->
22
+ dep.group == project.group && dep.name == project.name
23
+ }
24
+ }
25
+ if (!dependsOnThisLib && proj != project) return
26
+
27
+ if (!proj.plugins.hasPlugin('com.android.application') && !proj.plugins.hasPlugin('com.android.library')) {
28
+ return
29
+ }
30
+
31
+ def variants = proj.android.hasProperty('applicationVariants') ? proj.android.applicationVariants : proj.android.libraryVariants
32
+ // Touch the prefab_config.json files to ensure that in ExternalNativeJsonGenerator.kt we will re-trigger the prefab CLI to
33
+ // generate a libnameConfig.cmake file that will contain our native library (.so).
34
+ // See this condition: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/ExternalNativeJsonGenerator.kt;l=207-219?q=createPrefabBuildSystemGlue
35
+ variants.all { variant ->
36
+ def variantName = variant.name
37
+ abis.each { abi ->
38
+ def searchDir = new File(proj.projectDir, ".cxx/${variantName}")
39
+ if (!searchDir.exists()) return
40
+ def matches = []
41
+ searchDir.eachDir { randomDir ->
42
+ def prefabFile = new File(randomDir, "${abi}/prefab_config.json")
43
+ if (prefabFile.exists()) matches << prefabFile
44
+ }
45
+ matches.each { prefabConfig ->
46
+ prefabConfig.setLastModified(System.currentTimeMillis())
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }