@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.
- package/README.md +182 -0
- package/ReactNativeTerminal.podspec +82 -0
- package/android/CMakeLists.txt +76 -0
- package/android/build.gradle +121 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/src/main/assets/fonts/DejaVuSansMono.ttf +0 -0
- package/android/src/main/cpp/cpp-adapter.cpp +180 -0
- package/android/src/main/java/com/margelo/nitro/fressh/HybridTerminal.kt +146 -0
- package/android/src/main/java/com/margelo/nitro/fressh/ReactNativeTerminalModule.kt +43 -0
- package/android/src/main/java/com/margelo/nitro/fressh/ReactNativeTerminalPackage.kt +32 -0
- package/android/src/main/jniLibs/arm64-v8a/libshim_uniffi.so +0 -0
- package/android/src/main/jniLibs/x86_64/libshim_uniffi.so +0 -0
- package/babel.config.js +12 -0
- package/cpp/README.md +14 -0
- package/cpp/generated/shim_uniffi.cpp +4246 -0
- package/cpp/generated/shim_uniffi.hpp +364 -0
- package/ios/FresshTerminalRenderABI.h +46 -0
- package/ios/HybridTerminal.swift +144 -0
- package/ios/ReactNativeTerminalUniffi.mm +77 -0
- package/ios/fetch-angle.sh +39 -0
- package/ios/fonts/DejaVuSansMono.ttf +0 -0
- package/libEGL.xcframework/Info.plist +44 -0
- package/libEGL.xcframework/ios-arm64/libEGL.framework/Info.plist +0 -0
- package/libEGL.xcframework/ios-arm64/libEGL.framework/libEGL +0 -0
- package/libEGL.xcframework/ios-arm64_x86_64-simulator/libEGL.framework/Info.plist +0 -0
- package/libEGL.xcframework/ios-arm64_x86_64-simulator/libEGL.framework/libEGL +0 -0
- package/libGLESv2.xcframework/Info.plist +44 -0
- package/libGLESv2.xcframework/ios-arm64/libGLESv2.framework/Info.plist +0 -0
- package/libGLESv2.xcframework/ios-arm64/libGLESv2.framework/libGLESv2 +0 -0
- package/libGLESv2.xcframework/ios-arm64_x86_64-simulator/libGLESv2.framework/Info.plist +0 -0
- package/libGLESv2.xcframework/ios-arm64_x86_64-simulator/libGLESv2.framework/libGLESv2 +0 -0
- package/nitro/Terminal.nitro.ts +40 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/ReactNativeTerminal+autolinking.cmake +83 -0
- package/nitrogen/generated/android/ReactNativeTerminal+autolinking.gradle +27 -0
- package/nitrogen/generated/android/ReactNativeTerminalOnLoad.cpp +56 -0
- package/nitrogen/generated/android/ReactNativeTerminalOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JHybridTerminalSpec.cpp +76 -0
- package/nitrogen/generated/android/c++/JHybridTerminalSpec.hpp +68 -0
- package/nitrogen/generated/android/c++/views/JHybridTerminalStateUpdater.cpp +64 -0
- package/nitrogen/generated/android/c++/views/JHybridTerminalStateUpdater.hpp +49 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/HybridTerminalSpec.kt +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/ReactNativeTerminalOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/views/HybridTerminalManager.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/fressh/views/HybridTerminalStateUpdater.kt +23 -0
- package/nitrogen/generated/ios/ReactNativeTerminal+autolinking.rb +62 -0
- package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Bridge.cpp +33 -0
- package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Bridge.hpp +57 -0
- package/nitrogen/generated/ios/ReactNativeTerminal-Swift-Cxx-Umbrella.hpp +43 -0
- package/nitrogen/generated/ios/ReactNativeTerminalAutolinking.mm +33 -0
- package/nitrogen/generated/ios/ReactNativeTerminalAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridTerminalSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridTerminalSpecSwift.hpp +96 -0
- package/nitrogen/generated/ios/c++/views/HybridTerminalComponent.mm +132 -0
- package/nitrogen/generated/ios/swift/HybridTerminalSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridTerminalSpec_cxx.swift +204 -0
- package/nitrogen/generated/shared/c++/HybridTerminalSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridTerminalSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/views/HybridTerminalComponent.cpp +110 -0
- package/nitrogen/generated/shared/c++/views/HybridTerminalComponent.hpp +113 -0
- package/nitrogen/generated/shared/json/TerminalConfig.json +12 -0
- package/package.json +97 -0
- package/react-native.config.js +12 -0
- package/shim_uniffi.xcframework/Info.plist +44 -0
- package/shim_uniffi.xcframework/ios-arm64/libshim_uniffi.a +0 -0
- package/shim_uniffi.xcframework/ios-arm64_x86_64-simulator/libshim_uniffi.a +0 -0
- package/src/Terminal.tsx +135 -0
- package/src/generated/shim_uniffi-ffi.ts +320 -0
- package/src/generated/shim_uniffi.ts +2527 -0
- package/src/index.ts +52 -0
- package/src/ssh.ts +239 -0
- package/tsconfig.json +31 -0
- package/ubrn.config.yaml +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# @fressh/react-native-terminal
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fressh/react-native-terminal)
|
|
4
|
+
[](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
|
+
}
|
|
Binary file
|