Skip to content

Commit 63ffa17

Browse files
committed
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.
1 parent 5b94aa8 commit 63ffa17

File tree

8 files changed

+53
-40
lines changed

8 files changed

+53
-40
lines changed

poetry/inspection/info.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -444,17 +444,14 @@ def _pep517_metadata(cls, path): # type (Path) -> PackageInfo
444444
with temporary_directory() as tmp_dir:
445445
# TODO: cache PEP 517 build environment corresponding to each project venv
446446
venv_dir = Path(tmp_dir) / ".venv"
447-
EnvManager.build_venv(venv_dir.as_posix())
447+
EnvManager.build_venv(venv_dir.as_posix(), with_pip=True)
448448
venv = VirtualEnv(venv_dir, venv_dir)
449449

450450
dest_dir = Path(tmp_dir) / "dist"
451451
dest_dir.mkdir()
452452

453453
try:
454-
venv.run(
455-
"python",
456-
"-m",
457-
"pip",
454+
venv.run_pip(
458455
"install",
459456
"--disable-pip-version-check",
460457
"--ignore-installed",

poetry/puzzle/provider.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def _formatter_elapsed(self):
5252

5353
class Provider:
5454

55-
UNSAFE_PACKAGES = {"setuptools", "distribute", "pip", "wheel"}
55+
UNSAFE_PACKAGES = {"setuptools"}
5656

5757
def __init__(
5858
self, package, pool, io, env=None

poetry/utils/env.py

+32-16
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from packaging.tags import interpreter_name
2727
from packaging.tags import interpreter_version
2828
from packaging.tags import sys_tags
29+
from virtualenv.seed.wheels.embed import get_embed_wheel
2930

3031
from poetry.core.semver import parse_constraint
3132
from poetry.core.semver.version import Version
@@ -679,19 +680,25 @@ def create_venv(
679680

680681
@classmethod
681682
def build_venv(
682-
cls, path, executable=None
683-
): # type: (Union[Path,str], Optional[Union[str, Path]]) -> virtualenv.run.session.Session
683+
cls, path, executable=None, with_pip=False
684+
): # type: (Union[Path,str], Optional[Union[str, Path]], bool) -> virtualenv.run.session.Session
684685
if isinstance(executable, Path):
685686
executable = executable.resolve().as_posix()
686-
return virtualenv.cli_run(
687-
[
688-
"--no-download",
689-
"--no-periodic-update",
690-
"--python",
691-
executable or sys.executable,
692-
str(path),
693-
]
694-
)
687+
688+
opts = [
689+
"--no-download",
690+
"--no-periodic-update",
691+
"--python",
692+
executable or sys.executable,
693+
]
694+
695+
if not with_pip:
696+
# we cannot drop setuptools yet because we do editable installs (git, path) in project envs
697+
opts.extend(["--no-pip", "--no-wheel"])
698+
699+
opts.append(str(path))
700+
701+
return virtualenv.cli_run(opts)
695702

696703
@classmethod
697704
def remove_venv(cls, path): # type: (Union[Path,str]) -> None
@@ -787,12 +794,21 @@ def marker_env(self):
787794

788795
return self._marker_env
789796

797+
def get_embedded_wheel(self, distribution):
798+
return get_embed_wheel(
799+
distribution, "{}.{}".format(self.version_info[0], self.version_info[1])
800+
).path
801+
790802
@property
791803
def pip(self): # type: () -> str
792804
"""
793805
Path to current pip executable
794806
"""
795-
return self._bin("pip")
807+
# we do not use as_posix() here due to issues with windows pathlib2 implementation
808+
path = self._bin("pip")
809+
if not Path(path).exists():
810+
return str(self.get_embedded_wheel("pip") / "pip")
811+
return path
796812

797813
@property
798814
def platform(self): # type: () -> str
@@ -1010,7 +1026,7 @@ def get_python_implementation(self): # type: () -> str
10101026
def get_pip_command(self): # type: () -> List[str]
10111027
# If we're not in a venv, assume the interpreter we're running on
10121028
# has a pip and use that
1013-
return [sys.executable, "-m", "pip"]
1029+
return [sys.executable, self.pip]
10141030

10151031
def get_paths(self): # type: () -> Dict[str, str]
10161032
# We can't use sysconfig.get_paths() because
@@ -1112,7 +1128,7 @@ def get_python_implementation(self): # type: () -> str
11121128
def get_pip_command(self): # type: () -> List[str]
11131129
# We're in a virtualenv that is known to be sane,
11141130
# so assume that we have a functional pip
1115-
return [self._bin("pip")]
1131+
return [self._bin("python"), self.pip]
11161132

11171133
def get_supported_tags(self): # type: () -> List[Tag]
11181134
file_path = Path(packaging.tags.__file__)
@@ -1167,7 +1183,7 @@ def is_venv(self): # type: () -> bool
11671183

11681184
def is_sane(self):
11691185
# A virtualenv is considered sane if both "python" and "pip" exist.
1170-
return os.path.exists(self._bin("python")) and os.path.exists(self._bin("pip"))
1186+
return os.path.exists(self._bin("python"))
11711187

11721188
def _run(self, cmd, **kwargs):
11731189
with self.temp_environ():
@@ -1217,7 +1233,7 @@ def __init__(self, path=None, base=None, execute=False):
12171233
self.executed = []
12181234

12191235
def get_pip_command(self): # type: () -> List[str]
1220-
return [self._bin("python"), "-m", "pip"]
1236+
return [self._bin("python"), self.pip]
12211237

12221238
def _run(self, cmd, **kwargs):
12231239
self.executed.append(cmd)

tests/inspection/test_info.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def test_info_setup_simple(mocker, demo_setup):
170170
def test_info_setup_simple_py2(mocker, demo_setup):
171171
spy = mocker.spy(VirtualEnv, "run")
172172
info = PackageInfo.from_directory(demo_setup)
173-
assert spy.call_count == 2
173+
assert spy.call_count == 1
174174
demo_check_info(info, requires_dist={"package"})
175175

176176

@@ -234,7 +234,7 @@ def test_info_setup_missing_mandatory_should_trigger_pep517(
234234
except PackageInfoError:
235235
assert spy.call_count == 3
236236
else:
237-
assert spy.call_count == 2
237+
assert spy.call_count == 1
238238

239239

240240
def test_info_prefer_poetry_config_over_egg_info():

tests/installation/test_installer.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -370,15 +370,19 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
370370
package_b = get_package("b", "1.1")
371371
package_c = get_package("c", "1.2")
372372
package_pip = get_package("pip", "20.0.0")
373+
package_setuptools = get_package("setuptools", "20.0.0")
374+
373375
repo.add_package(package_a)
374376
repo.add_package(package_b)
375377
repo.add_package(package_c)
376378
repo.add_package(package_pip)
379+
repo.add_package(package_setuptools)
377380

378381
installed.add_package(package_a)
379382
installed.add_package(package_b)
380383
installed.add_package(package_c)
381-
installed.add_package(package_pip) # Always required and never removed.
384+
installed.add_package(package_pip)
385+
installed.add_package(package_setuptools) # Always required and never removed.
382386
installed.add_package(package) # Root package never removed.
383387

384388
package.add_dependency(Factory.create_dependency("A", "~1.0"))
@@ -388,8 +392,8 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
388392

389393
assert 0 == installer.executor.installations_count
390394
assert 0 == installer.executor.updates_count
391-
assert 2 == installer.executor.removals_count
392-
assert {"b", "c"} == set(r.name for r in installer.executor.removals)
395+
assert 3 == installer.executor.removals_count
396+
assert {"b", "c", "pip"} == set(r.name for r in installer.executor.removals)
393397

394398

395399
def test_run_whitelist_add(installer, locker, repo, package):

tests/installation/test_installer_old.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -330,15 +330,19 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
330330
package_b = get_package("b", "1.1")
331331
package_c = get_package("c", "1.2")
332332
package_pip = get_package("pip", "20.0.0")
333+
package_setuptools = get_package("setuptools", "20.0.0")
334+
333335
repo.add_package(package_a)
334336
repo.add_package(package_b)
335337
repo.add_package(package_c)
336338
repo.add_package(package_pip)
339+
repo.add_package(package_setuptools)
337340

338341
installed.add_package(package_a)
339342
installed.add_package(package_b)
340343
installed.add_package(package_c)
341-
installed.add_package(package_pip) # Always required and never removed.
344+
installed.add_package(package_pip)
345+
installed.add_package(package_setuptools) # Always required and never removed.
342346
installed.add_package(package) # Root package never removed.
343347

344348
package.add_dependency(Factory.create_dependency("A", "~1.0"))
@@ -353,7 +357,7 @@ def test_run_install_remove_untracked(installer, locker, repo, package, installe
353357
assert len(updates) == 0
354358

355359
removals = installer.installer.removals
356-
assert set(r.name for r in removals) == {"b", "c"}
360+
assert set(r.name for r in removals) == {"b", "c", "pip"}
357361

358362

359363
def test_run_whitelist_add(installer, locker, repo, package):

tests/masonry/builders/test_editable_builder.py

+2-10
Original file line numberDiff line numberDiff line change
@@ -170,17 +170,9 @@ def test_builder_falls_back_on_setup_and_pip_for_packages_with_build_scripts(
170170
builder = EditableBuilder(extended_poetry, env, NullIO())
171171

172172
builder.build()
173-
174173
assert [
175-
[
176-
"python",
177-
"-m",
178-
"pip",
179-
"install",
180-
"-e",
181-
str(extended_poetry.file.parent),
182-
"--no-deps",
183-
]
174+
env.get_pip_command()
175+
+ ["install", "-e", str(extended_poetry.file.parent), "--no-deps"]
184176
] == env.executed
185177

186178

tests/puzzle/test_solver.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2192,7 +2192,7 @@ def test_solver_remove_untracked_keeps_critical_package(
21922192
package, pool, installed, locked, io
21932193
):
21942194
solver = Solver(package, pool, installed, locked, io, remove_untracked=True)
2195-
package_pip = get_package("pip", "1.0")
2195+
package_pip = get_package("setuptools", "1.0")
21962196
installed.add_package(package_pip)
21972197

21982198
ops = solver.solve()

0 commit comments

Comments
 (0)