Imported Upstream version 66.1.0 upstream/66.1.0
authorJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:02:59 +0000 (17:02 +0900)
committerJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:02:59 +0000 (17:02 +0900)
17 files changed:
.bumpversion.cfg
CHANGES.rst
docs/artwork.rst
docs/build_meta.rst
docs/deprecated/easy_install.rst
docs/userguide/declarative_config.rst
docs/userguide/package_discovery.rst
docs/userguide/pyproject_config.rst
pkg_resources/__init__.py
setup.cfg
setuptools/__init__.py
setuptools/command/egg_info.py
setuptools/config/_apply_pyprojecttoml.py
setuptools/config/pyprojecttoml.py
setuptools/config/setupcfg.py
setuptools/dist.py
setuptools/tests/test_sdist.py

index a7f4e5095cbadcfa13eeab5dcd085ae6811f7676..d997ebcd557bd8de890acab590dcebd8d3201393 100644 (file)
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 66.0.0
+current_version = 66.1.0
 commit = True
 tag = True
 
index a5d7a4a0b23ddee93f1f6b36e4d9603fab72e02c..7a9d81763fc2bf1a687a74cc576d9953a4223fd8 100644 (file)
@@ -1,3 +1,20 @@
+v66.1.0
+-------
+
+
+Changes
+^^^^^^^
+* #3685: Fix improper usage of deprecated/removed ``pkgutil`` APIs in Python 3.12+.
+* #3779: Files referenced by ``file:`` in ``setup.cfg`` and by ``project.readme.file``,
+  ``project.license.file`` or ``tool.setuptools.dynamic.*.file`` in
+  ``pyproject.toml`` are now automatically included in the generated sdists.
+
+Misc
+^^^^
+* #3776: Added note about using the ``--pep-517`` flag with ``pip`` to workaround
+  ``InvalidVersion`` errors for packages that are already installed in the system.
+
+
 v66.0.0
 -------
 
index 907e62a667826296874cb57c391dc28c223c2aff..d815561e9d3c16b0bfc3ea0c3c2d69d3a8489a5c 100644 (file)
@@ -65,7 +65,7 @@ Inspiration
 This design was inspired by :user:`cajhne`'s `original proposal`_ and the
 ancient symbol of the ouroboros_.
 It features a snake moving in a circular trajectory not only as a reference to
-the Python programming language but also to the `wheel package format`_ as one
+the Python programming language but also to the :pep:`wheel package format <427>` as one
 of the distribution formats supported by setuptools.
 The shape of the snake also resembles a cog, which together with the hammer is
 a nod to the two words that compose the name of the project.
@@ -115,5 +115,4 @@ https://github.com/pypa/setuptools or https://setuptools.pypa.io.
 .. _setuptools repository: https://github.com/pypa/setuptools
 .. _install the correct fonts: https://wiki.inkscape.org/wiki/Installing_fonts
 .. _original proposal: https://github.com/pypa/setuptools/issues/2227#issuecomment-653628344
-.. _wheel package format: https://www.python.org/dev/peps/pep-0427/
 .. _ouroboros: https://en.wikipedia.org/wiki/Ouroboros
index 08fd8a7bbb25c32d6ba04ed8316fa72f3f8ffa1f..197e591755182b4d7fe1f2dd4811697363cc2c03 100644 (file)
@@ -16,7 +16,7 @@ overhaul. Because ``setup.py`` scripts allow for arbitrary execution, it
 is difficult to provide a reliable user experience across environments
 and history.
 
-`PEP 517 <https://www.python.org/dev/peps/pep-0517/>`_ came to
+:pep:`517` came to
 the rescue and specified a new standard for packaging and distributing Python
 modules. Under PEP 517:
 
index 3cf3bea986f8c5db6572aae032ade445b27e0165..ab3d38a061b371d7dc4548914eaa4f8962ccad11 100644 (file)
@@ -989,9 +989,7 @@ The following section lists only the easiest and most relevant approaches [1]_.
 
 `Use "virtualenv"`_
 
-.. [1] There are older ways to achieve custom installation using various ``easy_install`` and ``setup.py install`` options, combined with ``PYTHONPATH`` and/or ``PYTHONUSERBASE`` alterations, but all of these are effectively deprecated by the User scheme brought in by `PEP-370`_.
-
-.. _PEP-370: http://www.python.org/dev/peps/pep-0370/
+.. [1] There are older ways to achieve custom installation using various ``easy_install`` and ``setup.py install`` options, combined with ``PYTHONPATH`` and/or ``PYTHONUSERBASE`` alterations, but all of these are effectively deprecated by the User scheme brought in by :pep:`370`.
 
 
 Use the "--user" option
