Skip to content

Commit 0f72ed2

Browse files
deepseek-assistantPlanteAmigor
authored andcommitted
feat: setup 新增 --remove 卸载模块功能
- ./ov-cli setup --remove chat 卸载 chat 独有包(wcwidth/PyMuPDF/soxr) - 自动计算共享包:tts 和 asr 共享 soundfile,一方移除时不删 - 卸载前用户确认 - 更新 .ov-cli-features 记录
1 parent 78a8c84 commit 0f72ed2

3 files changed

Lines changed: 89 additions & 1 deletion

File tree

ov_cli/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ def main():
290290
p.add_argument("--optimum-dir", help=TR("optimum-intel 源码目录", "optimum-intel source"))
291291
p.add_argument("--with", dest="with_features", default="all",
292292
help=TR("按需安装 (chat,image,asr,tts,ui,mcp,server,convert)", "Features (chat,image,asr,tts,ui,mcp,server,convert)"))
293+
p.add_argument("--remove", dest="remove_features", default="",
294+
help=TR("移除模块 (chat,image,asr,tts,ui,mcp,server,convert)", "Remove features (chat,image,asr,tts,ui,mcp,server,convert)"))
293295
p.add_argument("--fix", action="store_true", help=TR("修复模式", "Fix mode"))
294296

295297
# convert

ov_cli/features.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,22 @@ def get_extra_pips(features: set[str]) -> list[str]:
7878
for f in features:
7979
pkgs.extend(_FEATURE_EXTRA_PIPS.get(f, []))
8080
return pkgs
81+
82+
83+
def get_exclusive_packages(removed: set[str], remaining: set[str]) -> dict[str, list[str]]:
84+
"""计算被移除功能独有的 pip 包:只在 removed 中、不在 remaining 中。
85+
86+
返回 { 功能名: [包列表] },只包含该功能独有且不在剩余功能中的包。
87+
"""
88+
result = {}
89+
for f in removed:
90+
pkgs = set(_FEATURE_PACKAGES.get(f, []))
91+
extra = set(_FEATURE_EXTRA_PIPS.get(f, []))
92+
# 排除剩余功能也需要的包
93+
for rf in remaining:
94+
pkgs -= set(_FEATURE_PACKAGES.get(rf, []))
95+
extra -= set(_FEATURE_EXTRA_PIPS.get(rf, []))
96+
exclusive = sorted(pkgs | extra)
97+
if exclusive:
98+
result[f] = exclusive
99+
return result

ov_cli/setup.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import os, sys, json, shutil, subprocess, tempfile
1111
import ov_cli
1212
from ov_cli import TR
13-
from ov_cli.features import get_packages, get_extra_pips, save as _save_features
13+
from ov_cli.features import get_packages, get_extra_pips, get_exclusive_packages, get_installed, save as _save_features
1414

1515

1616
_ALL_FEATURES = {"chat", "image", "asr", "tts", "ui", "mcp", "server", "convert"}
@@ -300,6 +300,60 @@ def _install_features(pip, features: set[str], workspace, fix_mode=False):
300300
"--index-url", "https://download.pytorch.org/whl/cpu"])
301301

302302

303+
def _remove_features(pip, venv_path, removed: set[str]):
304+
"""卸载指定功能独有(不被其他已装功能需要)的 pip 包。"""
305+
installed = get_installed(venv_path)
306+
remaining = installed - removed
307+
308+
if not removed:
309+
print(f" {TR('没有指定要移除的模块', 'No features to remove')}")
310+
return
311+
312+
invalid = removed - _ALL_FEATURES
313+
if invalid:
314+
print(f" ❌ {TR('不支持的功能', 'Unsupported features')}: {', '.join(sorted(invalid))}")
315+
print(f" {TR('支持', 'Supported')}: {', '.join(sorted(_ALL_FEATURES))}")
316+
return
317+
318+
not_installed = removed - installed
319+
if not_installed:
320+
print(f" - {TR('以下模块未安装,跳过', 'Already not installed')}: {', '.join(sorted(not_installed))}")
321+
322+
to_remove = removed - not_installed
323+
if not to_remove:
324+
print(f" {TR('没有需要移除的模块', 'Nothing to remove')}")
325+
return
326+
327+
exclusive = get_exclusive_packages(to_remove, remaining)
328+
if not exclusive:
329+
print(f" {TR('所有包均为其他模块共享,无需卸载', 'All packages are shared, nothing to uninstall')}")
330+
else:
331+
print(f" {TR('将卸载以下独有包', 'Will uninstall exclusive packages')}:")
332+
for f, pkgs in sorted(exclusive.items()):
333+
print(f" • {f}: {', '.join(pkgs)}")
334+
print()
335+
try:
336+
r = input(f" {TR('确认卸载?(y/N)', 'Confirm uninstall? (y/N)')}: ").strip().lower()
337+
if r != "y":
338+
print(f" {TR('已取消', 'Cancelled')}")
339+
return
340+
except (EOFError, KeyboardInterrupt):
341+
print()
342+
print(f" {TR('已取消', 'Cancelled')}")
343+
return
344+
all_pkgs = sorted(set(p for pkgs in exclusive.values() for p in pkgs))
345+
try:
346+
subprocess.check_call([pip, "uninstall", "-y"] + all_pkgs)
347+
print(f" ✓ {TR('卸载完成', 'Uninstall done')}")
348+
except subprocess.CalledProcessError:
349+
print(f" ⚠ {TR('部分包卸载失败(可能已被手动移除)', 'Some packages may already be removed')}")
350+
351+
_save_features(venv_path, remaining)
352+
print(f" ✓ {TR('已更新安装记录', 'Features list updated')}: {', '.join(sorted(remaining))}")
353+
print(f" {TR('基础依赖(openvino 等)始终保持不变', 'Base deps (openvino etc.) are kept')}")
354+
print(f" {TR('如需彻底清理,可重建环境', 'For full cleanup, recreate with ./ov-cli setup')}")
355+
356+
303357
def cmd_setup(args, workspace):
304358
"""ov-cli setup: 创建虚拟环境并安装依赖"""
305359

@@ -363,6 +417,19 @@ def cmd_setup(args, workspace):
363417
print(f" {TR('✅ 修复完成', '✅ Fix done')}")
364418
return
365419

420+
# ── 移除模式 ──
421+
if args.remove_features:
422+
raw = args.remove_features.strip()
423+
to_remove = {s.strip() for s in raw.split(",") if s.strip()}
424+
venv_path = args.venv or os.path.join(workspace, ".venv")
425+
if not os.path.isdir(venv_path):
426+
print(f" {TR('错误: 未找到虚拟环境', 'Error: venv not found')}: {venv_path}")
427+
print(f" {TR('请先运行', 'Run first')}: ./ov-cli setup")
428+
sys.exit(1)
429+
pip = _pip_path(venv_path)
430+
_remove_features(pip, venv_path, to_remove)
431+
return
432+
366433
# ── 打印安装概要 ──
367434
print(f" {TR('即将安装以下模块', 'Will install:')}")
368435
for f in sorted(features):

0 commit comments

Comments
 (0)