diff --git a/src/pip/_internal/utils/setuptools_build.py b/src/pip/_internal/utils/setuptools_build.py index 9d65ceba4ab..1b80c13c728 100644 --- a/src/pip/_internal/utils/setuptools_build.py +++ b/src/pip/_internal/utils/setuptools_build.py @@ -1,21 +1,45 @@ import sys +import textwrap from typing import List, Optional, Sequence # Shim to wrap setup.py invocation with setuptools -# -# We set sys.argv[0] to the path to the underlying setup.py file so -# setuptools / distutils don't take the path to the setup.py to be "-c" when -# invoking via the shim. This avoids e.g. the following manifest_maker -# warning: "warning: manifest_maker: standard file '-c' not found". -_SETUPTOOLS_SHIM = ( - "import io, os, sys, setuptools, tokenize; sys.argv[0] = {0!r}; __file__={0!r};" - "f = getattr(tokenize, 'open', open)(__file__) " - "if os.path.exists(__file__) " - "else io.StringIO('from setuptools import setup; setup()');" - "code = f.read().replace('\\r\\n', '\\n');" - "f.close();" - "exec(compile(code, __file__, 'exec'))" -) +_SETUPTOOLS_SHIM = textwrap.dedent( + """ + exec(compile(''' + # This is -- a shim that pip uses to run setup.py + # + # - It imports setuptools before invoking setup.py, to enable projects that directly + # import from `distutils.core` to work with newer packaging standards. + # - Provides a clearer error message when setuptools is not installed. + # - It sets `sys.argv[0]` to the underlying `setup.py`, when invoking `setup.py` so + # setuptools doesn't think the script is `-c`. This avoids the following warning: + # manifest_maker: standard file '-c' not found". + # - It generates a shim setup.py, for handling setup.cfg-only projects. + import os, sys, tokenize + + try: + import setuptools + except ImportError as error: + raise RuntimeError( + "setuptools is not available in the build environment, but is required " + "to use setup.py-based projects with pip." + ) from error + + __file__ = {!r} + sys.argv[0] = __file__ + + if os.path.exists(__file__): + filename = __file__ + with tokenize.open(__file__) as f: + setup_py_code = f.read() + else: + filename = "" + setup_py_code = "from setuptools import setup; setup()" + + exec(compile(setup_py_code, filename, "exec")) + ''', "", "exec")) + """ +).rstrip() def make_setuptools_shim_args( diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index c142c9e9b5e..3d29b3d4201 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -943,11 +943,14 @@ def test_make_setuptools_shim_args() -> None: ) assert args[1:3] == ["-u", "-c"] - # Spot-check key aspects of the command string. - assert "sys.argv[0] = '/dir/path/setup.py'" in args[3] - assert "__file__='/dir/path/setup.py'" in args[3] assert args[4:] == ["--some", "--option", "--no-user-cfg"] + shim = args[3] + # Spot-check key aspects of the command string. + assert "import setuptools" in shim + assert "__file__ = '/dir/path/setup.py'" in args[3] + assert "sys.argv[0] = __file__" in args[3] + @pytest.mark.parametrize("global_options", [None, [], ["--some", "--option"]]) def test_make_setuptools_shim_args__global_options(