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