Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop poetry's dependency on pip and setuptools in project virtual environment #2826

Merged
merged 8 commits into from
Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
472 changes: 265 additions & 207 deletions poetry.lock

Large diffs are not rendered by default.

16 changes: 4 additions & 12 deletions poetry/inspection/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
from poetry.core.utils.helpers import temporary_directory
from poetry.core.version.markers import InvalidMarker
from poetry.utils.env import EnvCommandError
from poetry.utils.env import EnvManager
from poetry.utils.env import VirtualEnv
from poetry.utils.env import ephemeral_environment
from poetry.utils.setup_reader import SetupReader


Expand Down Expand Up @@ -451,20 +450,13 @@ def _pep517_metadata(cls, path: Path) -> "PackageInfo":
except PackageInfoError:
pass

with temporary_directory() as tmp_dir:
with ephemeral_environment(pip=True, wheel=True, setuptools=True) as venv:
# TODO: cache PEP 517 build environment corresponding to each project venv
venv_dir = Path(tmp_dir) / ".venv"
EnvManager.build_venv(venv_dir.as_posix())
venv = VirtualEnv(venv_dir, venv_dir)

dest_dir = Path(tmp_dir) / "dist"
dest_dir = venv.path.parent / "dist"
dest_dir.mkdir()

try:
venv.run(
"python",
"-m",
"pip",
venv.run_pip(
"install",
"--disable-pip-version-check",
"--ignore-installed",
Expand Down
30 changes: 11 additions & 19 deletions poetry/installation/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
from poetry.utils._compat import decode
from poetry.utils.env import EnvCommandError
from poetry.utils.helpers import safe_rmtree
from poetry.utils.pip import pip_editable_install

from ..utils.pip import pip_install
from .authenticator import Authenticator
from .chef import Chef
from .chooser import Chooser
Expand Down Expand Up @@ -474,12 +476,9 @@ def _install(self, operation: Union[Install, Update]) -> int:
)
)
self._write(operation, message)

args = ["install", "--no-deps", str(archive)]
if operation.job_type == "update":
args.insert(2, "-U")

return self.run_pip(*args)
return pip_install(
str(archive), self._env, upgrade=operation.job_type == "update"
)

def _update(self, operation: Union[Install, Update]) -> int:
return self._install(operation)
Expand Down Expand Up @@ -533,11 +532,9 @@ def _install_directory(self, operation: Union[Install, Update]) -> int:
self._write(operation, message)

if package.root_dir:
req = os.path.join(str(package.root_dir), package.source_url)
req = package.root_dir / package.source_url
else:
req = os.path.realpath(package.source_url)

args = ["install", "--no-deps", "-U"]
req = Path(package.source_url).resolve(strict=False)

pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))

Expand Down Expand Up @@ -572,18 +569,13 @@ def _install_directory(self, operation: Union[Install, Update]) -> int:

with builder.setup_py():
if package.develop:
args.append("-e")

args.append(req)

return self.run_pip(*args)
return pip_editable_install(req, self._env)
return pip_install(req, self._env, upgrade=True)

if package.develop:
args.append("-e")

args.append(req)
return pip_editable_install(req, self._env)

return self.run_pip(*args)
return pip_install(req, self._env, upgrade=True)

def _install_git(self, operation: Union[Install, Update]) -> int:
from poetry.core.vcs import Git
Expand Down
30 changes: 15 additions & 15 deletions poetry/installation/pip_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import tempfile
import urllib.parse

from pathlib import Path
from subprocess import CalledProcessError
from typing import TYPE_CHECKING
from typing import Any
Expand All @@ -10,12 +11,13 @@
from cleo.io.io import IO

from poetry.core.pyproject.toml import PyProjectTOML
from poetry.installation.base_installer import BaseInstaller
from poetry.repositories.pool import Pool
from poetry.utils._compat import encode
from poetry.utils.env import Env
from poetry.utils.helpers import safe_rmtree

from .base_installer import BaseInstaller
from poetry.utils.pip import pip_editable_install
from poetry.utils.pip import pip_install


if TYPE_CHECKING:
Expand Down Expand Up @@ -188,12 +190,12 @@ def install_directory(self, package: "Package") -> Union[str, int]:

from poetry.factory import Factory

req: Path

if package.root_dir:
req = (package.root_dir / package.source_url).as_posix()
else:
req = os.path.realpath(package.source_url)

args = ["install", "--no-deps", "-U"]
req = Path(package.source_url).resolve(strict=False)

pyproject = PyProjectTOML(os.path.join(req, "pyproject.toml"))

Expand Down Expand Up @@ -228,18 +230,16 @@ def install_directory(self, package: "Package") -> Union[str, int]:

with builder.setup_py():
if package.develop:
args.append("-e")

args.append(req)

return self.run(*args)
return pip_editable_install(
directory=req, environment=self._env
)
return pip_install(
path=req, environment=self._env, deps=False, upgrade=True
)

if package.develop:
args.append("-e")

args.append(req)

return self.run(*args)
return pip_editable_install(directory=req, environment=self._env)
return pip_install(path=req, environment=self._env, deps=False, upgrade=True)

