diff options
| author | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2022-08-12 14:56:55 +0100 |
|---|---|---|
| committer | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2022-08-12 14:56:55 +0100 |
| commit | 8f2cc1f47f795de68a2a5a93e7e59fd91a6346d2 (patch) | |
| tree | a1905241aebb3fb984fc072607f6ca29360962fa /setuptools | |
| parent | 207a56d2109548168403455f2b57999d81355a0f (diff) | |
| parent | 66994535aceb1d4865957ab7ec70762d15716c25 (diff) | |
| download | python-setuptools-git-8f2cc1f47f795de68a2a5a93e7e59fd91a6346d2.tar.gz | |
Avoid crashing on build_py errors during editable installs (#3506)
Diffstat (limited to 'setuptools')
| -rw-r--r-- | setuptools/command/build.py | 4 | ||||
| -rw-r--r-- | setuptools/command/editable_wheel.py | 45 | ||||
| -rw-r--r-- | setuptools/tests/test_editable_install.py | 51 |
3 files changed, 93 insertions, 7 deletions
diff --git a/setuptools/command/build.py b/setuptools/command/build.py index 283999da..c0676d8e 100644 --- a/setuptools/command/build.py +++ b/setuptools/command/build.py @@ -20,7 +20,7 @@ class build(_build): # copy to avoid sharing the object with parent class sub_commands = _build.sub_commands[:] - def run(self): + def get_sub_commands(self): subcommands = {cmd[0] for cmd in _build.sub_commands} if subcommands - _ORIGINAL_SUBCOMMANDS: msg = """ @@ -30,7 +30,7 @@ class build(_build): """ warnings.warn(msg, SetuptoolsDeprecationWarning) self.sub_commands = _build.sub_commands - super().run() + return super().get_sub_commands() class SubCommand(Protocol): diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py index 1bb7ddfb..8a53de65 100644 --- a/setuptools/command/editable_wheel.py +++ b/setuptools/command/editable_wheel.py @@ -37,6 +37,7 @@ from typing import ( ) from setuptools import Command, SetuptoolsDeprecationWarning, errors, namespaces +from setuptools.command.build_py import build_py as build_py_cls from setuptools.discovery import find_package_path from setuptools.dist import Distribution @@ -254,13 +255,55 @@ class editable_wheel(Command): self, dist_name: str, unpacked_wheel: _Path, build_lib: _Path, tmp_dir: _Path ) -> Tuple[List[str], Dict[str, str]]: self._configure_build(dist_name, unpacked_wheel, build_lib, tmp_dir) - self.run_command("build") + self._run_build_subcommands() files, mapping = self._collect_build_outputs() self._run_install("headers") self._run_install("scripts") self._run_install("data") return files, mapping + def _run_build_subcommands(self): + """ + Issue #3501 indicates that some plugins/customizations might rely on: + + 1. ``build_py`` not running + 2. ``build_py`` always copying files to ``build_lib`` + + However both these assumptions may be false in editable_wheel. + This method implements a temporary workaround to support the ecosystem + while the implementations catch up. + """ + # TODO: Once plugins/customisations had the chance to catch up, replace + # `self._run_build_subcommands()` with `self.run_command("build")`. + # Also remove _safely_run, TestCustomBuildPy. Suggested date: Aug/2023. + build: Command = self.get_finalized_command("build") + for name in build.get_sub_commands(): + cmd = self.distribution.get_command_obj(name) + if name == "build_py" and type(cmd) != build_py_cls: + self._safely_run(name) + else: + self.run_command(name) + + def _safely_run(self, cmd_name: str): + try: + return self.run_command(cmd_name) + except Exception: + msg = f"""{traceback.format_exc()}\n + If you are seeing this warning it is very likely that a setuptools + plugin or customization overrides the `{cmd_name}` command, without + tacking into consideration how editable installs run build steps + starting from v64.0.0. + + Plugin authors and developers relying on custom build steps are encouraged + to update their `{cmd_name}` implementation considering the information in + https://setuptools.pypa.io/en/latest/userguide/extension.html + about editable installs. + + For the time being `setuptools` will silence this error and ignore + the faulty command, but this behaviour will change in future versions.\n + """ + warnings.warn(msg, SetuptoolsDeprecationWarning, stacklevel=2) + def _create_wheel_file(self, bdist_wheel): from wheel.wheelfile import WheelFile diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py index ea31cb46..f7612377 100644 --- a/setuptools/tests/test_editable_install.py +++ b/setuptools/tests/test_editable_install.py @@ -558,8 +558,9 @@ class TestOverallBehaviour: @pytest.mark.parametrize("layout", EXAMPLES.keys()) def test_editable_install(self, tmp_path, venv, layout, editable_opts): - opts = editable_opts - project = install_project("mypkg", venv, tmp_path, self.EXAMPLES[layout], *opts) + project, _ = install_project( + "mypkg", venv, tmp_path, self.EXAMPLES[layout], *editable_opts + ) # Ensure stray files are not importable cmd_import_error = """\ @@ -758,13 +759,55 @@ def test_pbr_integration(tmp_path, venv, editable_opts): assert b"Hello world!" in out +class TestCustomBuildPy: + """ + Issue #3501 indicates that some plugins/customizations might rely on: + + 1. ``build_py`` not running + 2. ``build_py`` always copying files to ``build_lib`` + + During the transition period setuptools should prevent potential errors from + happening due to those assumptions. + """ + # TODO: Remove tests after _run_build_steps is removed. + + FILES = { + **TestOverallBehaviour.EXAMPLES["flat-layout"], + "setup.py": dedent("""\ + import pathlib + from setuptools import setup + from setuptools.command.build_py import build_py as orig + + class my_build_py(orig): + def run(self): + super().run() + raise ValueError("TEST_RAISE") + + setup(cmdclass={"build_py": my_build_py}) + """), + } + + def test_safeguarded_from_errors(self, tmp_path, venv): + """Ensure that errors in custom build_py are reported as warnings""" + # Warnings should show up + _, out = install_project("mypkg", venv, tmp_path, self.FILES) + assert b"SetuptoolsDeprecationWarning" in out + assert b"ValueError: TEST_RAISE" in out + # but installation should be successful + out = venv.run(["python", "-c", "import mypkg.mod1; print(mypkg.mod1.var)"]) + assert b"42" in out + + def install_project(name, venv, tmp_path, files, *opts): project = tmp_path / name project.mkdir() jaraco.path.build(files, prefix=project) opts = [*opts, "--no-build-isolation"] # force current version of setuptools - venv.run(["python", "-m", "pip", "install", "-e", str(project), *opts]) - return project + out = venv.run( + ["python", "-m", "pip", "-v", "install", "-e", str(project), *opts], + stderr=subprocess.STDOUT, + ) + return project, out # ---- Assertion Helpers ---- |
