Commit f739381e by Arun Babu Neelicattu

Drop pip from critical packages in project venv

Use embedded pip wheel from virtualenv package. This avoids the need
for pip to be a critical package in the project's virtual environment.
parent 0eaf9430
......@@ -454,17 +454,14 @@ class PackageInfo:
with temporary_directory() as tmp_dir:
# TODO: cache PEP 517 build environment corresponding to each project venv
venv_dir = Path(tmp_dir) / ".venv"
EnvManager.build_venv(venv_dir.as_posix())
EnvManager.build_venv(venv_dir.as_posix(), with_pip=True)
venv = VirtualEnv(venv_dir, venv_dir)
dest_dir = Path(tmp_dir) / "dist"
dest_dir.mkdir()
try:
venv.run(
"python",
"-m",
"pip",
venv.run_pip(
"install",
"--disable-pip-version-check",
"--ignore-installed",
......
......@@ -54,7 +54,7 @@ class Indicator(ProgressIndicator):
class Provider:
UNSAFE_PACKAGES = {"setuptools", "distribute", "pip", "wheel"}
UNSAFE_PACKAGES = {"setuptools"}
def __init__(
self, package: Package, pool: Pool, io: Any, env: Optional[Env] = None
......
......@@ -30,6 +30,7 @@ from packaging.tags import Tag
from packaging.tags import interpreter_name
from packaging.tags import interpreter_version
from packaging.tags import sys_tags
from virtualenv.seed.wheels.embed import get_embed_wheel
from poetry.core.semver.helpers import parse_constraint
from poetry.core.semver.version import Version
......@@ -810,6 +811,7 @@ class EnvManager(object):
path: Union[Path, str],
executable: Optional[Union[str, Path]] = None,
flags: Dict[str, bool] = None,
with_pip: bool = False,
) -> virtualenv.run.session.Session:
flags = flags or {}
......@@ -821,12 +823,17 @@ class EnvManager(object):
"--no-periodic-update",
"--python",
executable or sys.executable,
str(path),
]
if not with_pip:
# we cannot drop setuptools yet because we do editable installs (git, path) in project envs
args.extend(["--no-pip", "--no-wheel"])
for flag, value in flags.items():
if value is True:
args.insert(0, "--{}".format(flag))
args.append("--{}".format(flag))
args.append(str(path))
return virtualenv.cli_run(args)
......@@ -929,12 +936,21 @@ class Env(object):
return self._marker_env
def get_embedded_wheel(self, distribution):
return get_embed_wheel(
distribution, "{}.{}".format(self.version_info[0], self.version_info[1])
).path
@property
def pip(self) -> str:
"""
Path to current pip executable
"""
return self._bin("pip")
# we do not use as_posix() here due to issues with windows pathlib2 implementation
path = self._bin("pip")
if not Path(path).exists():
return str(self.get_embedded_wheel("pip") / "pip")
return path
@property
def platform(self) -> str:
......@@ -1181,7 +1197,7 @@ class SystemEnv(Env):
def get_pip_command(self) -> List[str]:
# If we're not in a venv, assume the interpreter we're running on
# has a pip and use that
return [sys.executable, "-m", "pip"]
return [sys.executable, self.pip]
def get_paths(self) -> Dict[str, str]:
# We can't use sysconfig.get_paths() because
......@@ -1289,7 +1305,7 @@ class VirtualEnv(Env):
def get_pip_command(self) -> List[str]:
# We're in a virtualenv that is known to be sane,
# so assume that we have a functional pip
return [self._bin("pip")]
return [self._bin("python"), self.pip]
def get_supported_tags(self) -> List[Tag]:
file_path = Path(packaging.tags.__file__)
......@@ -1343,8 +1359,8 @@ class VirtualEnv(Env):
return True
def is_sane(self) -> bool:
# A virtualenv is considered sane if both "python" and "pip" exist.
return os.path.exists(self.python) and os.path.exists(self._bin("pip"))
# A virtualenv is considered sane if "python" exists.
return os.path.exists(self.python)
def _run(self, cmd: List[str], **kwargs: Any) -> Optional[int]:
with self.temp_environ():
......@@ -1396,7 +1412,7 @@ class NullEnv(SystemEnv):
self.executed = []
def get_pip_command(self) -> List[str]:
return [self._bin("python"), "-m", "pip"]
return [self._bin("python"), self.pip]
def _run(self, cmd: List[str], **kwargs: Any) -> int:
self.executed.append(cmd)
......
......@@ -217,7 +217,7 @@ def test_info_setup_missing_mandatory_should_trigger_pep517(
except PackageInfoError:
assert spy.call_count == 3
else:
assert spy.call_count == 2
assert spy.call_count == 1
def test_info_prefer_poetry_config_over_egg_info():
......
......@@ -392,15 +392,19 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
package_b = get_package("b", "1.1")
package_c = get_package("c", "1.2")
package_pip = get_package("pip", "20.0.0")
package_setuptools = get_package("setuptools", "20.0.0")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_pip)
repo.add_package(package_setuptools)
installed.add_package(package_a)
installed.add_package(package_b)
installed.add_package(package_c)
installed.add_package(package_pip) # Always required and never removed.
installed.add_package(package_pip)
installed.add_package(package_setuptools) # Always required and never removed.
installed.add_package(package) # Root package never removed.
package.add_dependency(Factory.create_dependency("A", "~1.0"))
......@@ -410,8 +414,8 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
assert 0 == installer.executor.installations_count
assert 0 == installer.executor.updates_count
assert 2 == installer.executor.removals_count
assert {"b", "c"} == set(r.name for r in installer.executor.removals)
assert 3 == installer.executor.removals_count
assert {"b", "c", "pip"} == set(r.name for r in installer.executor.removals)
def test_run_whitelist_add(installer, locker, repo, package):
......
......@@ -318,15 +318,19 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
package_b = get_package("b", "1.1")
package_c = get_package("c", "1.2")
package_pip = get_package("pip", "20.0.0")
package_setuptools = get_package("setuptools", "20.0.0")
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_pip)
repo.add_package(package_setuptools)
installed.add_package(package_a)
installed.add_package(package_b)
installed.add_package(package_c)
installed.add_package(package_pip) # Always required and never removed.
installed.add_package(package_pip)
installed.add_package(package_setuptools) # Always required and never removed.
installed.add_package(package) # Root package never removed.
package.add_dependency(Factory.create_dependency("A", "~1.0"))
......@@ -341,7 +345,7 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
assert len(updates) == 0
removals = installer.installer.removals
assert set(r.name for r in removals) == {"b", "c"}
assert set(r.name for r in removals) == {"b", "c", "pip"}
def test_run_whitelist_add(installer, locker, repo, package):
......
......@@ -185,17 +185,9 @@ def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
builder = EditableBuilder(extended_poetry, env, NullIO())
builder.build()
assert [
[
"python",
"-m",
"pip",
"install",
"-e",
str(extended_poetry.file.parent),
"--no-deps",
]
env.get_pip_command()
+ ["install", "-e", str(extended_poetry.file.parent), "--no-deps"]
] == env.executed
......
......@@ -2407,7 +2407,7 @@ def test_solver_remove_untracked_keeps_critical_package(
package, pool, installed, locked, io
):
solver = Solver(package, pool, installed, locked, io, remove_untracked=True)
package_pip = get_package("pip", "1.0")
package_pip = get_package("setuptools", "1.0")
installed.add_package(package_pip)
ops = solver.solve()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment