From 1348c4f24353c0f49575c371e6d5a639e8b1ee4b Mon Sep 17 00:00:00 2001 From: Zhengshou Lai Date: Thu, 16 Jul 2026 19:43:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Ubuntu=2024.04=20=E7=AD=89=20PEP668=20?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=20Python=20=E5=AE=89=E8=A3=85=E6=97=B6?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=8A=A0=20--break-system-packages=EF=BC=88?= =?UTF-8?q?=E5=AE=89=E8=A3=85/=E5=8D=87=E7=BA=A7/=E5=8D=B8=E8=BD=BD?= =?UTF-8?q?=E5=85=A8=E9=93=BE=E8=B7=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- myagents/commands/uninstall.py | 8 ++++++- myagents/commands/upgrade.py | 9 +++++++- tests/test_upgrade.py | 38 ++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/myagents/commands/uninstall.py b/myagents/commands/uninstall.py index 4bef9bd..515aa45 100644 --- a/myagents/commands/uninstall.py +++ b/myagents/commands/uninstall.py @@ -57,10 +57,16 @@ def _remove_path(path: Path, removed: list[Path]) -> None: def _pip_uninstall() -> list[str]: """Uninstall the pip packages (removes their pip-owned entry points).""" + import sysconfig + if os.environ.get("VIRTUAL_ENV") and shutil.which("uv"): base = ["uv", "pip", "uninstall"] else: - base = [sys.executable, "-m", "pip", "uninstall", "-y"] + base = [sys.executable, "-m", "pip", "uninstall"] + stdlib = Path(sysconfig.get_path("stdlib")) + if (stdlib / "EXTERNALLY-MANAGED").exists(): + base.append("--break-system-packages") + base.append("-y") warnings: list[str] = [] for pkg in PY_PACKAGES: result = subprocess.run([*base, pkg], capture_output=True, text=True) diff --git a/myagents/commands/upgrade.py b/myagents/commands/upgrade.py index 87cebcc..225e6b1 100644 --- a/myagents/commands/upgrade.py +++ b/myagents/commands/upgrade.py @@ -147,11 +147,18 @@ def _extract(tarball: Path, dest: Path) -> None: def _pip_install(pkg_dir: Path) -> subprocess.CompletedProcess: import os + import sysconfig if os.environ.get("VIRTUAL_ENV") and shutil.which("uv"): cmd = ["uv", "pip", "install", "-e", str(pkg_dir), "--quiet"] else: - cmd = [sys.executable, "-m", "pip", "install", "-e", str(pkg_dir), "--quiet"] + cmd = [sys.executable, "-m", "pip", "install"] + # Ubuntu 24.04+ marks the system Python externally-managed (PEP 668); + # without the flag pip refuses to install there at all. + stdlib = Path(sysconfig.get_path("stdlib")) + if (stdlib / "EXTERNALLY-MANAGED").exists(): + cmd.append("--break-system-packages") + cmd += ["-e", str(pkg_dir), "--quiet"] return subprocess.run(cmd, capture_output=True, text=True) diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index 813b369..cb79967 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -230,3 +230,41 @@ class TestFetch: dest = tmp_path / "out.tar.gz" up_mod._download("http://x/f", "u", "p", dest) assert dest.read_bytes() == payload + + +class TestPipInstallFlags: + def test_uses_break_system_packages_on_externally_managed( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + import sysconfig + + em = tmp_path / "EXTERNALLY-MANAGED" + em.write_text("[externally-managed]\n") + monkeypatch.setattr( + sysconfig, "get_path", lambda name: str(tmp_path) if name == "stdlib" else "" + ) + seen: list[list[str]] = [] + monkeypatch.setattr( + "subprocess.run", + lambda cmd, **kw: seen.append(cmd) + or type("R", (), {"returncode": 0, "stderr": ""})(), + ) + up_mod._pip_install(tmp_path / "pkg") + assert "--break-system-packages" in seen[0] + + def test_skips_flag_without_externally_managed( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + import sysconfig + + monkeypatch.setattr( + sysconfig, "get_path", lambda name: str(tmp_path) if name == "stdlib" else "" + ) + seen: list[list[str]] = [] + monkeypatch.setattr( + "subprocess.run", + lambda cmd, **kw: seen.append(cmd) + or type("R", (), {"returncode": 0, "stderr": ""})(), + ) + up_mod._pip_install(tmp_path / "pkg") + assert "--break-system-packages" not in seen[0]