def install_git(self, package: "Package") -> None:
from poetry.core.packages.package import Package
Expand Down
6 changes: 3 additions & 3 deletions poetry/masonry/builders/editable.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from poetry.utils._compat import WINDOWS
from poetry.utils._compat import decode
from poetry.utils.helpers import is_dir_writable
from poetry.utils.pip import pip_editable_install


if TYPE_CHECKING:
Expand Down Expand Up @@ -56,7 +57,6 @@ def build(self) -> None:
self._debug(
" - <warning>Falling back on using a <b>setup.py</b></warning>"
)

return self._setup_build()

self._run_build_script(self._package.build_script)
Expand Down Expand Up @@ -85,14 +85,14 @@ def _setup_build(self) -> None:

try:
if self._env.pip_version < Version(19, 0):
self._env.run_pip("install", "-e", str(self._path), "--no-deps")
pip_editable_install(self._path, self._env)
else:
# Temporarily rename pyproject.toml
shutil.move(
str(self._poetry.file), str(self._poetry.file.with_suffix(".tmp"))
)
try:
self._env.run_pip("install", "-e", str(self._path), "--no-deps")
pip_editable_install(self._path, self._env)
finally:
shutil.move(
str(self._poetry.file.with_suffix(".tmp")),
Expand Down
2 changes: 1 addition & 1 deletion poetry/puzzle/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def _formatter_elapsed(self) -> str:

class Provider:

UNSAFE_PACKAGES = {"setuptools", "distribute", "pip", "wheel"}
UNSAFE_PACKAGES = set()

def __init__(
self, package: Package, pool: Pool, io: Any, env: Optional[Env] = None
Expand Down
72 changes: 64 additions & 8 deletions poetry/utils/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from pathlib import Path
from subprocess import CalledProcessError
from typing import Any
from typing import ContextManager
from typing import Dict
from typing import Iterator
from typing import List
Expand All @@ -30,6 +31,7 @@
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
Expand All @@ -42,6 +44,7 @@
from poetry.utils._compat import list_to_shell_command
from poetry.utils.helpers import is_dir_writable
from poetry.utils.helpers import paths_csv
from poetry.utils.helpers import temporary_directory


GET_ENVIRONMENT_INFO = """\
Expand Down Expand Up @@ -810,6 +813,9 @@ def build_venv(
path: Union[Path, str],
executable: Optional[Union[str, Path]] = None,
flags: Dict[str, bool] = None,
with_pip: bool = False,
with_wheel: Optional[bool] = None,
with_setuptools: Optional[bool] = None,
) -> virtualenv.run.session.Session:
flags = flags or {}

Expand All @@ -821,12 +827,27 @@ def build_venv(
"--no-periodic-update",
"--python",
executable or sys.executable,
str(path),
]

if not with_pip:
args.append("--no-pip")
else:
if with_wheel is None:
abn marked this conversation as resolved.
Show resolved Hide resolved
# we want wheels to be enabled when pip is required and it has
# not been explicitly disabled
with_wheel = True

if with_wheel is None or not with_wheel:
args.append("--no-wheel")

if with_setuptools is None or not with_setuptools:
args.append("--no-setuptools")

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)

Expand Down Expand Up @@ -929,12 +950,21 @@ def marker_env(self) -> Dict[str, Any]:

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:
Expand Down Expand Up @@ -1057,6 +1087,9 @@ def is_sane(self) -> bool:
return True

def run(self, bin: str, *args: str, **kwargs: Any) -> Union[str, int]:
if bin == "pip":
return self.run_pip(*args, **kwargs)

bin = self._bin(bin)
cmd = [bin] + list(args)
return self._run(cmd, **kwargs)
Expand Down Expand Up @@ -1101,6 +1134,9 @@ def _run(self, cmd: List[str], **kwargs: Any) -> Union[int, str]:
return decode(output)

def execute(self, bin: str, *args: str, **kwargs: Any) -> Optional[int]:
if bin == "pip":
return self.run_pip(*args, **kwargs)

bin = self._bin(bin)

if not self._is_windows:
Expand Down Expand Up @@ -1181,7 +1217,7 @@ def get_python_implementation(self) -> str:
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
Expand Down Expand Up @@ -1289,7 +1325,7 @@ def get_python_implementation(self) -> str:
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__)
Expand Down Expand Up @@ -1343,8 +1379,8 @@ def is_venv(self) -> bool:
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():
Expand Down Expand Up @@ -1396,7 +1432,7 @@ def __init__(
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)
Expand All @@ -1414,6 +1450,26 @@ def _bin(self, bin: str) -> str:
return bin


@contextmanager
def ephemeral_environment(
executable=None,
pip: bool = False,
wheel: Optional[bool] = None,
setuptools: Optional[bool] = None,
) -> ContextManager[VirtualEnv]:
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(
path=venv_dir.as_posix(),
executable=executable,
with_pip=pip,
with_wheel=wheel,
with_setuptools=setuptools,
)
yield VirtualEnv(venv_dir, venv_dir)


class MockEnv(NullEnv):
def __init__(
self,
Expand Down
Loading