feat: add HarmonyOS OpenHarmony support
Harvest the HarmonyOS/OpenHarmony port from PR #2634 and make it publish-safe by target-gating unsupported host dependencies out of the OHOS TUI graph. Self-update is disabled on OHOS, PTY shell mode reports unsupported, and Starlark execpolicy parsing returns an explicit unsupported-platform error until upstream starlark/rustyline/nix support catches up. Add OHOS SDK setup docs and launcher scripts, install the rustls ring provider for rustls-no-provider entrypoints, and keep the packaged codewhale-tui OHOS graph free of starlark, rustyline, nix@0.28, portable-pty, and arboard. Validation: cargo fmt --all -- --check; git diff --check; git diff --cached --check; cargo check -p codewhale-cli --locked; cargo check -p codewhale-app-server --locked; cargo check -p codewhale-tui --locked; cargo test -p codewhale-cli --locked update::tests::; cargo test -p codewhale-release --locked; cargo test -p codewhale-tui --locked background_tty_command_has_controlling_terminal; cargo test -p codewhale-tui --locked clipboard; cargo package -p codewhale-tui --allow-dirty --no-verify --locked; packaged OHOS cargo tree checks. OHOS target check still requires a loaded OpenHarmony SDK/sysroot and currently stops in ring with missing assert.h when CC/CFLAGS/linker are unset. Harvested from PR #2634 by @shenjackyuanjie. Co-authored-by: shenjackyuanjie <54507071+shenjackyuanjie@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# HarmonyOS/OpenHarmony cross-build paths are intentionally not configured
|
||||
# here. Cargo does not expand environment variables inside target linker paths
|
||||
# or CMake toolchain paths, so checked-in absolute SDK paths make the workspace
|
||||
# machine-specific.
|
||||
#
|
||||
# See docs/HarmonyOS.md for setup details.
|
||||
#
|
||||
# Set OHOS_NATIVE_SDK to the OpenHarmony native SDK directory, then load one of:
|
||||
#
|
||||
# PowerShell:
|
||||
# . .\scripts\ohos-env.ps1
|
||||
#
|
||||
# Linux/macOS:
|
||||
# . ./scripts/ohos-env.sh
|
||||
#
|
||||
# The setup scripts export Cargo's target-specific linker, AR, CC, CXX, CFLAGS,
|
||||
# CXXFLAGS, CARGO_ENCODED_RUSTFLAGS, CC_SHELL_ESCAPED_FLAGS, and
|
||||
# CMAKE_TOOLCHAIN_FILE variables for aarch64-unknown-linux-ohos.
|
||||
@@ -50,6 +50,8 @@ docs/*.pdf
|
||||
# Local dev scripts and temp files
|
||||
*.sh
|
||||
*.cmd
|
||||
!ohos-clang.sh
|
||||
!ohos-clangxx.sh
|
||||
!scripts/**
|
||||
!.github/scripts/**
|
||||
test.txt
|
||||
|
||||
+10
-1
@@ -14,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
`/restore` now shows the 20 most recent snapshots, numeric restore targets can
|
||||
reach beyond that default listing up to a bounded index, and list requests
|
||||
above the visible cap fail explicitly instead of silently truncating.
|
||||
- Added HarmonyOS/OpenHarmony support scaffolding: environment-driven
|
||||
`OHOS_NATIVE_SDK` setup scripts and compiler wrappers, platform docs,
|
||||
explicit Rustls ring-provider installation for the no-provider TLS build, and
|
||||
OHOS fallbacks for unsupported keyring, clipboard, sandbox, browser-open, TTY,
|
||||
execpolicy Starlark parsing, and self-update surfaces.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -33,13 +38,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
allowlist approval is merged.
|
||||
- Documented the agent and sub-agent stewardship ethos so future automation
|
||||
preserves human issue intake, careful PR review, and contributor credit.
|
||||
- Moved the TUI Starlark execpolicy parser and PTY support behind non-OHOS
|
||||
target dependencies so published OpenHarmony builds no longer pull `nix` 0.28
|
||||
through `rustyline` or `portable-pty`.
|
||||
|
||||
### Community
|
||||
|
||||
Thanks to **@cyq1017** for the restore-listing implementation (#2513) and
|
||||
**@wywsoor** for the broader macOS/iTerm rollback UX report (#2494), and
|
||||
**@HUQIANTAO** for the `web_run` lock-splitting work (#2502) and turn-metadata
|
||||
prefix-cache stability work (#2517).
|
||||
prefix-cache stability work (#2517), and **@shenjackyuanjie** for the
|
||||
HarmonyOS/OpenHarmony port and MatePad Edge validation trail (#2634).
|
||||
|
||||
## [0.8.53] - 2026-06-03
|
||||
|
||||
|
||||
Generated
+114
-300
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -38,7 +38,8 @@ chrono = { version = "0.4.43", features = ["serde"] }
|
||||
clap = { version = "4.5.54", features = ["derive"] }
|
||||
clap_complete = "4.5"
|
||||
dirs = "6.0.0"
|
||||
reqwest = { version = "0.13.1", default-features = false, features = ["json", "rustls", "socks"] }
|
||||
reqwest = { version = "0.13.1", default-features = false, features = ["json", "rustls-no-provider", "socks"] }
|
||||
rustls = { version = "0.23.36", default-features = false, features = ["ring", "std", "tls12"] }
|
||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
|
||||
@@ -143,6 +143,8 @@ codewhale doctor # セットアップを検証
|
||||
|
||||
`npm i -g codewhale` は v0.8.8 以降、glibc ベースの ARM64 Linux で動作します。[Releases ページ](https://github.com/Hmbown/CodeWhale/releases) からビルド済みバイナリをダウンロードし、`PATH` 上に並べて配置することもできます。
|
||||
|
||||
HarmonyOS PC と OpenHarmony クロスビルドの設定は [docs/HarmonyOS.md](docs/HarmonyOS.md) を参照してください。
|
||||
|
||||
### 中国 / ミラーフレンドリーなインストール
|
||||
|
||||
中国本土から GitHub または npm のダウンロードが遅い場合は、Cargo レジストリのミラーを利用してください:
|
||||
|
||||
@@ -221,6 +221,8 @@ Prebuilt binary pairs and platform archives are published for Linux x64, Linux
|
||||
ARM64, macOS x64, macOS ARM64, and Windows x64. For other targets, see
|
||||
[docs/INSTALL.md](docs/INSTALL.md).
|
||||
|
||||
For HarmonyOS PC and OpenHarmony cross-build setup, see [docs/HarmonyOS.md](docs/HarmonyOS.md).
|
||||
|
||||
### China / Mirror-friendly Installation
|
||||
|
||||
If GitHub or npm downloads are slow from mainland China, use
|
||||
|
||||
@@ -183,6 +183,8 @@ Hãy chỉ định mô hình hoặc cấp độ suy nghĩ cố định nếu b
|
||||
|
||||
Lệnh cài đặt `npm i -g codewhale` hoạt động trên môi trường Linux ARM64 nền glibc từ phiên bản v0.8.8 trở đi. Bạn cũng có thể tải trực tiếp các tệp binary dựng sẵn từ [trang phát hành Releases](https://github.com/Hmbown/CodeWhale/releases) và đặt chúng cạnh nhau trong một thư mục thuộc biến `PATH`.
|
||||
|
||||
Xem [docs/HarmonyOS.md](docs/HarmonyOS.md) để cấu hình HarmonyOS PC và cross-build OpenHarmony.
|
||||
|
||||
### Cài đặt thân thiện qua Mirror (Tại Trung Quốc)
|
||||
|
||||
Nếu việc tải xuống từ GitHub hoặc npm bị chậm từ Trung Quốc đại lục, bạn hãy sử dụng mirror registry cho Cargo:
|
||||
|
||||
@@ -186,6 +186,8 @@ Auto 模式同时控制两个设置:
|
||||
|
||||
从 v0.8.8 起,`npm i -g codewhale` 直接支持 glibc 系的 ARM64 Linux。你也可以从 [Releases 页面](https://github.com/Hmbown/CodeWhale/releases) 下载预编译二进制,放到 `PATH` 目录中。
|
||||
|
||||
HarmonyOS PC 运行和 OpenHarmony 交叉编译配置见 [docs/HarmonyOS.md](docs/HarmonyOS.md)。
|
||||
|
||||
### 中国大陆 / 镜像友好安装
|
||||
|
||||
如果在中国大陆访问 GitHub 或 npm 下载较慢,可以通过 Cargo 注册表镜像安装:
|
||||
|
||||
@@ -21,6 +21,7 @@ codewhale-state = { path = "../state", version = "0.8.53" }
|
||||
codewhale-tools = { path = "../tools", version = "0.8.53" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
rustls.workspace = true
|
||||
tokio.workspace = true
|
||||
tower-http.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -27,6 +27,8 @@ struct Cli {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
install_rustls_crypto_provider();
|
||||
|
||||
let cli = Cli::parse();
|
||||
let listen: SocketAddr = format!("{}:{}", cli.host, cli.port)
|
||||
.parse()
|
||||
@@ -41,6 +43,10 @@ async fn main() -> Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
fn install_rustls_crypto_provider() {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
}
|
||||
|
||||
fn app_server_token_from_env() -> Option<String> {
|
||||
std::env::var("CODEWHALE_APP_SERVER_TOKEN")
|
||||
.ok()
|
||||
|
||||
@@ -38,6 +38,7 @@ dirs.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
reqwest = { workspace = true, features = ["blocking"] }
|
||||
rustls.workspace = true
|
||||
semver.workspace = true
|
||||
tokio.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
@@ -471,7 +471,13 @@ struct AppServerArgs {
|
||||
|
||||
const MCP_SERVER_DEFINITIONS_KEY: &str = "mcp.server_definitions";
|
||||
|
||||
fn install_rustls_crypto_provider() {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
}
|
||||
|
||||
pub fn run_cli() -> std::process::ExitCode {
|
||||
install_rustls_crypto_provider();
|
||||
|
||||
match run() {
|
||||
Ok(()) => std::process::ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
|
||||
@@ -20,6 +20,12 @@ use std::io::Write;
|
||||
|
||||
/// Run the self-update workflow.
|
||||
pub fn run_update(beta: bool, check_only: bool, proxy_arg: Option<String>) -> Result<()> {
|
||||
#[cfg(target_env = "ohos")]
|
||||
{
|
||||
let _ = (beta, check_only, proxy_arg);
|
||||
bail!("self-update is not supported on HarmonyOS/OpenHarmony yet");
|
||||
}
|
||||
|
||||
let current_exe =
|
||||
std::env::current_exe().context("failed to determine current executable path")?;
|
||||
let targets = update_targets_for_exe(¤t_exe);
|
||||
@@ -353,6 +359,8 @@ pub(crate) fn validate_and_build_proxy(proxy_str: &str) -> Result<Proxy> {
|
||||
}
|
||||
|
||||
fn update_http_client(proxy: Option<&Proxy>) -> Result<reqwest::blocking::Client> {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
|
||||
let mut builder = reqwest::blocking::Client::builder();
|
||||
if let Some(proxy) = proxy {
|
||||
builder = builder.proxy(proxy.clone());
|
||||
|
||||
@@ -9,6 +9,7 @@ description = "Shared CodeWhale release discovery and version comparison helpers
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
reqwest = { workspace = true, features = ["blocking"] }
|
||||
rustls.workspace = true
|
||||
semver.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ keyring = { version = "3", features = ["apple-native"] }
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
keyring = { version = "3", features = ["windows-native"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
[target.'cfg(all(target_os = "linux", not(target_env = "ohos")))'.dependencies]
|
||||
keyring = { version = "3", features = ["linux-native-sync-persistent", "crypto-rust"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
+46
-10
@@ -92,7 +92,7 @@ pub trait KeyringStore: Send + Sync {
|
||||
/// Wraps the platform credential store:
|
||||
/// - **macOS**: Keychain (via `security` framework)
|
||||
/// - **Windows**: Credential Manager
|
||||
/// - **Linux**: Secret Service (GNOME Keyring / kwallet via dbus)
|
||||
/// - **Linux**: Secret Service (GNOME Keyring / kwallet via dbus), excluding OHOS
|
||||
///
|
||||
/// This backend is opt-in -- set the [`SECRET_BACKEND_ENV`] environment
|
||||
/// variable to `system` or `keyring` to activate it. On platforms without
|
||||
@@ -124,7 +124,11 @@ impl DefaultKeyringStore {
|
||||
/// Probe the OS keyring without writing anything. Returns `Ok(())` if
|
||||
/// a backend is reachable, otherwise an error describing why not.
|
||||
pub fn probe(&self) -> Result<(), SecretsError> {
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
{
|
||||
// `Entry::new` is enough to validate the native macOS/Windows
|
||||
// backend path. Avoid a dummy read there because it can trigger
|
||||
@@ -149,7 +153,11 @@ impl DefaultKeyringStore {
|
||||
Err(other) => Err(SecretsError::Keyring(other.to_string())),
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
)))]
|
||||
{
|
||||
let _ = &self.service;
|
||||
Err(SecretsError::Keyring(unsupported_keyring_message()))
|
||||
@@ -159,7 +167,11 @@ impl DefaultKeyringStore {
|
||||
|
||||
impl KeyringStore for DefaultKeyringStore {
|
||||
fn get(&self, key: &str) -> Result<Option<String>, SecretsError> {
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
{
|
||||
let entry = keyring::Entry::new(&self.service, key)
|
||||
.map_err(|err| SecretsError::Keyring(err.to_string()))?;
|
||||
@@ -169,7 +181,11 @@ impl KeyringStore for DefaultKeyringStore {
|
||||
Err(err) => Err(SecretsError::Keyring(err.to_string())),
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
)))]
|
||||
{
|
||||
let _ = key;
|
||||
Err(SecretsError::Keyring(unsupported_keyring_message()))
|
||||
@@ -177,7 +193,11 @@ impl KeyringStore for DefaultKeyringStore {
|
||||
}
|
||||
|
||||
fn set(&self, key: &str, value: &str) -> Result<(), SecretsError> {
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
{
|
||||
let entry = keyring::Entry::new(&self.service, key)
|
||||
.map_err(|err| SecretsError::Keyring(err.to_string()))?;
|
||||
@@ -185,7 +205,11 @@ impl KeyringStore for DefaultKeyringStore {
|
||||
.set_password(value)
|
||||
.map_err(|err| SecretsError::Keyring(err.to_string()))
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
)))]
|
||||
{
|
||||
let _ = (key, value);
|
||||
Err(SecretsError::Keyring(unsupported_keyring_message()))
|
||||
@@ -193,7 +217,11 @@ impl KeyringStore for DefaultKeyringStore {
|
||||
}
|
||||
|
||||
fn delete(&self, key: &str) -> Result<(), SecretsError> {
|
||||
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
|
||||
#[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
{
|
||||
let entry = keyring::Entry::new(&self.service, key)
|
||||
.map_err(|err| SecretsError::Keyring(err.to_string()))?;
|
||||
@@ -202,7 +230,11 @@ impl KeyringStore for DefaultKeyringStore {
|
||||
Err(err) => Err(SecretsError::Keyring(err.to_string())),
|
||||
}
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
)))]
|
||||
{
|
||||
let _ = key;
|
||||
Err(SecretsError::Keyring(unsupported_keyring_message()))
|
||||
@@ -214,7 +246,11 @@ impl KeyringStore for DefaultKeyringStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
)))]
|
||||
fn unsupported_keyring_message() -> String {
|
||||
"system keyring backend is unsupported on this platform".to_string()
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ path = "src/bin/deepseek_tui_legacy_shim.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
arboard = "3.4"
|
||||
codewhale-config = { path = "../config", version = "0.8.53" }
|
||||
codewhale-protocol = { path = "../protocol", version = "0.8.53" }
|
||||
codewhale-release = { path = "../release", version = "0.8.53" }
|
||||
@@ -47,10 +46,10 @@ fd-lock = "4.0.4"
|
||||
futures-util = "0.3.31"
|
||||
ratatui = "0.30"
|
||||
regex = "1.11"
|
||||
reqwest = { version = "0.13.1", default-features = false, features = ["blocking", "json", "stream", "multipart", "form", "rustls", "http2", "gzip", "brotli"] }
|
||||
reqwest = { version = "0.13.1", default-features = false, features = ["blocking", "json", "stream", "multipart", "form", "rustls-no-provider", "http2", "gzip", "brotli"] }
|
||||
rustls.workspace = true
|
||||
qrcode = { version = "0.14", default-features = false }
|
||||
similar = "2"
|
||||
rustyline = "15.0.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = { version = "1.0.149", features = ["preserve_order"] }
|
||||
schemars = { version = "1.2.1", features = ["derive", "preserve_order"] }
|
||||
@@ -71,9 +70,7 @@ tower-http = { version = "0.6", features = ["cors"] }
|
||||
wait-timeout = "0.2"
|
||||
multimap = "0.10.0"
|
||||
shlex = "1.3.0"
|
||||
starlark = "0.13.0"
|
||||
tiny_http = "0.12"
|
||||
portable-pty = "0.9"
|
||||
zeroize = "1.8.2"
|
||||
ignore = "0.4"
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
@@ -91,6 +88,13 @@ vt100 = "0.15"
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "windows", all(target_os = "linux", not(target_env = "ohos"))))'.dependencies]
|
||||
arboard = "3.4"
|
||||
|
||||
[target.'cfg(not(target_env = "ohos"))'.dependencies]
|
||||
portable-pty = "0.9"
|
||||
starlark = "0.13.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6.3"
|
||||
objc2-foundation = { version = "0.3.2", default-features = false, features = ["std", "NSArray", "NSDictionary", "NSError", "NSObject", "NSString", "NSURL"] }
|
||||
|
||||
@@ -62,6 +62,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
pub fn apply_to_pty_command<I, K, V>(cmd: &mut portable_pty::CommandBuilder, overrides: I)
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
use starlark::Error as StarlarkError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -23,6 +24,9 @@ pub enum Error {
|
||||
},
|
||||
#[error("expected example to not match rule `{rule}`: {example}")]
|
||||
ExampleDidMatch { rule: String, example: String },
|
||||
#[error("{0}")]
|
||||
UnsupportedPlatform(String),
|
||||
#[error("starlark error: {0}")]
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
Starlark(StarlarkError),
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ pub mod decision;
|
||||
pub mod error;
|
||||
pub mod execpolicycheck;
|
||||
pub mod matcher;
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
pub mod parser;
|
||||
#[cfg(target_env = "ohos")]
|
||||
pub mod parser_ohos;
|
||||
pub mod policy;
|
||||
pub mod rule;
|
||||
pub mod rules;
|
||||
@@ -17,7 +20,10 @@ pub use decision::Decision;
|
||||
pub use error::Error;
|
||||
pub use error::Result;
|
||||
pub use execpolicycheck::ExecPolicyCheckCommand;
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
pub use parser::PolicyParser;
|
||||
#[cfg(target_env = "ohos")]
|
||||
pub use parser_ohos::PolicyParser;
|
||||
pub use policy::Evaluation;
|
||||
pub use policy::Policy;
|
||||
pub use rule::Rule;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use super::error::Error;
|
||||
use super::error::Result;
|
||||
|
||||
pub struct PolicyParser;
|
||||
|
||||
impl Default for PolicyParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PolicyParser {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
pub fn parse(&mut self, _policy_identifier: &str, _policy_file_contents: &str) -> Result<()> {
|
||||
Err(Error::UnsupportedPlatform(
|
||||
"Starlark execpolicy files are not supported on HarmonyOS/OpenHarmony yet because upstream starlark-rust still depends on a rustyline/nix chain that does not compile for OHOS.".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn build(self) -> super::policy::Policy {
|
||||
super::policy::Policy::empty()
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,10 @@ fn configure_windows_console_utf8() {
|
||||
#[cfg(not(windows))]
|
||||
fn configure_windows_console_utf8() {}
|
||||
|
||||
fn install_rustls_crypto_provider() {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "codewhale-tui",
|
||||
@@ -846,6 +850,7 @@ enum SandboxCommand {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
configure_windows_console_utf8();
|
||||
install_rustls_crypto_provider();
|
||||
|
||||
// ── Process hardening (#2183) ─────────────────────────────────────────
|
||||
// MUST run before Tokio is booted and before any threads are spawned.
|
||||
|
||||
@@ -35,13 +35,13 @@ pub mod process_hardening;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod seatbelt;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
pub mod landlock;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
pub mod seccomp;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
pub mod bwrap;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -223,7 +223,7 @@ pub enum SandboxType {
|
||||
MacosSeatbelt,
|
||||
|
||||
/// Linux Landlock sandboxing (kernel 5.13+).
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
LinuxLandlock,
|
||||
|
||||
/// Windows process-containment helper.
|
||||
@@ -240,7 +240,7 @@ impl std::fmt::Display for SandboxType {
|
||||
SandboxType::None => write!(f, "none"),
|
||||
#[cfg(target_os = "macos")]
|
||||
SandboxType::MacosSeatbelt => write!(f, "macos-seatbelt"),
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
SandboxType::LinuxLandlock => write!(f, "linux-landlock"),
|
||||
#[cfg(target_os = "windows")]
|
||||
SandboxType::Windows => write!(f, "windows-sandbox"),
|
||||
@@ -305,7 +305,7 @@ pub fn get_platform_sandbox() -> Option<SandboxType> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
{
|
||||
if landlock::is_available() {
|
||||
return Some(SandboxType::LinuxLandlock);
|
||||
@@ -410,7 +410,7 @@ impl SandboxManager {
|
||||
#[cfg(target_os = "macos")]
|
||||
SandboxType::MacosSeatbelt => Self::prepare_seatbelt(spec),
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
SandboxType::LinuxLandlock => self.prepare_landlock(spec),
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -467,7 +467,7 @@ impl SandboxManager {
|
||||
/// If `prefer_bwrap` is set and `/usr/bin/bwrap` is available, routes the
|
||||
/// command through bubblewrap for stronger filesystem isolation (#2184).
|
||||
/// Otherwise falls back to Landlock markers.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
fn prepare_landlock(&self, spec: &CommandSpec) -> ExecEnv {
|
||||
// Check if bwrap passthrough should be used (#2184).
|
||||
if self.prefer_bwrap && bwrap::is_available() {
|
||||
@@ -539,7 +539,10 @@ impl SandboxManager {
|
||||
/// This helps distinguish between legitimate command failures and
|
||||
/// sandbox-blocked operations.
|
||||
pub fn was_denied(sandbox_type: SandboxType, exit_code: i32, stderr: &str) -> bool {
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
)))]
|
||||
let _ = (exit_code, stderr);
|
||||
|
||||
match sandbox_type {
|
||||
@@ -548,7 +551,7 @@ impl SandboxManager {
|
||||
#[cfg(target_os = "macos")]
|
||||
SandboxType::MacosSeatbelt => seatbelt::detect_denial(exit_code, stderr),
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
SandboxType::LinuxLandlock => landlock::detect_denial(exit_code, stderr),
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -558,7 +561,10 @@ impl SandboxManager {
|
||||
|
||||
/// Get a human-readable description of why a command was blocked.
|
||||
pub fn denial_message(sandbox_type: SandboxType, stderr: &str) -> String {
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
)))]
|
||||
let _ = stderr;
|
||||
|
||||
match sandbox_type {
|
||||
@@ -578,7 +584,7 @@ impl SandboxManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
SandboxType::LinuxLandlock => {
|
||||
// Seccomp patterns checked first because they are more specific (#2182).
|
||||
if stderr.contains("Bad system call")
|
||||
@@ -825,7 +831,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
fn test_parity_linux_landlock_available() {
|
||||
let st = get_platform_sandbox();
|
||||
assert!(matches!(st, Some(SandboxType::LinuxLandlock)));
|
||||
@@ -844,7 +850,7 @@ mod tests {
|
||||
0,
|
||||
""
|
||||
));
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
assert!(!SandboxManager::was_denied(
|
||||
SandboxType::LinuxLandlock,
|
||||
0,
|
||||
@@ -855,7 +861,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
fn test_parity_seccomp_sigsys_detected() {
|
||||
assert!(SandboxManager::was_denied(
|
||||
SandboxType::LinuxLandlock,
|
||||
@@ -891,7 +897,7 @@ mod tests {
|
||||
let spec = CommandSpec::shell("true", PathBuf::from("/tmp"), Duration::from_secs(5))
|
||||
.with_policy(SandboxPolicy::default());
|
||||
let env = manager.prepare(&spec);
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
{
|
||||
let marker = env.env.get("DEEPSEEK_SANDBOX");
|
||||
assert!(marker.is_none_or(|v| v != "bwrap"));
|
||||
@@ -905,7 +911,7 @@ mod tests {
|
||||
let spec = CommandSpec::shell("true", PathBuf::from("/tmp"), Duration::from_secs(5))
|
||||
.with_policy(SandboxPolicy::default());
|
||||
let env = manager.prepare(&spec);
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
{
|
||||
if crate::sandbox::bwrap::is_available() {
|
||||
let marker = env.env.get("DEEPSEEK_SANDBOX");
|
||||
|
||||
@@ -45,18 +45,18 @@
|
||||
/// hardening is defense-in-depth — the sandbox still protects child processes
|
||||
/// even if these prctls fail (e.g., in a container where some are restricted).
|
||||
pub fn apply_process_hardening() {
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
{
|
||||
apply_linux_hardening();
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[cfg(not(all(target_os = "linux", not(target_env = "ohos"))))]
|
||||
{
|
||||
tracing::debug!("Process hardening skipped: not on Linux");
|
||||
}
|
||||
}
|
||||
|
||||
/// Linux-specific hardening implementation.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
fn apply_linux_hardening() {
|
||||
// ── PR_SET_DUMPABLE = 0 ────────────────────────────────────────────────
|
||||
//
|
||||
|
||||
@@ -155,18 +155,18 @@ fn probe_git(workspace: &Path) -> GitProbe {
|
||||
}
|
||||
|
||||
fn probe_bwrap_available() -> bool {
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
{
|
||||
crate::sandbox::bwrap::is_available()
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[cfg(not(all(target_os = "linux", not(target_env = "ohos"))))]
|
||||
{
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn probe_cgroup_version() -> Option<u8> {
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
{
|
||||
let path = std::path::Path::new("/sys/fs/cgroup/cgroup.controllers");
|
||||
if path.exists() {
|
||||
@@ -178,7 +178,7 @@ fn probe_cgroup_version() -> Option<u8> {
|
||||
}
|
||||
None
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[cfg(not(all(target_os = "linux", not(target_env = "ohos"))))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ use windows::Win32::System::JobObjects::{
|
||||
#[cfg(windows)]
|
||||
use windows::core::PCWSTR;
|
||||
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
use portable_pty::{CommandBuilder, PtySize, native_pty_system};
|
||||
|
||||
use super::shell_output::{summarize_output, truncate_with_meta};
|
||||
@@ -130,6 +131,7 @@ pub struct ShellDeltaResult {
|
||||
|
||||
enum ShellChild {
|
||||
Process(Child),
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
Pty(Box<dyn portable_pty::Child + Send>),
|
||||
}
|
||||
|
||||
@@ -165,7 +167,7 @@ fn kill_child_process_group(child: &mut Child) -> std::io::Result<()> {
|
||||
/// path (`kill_child_process_group` from the cancellation token) still
|
||||
/// handles normal shutdown; abnormal exit can leak children — tracked as a
|
||||
/// follow-up watchdog item per the original issue's acceptance criteria.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
fn install_parent_death_signal(cmd: &mut Command) {
|
||||
use std::os::unix::process::CommandExt;
|
||||
// SAFETY: `pre_exec` runs in the child between fork and exec. The closure
|
||||
@@ -227,7 +229,7 @@ fn push_shell_args(cmd: &mut Command, _program: &str, args: &[String]) {
|
||||
cmd.args(args);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[cfg(not(all(target_os = "linux", not(target_env = "ohos"))))]
|
||||
fn install_parent_death_signal(_cmd: &mut Command) {
|
||||
// No kernel-level equivalent on macOS / Windows. The cooperative
|
||||
// cancellation + process_group SIGKILL path covers normal shutdown;
|
||||
@@ -363,6 +365,7 @@ impl ShellExitStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
fn from_pty(status: portable_pty::ExitStatus) -> Self {
|
||||
let code = i32::try_from(status.exit_code()).unwrap_or(i32::MAX);
|
||||
Self {
|
||||
@@ -378,6 +381,7 @@ impl ShellChild {
|
||||
ShellChild::Process(child) => child
|
||||
.try_wait()
|
||||
.map(|status| status.map(ShellExitStatus::from_std)),
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
ShellChild::Pty(child) => child
|
||||
.try_wait()
|
||||
.map(|status| status.map(ShellExitStatus::from_pty)),
|
||||
@@ -387,6 +391,7 @@ impl ShellChild {
|
||||
fn wait(&mut self) -> std::io::Result<ShellExitStatus> {
|
||||
match self {
|
||||
ShellChild::Process(child) => child.wait().map(ShellExitStatus::from_std),
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
ShellChild::Pty(child) => child.wait().map(ShellExitStatus::from_pty),
|
||||
}
|
||||
}
|
||||
@@ -397,6 +402,7 @@ impl ShellChild {
|
||||
ShellChild::Process(child) => kill_child_process_group(child),
|
||||
#[cfg(not(unix))]
|
||||
ShellChild::Process(child) => child.kill(),
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
ShellChild::Pty(child) => child.kill(),
|
||||
}
|
||||
}
|
||||
@@ -404,6 +410,7 @@ impl ShellChild {
|
||||
|
||||
enum StdinWriter {
|
||||
Pipe(ChildStdin),
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
Pty(Box<dyn Write + Send>),
|
||||
}
|
||||
|
||||
@@ -411,6 +418,7 @@ impl StdinWriter {
|
||||
fn write_all(&mut self, data: &[u8]) -> std::io::Result<()> {
|
||||
match self {
|
||||
StdinWriter::Pipe(stdin) => stdin.write_all(data),
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
StdinWriter::Pty(writer) => writer.write_all(data),
|
||||
}
|
||||
}
|
||||
@@ -418,6 +426,7 @@ impl StdinWriter {
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
match self {
|
||||
StdinWriter::Pipe(stdin) => stdin.flush(),
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
StdinWriter::Pty(writer) => writer.flush(),
|
||||
}
|
||||
}
|
||||
@@ -523,9 +532,15 @@ impl BackgroundShell {
|
||||
// Without this kill, handle.join() blocks indefinitely, freezing the UI
|
||||
// event loop that calls list_jobs() → poll() → collect_output().
|
||||
#[cfg(unix)]
|
||||
if let Some(ShellChild::Process(ref mut proc)) = self.child {
|
||||
if let Some(child) = self.child.as_mut() {
|
||||
match child {
|
||||
ShellChild::Process(proc) => {
|
||||
let _ = kill_child_process_group(proc);
|
||||
}
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
ShellChild::Pty(_) => {}
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
terminate_and_close_windows_job(self.windows_job.take());
|
||||
if let Some(handle) = self.stdout_thread.take() {
|
||||
@@ -619,7 +634,8 @@ impl BackgroundShell {
|
||||
/// Kill the process
|
||||
fn kill(&mut self) -> Result<()> {
|
||||
if let Some(ref mut child) = self.child {
|
||||
if let ShellChild::Process(proc) = child {
|
||||
match child {
|
||||
ShellChild::Process(proc) => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
terminate_windows_job(self.windows_job.as_ref(), proc)
|
||||
@@ -631,11 +647,14 @@ impl BackgroundShell {
|
||||
proc.kill().context("Failed to kill process")?;
|
||||
let _ = proc.wait();
|
||||
}
|
||||
} else {
|
||||
}
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
ShellChild::Pty(child) => {
|
||||
child.kill().context("Failed to kill process")?;
|
||||
let _ = child.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.status = ShellStatus::Killed;
|
||||
self.collect_output();
|
||||
Ok(())
|
||||
@@ -717,11 +736,15 @@ impl Drop for BackgroundShell {
|
||||
&& let Some(ref mut child) = self.child
|
||||
{
|
||||
#[cfg(windows)]
|
||||
if let ShellChild::Process(proc) = child {
|
||||
match child {
|
||||
ShellChild::Process(proc) => {
|
||||
let _ = terminate_windows_job(self.windows_job.as_ref(), proc);
|
||||
} else {
|
||||
}
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
ShellChild::Pty(child) => {
|
||||
let _ = child.kill();
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
@@ -1276,6 +1299,13 @@ impl ShellManager {
|
||||
let program = exec_env.program();
|
||||
let args = exec_env.args();
|
||||
|
||||
#[cfg(target_env = "ohos")]
|
||||
if tty {
|
||||
return Err(anyhow!(
|
||||
"TTY shell mode is not supported on HarmonyOS/OpenHarmony yet."
|
||||
));
|
||||
}
|
||||
|
||||
let stdout_buffer = Arc::new(Mutex::new(Vec::new()));
|
||||
let stderr_buffer = if tty {
|
||||
None
|
||||
@@ -1287,6 +1317,11 @@ impl ShellManager {
|
||||
let mut windows_job = None;
|
||||
|
||||
let (child, stdin, stdout_thread, stderr_thread) = if tty {
|
||||
#[cfg(target_env = "ohos")]
|
||||
unreachable!("OHOS TTY mode returns before PTY setup");
|
||||
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
{
|
||||
let pty_system = native_pty_system();
|
||||
let pair = pty_system
|
||||
.openpty(PtySize {
|
||||
@@ -1326,6 +1361,7 @@ impl ShellManager {
|
||||
stdout_thread,
|
||||
None,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let mut cmd = Command::new(program);
|
||||
push_shell_args(&mut cmd, program, args);
|
||||
|
||||
@@ -285,7 +285,7 @@ fn test_write_stdin_streams_output() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_env = "ohos")))]
|
||||
fn background_tty_command_has_controlling_terminal() {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let mut manager = ShellManager::new(tmp.path().to_path_buf());
|
||||
|
||||
@@ -12,17 +12,34 @@ use std::io::{self, IsTerminal, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
#[cfg(any(
|
||||
all(test, unix),
|
||||
all(
|
||||
any(target_os = "macos", target_os = "windows", target_os = "linux"),
|
||||
not(test)
|
||||
)
|
||||
all(not(test), target_os = "macos"),
|
||||
all(not(test), target_os = "windows"),
|
||||
all(not(test), target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
use std::process::{Command, Stdio};
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
use arboard::{Clipboard, ImageData};
|
||||
use base64::Engine as _;
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
use image::{ImageBuffer, Rgba};
|
||||
|
||||
const OSC52_MAX_BYTES: usize = 100 * 1024;
|
||||
@@ -53,6 +70,7 @@ impl PastedImage {
|
||||
}
|
||||
|
||||
/// Clipboard payloads supported by the TUI.
|
||||
#[cfg_attr(all(target_env = "ohos", not(test)), allow(dead_code))]
|
||||
pub enum ClipboardContent {
|
||||
Text(String),
|
||||
Image(PastedImage),
|
||||
@@ -60,7 +78,19 @@ pub enum ClipboardContent {
|
||||
|
||||
/// Clipboard reader/writer helper.
|
||||
pub struct ClipboardHandler {
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
clipboard: Option<Clipboard>,
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
clipboard_init_attempted: bool,
|
||||
#[cfg(test)]
|
||||
written_text: Vec<String>,
|
||||
@@ -74,7 +104,19 @@ impl ClipboardHandler {
|
||||
/// server (headless, WSL2) never blocks the TUI event loop.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
clipboard: None,
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
clipboard_init_attempted: false,
|
||||
#[cfg(test)]
|
||||
written_text: Vec::new(),
|
||||
@@ -89,6 +131,12 @@ impl ClipboardHandler {
|
||||
/// temporary thread and give it 500 ms; if it doesn't return in time the
|
||||
/// handler stays in fallback/no-op mode and `read`/`write_text` fall
|
||||
/// through to their OSC 52 and pbcopy/powershell fallbacks.
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
fn ensure_clipboard(&mut self) {
|
||||
if self.clipboard_init_attempted {
|
||||
return;
|
||||
@@ -110,11 +158,18 @@ impl ClipboardHandler {
|
||||
/// `workspace` is used as a fallback location when `~/.codewhale/` cannot
|
||||
/// be resolved (e.g. running with a stripped HOME in CI sandboxes).
|
||||
pub fn read(&mut self, workspace: &Path) -> Option<ClipboardContent> {
|
||||
#[cfg(all(target_os = "linux", not(test)))]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos"), not(test)))]
|
||||
if let Ok(text) = read_text_with_wlpaste() {
|
||||
return Some(ClipboardContent::Text(text));
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
{
|
||||
self.ensure_clipboard();
|
||||
let clipboard = self.clipboard.as_mut()?;
|
||||
if let Ok(text) = clipboard.get_text() {
|
||||
@@ -126,7 +181,9 @@ impl ClipboardHandler {
|
||||
{
|
||||
return Some(ClipboardContent::Image(pasted));
|
||||
}
|
||||
}
|
||||
|
||||
let _ = workspace;
|
||||
None
|
||||
}
|
||||
|
||||
@@ -140,17 +197,24 @@ impl ClipboardHandler {
|
||||
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
if write_text_with_wlcopy(text).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
{
|
||||
self.ensure_clipboard();
|
||||
if let Some(clipboard) = self.clipboard.as_mut()
|
||||
&& clipboard.set_text(text.to_string()).is_ok()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if write_text_with_pbcopy(text).is_ok() {
|
||||
@@ -215,17 +279,17 @@ fn write_text_with_stdin_command(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", not(test)))]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos"), not(test)))]
|
||||
fn write_text_with_wlcopy(text: &str) -> Result<()> {
|
||||
write_text_with_wlcopy_using_argv("wl-copy", text)
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", not(test)))]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos"), not(test)))]
|
||||
fn read_text_with_wlpaste() -> Result<String> {
|
||||
read_text_with_wlpaste_using_argv("wl-paste")
|
||||
}
|
||||
|
||||
#[cfg(any(all(test, unix), target_os = "linux"))]
|
||||
#[cfg(any(all(test, unix), all(target_os = "linux", not(target_env = "ohos"))))]
|
||||
fn read_text_with_wlpaste_using_argv(program: &str) -> Result<String> {
|
||||
let output = Command::new(program)
|
||||
.arg("--no-newline")
|
||||
@@ -241,7 +305,7 @@ fn read_text_with_wlpaste_using_argv(program: &str) -> Result<String> {
|
||||
String::from_utf8(output.stdout).context("wl-paste returned non-UTF-8 text")
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", not(test)))]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos"), not(test)))]
|
||||
fn write_text_with_wlcopy_using_argv(program: &str, text: &str) -> Result<()> {
|
||||
let mut child = Command::new(program)
|
||||
.stdin(Stdio::piped())
|
||||
@@ -310,12 +374,24 @@ fn clipboard_images_dir_for_home(workspace: &Path, home: Option<&Path>) -> PathB
|
||||
|
||||
/// Encode an RGBA `ImageData` from arboard as PNG and persist it. Returns
|
||||
/// the resulting path along with metadata used to render the paste hint.
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
fn save_image_as_png(workspace: &Path, image: &ImageData) -> Result<PastedImage> {
|
||||
save_image_as_png_in(&clipboard_images_dir(workspace), image)
|
||||
}
|
||||
|
||||
/// Lower-level variant that writes into an explicit directory. Exposed so the
|
||||
/// unit tests don't have to scribble inside the user's real home directory.
|
||||
#[cfg(any(
|
||||
test,
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
all(target_os = "linux", not(target_env = "ohos"))
|
||||
))]
|
||||
fn save_image_as_png_in(dir: &Path, image: &ImageData) -> Result<PastedImage> {
|
||||
std::fs::create_dir_all(dir).context("create clipboard-images dir")?;
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ fn browser_open_command(url: &str) -> Result<Command> {
|
||||
Ok(command)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
{
|
||||
let mut command = Command::new("xdg-open");
|
||||
command.arg(url);
|
||||
@@ -256,7 +256,11 @@ fn browser_open_command(url: &str) -> Result<Command> {
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
all(target_os = "linux", not(target_env = "ohos")),
|
||||
target_os = "windows"
|
||||
)))]
|
||||
Err(anyhow::anyhow!(
|
||||
"browser opening is unsupported on this platform"
|
||||
))
|
||||
@@ -863,7 +867,7 @@ mod project_mapping_tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
|
||||
{
|
||||
assert_eq!(command.get_program(), "xdg-open");
|
||||
assert_eq!(
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# HarmonyOS and OpenHarmony
|
||||
|
||||
This page covers CodeWhale on HarmonyOS PC and OpenHarmony cross-build setups.
|
||||
|
||||
## Running On HarmonyOS PC
|
||||
|
||||
HarmonyOS PC can use the normal Linux ARM64 package when its userspace is
|
||||
glibc-compatible:
|
||||
|
||||
```bash
|
||||
npm i -g codewhale
|
||||
codewhale --version
|
||||
```
|
||||
|
||||
You can also download `codewhale-linux-arm64` and
|
||||
`codewhale-tui-linux-arm64` from the GitHub Releases page and place both
|
||||
binaries on `PATH`.
|
||||
|
||||
## Cross-Compiling To OpenHarmony
|
||||
|
||||
The repository does not check in machine-specific SDK paths. Set
|
||||
`OHOS_NATIVE_SDK` to the OpenHarmony native SDK directory, the directory that
|
||||
contains `llvm/bin`, `sysroot`, and `build/cmake/ohos.toolchain.cmake`.
|
||||
|
||||
On Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:OHOS_NATIVE_SDK="<path-to-openharmony-native-sdk>"
|
||||
. .\scripts\ohos-env.ps1
|
||||
rustup target add aarch64-unknown-linux-ohos
|
||||
cargo build --target aarch64-unknown-linux-ohos -p codewhale-cli
|
||||
```
|
||||
|
||||
On Linux or macOS:
|
||||
|
||||
```bash
|
||||
export OHOS_NATIVE_SDK=/path/to/openharmony/native
|
||||
. ./scripts/ohos-env.sh
|
||||
rustup target add aarch64-unknown-linux-ohos
|
||||
cargo build --target aarch64-unknown-linux-ohos -p codewhale-cli
|
||||
```
|
||||
|
||||
The setup scripts export Cargo's target-specific `linker`, `AR`, `CC`, `CXX`,
|
||||
`CFLAGS`, `CXXFLAGS`, `CARGO_ENCODED_RUSTFLAGS`, `CC_SHELL_ESCAPED_FLAGS`, and
|
||||
CMake toolchain variables for `aarch64-unknown-linux-ohos`.
|
||||
|
||||
## Compiler Wrappers
|
||||
|
||||
For ad-hoc compiler calls, use the root wrappers. They read the same
|
||||
`OHOS_NATIVE_SDK` variable and do not contain local paths.
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
.\ohos-clang.ps1 --version
|
||||
.\ohos-clangxx.ps1 --version
|
||||
```
|
||||
|
||||
Linux or macOS:
|
||||
|
||||
```bash
|
||||
sh ./ohos-clang.sh --version
|
||||
sh ./ohos-clangxx.sh --version
|
||||
```
|
||||
|
||||
If you want to run the POSIX wrappers directly as `./ohos-clang.sh`, make them
|
||||
executable first:
|
||||
|
||||
```bash
|
||||
chmod +x ./ohos-clang.sh ./ohos-clangxx.sh
|
||||
```
|
||||
|
||||
## Cargo Config
|
||||
|
||||
`.cargo/config.toml` intentionally does not set a checked-in linker path.
|
||||
Cargo cannot expand environment variables inside `linker` or CMake toolchain
|
||||
path values there, so those values are exported by `scripts/ohos-env.ps1` and
|
||||
`scripts/ohos-env.sh` instead.
|
||||
@@ -44,6 +44,8 @@ systems such as Alpine should use [Build from source](#7-build-from-source).
|
||||
> and `codewhale-tui-linux-arm64`, so a plain `npm i -g codewhale` works
|
||||
> on any glibc-based ARM64 Linux. If you're stuck on v0.8.7, jump to
|
||||
> [Build from source](#7-build-from-source) — `cargo install` works fine.
|
||||
> For HarmonyOS PC and OpenHarmony cross-build setup, see
|
||||
> [HarmonyOS and OpenHarmony](HarmonyOS.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ PR is harvested, superseded, deferred, or closed.
|
||||
1. Stabilization and PR harvest: finish #2721 and #2722 before new feature work.
|
||||
2. Provider/model/auth correctness: land narrow correctness fixes that match the
|
||||
current provider architecture.
|
||||
3. HarmonyOS/MatePad Edge intake: keep #2634 active, scoped, and credited while
|
||||
the OHOS/Nix dependency clearance work finishes upstream.
|
||||
3. HarmonyOS/MatePad Edge intake: keep #2634 credited while the local harvest
|
||||
clears the OHOS/Nix dependency chain; full target-build success still needs a
|
||||
host with the OpenHarmony native SDK loaded.
|
||||
4. File decomposition Phase 1: split safe, test-covered config/provider and TUI
|
||||
view surfaces before adding larger workflow UX.
|
||||
5. WhaleFlow MVP: typed IR, executor skeleton, replay, and pod monitor before
|
||||
@@ -42,7 +43,7 @@ harvest/stewardship commits:
|
||||
| #2708 Windows sub-agent completion halves TUI render width | Cherry-picked as `e933a11d7`; follow-up fix `72653f8ef` invalidates reused fanout-card rows. | `cargo test -p codewhale-tui --locked subagent`; `cargo test -p codewhale-tui --locked terminal_size`; `cargo clippy -p codewhale-tui --locked -- -D warnings` passed. |
|
||||
| #2627 Xiaomi MiMo Token Plan mode | Harvested only the auth-header behavior as `5aa68d986`; did not merge the conflicting mode/env changes. | `cargo test -p codewhale-tui --bin codewhale-tui --locked xiaomi_mimo`; `cargo test -p codewhale-secrets --locked xiaomi_mimo`; `cargo test -p codewhale-config --locked xiaomi_mimo`; `cargo clippy -p codewhale-tui --locked -- -D warnings` passed. |
|
||||
| #2636 project-context mtime cache | Defer direct merge; harvest only after cache key/signature is widened. | Must include constitution changes, auto-generated context deletion, canonical path equivalence, and overwrite detection before landing. |
|
||||
| #2634 HarmonyOS port | Active HarmonyOS/MatePad Edge lane; do not close. | User-supplied MatePad Edge demo (`https://bilibili.com/video/av116689597368905`) confirms real-device interest. PR remains draft/blocked while the author waits on upstream Nix/dependency clearance and carries local patches; full port needs OHOS target checks plus sandbox, TLS, keyring, clipboard, browser-open, and self-update review before merge. |
|
||||
| #2634 HarmonyOS port | Locally harvested with additional Nix-chain clearance; keep credited and do not close until the integration branch is public. | User-supplied MatePad Edge demo (`https://bilibili.com/video/av116689597368905`) confirms real-device interest. Added env-driven OpenHarmony SDK setup, OHOS platform guards/fallbacks, self-update disablement, and OHOS target gating for Starlark execpolicy parsing plus PTY support so published OHOS builds do not pull `nix` 0.28 through `rustyline` or `portable-pty`. `cargo check --workspace --all-features --locked`, focused PTY/clipboard tests, and `cargo tree --locked -p codewhale-tui --target aarch64-unknown-linux-ohos -i nix@0.28.0` passed; full OHOS target check is blocked on this host because `OHOS_NATIVE_SDK`/target CC/sysroot are not configured and `ring` cannot find `assert.h`. |
|
||||
| #2687 append-only mode/approval prompt | Defer direct merge; draft has compile failures and Plan-mode prompt correctness risks. | Any future harvest must keep stable `message[0]` genuinely mode-agnostic, preserve mode/approval suffixes after capacity replans, and distinguish external overrides from persisted generated prompts. |
|
||||
| #2581 provider fallback chain design doc | Manually harvested as `docs/rfcs/2574-provider-fallback-chain.md` because the current PR head has no net file changes. | Keep issue #2574 open for implementation; close/comment on #2581 after the integration branch is public, crediting @idling11 and reporter @hsdbeebou. |
|
||||
| #2530 mention depth-cap hint | Already present in the current v0.9 stack as `a97675824` and `29f57665e`. | `cargo test -p codewhale-tui --locked try_autocomplete_file_mention_no_match` passed. |
|
||||
@@ -99,7 +100,7 @@ harvest/stewardship commits:
|
||||
| #2631 estimated_input_tokens cache | Mergeable | Already harvested into the 22-commit stack. |
|
||||
| #2632 tool-catalog JSON cache | Mergeable | Already harvested into the 22-commit stack. |
|
||||
| #2633 capacity reverse scans | Mergeable | Already harvested into the 22-commit stack. |
|
||||
| #2634 HarmonyOS port | Draft/blocked | Keep as active HarmonyOS/MatePad Edge lane. Do not merge wholesale until upstream Nix/dependency clearance, OHOS target checks, and sandbox/TLS/keyring/clipboard/browser/self-update review are complete. |
|
||||
| #2634 HarmonyOS port | Draft / locally harvested | Harvested with credit and extra Nix-chain fixes. Keep the original PR open for now; comment after the integration branch is public and request a real OHOS SDK build confirmation from the contributor before closing. |
|
||||
| #2635 output rows cache | Mergeable | Already harvested into the 22-commit stack. |
|
||||
| #2636 project-context cache | Conflicting | Defer/harvest only after cache correctness fixes. |
|
||||
| #2639 POST /v1/sessions endpoint | Mergeable | Defer; app-server contract needs focused review. |
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:OHOS_NATIVE_SDK)) {
|
||||
[Console]::Error.WriteLine("error: set OHOS_NATIVE_SDK to the OpenHarmony native SDK directory. It must contain llvm\bin and sysroot.")
|
||||
exit 1
|
||||
}
|
||||
|
||||
$sdk = $env:OHOS_NATIVE_SDK
|
||||
$clang = [System.IO.Path]::Combine($sdk, "llvm", "bin", "clang.exe")
|
||||
$sysroot = [System.IO.Path]::Combine($sdk, "sysroot")
|
||||
|
||||
if (-not (Test-Path -LiteralPath $clang -PathType Leaf -ErrorAction SilentlyContinue)) {
|
||||
[Console]::Error.WriteLine("error: OHOS_NATIVE_SDK does not contain llvm\bin\clang.exe: $sdk")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $sysroot -PathType Container -ErrorAction SilentlyContinue)) {
|
||||
[Console]::Error.WriteLine("error: OHOS_NATIVE_SDK does not contain sysroot: $sdk")
|
||||
exit 1
|
||||
}
|
||||
|
||||
& $clang -target aarch64-linux-ohos "--sysroot=$sysroot" -D__MUSL__ @args
|
||||
exit $LASTEXITCODE
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
if [ -z "${OHOS_NATIVE_SDK:-}" ]; then
|
||||
echo "error: set OHOS_NATIVE_SDK to the OpenHarmony native SDK directory. It must contain llvm/bin and sysroot." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sdk=$OHOS_NATIVE_SDK
|
||||
clang=$sdk/llvm/bin/clang
|
||||
sysroot=$sdk/sysroot
|
||||
|
||||
if [ ! -x "$clang" ]; then
|
||||
echo "error: OHOS_NATIVE_SDK does not contain executable llvm/bin/clang: $sdk" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$sysroot" ]; then
|
||||
echo "error: OHOS_NATIVE_SDK does not contain sysroot: $sdk" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$clang" -target aarch64-linux-ohos "--sysroot=$sysroot" -D__MUSL__ "$@"
|
||||
@@ -0,0 +1,23 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:OHOS_NATIVE_SDK)) {
|
||||
[Console]::Error.WriteLine("error: set OHOS_NATIVE_SDK to the OpenHarmony native SDK directory. It must contain llvm\bin and sysroot.")
|
||||
exit 1
|
||||
}
|
||||
|
||||
$sdk = $env:OHOS_NATIVE_SDK
|
||||
$clangxx = [System.IO.Path]::Combine($sdk, "llvm", "bin", "clang++.exe")
|
||||
$sysroot = [System.IO.Path]::Combine($sdk, "sysroot")
|
||||
|
||||
if (-not (Test-Path -LiteralPath $clangxx -PathType Leaf -ErrorAction SilentlyContinue)) {
|
||||
[Console]::Error.WriteLine("error: OHOS_NATIVE_SDK does not contain llvm\bin\clang++.exe: $sdk")
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $sysroot -PathType Container -ErrorAction SilentlyContinue)) {
|
||||
[Console]::Error.WriteLine("error: OHOS_NATIVE_SDK does not contain sysroot: $sdk")
|
||||
exit 1
|
||||
}
|
||||
|
||||
& $clangxx -target aarch64-linux-ohos "--sysroot=$sysroot" -D__MUSL__ @args
|
||||
exit $LASTEXITCODE
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
if [ -z "${OHOS_NATIVE_SDK:-}" ]; then
|
||||
echo "error: set OHOS_NATIVE_SDK to the OpenHarmony native SDK directory. It must contain llvm/bin and sysroot." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sdk=$OHOS_NATIVE_SDK
|
||||
clangxx=$sdk/llvm/bin/clang++
|
||||
sysroot=$sdk/sysroot
|
||||
|
||||
if [ ! -x "$clangxx" ]; then
|
||||
echo "error: OHOS_NATIVE_SDK does not contain executable llvm/bin/clang++: $sdk" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$sysroot" ]; then
|
||||
echo "error: OHOS_NATIVE_SDK does not contain sysroot: $sdk" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$clangxx" -target aarch64-linux-ohos "--sysroot=$sysroot" -D__MUSL__ "$@"
|
||||
@@ -0,0 +1,57 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Stop-OhosEnv {
|
||||
param([string]$Message)
|
||||
|
||||
[Console]::Error.WriteLine("error: $Message")
|
||||
throw "OpenHarmony Cargo environment setup failed."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:OHOS_NATIVE_SDK)) {
|
||||
Stop-OhosEnv "set OHOS_NATIVE_SDK to the OpenHarmony native SDK directory."
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $env:OHOS_NATIVE_SDK -PathType Container -ErrorAction SilentlyContinue)) {
|
||||
Stop-OhosEnv "OHOS_NATIVE_SDK does not exist: $env:OHOS_NATIVE_SDK"
|
||||
}
|
||||
|
||||
$sdk = (Resolve-Path -LiteralPath $env:OHOS_NATIVE_SDK -ErrorAction Stop).Path
|
||||
$clang = [System.IO.Path]::Combine($sdk, "llvm", "bin", "clang.exe")
|
||||
$clangxx = [System.IO.Path]::Combine($sdk, "llvm", "bin", "clang++.exe")
|
||||
$ar = [System.IO.Path]::Combine($sdk, "llvm", "bin", "llvm-ar.exe")
|
||||
$sysroot = [System.IO.Path]::Combine($sdk, "sysroot")
|
||||
$cmakeToolchain = [System.IO.Path]::Combine($sdk, "build", "cmake", "ohos.toolchain.cmake")
|
||||
|
||||
$requiredFiles = @($clang, $clangxx, $ar, $cmakeToolchain)
|
||||
foreach ($path in $requiredFiles) {
|
||||
if (-not (Test-Path -LiteralPath $path -PathType Leaf -ErrorAction SilentlyContinue)) {
|
||||
Stop-OhosEnv "required OpenHarmony SDK file is missing: $path"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $sysroot -PathType Container -ErrorAction SilentlyContinue)) {
|
||||
Stop-OhosEnv "required OpenHarmony SDK sysroot is missing: $sysroot"
|
||||
}
|
||||
|
||||
$target = "aarch64_unknown_linux_ohos"
|
||||
$targetUpper = "AARCH64_UNKNOWN_LINUX_OHOS"
|
||||
$commonFlags = "-target aarch64-linux-ohos --sysroot=`"$sysroot`" -D__MUSL__"
|
||||
|
||||
$env:CARGO_TARGET_AARCH64_UNKNOWN_LINUX_OHOS_LINKER = $clang
|
||||
$env:AR_aarch64_unknown_linux_ohos = $ar
|
||||
$env:CC_aarch64_unknown_linux_ohos = $clang
|
||||
$env:CXX_aarch64_unknown_linux_ohos = $clangxx
|
||||
$env:CC_SHELL_ESCAPED_FLAGS = "1"
|
||||
Set-Item -Path "Env:CFLAGS_$target" -Value $commonFlags
|
||||
Set-Item -Path "Env:CXXFLAGS_$target" -Value $commonFlags
|
||||
Set-Item -Path "Env:CMAKE_TOOLCHAIN_FILE_$target" -Value $cmakeToolchain
|
||||
|
||||
$separator = [char]0x1f
|
||||
$env:CARGO_ENCODED_RUSTFLAGS = @(
|
||||
"-Clink-arg=-target",
|
||||
"-Clink-arg=aarch64-linux-ohos",
|
||||
"-Clink-arg=--sysroot=$sysroot",
|
||||
"-Clink-arg=-D__MUSL__"
|
||||
) -join $separator
|
||||
|
||||
Write-Host "Configured OpenHarmony Cargo environment for $targetUpper from $sdk"
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
if [ -z "${OHOS_NATIVE_SDK:-}" ]; then
|
||||
echo "error: set OHOS_NATIVE_SDK to the OpenHarmony native SDK directory." >&2
|
||||
return 1 2>/dev/null || exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$OHOS_NATIVE_SDK" ]; then
|
||||
echo "error: OHOS_NATIVE_SDK does not exist: $OHOS_NATIVE_SDK" >&2
|
||||
return 1 2>/dev/null || exit 1
|
||||
fi
|
||||
|
||||
sdk=$(cd "$OHOS_NATIVE_SDK" && pwd)
|
||||
clang=$sdk/llvm/bin/clang
|
||||
clangxx=$sdk/llvm/bin/clang++
|
||||
ar=$sdk/llvm/bin/llvm-ar
|
||||
sysroot=$sdk/sysroot
|
||||
cmake_toolchain=$sdk/build/cmake/ohos.toolchain.cmake
|
||||
|
||||
for file in "$clang" "$clangxx" "$ar" "$cmake_toolchain"; do
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "error: required OpenHarmony SDK file is missing: $file" >&2
|
||||
return 1 2>/dev/null || exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -d "$sysroot" ]; then
|
||||
echo "error: required OpenHarmony SDK sysroot is missing: $sysroot" >&2
|
||||
return 1 2>/dev/null || exit 1
|
||||
fi
|
||||
|
||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_OHOS_LINKER=$clang
|
||||
export AR_aarch64_unknown_linux_ohos=$ar
|
||||
export CC_aarch64_unknown_linux_ohos=$clang
|
||||
export CXX_aarch64_unknown_linux_ohos=$clangxx
|
||||
export CC_SHELL_ESCAPED_FLAGS=1
|
||||
export CFLAGS_aarch64_unknown_linux_ohos="-target aarch64-linux-ohos --sysroot=\"$sysroot\" -D__MUSL__"
|
||||
export CXXFLAGS_aarch64_unknown_linux_ohos="-target aarch64-linux-ohos --sysroot=\"$sysroot\" -D__MUSL__"
|
||||
export CMAKE_TOOLCHAIN_FILE_aarch64_unknown_linux_ohos=$cmake_toolchain
|
||||
|
||||
sep=$(printf '\037')
|
||||
export CARGO_ENCODED_RUSTFLAGS="-Clink-arg=-target${sep}-Clink-arg=aarch64-linux-ohos${sep}-Clink-arg=--sysroot=$sysroot${sep}-Clink-arg=-D__MUSL__"
|
||||
|
||||
echo "Configured OpenHarmony Cargo environment for AARCH64_UNKNOWN_LINUX_OHOS from $sdk"
|
||||
Reference in New Issue
Block a user