From: JinWang An Date: Mon, 27 Mar 2023 08:02:54 +0000 (+0900) Subject: Imported Upstream version 64.0.2 X-Git-Tag: upstream/64.0.2^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=5d8f0ca3f16127c50ec0b36d5fa74466650b9b41;p=platform%2Fupstream%2Fpython-setuptools.git Imported Upstream version 64.0.2 --- diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e14a394..617ffcb 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 64.0.1 +current_version = 64.0.2 commit = True tag = True diff --git a/CHANGES.rst b/CHANGES.rst index 20aba6c..3efa5f7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,19 @@ +v64.0.2 +------- + + +Misc +^^^^ +* #3506: Suppress errors in custom ``build_py`` implementations when running editable + installs in favor of a warning indicating what is the most appropriate + migration path. + This is a *transitional* measure. Errors might be raised in future versions of + ``setuptools``. +* #3512: Added capability of handling namespace packages created + accidentally/purposefully via discovery configuration during editable installs. + This should emulate the behaviour of a non-editable installation. + + v64.0.1 ------- @@ -40,6 +56,10 @@ Breaking Changes end users. The *strict* editable installation is not able to detect if files are added or removed from the project (a new installation is required). + This implementation might also affect plugins and customizations that assume + certain ``build`` subcommands don't run during editable installs or that they + always copy files to the temporary build directory. + .. important:: The *editable* aspect of the *editable install* supported this implementation is restricted to the Python modules contained in the distributed package. diff --git a/docs/userguide/development_mode.rst b/docs/userguide/development_mode.rst index e7c755a..ddf9a3f 100644 --- a/docs/userguide/development_mode.rst +++ b/docs/userguide/development_mode.rst @@ -5,7 +5,7 @@ When creating a Python project, developers usually want to implement and test changes iteratively, before cutting a release and preparing a distribution archive. In normal circumstances this can be quite cumbersome and require the developers -to manipulate the ``PATHONPATH`` environment variable or to continuous re-build +to manipulate the ``PYTHONPATH`` environment variable or to continuous re-build and re-install the project. To facilitate iterative exploration and experimentation, setuptools allows diff --git a/setup.cfg b/setup.cfg index 9093b09..840b82e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = setuptools -version = 64.0.1 +version = 64.0.2 author = Python Packaging Authority author_email = distutils-sig@python.org description = Easily download, build, install, upgrade, and uninstall Python packages diff --git a/setuptools/command/build.py b/setuptools/command/build.py index 283999d..c0676d8 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 1bb7ddf..2631a08 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 @@ -585,7 +628,14 @@ def _absolute_root(path: _Path) -> str: def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]: """By carefully designing ``package_dir``, it is possible to implement the logical structure of PEP 420 in a package without the corresponding directories. - This function will try to find this kind of namespaces. + + Moreover a parent package can be purposefully/accidentally skipped in the discovery + phase (e.g. ``find_packages(include=["mypkg.*"])``, when ``mypkg.foo`` is included + by ``mypkg`` itself is not). + We consider this case to also be a virtual namespace (ignoring the original + directory) to emulate a non-editable installation. + + This function will try to find these kinds of namespaces. """ for pkg in pkg_roots: if "." not in pkg: @@ -594,7 +644,8 @@ def _find_virtual_namespaces(pkg_roots: Dict[str, str]) -> Iterator[str]: for i in range(len(parts) - 1, 0, -1): partial_name = ".".join(parts[:i]) path = Path(find_package_path(partial_name, pkg_roots, "")) - if not path.exists(): + if not path.exists() or partial_name not in pkg_roots: + # partial_name not in pkg_roots ==> purposefully/accidentally skipped yield partial_name diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py index ea31cb4..67d377e 100644 --- a/setuptools/tests/test_editable_install.py +++ b/setuptools/tests/test_editable_install.py @@ -269,6 +269,54 @@ class TestPep420Namespaces: venv.run(["python", "-m", "pip", "install", "-e", str(pkg_C), *opts]) venv.run(["python", "-c", "from myns.n import pkgA, pkgB, pkgC"]) + def test_namespace_accidental_config_in_lenient_mode(self, venv, tmp_path): + """Sometimes users might specify an ``include`` pattern that ignores parent + packages. In a normal installation this would ignore all modules inside the + parent packages, and make them namespaces (reported in issue #3504), + so the editable mode should preserve this behaviour. + """ + files = { + "pkgA": { + "pyproject.toml": dedent("""\ + [build-system] + requires = ["setuptools", "wheel"] + build-backend = "setuptools.build_meta" + + [project] + name = "pkgA" + version = "3.14159" + + [tool.setuptools] + packages.find.include = ["mypkg.*"] + """), + "mypkg": { + "__init__.py": "", + "other.py": "b = 1", + "n": { + "__init__.py": "", + "pkgA.py": "a = 1", + }, + }, + "MANIFEST.in": EXAMPLE["MANIFEST.in"], + }, + } + jaraco.path.build(files, prefix=tmp_path) + pkg_A = tmp_path / "pkgA" + + # use pip to install to the target directory + opts = ["--no-build-isolation"] # force current version of setuptools + venv.run(["python", "-m", "pip", "-v", "install", "-e", str(pkg_A), *opts]) + out = venv.run(["python", "-c", "from mypkg.n import pkgA; print(pkgA.a)"]) + assert str(out, "utf-8").strip() == "1" + cmd = """\ + try: + import mypkg.other + except ImportError: + print("mypkg.other not defined") + """ + out = venv.run(["python", "-c", dedent(cmd)]) + assert "mypkg.other not defined" in str(out, "utf-8") + # Moved here from test_develop: @pytest.mark.xfail( @@ -490,7 +538,7 @@ def test_pkg_roots(tmp_path): assert ns == {"f", "f.g"} ns = set(_find_virtual_namespaces(roots)) - assert ns == {"a.b.c.x", "a.b.c.x.y", "m", "m.n", "m.n.o", "m.n.o.p"} + assert ns == {"a.b", "a.b.c.x", "a.b.c.x.y", "m", "m.n", "m.n.o", "m.n.o.p"} class TestOverallBehaviour: @@ -558,8 +606,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 +807,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 ----