index adedb0c0db4bdbe72f09e4a3540383894fa98b1c..d57351668914a63eee74c63cbff56108d663c643 100644 (file)
@@ -30,7 +30,6 @@ boilerplate code in some cases.
     description = My package description
     long_description = file: README.rst, CHANGELOG.rst, LICENSE.rst
     keywords = one, two
-    python_requires = >=3.7
     license = BSD-3-Clause
     classifiers =
         Framework :: Django
@@ -40,6 +39,7 @@ boilerplate code in some cases.
     zip_safe = False
     include_package_data = True
     packages = find:
+    python_requires = >=3.7
     install_requires =
         requests
         importlib-metadata; python_version<"3.8"
@@ -169,11 +169,14 @@ Special directives:
       The ``file:`` directive is sandboxed and won't reach anything outside the
       project directory (i.e. the directory containing ``setup.cfg``/``pyproject.toml``).
 
-  .. attention::
-      When using the ``file:`` directive, please make sure that all necessary
-      files are included in the ``sdist``. You can do that via ``MANIFEST.in``
-      or using plugins such as ``setuptools-scm``.
-      Please have a look on :doc:`/userguide/miscellaneous` for more information.
+  .. note::
+      If you are using an old version of ``setuptools``, you might need to ensure
+      that all files referenced by the ``file:`` directive are included in the ``sdist``
+      (you can do that via ``MANIFEST.in`` or using plugins such as ``setuptools-scm``,
+      please have a look on :doc:`/userguide/miscellaneous` for more information).
+
+      .. versionchanged:: 66.1.0
+         Newer versions of ``setuptools`` will automatically add these files to the ``sdist``.
 
 
 Metadata
index 7dda84a88243768c189e5cd85229ae55f47d5084..9577a534684ebf22c6fd1f7a01035cd3076c2651 100644 (file)
@@ -508,7 +508,7 @@ available to your interpreter.
 Legacy Namespace Packages
 =========================
 The fact you can create namespace packages so effortlessly above is credited
-to `PEP 420 <https://www.python.org/dev/peps/pep-0420/>`_. It used to be more
+to :pep:`420`. It used to be more
 cumbersome to accomplish the same result. Historically, there were two methods
 to create namespace packages. One is the ``pkg_resources`` style supported by
 ``setuptools`` and the other one being ``pkgutils`` style offered by
