diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e467b3e50b1..359b45c3d02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,7 @@ jobs: - ".github/workflows/ci.yml" - "src/**" - "tests/**" + - "tools/protected_pip.py" if: github.event_name == 'pull_request' pre-commit: diff --git a/tools/protected_pip.py b/tools/protected_pip.py index 48230719ee2..7e06bdbd24d 100644 --- a/tools/protected_pip.py +++ b/tools/protected_pip.py @@ -1,38 +1,61 @@ +"""Maintain and use a protected copy of pip for use during development. + +The protected copy of pip can be used to manipulate the environment while keeping a +potentially-non-functional installation of in-development pip in the development virtual +environment. + +This allows for setting up the test environments and exercising the in-development code, +even when it is not functional-enough to install the packages for setting up the +environment that it is being used it. +""" + import os -import pathlib import shutil import subprocess import sys from glob import glob -from typing import Iterable, Union +from typing import List VIRTUAL_ENV = os.environ["VIRTUAL_ENV"] -TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, "pip") - - -def pip(args: Iterable[Union[str, pathlib.Path]]) -> None: - # First things first, get a recent (stable) version of pip. - if not os.path.exists(TOX_PIP_DIR): - subprocess.check_call( - [ - sys.executable, - "-m", - "pip", - "--disable-pip-version-check", - "install", - "-t", - TOX_PIP_DIR, - "pip", - ] - ) - shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, "pip-*.dist-info"))[0]) - # And use that version. - pypath_env = os.environ.get("PYTHONPATH") - pypath = pypath_env.split(os.pathsep) if pypath_env is not None else [] - pypath.insert(0, TOX_PIP_DIR) - os.environ["PYTHONPATH"] = os.pathsep.join(pypath) - subprocess.check_call([sys.executable, "-m", "pip", *(os.fspath(a) for a in args)]) +PROTECTED_PIP_DIR = os.path.join(VIRTUAL_ENV, "pip") + + +def _setup_protected_pip() -> None: + # This setup happens before any development version of pip is installed in this + # environment. So, at this point, the existing pip installation should be from a + # stable release and can be safely used to create the protected copy. + subprocess.check_call( + [ + sys.executable, + "-m", + "pip", + "--disable-pip-version-check", + "install", + "-t", + PROTECTED_PIP_DIR, + "pip", + ] + ) + # Make it impossible for pip (and other Python tooling) to discover this protected + # installation of pip using the metadata, by deleting the metadata. + shutil.rmtree(glob(os.path.join(PROTECTED_PIP_DIR, "pip-*.dist-info"))[0]) + + +def main(args: List[str]) -> None: + # If we don't have a protected pip, let's set it up. + if not os.path.exists(PROTECTED_PIP_DIR): + _setup_protected_pip() + + # Run Python, with the protected pip copy on PYTHONPATH. + pypath_env = os.environ.get("PYTHONPATH", "") + old_PYTHONPATH_entries = pypath_env.split(os.pathsep) if pypath_env else [] + new_PYTHONPATH = os.pathsep.join([PROTECTED_PIP_DIR] + old_PYTHONPATH_entries) + + subprocess.check_call( + [sys.executable, "-m", "pip"] + args, + env={"PYTHONPATH": new_PYTHONPATH}, + ) if __name__ == "__main__": - pip(sys.argv[1:]) + main(sys.argv[1:])