fix(harmony): discover existing official HDC installations

This commit is contained in:
2026-09-10 14:45:52 +08:00
parent 5666bb8f40
commit b83454c683
8 changed files with 134 additions and 6 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
"id": "dev.trove.harmony-connector", "id": "dev.trove.harmony-connector",
"name": "Harmony Connector", "name": "Harmony Connector",
"description": "连接鸿蒙设备,收发完整文件夹、管理应用,查看手机画面并通过键鼠操作。", "description": "连接鸿蒙设备,收发完整文件夹、管理应用,查看手机画面并通过键鼠操作。",
"version": "0.3.1", "version": "0.3.2",
"engine": { "trove": ">=0.1.0 <1.0.0" }, "engine": { "trove": ">=0.1.0 <1.0.0" },
"icon": "assets/icon.svg", "icon": "assets/icon.svg",
"ui": { "ui": {
+1 -1
View File
@@ -204,7 +204,7 @@ dependencies = [
[[package]] [[package]]
name = "harmony-connector" name = "harmony-connector"
version = "0.3.1" version = "0.3.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "harmony-connector" name = "harmony-connector"
version = "0.3.1" version = "0.3.2"
edition = "2021" edition = "2021"
publish = false publish = false
@@ -129,11 +129,65 @@ fn tool_version(path: &Path, cancel: &AtomicBool) -> Result<String> {
} }
Ok(version.trim().to_owned()) Ok(version.trim().to_owned())
} }
fn installed_toolkits(home: &Path) -> Vec<PathBuf> {
let root = home.join("Library/HarmonyConnector/tools");
let mut versions: Vec<_> = fs::read_dir(&root)
.into_iter()
.flatten()
.flatten()
.filter(|entry| entry.path().is_dir())
.filter_map(|entry| {
let version = entry
.file_name()
.to_str()?
.split('.')
.map(str::parse::<u64>)
.collect::<std::result::Result<Vec<_>, _>>()
.ok()?;
Some((version, entry.path()))
})
.collect();
versions.sort_by(|a, b| b.cmp(a));
let mut candidates: Vec<_> = versions
.into_iter()
.filter_map(|(_, path)| selected_tool(&path))
.collect();
candidates.extend(selected_tool(&root));
if !candidates.is_empty() {
return candidates;
}
// Only inspect named, extracted toolkits directly under Downloads. ZIPs
// and unfinished browser downloads are not executable installations.
let mut downloads: Vec<_> = fs::read_dir(home.join("Downloads"))
.into_iter()
.flatten()
.flatten()
.filter(|entry| {
let name = entry.file_name().to_string_lossy().to_ascii_lowercase();
entry.path().is_dir()
&& (name.starts_with("command-line-tools") || name.starts_with("commandline-tools"))
})
.map(|entry| entry.path())
.collect();
downloads.sort();
candidates.extend(
downloads
.into_iter()
.filter_map(|path| selected_tool(&path)),
);
candidates
}
fn discover() -> Option<PathBuf> { fn discover() -> Option<PathBuf> {
let mut candidates = Vec::new(); let mut candidates = Vec::new();
if let Some(path) = env::var_os("PATH") { if let Some(path) = env::var_os("PATH") {
candidates.extend(env::split_paths(&path).map(|p| p.join("hdc"))); candidates.extend(env::split_paths(&path).map(|p| p.join("hdc")));
} }
if let Some(path) = candidates.iter().find(|p| executable(p)) {
return path.canonicalize().ok();
}
if let Some(home) = env::var_os("HOME") {
candidates.extend(installed_toolkits(Path::new(&home)));
}
for root in [ for root in [
Some(PathBuf::from("/Applications")), Some(PathBuf::from("/Applications")),
env::var_os("HOME").map(|p| PathBuf::from(p).join("Applications")), env::var_os("HOME").map(|p| PathBuf::from(p).join("Applications")),
@@ -74,6 +74,63 @@ fn rechecks_hdc_installed_after_manager_start() {
); );
} }
#[test]
fn automatically_discovers_downloaded_toolkit_without_manual_configuration() {
const CHILD: &str = "HARMONY_DOWNLOADED_TOOL_TEST";
if let Some(root) = env::var_os(CHILD) {
let root = PathBuf::from(root);
let installations = root.join("Library/HarmonyConnector/tools");
let previous = installations.join("9.9.0.1/toolchains/hdc");
let current = installations.join("26.0.0.821/toolchains/hdc");
version_tool(&previous);
version_tool(&current);
// An incomplete, newer extraction must not hide a usable installation.
fs::create_dir_all(installations.join("27.0.0.1/toolchains")).unwrap();
let manager = Manager::new();
let result = manager.call("tool", json!({})).unwrap();
assert_eq!(result["available"], true, "{result}");
assert_eq!(result["path"], json!(current.canonicalize().unwrap()));
assert!(!root.join("data/harmony-tools.json").exists());
// A user's explicit selection still wins over automatic discovery.
let chosen = root.join("chosen/hdc");
version_tool(&chosen);
manager.call("configure", json!({"path": chosen})).unwrap();
assert_eq!(
Manager::new().call("tool", json!({})).unwrap()["path"],
json!(chosen.canonicalize().unwrap())
);
fs::remove_file(chosen).unwrap();
fs::remove_dir_all(installations).unwrap();
// Also support a normal Finder extraction in Downloads, retaining the
// official command-line-tools/sdk/default/openharmony layout.
let downloaded = root.join("Downloads/commandline-tools-mac-arm64-26.0.0.821/command-line-tools/sdk/default/openharmony/toolchains/hdc");
version_tool(&downloaded);
let result = Manager::new().call("tool", json!({})).unwrap();
assert_eq!(result["available"], true);
assert_eq!(result["path"], json!(downloaded.canonicalize().unwrap()));
return;
}
let root = tempfile::tempdir().unwrap();
let output = std::process::Command::new(env::current_exe().unwrap())
.args([
"--exact",
"hdc::tests::automatically_discovers_downloaded_toolkit_without_manual_configuration",
"--nocapture",
])
.env(CHILD, root.path())
.env("HOME", root.path())
.env("PATH", root.path().join("bin"))
.env("TROVE_DATA_DIR", root.path().join("data"))
.output()
.unwrap();
assert!(
output.status.success(),
"{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn fixture(debug: bool) -> (tempfile::TempDir, Arc<Manager>) { fn fixture(debug: bool) -> (tempfile::TempDir, Arc<Manager>) {
let directory = tempfile::tempdir().unwrap(); let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("hdc"); let path = directory.path().join("hdc");
+4 -2
View File
@@ -1,6 +1,6 @@
# Harmony Connector # Harmony Connector
插件 ID:`dev.trove.harmony-connector`。当前版本:`0.3.1`。 插件 ID:`dev.trove.harmony-connector`。当前版本:`0.3.2`。
macOS 上的鸿蒙设备连接工具。通过 USB 收发文件与完整文件夹;使用官方 HDC 管理应用、保存截图、导出日志和调试应用数据。功能按连接工具、设备实际能力和授权开放,不按 HarmonyOS 大版本或机型名称限制。 macOS 上的鸿蒙设备连接工具。通过 USB 收发文件与完整文件夹;使用官方 HDC 管理应用、保存截图、导出日志和调试应用数据。功能按连接工具、设备实际能力和授权开放,不按 HarmonyOS 大版本或机型名称限制。
@@ -39,7 +39,7 @@ macOS 上的鸿蒙设备连接工具。通过 USB 收发文件与完整文件夹
2. 完整解压 ZIP,点击「选择已解压的工具目录」。可选择包含 `command-line-tools` 的解压目录、`command-line-tools` 本身,或其 `toolchains` 目录;插件定位 HDC、运行版本检查,成功后保存路径。错误选择和取消不会覆盖已有配置。 2. 完整解压 ZIP,点击「选择已解压的工具目录」。可选择包含 `command-line-tools` 的解压目录、`command-line-tools` 本身,或其 `toolchains` 目录;插件定位 HDC、运行版本检查,成功后保存路径。错误选择和取消不会覆盖已有配置。
3. 手机连接 USB,启用开发者模式与 USB 调试,解锁并允许这台 Mac 的调试请求。配置成功后自动检查设备;「刷新连接」也会重新检查工具,支持启动后才安装到 PATH 的 HDC。 3. 手机连接 USB,启用开发者模式与 USB 调试,解锁并允许这台 Mac 的调试请求。配置成功后自动检查设备;「刷新连接」也会重新检查工具,支持启动后才安装到 PATH 的 HDC。
已有 DevEco Studio 或 SDK 的用户也可点击「选择 HDC 文件」。插件启动时尝试发现 PATH 或 `/Applications`、`~/Applications` 中 DevEco Studio 的内置工具。典型路径: 已有 DevEco Studio 或 SDK 的用户也可点击「选择 HDC 文件」。插件保留手动选择的路径;未配置时,按 PATH、`~/Library/HarmonyConnector/tools` 中的工具安装、`~/Downloads` 中已解压的 Command Line Tools、`/Applications` 和 `~/Applications` 中的 DevEco Studio 依次查找。工具安装目录按数字版本从新到旧检查,跳过未包含可执行 HDC 的目录;Downloads 只检查名称以 `command-line-tools` 或 `commandline-tools` 开头的直接子目录及 SDK 的标准位置,不递归扫描用户文件、不将 ZIP 视为已安装工具。典型路径:
```text ```text
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc /Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
@@ -111,6 +111,8 @@ curios/harmony-connector/backend/harmony-connector --check-screen /absolute/path
0.3.1 的安装引导验证通过 41 项原生测试、30 项 UI/格式/坐标测试和 6 项 USB/协议检查(共 77 项)。新增回归覆盖官网下载入口、选择已解压目录、取消/错误选择保留配置、启动后安装工具、工具丢失后返回引导。浏览器以明确标记的模拟工具检查了引导和配置后的界面切换。使用已下载的官方工具目录调用实际发布后端,成功定位 HDC 并返回 `Ver: 3.2.0f`;设备当时仍为 Unauthorized,后续投屏验收因无已授权设备而停止。 0.3.1 的安装引导验证通过 41 项原生测试、30 项 UI/格式/坐标测试和 6 项 USB/协议检查(共 77 项)。新增回归覆盖官网下载入口、选择已解压目录、取消/错误选择保留配置、启动后安装工具、工具丢失后返回引导。浏览器以明确标记的模拟工具检查了引导和配置后的界面切换。使用已下载的官方工具目录调用实际发布后端,成功定位 HDC 并返回 `Ver: 3.2.0f`;设备当时仍为 Unauthorized,后续投屏验收因无已授权设备而停止。
0.3.2 补上工具安装目录和 Downloads 的自动发现。回归在独立 HOME、空 PATH 工具目录、未保存任何设置的进程中运行真实 `Manager::new → hdc.tool`,验证数字版本选择、未完成解压跳过、手动路径优先和官方解压目录布局。实际 Mac 验证通过 Wire 请求 `hdc.tool`,使用空插件数据目录和系统 PATH,未调用 `configure`;0.3.1 返回 `available: false`,0.3.2 自动找到之前解压的工具并返回 `Ver: 3.2.0f`。USB 目标仍为 Unauthorized。该版 42 项原生测试、30 项 UI/格式/坐标测试及 6 项 USB/协议检查全部通过。
## 构建与发布 ## 构建与发布
要求 macOS、Xcode Command Line Tools、Rust/Cargo、Node.js、pnpm、make、pkg-config。固定源码归档与哈希位于 `vendor/harmony-connector/`。 要求 macOS、Xcode Command Line Tools、Rust/Cargo、Node.js、pnpm、make、pkg-config。固定源码归档与哈希位于 `vendor/harmony-connector/`。
+16 -1
View File
@@ -2,7 +2,7 @@
"schemaVersion": 1, "schemaVersion": 1,
"id": "trove-official", "id": "trove-official",
"name": "Trove 官方软件源", "name": "Trove 官方软件源",
"generatedAt": "2026-09-10T06:33:16.439Z", "generatedAt": "2026-09-10T06:45:52.251Z",
"curios": [ "curios": [
{ {
"id": "dev.trove.qa.host", "id": "dev.trove.qa.host",
@@ -200,6 +200,21 @@
"name": "Harmony Connector", "name": "Harmony Connector",
"description": "连接鸿蒙设备,收发完整文件夹、管理应用,查看手机画面并通过键鼠操作。", "description": "连接鸿蒙设备,收发完整文件夹、管理应用,查看手机画面并通过键鼠操作。",
"versions": [ "versions": [
{
"version": "0.3.2",
"engine": {
"trove": ">=0.1.0 <1.0.0"
},
"releasedAt": "2026-09-10T06:45:52.251Z",
"artifacts": [
{
"target": "aarch64-apple-darwin",
"url": "packages/dev.trove.harmony-connector/0.3.2/aarch64-apple-darwin.curio",
"sha256": "eb0ec5d5a8b7f4e3150cd80351eaadd5ef13535d8cbf388e955218b0f60b0f3d",
"size": 2939665
}
]
},
{ {
"version": "0.3.1", "version": "0.3.1",
"engine": { "engine": {