Skip to content

Commit 6d02fe2

Browse files
authored
Merge pull request #11262 from pradyunsg/reuse-environment-runner-sccript
Move `__pip-runner__` script into a module
2 parents dc00479 + 470b217 commit 6d02fe2

File tree

3 files changed

+72
-48
lines changed

3 files changed

+72
-48
lines changed

src/pip/__pip-runner__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Execute exactly this copy of pip, within a different environment.
2+
3+
This file is named as it is, to ensure that this module can't be imported via
4+
an import statement.
5+
"""
6+
7+
import runpy
8+
import sys
9+
import types
10+
from importlib.machinery import ModuleSpec, PathFinder
11+
from os.path import dirname
12+
from typing import Optional, Sequence, Union
13+
14+
PIP_SOURCES_ROOT = dirname(dirname(__file__))
15+
16+
17+
class PipImportRedirectingFinder:
18+
@classmethod
19+
def find_spec(
20+
self,
21+
fullname: str,
22+
path: Optional[Sequence[Union[bytes, str]]] = None,
23+
target: Optional[types.ModuleType] = None,
24+
) -> Optional[ModuleSpec]:
25+
if fullname != "pip":
26+
return None
27+
28+
spec = PathFinder.find_spec(fullname, [PIP_SOURCES_ROOT], target)
29+
assert spec, (PIP_SOURCES_ROOT, fullname)
30+
return spec
31+
32+
33+
sys.meta_path.insert(0, PipImportRedirectingFinder())
34+
35+
assert __name__ == "__main__", "Cannot run __pip-runner__.py as a non-main module"
36+
runpy.run_module("pip", run_name="__main__", alter_sys=True)

src/pip/_internal/build_env.py

Lines changed: 16 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Build Environment used for isolation during sdist building
22
"""
33

4-
import contextlib
54
import logging
65
import os
76
import pathlib
@@ -10,7 +9,7 @@
109
from collections import OrderedDict
1110
from sysconfig import get_paths
1211
from types import TracebackType
13-
from typing import TYPE_CHECKING, Generator, Iterable, List, Optional, Set, Tuple, Type
12+
from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type
1413

1514
from pip._vendor.certifi import where
1615
from pip._vendor.packaging.requirements import Requirement
@@ -28,29 +27,6 @@
2827

2928
logger = logging.getLogger(__name__)
3029

31-
PIP_RUNNER = """
32-
import importlib.util
33-
import os
34-
import runpy
35-
import sys
36-
37-
38-
class PipImportRedirectingFinder:
39-
40-
@classmethod
41-
def find_spec(cls, fullname, path=None, target=None):
42-
if not fullname.startswith("pip."):
43-
return None
44-
45-
# Import pip from the current source directory
46-
location = os.path.join({source!r}, *fullname.split("."))
47-
return importlib.util.spec_from_file_location(fullname, location)
48-
49-
50-
sys.meta_path.insert(0, PipImportRedirectingFinder())
51-
runpy.run_module("pip", run_name="__main__")
52-
"""
53-
5430

5531
class _Prefix:
5632
def __init__(self, path: str) -> None:
@@ -63,26 +39,20 @@ def __init__(self, path: str) -> None:
6339
self.lib_dirs = get_prefixed_libs(path)
6440

6541

66-
@contextlib.contextmanager
67-
def _create_runnable_pip() -> Generator[str, None, None]:
68-
"""Create a "pip runner" file.
42+
def _get_runnable_pip() -> str:
43+
"""Get a file to pass to a Python executable, to run the currently-running pip.
6944
70-
The runner file ensures that import for pip happens using the currently-running pip.
71-
It will be used to install requirements into the build environment.
45+
This is used to run a pip subprocess, for installing requirements into the build
46+
environment.
7247
"""
7348
source = pathlib.Path(pip_location).resolve().parent
7449

75-
# Return the current instance if `source` is not a directory. It likely
76-
# means that this copy of pip is already standalone.
7750
if not source.is_dir():
78-
yield str(source)
79-
return
51+
# This would happen if someone is using pip from inside a zip file. In that
52+
# case, we can use that directly.
53+
return str(source)
8054

81-
with TempDirectory(kind="standalone-pip") as tmp_dir:
82-
pip_runner = os.path.join(tmp_dir.path, "__pip-runner__.py")
83-
with open(pip_runner, "w", encoding="utf8") as f:
84-
f.write(PIP_RUNNER.format(source=os.fsdecode(source)))
85-
yield pip_runner
55+
return os.fsdecode(source / "__pip-runner__.py")
8656

8757

8858
class BuildEnvironment:
@@ -223,15 +193,13 @@ def install_requirements(
223193
prefix.setup = True
224194
if not requirements:
225195
return
226-
with contextlib.ExitStack() as ctx:
227-
pip_runnable = ctx.enter_context(_create_runnable_pip())
228-
self._install_requirements(
229-
pip_runnable,
230-
finder,
231-
requirements,
232-
prefix,
233-
kind=kind,
234-
)
196+
self._install_requirements(
197+
_get_runnable_pip(),
198+
finder,
199+
requirements,
200+
prefix,
201+
kind=kind,
202+
)
235203

236204
@staticmethod
237205
def _install_requirements(
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
from pathlib import Path
3+
4+
from pip import __version__
5+
from tests.lib import PipTestEnvironment
6+
7+
8+
def test_runner_work_in_environments_with_no_pip(
9+
script: PipTestEnvironment, pip_src: Path
10+
) -> None:
11+
runner = pip_src / "src" / "pip" / "__pip-runner__.py"
12+
13+
# Ensure there's no pip installed in the environment
14+
script.pip("uninstall", "pip", "--yes", use_module=True)
15+
script.pip("--version", expect_error=True)
16+
17+
# The runner script should still invoke a usable pip
18+
result = script.run("python", os.fspath(runner), "--version")
19+
20+
assert __version__ in result.stdout

0 commit comments

Comments
 (0)