summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPradyun Gedam <pradyunsg@gmail.com>2022-07-15 11:09:17 +0100
committerGitHub <noreply@github.com>2022-07-15 11:09:17 +0100
commite975318b63022f545899405ebc9c5b9259a79915 (patch)
treed7e0b01d0ffdb35c0c4d5117fdc97da82fb8bbdd
parent534262d1d686a3583d48bda692623f1e87a43f2b (diff)
parentd36bd5a96e50c4beee10eb283e4e15688e0d0eb6 (diff)
downloadpip-e975318b63022f545899405ebc9c5b9259a79915.tar.gz
Merge pull request #11257 from pradyunsg/speedup-environment-creation
-rw-r--r--news/11257.feature.rst3
-rw-r--r--src/pip/_internal/build_env.py48
2 files changed, 36 insertions, 15 deletions
diff --git a/news/11257.feature.rst b/news/11257.feature.rst
new file mode 100644
index 000000000..1cb75b228
--- /dev/null
+++ b/news/11257.feature.rst
@@ -0,0 +1,3 @@
+Significantly speed up isolated environment creation, by using the same
+sources for pip instead of creating a standalone installation for each
+environment.
diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py
index ccf2b4bc4..4554b1e4f 100644
--- a/src/pip/_internal/build_env.py
+++ b/src/pip/_internal/build_env.py
@@ -7,7 +7,6 @@ import os
import pathlib
import sys
import textwrap
-import zipfile
from collections import OrderedDict
from sysconfig import get_paths
from types import TracebackType
@@ -29,6 +28,29 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
+PIP_RUNNER = """
+import importlib.util
+import os
+import runpy
+import sys
+
+
+class PipImportRedirectingFinder:
+
+ @classmethod
+ def find_spec(cls, fullname, path=None, target=None):
+ if not fullname.startswith("pip."):
+ return None
+
+ # Import pip from the current source directory
+ location = os.path.join({source!r}, *fullname.split("."))
+ return importlib.util.spec_from_file_location(fullname, location)
+
+
+sys.meta_path.insert(0, PipImportRedirectingFinder())
+runpy.run_module("pip", run_name="__main__")
+"""
+
class _Prefix:
def __init__(self, path: str) -> None:
@@ -42,29 +64,25 @@ class _Prefix:
@contextlib.contextmanager
-def _create_standalone_pip() -> Generator[str, None, None]:
- """Create a "standalone pip" zip file.
+def _create_runnable_pip() -> Generator[str, None, None]:
+ """Create a "pip runner" file.
- The zip file's content is identical to the currently-running pip.
+ The runner file ensures that import for pip happens using the currently-running pip.
It will be used to install requirements into the build environment.
"""
source = pathlib.Path(pip_location).resolve().parent
- # Return the current instance if `source` is not a directory. We can't build
- # a zip from this, and it likely means the instance is already standalone.
+ # Return the current instance if `source` is not a directory. It likely
+ # means that this copy of pip is already standalone.
if not source.is_dir():
yield str(source)
return
with TempDirectory(kind="standalone-pip") as tmp_dir:
- pip_zip = os.path.join(tmp_dir.path, "__env_pip__.zip")
- kwargs = {}
- if sys.version_info >= (3, 8):
- kwargs["strict_timestamps"] = False
- with zipfile.ZipFile(pip_zip, "w", **kwargs) as zf:
- for child in source.rglob("*"):
- zf.write(child, child.relative_to(source.parent).as_posix())
- yield os.path.join(pip_zip, "pip")
+ pip_runner = os.path.join(tmp_dir.path, "__pip-runner__.py")
+ with open(pip_runner, "w", encoding="utf8") as f:
+ f.write(PIP_RUNNER.format(source=os.fsdecode(source)))
+ yield pip_runner
class BuildEnvironment:
@@ -206,7 +224,7 @@ class BuildEnvironment:
if not requirements:
return
with contextlib.ExitStack() as ctx:
- pip_runnable = ctx.enter_context(_create_standalone_pip())
+ pip_runnable = ctx.enter_context(_create_runnable_pip())
self._install_requirements(
pip_runnable,
finder,