fix: Ubuntu 24.04 等 PEP668 系统 Python 安装时自动加 --break-system-packages(安装/升级/卸载全链路)

This commit is contained in:
Zhengshou Lai
2026-07-16 19:43:35 +08:00
parent 84ad0fa4cf
commit 1348c4f243
3 changed files with 53 additions and 2 deletions
+7 -1
View File
@@ -57,10 +57,16 @@ def _remove_path(path: Path, removed: list[Path]) -> None:
def _pip_uninstall() -> list[str]: def _pip_uninstall() -> list[str]:
"""Uninstall the pip packages (removes their pip-owned entry points).""" """Uninstall the pip packages (removes their pip-owned entry points)."""
import sysconfig
if os.environ.get("VIRTUAL_ENV") and shutil.which("uv"): if os.environ.get("VIRTUAL_ENV") and shutil.which("uv"):
base = ["uv", "pip", "uninstall"] base = ["uv", "pip", "uninstall"]
else: 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] = [] warnings: list[str] = []
for pkg in PY_PACKAGES: for pkg in PY_PACKAGES:
result = subprocess.run([*base, pkg], capture_output=True, text=True) result = subprocess.run([*base, pkg], capture_output=True, text=True)
+8 -1
View File
@@ -147,11 +147,18 @@ def _extract(tarball: Path, dest: Path) -> None:
def _pip_install(pkg_dir: Path) -> subprocess.CompletedProcess: def _pip_install(pkg_dir: Path) -> subprocess.CompletedProcess:
import os import os
import sysconfig
if os.environ.get("VIRTUAL_ENV") and shutil.which("uv"): if os.environ.get("VIRTUAL_ENV") and shutil.which("uv"):
cmd = ["uv", "pip", "install", "-e", str(pkg_dir), "--quiet"] cmd = ["uv", "pip", "install", "-e", str(pkg_dir), "--quiet"]
else: 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) return subprocess.run(cmd, capture_output=True, text=True)
+38
View File
@@ -230,3 +230,41 @@ class TestFetch:
dest = tmp_path / "out.tar.gz" dest = tmp_path / "out.tar.gz"
up_mod._download("http://x/f", "u", "p", dest) up_mod._download("http://x/f", "u", "p", dest)
assert dest.read_bytes() == payload 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]