index 633f4de7a25ee5e5de51fdedbe7fae57b30d38ff..c97984ba097af96a8ba88c6b08416553d137862e 100644 (file)
@@ -220,11 +220,14 @@ however please keep in mind that all non-comment lines must conform with :pep:`5
 (``pip``-specify syntaxes, e.g. ``-c/-r/-e`` flags, are not supported).
 
 
-.. attention::
-   When using the ``file`` directive, please make sure that all necessary
-   files are included in the ``sdist``. You can do that via ``MANIFEST.in``
-   or using plugins such as ``setuptools-scm``.
-   Please have a look on :doc:`/userguide/miscellaneous` for more information.
+.. note::
+   If you are using an old version of ``setuptools``, you might need to ensure
+   that all files referenced by the ``file`` directive are included in the ``sdist``
+   (you can do that via ``MANIFEST.in`` or using plugins such as ``setuptools-scm``,
+   please have a look on :doc:`/userguide/miscellaneous` for more information).
+
+   .. versionchanged:: 66.1.0
+      Newer versions of ``setuptools`` will automatically add these files to the ``sdist``.
 
 ----
 
index bba775b9087dcbf3cd0e1795bc8e71cd0971ece4..0ae951b6282fff0f3026e2f3b25b9654a7938380 100644 (file)
@@ -2187,10 +2187,10 @@ def resolve_egg_link(path):
     return next(dist_groups, ())
 
 
-register_finder(pkgutil.ImpImporter, find_on_path)
+if hasattr(pkgutil, 'ImpImporter'):
+    register_finder(pkgutil.ImpImporter, find_on_path)
 
-if hasattr(importlib_machinery, 'FileFinder'):
-    register_finder(importlib_machinery.FileFinder, find_on_path)
+register_finder(importlib_machinery.FileFinder, find_on_path)
 
 _declare_state('dict', _namespace_handlers={})
 _declare_state('dict', _namespace_packages={})
@@ -2344,11 +2344,11 @@ def file_ns_handler(importer, path_item, packageName, module):
         return subpath
 
 
-register_namespace_handler(pkgutil.ImpImporter, file_ns_handler)
-register_namespace_handler(zipimport.zipimporter, file_ns_handler)
+if hasattr(pkgutil, 'ImpImporter'):
+    register_namespace_handler(pkgutil.ImpImporter, file_ns_handler)
 
-if hasattr(importlib_machinery, 'FileFinder'):
-    register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler)
+register_namespace_handler(zipimport.zipimporter, file_ns_handler)
+register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler)
 
 
 def null_ns_handler(importer, path_item, packageName, module):
@@ -2675,7 +2675,14 @@ class Distribution:
     @property
     def parsed_version(self):
         if not hasattr(self, "_parsed_version"):
-            self._parsed_version = parse_version(self.version)
+            try:
+                self._parsed_version = parse_version(self.version)
+            except packaging.version.InvalidVersion as ex:
+                info = f"(package: {self.project_name})"
+                if hasattr(ex, "add_note"):
+                    ex.add_note(info)  # PEP 678
+                    raise
+                raise packaging.version.InvalidVersion(f"{str(ex)} {info}") from None
 
         return self._parsed_version
 
index cd0e6b0b569ab041e10c35f126f5886672595a13..04d88d32ce879a96a5faeccdefea27926ccf95c5 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = setuptools
-version = 66.0.0
+version = 66.1.0
 author = Python Packaging Authority
 author_email = distutils-sig@python.org
 description = Easily download, build, install, upgrade, and uninstall Python packages
index 6c24cc2b30421bad1cb5f8ca525bc42b57ad9761..89f6f06ec034571754922caec351f7f8c86359bb 100644 (file)
@@ -77,7 +77,28 @@ def _install_setup_requires(attrs):
     # Honor setup.cfg's options.
     dist.parse_config_files(ignore_option_errors=True)
     if dist.setup_requires:
+        _fetch_build_eggs(dist)
+
+
+def _fetch_build_eggs(dist):
+    try:
         dist.fetch_build_eggs(dist.setup_requires)
+    except Exception as ex:
+        msg = """
+        It is possible a package already installed in your system
+        contains an version that is invalid according to PEP 440.
+        You can try `pip install --use-pep517` as a workaround for this problem,
+        or rely on a new virtual environment.
+
+        If the problem refers to a package that is not installed yet,
+        please contact that package's maintainers or distributors.
+        """
+        if "InvalidVersion" in ex.__class__.__name__:
+            if hasattr(ex, "add_note"):
+                ex.add_note(msg)  # PEP 678
+            else:
+                dist.announce(f"\n{msg}\n")
+        raise
 
 
 def setup(**attrs):
index 1885efb00f24bbd3f0ca30e4eaf580b48c438e63..86e99dd207c67ce89f340eb08a3036ae170667f5 100644 (file)
@@ -565,6 +565,7 @@ class manifest_maker(sdist):
         if os.path.exists(self.template):
             self.read_template()
         self.add_license_files()
+        self._add_referenced_files()
         self.prune_file_list()
         self.filelist.sort()
         self.filelist.remove_duplicates()
@@ -619,9 +620,16 @@ class manifest_maker(sdist):
         license_files = self.distribution.metadata.license_files or []
         for lf in license_files:
             log.info("adding license file '%s'", lf)
-            pass
         self.filelist.extend(license_files)
 
+    def _add_referenced_files(self):
+        """Add files referenced by the config (e.g. `file:` directive) to filelist"""
+        referenced = getattr(self.distribution, '_referenced_files', [])
+        # ^-- fallback if dist comes from distutils or is a custom class
+        for rf in referenced:
+            log.debug("adding file referenced by config '%s'", rf)
+        self.filelist.extend(referenced)
+
     def prune_file_list(self):
         build = self.get_finalized_command('build')
         base_dir = self.distribution.get_fullname()
index 8af556169cd6cce0282fce9ee09e6d6bcfc452c5..c805e63940269dbbcc5dc293736b9cd6d12d0d2d 100644 (file)
@@ -16,7 +16,7 @@ from functools import partial, reduce
 from itertools import chain
 from types import MappingProxyType
 from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple,
-                    Type, Union)
+                    Type, Union, cast)
 
 from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
 
@@ -142,22 +142,29 @@ def _long_description(dist: "Distribution", val: _DictOrStr, root_dir: _Path):
     from setuptools.config import expand
 
     if isinstance(val, str):
-        text = expand.read_files(val, root_dir)
+        file: Union[str, list] = val
+        text = expand.read_files(file, root_dir)
         ctype = _guess_content_type(val)
     else:
-        text = val.get("text") or expand.read_files(val.get("file", []), root_dir)
+        file = val.get("file") or []
+        text = val.get("text") or expand.read_files(file, root_dir)
         ctype = val["content-type"]
 
     _set_config(dist, "long_description", text)
+
     if ctype:
         _set_config(dist, "long_description_content_type", ctype)
 
+    if file:
+        dist._referenced_files.add(cast(str, file))
+
 
 def _license(dist: "Distribution", val: dict, root_dir: _Path):
     from setuptools.config import expand
 
     if "file" in val:
         _set_config(dist, "license", expand.read_files([val["file"]], root_dir))
+        dist._referenced_files.add(val["file"])
     else:
         _set_config(dist, "license", val["text"])
 
index fee6fac6ae0ae0cf3983787e77a7423b745dd9ee..cedf56751f43113d358ab0ff69d27f9cebf58164 100644 (file)
@@ -8,7 +8,7 @@ import os
 import warnings
 from contextlib import contextmanager
 from functools import partial
-from typing import TYPE_CHECKING, Callable, Dict, Optional, Mapping, Union
+from typing import TYPE_CHECKING, Callable, Dict, Optional, Mapping, Set, Union
 
 from setuptools.errors import FileError, OptionError
 
@@ -84,8 +84,8 @@ def read_configuration(
 
     :param Distribution|None: Distribution object to which the configuration refers.
         If not given a dummy object will be created and discarded after the
-        configuration is read. This is used for auto-discovery of packages in the case
-        a dynamic configuration (e.g. ``attr`` or ``cmdclass``) is expanded.
+        configuration is read. This is used for auto-discovery of packages and in the
+        case a dynamic configuration (e.g. ``attr`` or ``cmdclass``) is expanded.
         When ``expand=False`` this object is simply ignored.
 
     :rtype: dict
@@ -211,6 +211,7 @@ class _ConfigExpander:
         self.dynamic_cfg = self.setuptools_cfg.get("dynamic", {})
         self.ignore_option_errors = ignore_option_errors
         self._dist = dist
+        self._referenced_files: Set[str] = set()
 
     def _ensure_dist(self) -> "Distribution":
         from setuptools.dist import Distribution
@@ -241,6 +242,7 @@ class _ConfigExpander:
             self._expand_cmdclass(package_dir)
             self._expand_all_dynamic(dist, package_dir)
 
+        dist._referenced_files.update(self._referenced_files)
         return self.config
 
     def _expand_packages(self):
@@ -310,6 +312,7 @@ class _ConfigExpander:
         with _ignore_errors(self.ignore_option_errors):
             root_dir = self.root_dir
             if "file" in directive:
+                self._referenced_files.update(directive["file"])
                 return _expand.read_files(directive["file"], root_dir)
             if "attr" in directive:
                 return _expand.read_attr(directive["attr"], package_dir, root_dir)
index c2a974de6368c9f4f9b9943c94a457227370f143..3df3b6e7687b431aa1a75a8b92502f553f8fb507 100644 (file)
@@ -12,7 +12,7 @@ from collections import defaultdict
 from functools import partial
 from functools import wraps
 from typing import (TYPE_CHECKING, Callable, Any, Dict, Generic, Iterable, List,
-                    Optional, Tuple, TypeVar, Union)
+                    Optional, Set, Tuple, TypeVar, Union)
 
 from distutils.errors import DistutilsOptionError, DistutilsFileError
 from setuptools.extern.packaging.requirements import Requirement, InvalidRequirement
@@ -172,6 +172,9 @@ def parse_configuration(
             distribution.src_root,
         )
         meta.parse()
+        distribution._referenced_files.update(
+            options._referenced_files, meta._referenced_files
+        )
 
     return meta, options
 
@@ -247,6 +250,10 @@ class ConfigHandler(Generic[Target]):
         self.sections = sections
         self.set_options: List[str] = []
         self.ensure_discovered = ensure_discovered
+        self._referenced_files: Set[str] = set()
+        """After parsing configurations, this property will enumerate
+        all files referenced by the "file:" directive. Private API for setuptools only.
+        """
 
     @property
     def parsers(self):
@@ -365,8 +372,7 @@ class ConfigHandler(Generic[Target]):
 
         return parser
 
-    @classmethod
-    def _parse_file(cls, value, root_dir: _Path):
+    def _parse_file(self, value, root_dir: _Path):
         """Represents value as a string, allowing including text
         from nearest files using `file:` directive.
 
@@ -388,7 +394,8 @@ class ConfigHandler(Generic[Target]):
             return value
 
         spec = value[len(include_directive) :]
-        filepaths = (path.strip() for path in spec.split(','))
+        filepaths = [path.strip() for path in spec.split(',')]
+        self._referenced_files.update(filepaths)
         return expand.read_files(filepaths, root_dir)
 
     def _parse_attr(self, value, package_dir, root_dir: _Path):
index 1c71e5eed2f1484020cc16cae3c5a3645f18afa3..cd34d74a9c88d11605ccff342e3e3cdde6b9abff 100644 (file)
@@ -17,7 +17,7 @@ from distutils.fancy_getopt import translate_longopt
 from glob import iglob
 import itertools
 import textwrap
-from typing import List, Optional, TYPE_CHECKING
+from typing import List, Optional, Set, TYPE_CHECKING
 from pathlib import Path
 
 from collections import defaultdict
@@ -481,6 +481,11 @@ class Distribution(_Distribution):
             },
         )
 
+        # Private API (setuptools-use only, not restricted to Distribution)
+        # Stores files that are referenced by the configuration and need to be in the
+        # sdist (e.g. `version = file: VERSION.txt`)
+        self._referenced_files: Set[str] = set()
+
         # Save the original dependencies before they are processed into the egg format
         self._orig_extras_require = {}
         self._orig_install_requires = []
index 30631c242967e7569270e55ced238fe26e9f9121..11de75c7bbe84b3af6839ac452f39775000f7d06 100644 (file)
@@ -498,6 +498,48 @@ class TestSdistTest:
             filename = filename.decode('latin-1')
             filename not in cmd.filelist.files
 
+    _EXAMPLE_DIRECTIVES = {
+        "setup.cfg - long_description and version": """
+            [metadata]
+            name = testing
+            version = file: VERSION.txt
+            license_files = DOWHATYOUWANT
+            long_description = file: README.rst, USAGE.rst
+            """,
+        "pyproject.toml - static readme/license files and dynamic version": """
+            [project]
+            name = "testing"
+            readme = "USAGE.rst"
+            license = {file = "DOWHATYOUWANT"}
+            dynamic = ["version"]
+            [tool.setuptools.dynamic]
+            version = {file = ["VERSION.txt"]}
+            """
+    }
+
+    @pytest.mark.parametrize("config", _EXAMPLE_DIRECTIVES.keys())
+    def test_add_files_referenced_by_config_directives(self, tmp_path, config):
+        config_file, _, _ = config.partition(" - ")
+        config_text = self._EXAMPLE_DIRECTIVES[config]
+        (tmp_path / 'VERSION.txt').write_text("0.42", encoding="utf-8")
+        (tmp_path / 'README.rst').write_text("hello world!", encoding="utf-8")
+        (tmp_path / 'USAGE.rst').write_text("hello world!", encoding="utf-8")
+        (tmp_path / 'DOWHATYOUWANT').write_text("hello world!", encoding="utf-8")
+        (tmp_path / config_file).write_text(config_text, encoding="utf-8")
+
+        dist = Distribution({"packages": []})
+        dist.script_name = 'setup.py'
+        dist.parse_config_files()
+
+        cmd = sdist(dist)
+        cmd.ensure_finalized()
+        with quiet():
+            cmd.run()
+
+        assert 'VERSION.txt' in cmd.filelist.files
+        assert 'USAGE.rst' in cmd.filelist.files
+        assert 'DOWHATYOUWANT' in cmd.filelist.files
+
     def test_pyproject_toml_in_sdist(self, tmpdir):
         """
         Check if pyproject.toml is included in source distribution if present