fix: Ubuntu 24.04 等 PEP668 系统 Python 安装时自动加 --break-system-packages(安装/升级/卸载全链路)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user