Imported Upstream version 60.10.0 upstream/60.10.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 18 Jul 2022 06:39:09 +0000 (15:39 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 18 Jul 2022 06:39:09 +0000 (15:39 +0900)
31 files changed:
CHANGES.rst
PKG-INFO
docs/conf.py
docs/deprecated/distutils-legacy.rst
docs/setuptools.rst
docs/userguide/datafiles.rst
docs/userguide/entry_point.rst
docs/userguide/miscellaneous.rst
docs/userguide/quickstart.rst
setup.cfg
setuptools.egg-info/PKG-INFO
setuptools.egg-info/SOURCES.txt
setuptools.egg-info/requires.txt
setuptools/_vendor/nspektr-0.3.0.dist-info/top_level.txt [new file with mode: 0644]
setuptools/_vendor/nspektr/__init__.py [new file with mode: 0644]
setuptools/_vendor/nspektr/_compat.py [new file with mode: 0644]
setuptools/_vendor/vendored.txt
setuptools/command/easy_install.py
setuptools/command/upload_docs.py
setuptools/dist.py
setuptools/extern/__init__.py
setuptools/package_index.py
setuptools/tests/environment.py
setuptools/tests/fixtures.py
setuptools/tests/test_build_meta.py
setuptools/tests/test_sphinx_upload_docs.py [deleted file]
setuptools/tests/test_upload_docs.py [deleted file]
setuptools/tests/test_virtualenv.py
tools/finalize.py
tools/vendored.py
tox.ini

index 339e81f..3c724e4 100644 (file)
@@ -1,3 +1,39 @@
+v60.10.0
+--------
+
+
+Changes
+^^^^^^^
+* #2971: Deprecated upload_docs command, to be removed in the future.
+* #3137: Use samefile from stdlib, supported on Windows since Python 3.2.
+* #3170: Adopt nspektr (vendored) to implement Distribution._install_dependencies.
+
+Documentation changes
+^^^^^^^^^^^^^^^^^^^^^
+* #3144: Added documentation on using console_scripts from setup.py, which was previously only shown in setup.cfg  -- by :user:`xhlulu`
+* #3148: Added clarifications about ``MANIFEST.in``, that include links to PyPUG docs
+  and more prominent mentions to using a revision control system plugin as an
+  alternative.
+* #3148: Removed mention to ``pkg_resources`` as the recommended way of accessing data
+  files, in favour of :doc:`importlib.resources`.
+  Additionally more emphasis was put on the fact that *package data files* reside
+  **inside** the *package directory* (and therefore should be *read-only*).
+
+Misc
+^^^^
+* #3120: Added workaround for intermittent failures of backend tests on PyPy.
+  These tests now are marked with `XFAIL
+  <https://docs.pytest.org/en/stable/how-to/skipping.html>`_, instead of erroring
+  out directly.
+* #3124: Improved configuration for :pypi:`rst-linker` (extension used to build the
+  changelog).
+* #3133: Enhanced isolation of tests using virtual environments - PYTHONPATH is not leaking to spawned subprocesses  -- by :user:`befeleme`
+* #3147: Added options to provide a pre-built ``setuptools`` wheel or sdist for being
+  used during tests with virtual environments.
+  Paths for these pre-built distribution files can now be set via the environment
+  variables: ``PRE_BUILT_SETUPTOOLS_SDIST`` and ``PRE_BUILT_SETUPTOOLS_WHEEL``.
+
+
 v60.9.3
 -------
 
@@ -543,7 +579,7 @@ Changes
 
 Documentation changes
 ^^^^^^^^^^^^^^^^^^^^^
-* #2792: Document how the legacy and non-legacy versions are compared, and reference to the `PEP 440 <https://www.python.org/dev/peps/pep-0440/>`_ scheme.
+* #2792: Document how the legacy and non-legacy versions are compared, and reference to the PEP 440 scheme.
 
 
 v58.1.0
@@ -4758,8 +4794,7 @@ how it parses version numbers.
   Jython.
 * Work around Jython #1980 and Jython #1981.
 * Distribute #334: Provide workaround for packages that reference ``sys.__stdout__``
-  such as numpy does. This change should address
-  `virtualenv #359 <https://github.com/pypa/virtualenv/issues/359>`_ as long
+  such as numpy does. This change should address pypa/virtualenv#359 as long
   as the system encoding is UTF-8 or the IO encoding is specified in the
   environment, i.e.::
 
@@ -4785,7 +4820,7 @@ how it parses version numbers.
 
 * BB Pull Request #14: Honor file permissions in zip files.
 * Distribute #327: Merged pull request #24 to fix a dependency problem with pip.
-* Merged pull request #23 to fix https://github.com/pypa/virtualenv/issues/301.
+* Merged pull request #23 to fix pypa/virtualenv#301.
 * If Sphinx is installed, the ``upload_docs`` command now runs ``build_sphinx``
   to produce uploadable documentation.
 * Distribute #326: ``upload_docs`` provided mangled auth credentials under Python 3.
index aa9bf27..6d63155 100644 (file)
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: setuptools
-Version: 60.9.3
+Version: 60.10.0
 Summary: Easily download, build, install, upgrade, and uninstall Python packages
 Home-page: https://github.com/pypa/setuptools
 Author: Python Packaging Authority
index bfd45a6..4c00d46 100644 (file)
@@ -10,7 +10,7 @@ link_files = {
         ),
         replace=[
             dict(
-                pattern=r'(Issue )?#(?P<issue>\d+)',
+                pattern=r'(?<!\w)(Issue )?#(?P<issue>\d+)',
                 url='{package_url}/issues/{issue}',
             ),
             dict(
@@ -54,7 +54,7 @@ link_files = {
                 url='{GH}/pypa/packaging/blob/{packaging_ver}/CHANGELOG.rst',
             ),
             dict(
-                pattern=r'PEP[- ](?P<pep_number>\d+)',
+                pattern=r'(?<![`/\w])PEP[- ](?P<pep_number>\d+)',
                 url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
             ),
             dict(
@@ -62,12 +62,12 @@ link_files = {
                 url='{GH}/jaraco/setuptools_svn/issues/{setuptools_svn}',
             ),
             dict(
-                pattern=r'pypa/distutils#(?P<distutils>\d+)',
-                url='{GH}/pypa/distutils/issues/{distutils}',
+                pattern=r'pypa/(?P<issue_repo>[\-\.\w]+)#(?P<issue_number>\d+)',
+                url='{GH}/pypa/{issue_repo}/issues/{issue_number}',
             ),
             dict(
-                pattern=r'pypa/distutils@(?P<distutils_commit>[\da-f]+)',
-                url='{GH}/pypa/distutils/commit/{distutils_commit}',
+                pattern=r'pypa/(?P<commit_repo>[\-\.\w]+)@(?P<commit_number>[\da-f]+)',
+                url='{GH}/pypa/{commit_repo}/commit/{commit_number}',
             ),
             dict(
                 pattern=r'^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n',
@@ -199,3 +199,8 @@ favicons = [
 ]
 
 intersphinx_mapping['pip'] = 'https://pip.pypa.io/en/latest', None
+intersphinx_mapping['PyPUG'] = ('https://packaging.python.org/en/latest/', None)
+intersphinx_mapping['packaging'] = ('https://packaging.pypa.io/en/latest/', None)
+intersphinx_mapping['importlib-resources'] = (
+    'https://importlib-resources.readthedocs.io/en/latest', None
+)
index 148dc25..e73cdff 100644 (file)
@@ -3,11 +3,10 @@ Porting from Distutils
 
 Setuptools and the PyPA have a `stated goal <https://github.com/pypa/packaging-problems/issues/127>`_ to make Setuptools the reference API for distutils.
 
-Since the 49.1.2 release, Setuptools includes a local, vendored copy of distutils (from late copies of CPython) that is disabled by default. To enable the use of this copy of distutils when invoking setuptools, set the enviroment variable:
+Since the 60.0.0 release, Setuptools includes a local, vendored copy of distutils (from late copies of CPython) that is enabled by default. To disable the use of this copy of distutils when invoking setuptools, set the enviroment variable:
 
-    SETUPTOOLS_USE_DISTUTILS=local
+    SETUPTOOLS_USE_DISTUTILS=stdlib
 
-This behavior is planned to become the default.
 
 Prefer Setuptools
 -----------------
@@ -20,12 +19,15 @@ As Distutils is deprecated, any usage of functions or objects from distutils is
 
 ``distutils.command.{build_clib,build_ext,build_py,sdist}`` → ``setuptools.command.*``
 
-``distutils.log`` → (no replacement yet)
+``distutils.log`` → :mod:`logging` (standard library)
 
-``distutils.version.*`` → ``packaging.version.*``
+``distutils.version.*`` → :doc:`packaging.version.* <packaging:version>`
 
 ``distutils.errors.*`` → ``setuptools.errors.*`` [#errors]_
 
+
+Migration advice is also provided by :pep:`PEP 632 <632#migration-advice>`.
+
 If a project relies on uses of ``distutils`` that do not have a suitable replacement above, please search the `Setuptools issue tracker <https://github.com/pypa/setuptools/issues/>`_ and file a request, describing the use-case so that Setuptools' maintainers can investigate. Please provide enough detail to help the maintainers understand how distutils is used, what value it provides, and why that behavior should be supported.
 
 
index d0fb9a9..aa63830 100644 (file)
@@ -21,8 +21,9 @@ Feature Highlights:
   individually in setup.py
 
 * Automatically include all relevant files in your source distributions,
-  without needing to create a ``MANIFEST.in`` file, and without having to force
-  regeneration of the ``MANIFEST`` file when your source tree changes.
+  without needing to create a |MANIFEST.in|_ file, and without having to force
+  regeneration of the ``MANIFEST`` file when your source tree changes
+  [#manifest]_.
 
 * Automatically generate wrapper scripts or Windows (console and GUI) .exe
   files for any number of "main" functions in your project.  (Note: this is not
@@ -211,3 +212,17 @@ set of steps to reproduce.
 
 .. _GitHub Discussions: https://github.com/pypa/setuptools/discussions
 .. _setuptools bug tracker: https://github.com/pypa/setuptools/
+
+
+----
+
+
+.. [#manifest] The default behaviour for ``setuptools`` will work well for pure
+   Python packages, or packages with simple C extensions (that don't require
+   any special C header). See :ref:`Controlling files in the distribution` and
+   :doc:`userguide/datafiles` for more information about complex scenarios, if
+   you want to include other types of files.
+
+
+.. |MANIFEST.in| replace:: ``MANIFEST.in``
+.. _MANIFEST.in: https://packaging.python.org/en/latest/guides/using-manifest-in/
index 69cf36e..9817e63 100644 (file)
@@ -5,11 +5,11 @@ Data Files Support
 The distutils have traditionally allowed installation of "data files", which
 are placed in a platform-specific location.  However, the most common use case
 for data files distributed with a package is for use *by* the package, usually
-by including the data files in the package directory.
+by including the data files **inside the package directory**.
 
-Setuptools offers three ways to specify data files to be included in your
-packages.  First, you can simply use the ``include_package_data`` keyword,
-e.g.::
+Setuptools offers three ways to specify this most common type of data files to
+be included in your package's [#datafiles]_.
+First, you can simply use the ``include_package_data`` keyword, e.g.::
 
     from setuptools import setup, find_packages
     setup(
@@ -18,9 +18,10 @@ e.g.::
     )
 
 This tells setuptools to install any data files it finds in your packages.
-The data files must be specified via the distutils' ``MANIFEST.in`` file.
+The data files must be specified via the |MANIFEST.in|_ file.
 (They can also be tracked by a revision control system, using an appropriate
-plugin.  See the section below on :ref:`Adding Support for Revision
+plugin such as :pypi:`setuptools-scm` or :pypi:`setuptools-svn`.
+See the section below on :ref:`Adding Support for Revision
 Control Systems` for information on how to write such plugins.)
 
 If you want finer-grained control over what files are included (for example,
@@ -87,14 +88,13 @@ When building an ``sdist``, the datafiles are also drawn from the
 ``package_name.egg-info/SOURCES.txt`` file, so make sure that this is removed if
 the ``setup.py`` ``package_data`` list is updated before calling ``setup.py``.
 
-(Note: although the ``package_data`` argument was previously only available in
-``setuptools``, it was also added to the Python ``distutils`` package as of
-Python 2.4; there is `some documentation for the feature`__ available on the
-python.org website.  If using the setuptools-specific ``include_package_data``
-argument, files specified by ``package_data`` will *not* be automatically
-added to the manifest unless they are listed in the MANIFEST.in file.)
+.. note::
+   If using the ``include_package_data`` argument, files specified by
+   ``package_data`` will *not* be automatically added to the manifest unless
+   they are listed in the |MANIFEST.in|_ file or by a plugin like
+   :pypi:`setuptools-scm` or :pypi:`setuptools-svn`.
 
-__ https://docs.python.org/3/distutils/setupscript.html#installing-package-data
+.. https://docs.python.org/3/distutils/setupscript.html#installing-package-data
 
 Sometimes, the ``include_package_data`` or ``package_data`` options alone
 aren't sufficient to precisely define what files you want included.  For
@@ -125,11 +125,13 @@ included as a result of using ``include_package_data``.
 In summary, the three options allow you to:
 
 ``include_package_data``
-    Accept all data files and directories matched by ``MANIFEST.in``.
+    Accept all data files and directories matched by |MANIFEST.in|_ or added by
+    a :ref:`plugin <Adding Support for Revision Control Systems>`.
 
 ``package_data``
     Specify additional patterns to match files that may or may
-    not be matched by ``MANIFEST.in`` or found in source control.
+    not be matched by |MANIFEST.in|_ or added by
+    a :ref:`plugin <Adding Support for Revision Control Systems>`.
 
 ``exclude_package_data``
     Specify patterns for data files and directories that should *not* be
@@ -154,14 +156,22 @@ Typically, existing programs manipulate a package's ``__file__`` attribute in
 order to find the location of data files.  However, this manipulation isn't
 compatible with PEP 302-based import hooks, including importing from zip files
 and Python Eggs.  It is strongly recommended that, if you are using data files,
-you should use the :ref:`ResourceManager API` of ``pkg_resources`` to access
-them.  The ``pkg_resources`` module is distributed as part of setuptools, so if
-you're using setuptools to distribute your package, there is no reason not to
-use its resource management API.  See also `Importlib Resources`_ for
-a quick example of converting code that uses ``__file__`` to use
-``pkg_resources`` instead.
+you should use :mod:`importlib.resources` to access them.
+:mod:`importlib.resources` was added to Python 3.7 and the latest version of
+the library is also available via the :pypi:`importlib-resources` backport.
+See :doc:`importlib-resources:using` for detailed instructions [#importlib]_.
+
+.. tip:: Files inside the package directory should be *read-only* to avoid a
+   series of common problems (e.g. when multiple users share a common Python
+   installation, when the package is loaded from a zip file, or when multiple
+   instances of a Python application run in parallel).
 
-.. _Importlib Resources: https://docs.python.org/3/library/importlib.html#module-importlib.resources
+   If your Python package needs to write to a file for shared data or configuration,
+   you can use standard platform/OS-specific system directories, such as
+   ``~/.local/config/$appname`` or ``/usr/share/$appname/$version`` (Linux specific) [#system-dirs]_.
+   A common approach is to add a read-only template file to the package
+   directory that is then copied to the correct system directory if no
+   pre-existing file is found.
 
 
 Non-Package Data Files
@@ -174,4 +184,23 @@ fall back to the platform-specific location for installing data files, there is
 no supported facility to reliably retrieve these resources.
 
 Instead, the PyPA recommends that any data files you wish to be accessible at
-run time be included in the package.
+run time be included **inside the package**.
+
+
+----
+
+.. [#datafiles] ``setuptools`` consider a *package data file* any non-Python
+   file **inside the package directory** (i.e., that co-exists in the same
+   location as the regular ``.py`` files being distributed).
+
+.. [#system-dirs] These locations can be discovered with the help of
+   third-party libraries such as :pypi:`platformdirs`.
+
+.. [#importlib] Recent versions of :mod:`importlib.resources` available in
+   Pythons' standard library should be API compatible with
+   :pypi:`importlib-metadata`. However this might vary depending on which version
+   of Python is installed.
+
+
+.. |MANIFEST.in| replace:: ``MANIFEST.in``
+.. _MANIFEST.in: https://packaging.python.org/en/latest/guides/using-manifest-in/
index 21edc69..ea73bb5 100644 (file)
@@ -54,11 +54,32 @@ above example, to create a command ``hello-world`` that invokes
 ``timmins.hello_world``, add a console script entry point to
 ``setup.cfg``:
 
-.. code-block:: ini
+.. tab:: setup.cfg
+
+       .. code-block:: ini
+
+               [options.entry_points]
+               console_scripts =
+                       hello-world = timmins:hello_world
+
+.. tab:: setup.py
+
+    .. code-block:: python
+       
+        from setuptools import setup
+
+        setup(
+            name='timmins',
+            version='0.0.1',
+            packages=['timmins'],
+                       # ...
+            entry_points={
+                               'console_scripts': [
+                                       'hello-world=timmins:hello_world',
+                               ]
+                       }
+        )
 
-    [options.entry_points]
-    console_scripts =
-        hello-world = timmins:hello_world
 
 After installing the package, a user may invoke that function by simply calling
 ``hello-world`` on the command line.
index 3df327d..5fd2f0a 100644 (file)
@@ -94,3 +94,66 @@ correctly when installed as a zipfile, correct any problems if you can, and
 then make an explicit declaration of ``True`` or ``False`` for the ``zip_safe``
 flag, so that it will not be necessary for ``bdist_egg`` to try to guess
 whether your project can work as a zipfile.
+
+
+.. _Controlling files in the distribution:
+
+Controlling files in the distribution
+-------------------------------------
+
+For the most common use cases, ``setuptools`` will automatically find out which
+files are necessary for distributing the package.
+This includes all :term:`pure Python modules <Pure Module>` in the
+``py_modules`` or ``packages`` configuration, and the C sources (but not C
+headers) listed as part of extensions when creating a :term:`Source
+Distribution (or "sdist")`.
+
+However, when building more complex packages (e.g. packages that include
+non-Python files, or that need to use custom C headers), you might find that
+not all files present in your project folder are included in package
+:term:`distribution archive <Distribution Package>`.
+
+In these situations you can use a ``setuptools``
+:ref:`plugin <Adding Support for Revision Control Systems>`,
+such as :pypi:`setuptools-scm` or :pypi:`setuptools-svn` to automatically
+include all files tracked by your Revision Control System into the ``sdist``.
+
+.. _Using MANIFEST.in:
+
+Alternatively, if you need finer control, you can add a ``MANIFEST.in`` file at
+the root of your project.
+This file contains instructions that tell ``setuptools`` which files exactly
+should be part of the ``sdist`` (or not).
+A comprehensive guide to ``MANIFEST.in`` syntax is available at the
+:doc:`PyPA's Packaging User Guide <PyPUG:guides/using-manifest-in>`.
+
+Once the correct files are present in the ``sdist``, they can then be used by
+binary extensions during the build process, or included in the final
+:term:`wheel <Wheel>` [#build-process]_ if you configure ``setuptools`` with
+``include_package_data=True``.
+
+.. important::
+   Please note that, when using ``include_package_data=True``, only files **inside
+   the package directory** are included in the final ``wheel``, by default.
+
+   So for example, if you create a :term:`Python project <Project>` that uses
+   :pypi:`setuptools-scm` and have a ``tests`` directory outside of the package
+   folder, the ``tests`` directory will be present in the ``sdist`` but not in the
+   ``wheel`` [#wheel-vs-sdist]_.
+
+   See :doc:`/userguide/datafiles` for more information.
+
+----
+
+.. [#build-process]
+   You can think about the build process as two stages: first the ``sdist``
+   will be created and then the ``wheel`` will be produced from that ``sdist``.
+
+.. [#wheel-vs-sdist]
+   This happens because the ``sdist`` can contain files that are useful during
+   development or the build process itself, but not in runtime (e.g. tests,
+   docs, examples, etc...).
+   The ``wheel``, on the other hand, is a file format that has been optimized
+   and is ready to be unpacked into a running installation of Python or
+   :term:`Virtual Environment`.
+   Therefore it only contains items that are required during runtime.
index 203d620..f318362 100644 (file)
@@ -180,7 +180,9 @@ can simply use the ``include_package_data`` keyword:
     include_package_data = True
 
 This tells setuptools to install any data files it finds in your packages.
-The data files must be specified via the distutils' ``MANIFEST.in`` file.
+The data files must be specified via the distutils' |MANIFEST.in|_ file
+or automatically added by a :ref:`Revision Control System plugin
+<Adding Support for Revision Control Systems>`.
 For more details, see :doc:`datafiles`
 
 
@@ -228,3 +230,7 @@ Resources on Python packaging
 Packaging in Python can be hard and is constantly evolving.
 `Python Packaging User Guide <https://packaging.python.org>`_ has tutorials and
 up-to-date references that can help you when it is time to distribute your work.
+
+
+.. |MANIFEST.in| replace:: ``MANIFEST.in``
+.. _MANIFEST.in: https://packaging.python.org/en/latest/guides/using-manifest-in/
index 4a4a9a2..d84f22b 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = setuptools
-version = 60.9.3
+version = 60.10.0
 author = Python Packaging Authority
 author_email = distutils-sig@python.org
 description = Easily download, build, install, upgrade, and uninstall Python packages
@@ -56,7 +56,6 @@ testing =
        pip>=19.1 # For proper file:// URLs support.
        jaraco.envs>=2.2
        pytest-xdist
-       sphinx>=4.3.2
        jaraco.path>=3.2.0
        build[virtualenv]
        filelock>=3.4.0
index aa9bf27..6d63155 100644 (file)
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: setuptools
-Version: 60.9.3
+Version: 60.10.0
 Summary: Easily download, build, install, upgrade, and uninstall Python packages
 Home-page: https://github.com/pypa/setuptools
 Author: Python Packaging Authority
index 3a6e8e9..c030483 100644 (file)
@@ -356,6 +356,9 @@ setuptools/_vendor/more_itertools/__init__.py
 setuptools/_vendor/more_itertools/more.py
 setuptools/_vendor/more_itertools/recipes.py
 setuptools/_vendor/more_itertools-8.8.0.dist-info/top_level.txt
+setuptools/_vendor/nspektr/__init__.py
+setuptools/_vendor/nspektr/_compat.py
+setuptools/_vendor/nspektr-0.3.0.dist-info/top_level.txt
 setuptools/_vendor/ordered_set-3.1.1.dist-info/top_level.txt
 setuptools/_vendor/packaging/__about__.py
 setuptools/_vendor/packaging/__init__.py
@@ -438,11 +441,9 @@ setuptools/tests/test_sandbox.py
 setuptools/tests/test_sdist.py
 setuptools/tests/test_setopt.py
 setuptools/tests/test_setuptools.py
-setuptools/tests/test_sphinx_upload_docs.py
 setuptools/tests/test_test.py
 setuptools/tests/test_unicode_utils.py
 setuptools/tests/test_upload.py
-setuptools/tests/test_upload_docs.py
 setuptools/tests/test_virtualenv.py
 setuptools/tests/test_wheel.py
 setuptools/tests/test_windows_wrappers.py
index 88970d7..6a6b228 100644 (file)
@@ -27,7 +27,6 @@ wheel
 pip>=19.1
 jaraco.envs>=2.2
 pytest-xdist
-sphinx>=4.3.2
 jaraco.path>=3.2.0
 build[virtualenv]
 filelock>=3.4.0
diff --git a/setuptools/_vendor/nspektr-0.3.0.dist-info/top_level.txt b/setuptools/_vendor/nspektr-0.3.0.dist-info/top_level.txt
new file mode 100644 (file)
index 0000000..b10ef50
--- /dev/null
@@ -0,0 +1 @@
+nspektr
diff --git a/setuptools/_vendor/nspektr/__init__.py b/setuptools/_vendor/nspektr/__init__.py
new file mode 100644 (file)
index 0000000..938bbdb
--- /dev/null
@@ -0,0 +1,145 @@
+import itertools
+import functools
+import contextlib
+
+from setuptools.extern.packaging.requirements import Requirement
+from setuptools.extern.packaging.version import Version
+from setuptools.extern.more_itertools import always_iterable
+from setuptools.extern.jaraco.context import suppress
+from setuptools.extern.jaraco.functools import apply
+
+from ._compat import metadata, repair_extras
+
+
+def resolve(req: Requirement) -> metadata.Distribution:
+    """
+    Resolve the requirement to its distribution.
+
+    Ignore exception detail for Python 3.9 compatibility.
+
+    >>> resolve(Requirement('pytest<3'))  # doctest: +IGNORE_EXCEPTION_DETAIL
+    Traceback (most recent call last):
+    ...
+    importlib.metadata.PackageNotFoundError: No package metadata was found for pytest<3
+    """
+    dist = metadata.distribution(req.name)
+    if not req.specifier.contains(Version(dist.version), prereleases=True):
+        raise metadata.PackageNotFoundError(str(req))
+    dist.extras = req.extras  # type: ignore
+    return dist
+
+
+@apply(bool)
+@suppress(metadata.PackageNotFoundError)
+def is_satisfied(req: Requirement):
+    return resolve(req)
+
+
+unsatisfied = functools.partial(itertools.filterfalse, is_satisfied)
+
+
+class NullMarker:
+    @classmethod
+    def wrap(cls, req: Requirement):
+        return req.marker or cls()
+
+    def evaluate(self, *args, **kwargs):
+        return True
+
+
+def find_direct_dependencies(dist, extras=None):
+    """
+    Find direct, declared dependencies for dist.
+    """
+    simple = (
+        req
+        for req in map(Requirement, always_iterable(dist.requires))
+        if NullMarker.wrap(req).evaluate(dict(extra=None))
+    )
+    extra_deps = (
+        req
+        for req in map(Requirement, always_iterable(dist.requires))
+        for extra in always_iterable(getattr(dist, 'extras', extras))
+        if NullMarker.wrap(req).evaluate(dict(extra=extra))
+    )
+    return itertools.chain(simple, extra_deps)
+
+
+def traverse(items, visit):
+    """
+    Given an iterable of items, traverse the items.
+
+    For each item, visit is called to return any additional items
+    to include in the traversal.
+    """
+    while True:
+        try:
+            item = next(items)
+        except StopIteration:
+            return
+        yield item
+        items = itertools.chain(items, visit(item))
+
+
+def find_req_dependencies(req):
+    with contextlib.suppress(metadata.PackageNotFoundError):
+        dist = resolve(req)
+        yield from find_direct_dependencies(dist)
+
+
+def find_dependencies(dist, extras=None):
+    """
+    Find all reachable dependencies for dist.
+
+    dist is an importlib.metadata.Distribution (or similar).
+    TODO: create a suitable protocol for type hint.
+
+    >>> deps = find_dependencies(resolve(Requirement('nspektr')))
+    >>> all(isinstance(dep, Requirement) for dep in deps)
+    True
+    >>> not any('pytest' in str(dep) for dep in deps)
+    True
+    >>> test_deps = find_dependencies(resolve(Requirement('nspektr[testing]')))
+    >>> any('pytest' in str(dep) for dep in test_deps)
+    True
+    """
+
+    def visit(req, seen=set()):
+        if req in seen:
+            return ()
+        seen.add(req)
+        return find_req_dependencies(req)
+
+    return traverse(find_direct_dependencies(dist, extras), visit)
+
+
+class Unresolved(Exception):
+    def __iter__(self):
+        return iter(self.args[0])
+
+
+def missing(ep):
+    """
+    Generate the unresolved dependencies (if any) of ep.
+    """
+    return unsatisfied(find_dependencies(ep.dist, repair_extras(ep.extras)))
+
+
+def check(ep):
+    """
+    >>> ep, = metadata.entry_points(group='console_scripts', name='pip')
+    >>> check(ep)
+    >>> dist = metadata.distribution('nspektr')
+
+    Since 'docs' extras are not installed, requesting them should fail.
+
+    >>> ep = metadata.EntryPoint(
+    ...     group=None, name=None, value='nspektr [docs]')._for(dist)
+    >>> check(ep)
+    Traceback (most recent call last):
+    ...
+    nspektr.Unresolved: [...]
+    """
+    missed = list(missing(ep))
+    if missed:
+        raise Unresolved(missed)
diff --git a/setuptools/_vendor/nspektr/_compat.py b/setuptools/_vendor/nspektr/_compat.py
new file mode 100644 (file)
index 0000000..3278379
--- /dev/null
@@ -0,0 +1,21 @@
+import contextlib
+import sys
+
+
+if sys.version_info >= (3, 10):
+    import importlib.metadata as metadata
+else:
+    import setuptools.extern.importlib_metadata as metadata  # type: ignore # noqa: F401
+
+
+def repair_extras(extras):
+    """
+    Repair extras that appear as match objects.
+
+    python/importlib_metadata#369 revealed a flaw in the EntryPoint
+    implementation. This function wraps the extras to ensure
+    they are proper strings even on older implementations.
+    """
+    with contextlib.suppress(AttributeError):
+        return list(item.group(0) for item in extras)
+    return extras
index db24b40..4320b35 100644 (file)
@@ -5,6 +5,7 @@ more_itertools==8.8.0
 jaraco.text==3.7.0
 importlib_resources==5.4.0
 importlib_metadata==4.11.1
+nspektr==0.3.0
 # required for importlib_metadata on older Pythons
 typing_extensions==4.0.1
 # required for importlib_resources and _metadata on older Pythons
index 5b73e6e..107850a 100644 (file)
@@ -70,7 +70,7 @@ from ..extern.jaraco.text import yield_lines
 warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
 
 __all__ = [
-    'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
+    'easy_install', 'PthDistributions', 'extract_wininst_cfg',
     'get_exe_prefixes',
 ]
 
@@ -79,22 +79,6 @@ def is_64bit():
     return struct.calcsize("P") == 8
 
 
-def samefile(p1, p2):
-    """
-    Determine if two paths reference the same file.
-
-    Augments os.path.samefile to work on Windows and
-    suppresses errors if the path doesn't exist.
-    """
-    both_exist = os.path.exists(p1) and os.path.exists(p2)
-    use_samefile = hasattr(os.path, 'samefile') and both_exist
-    if use_samefile:
-        return os.path.samefile(p1, p2)
-    norm_p1 = os.path.normpath(os.path.normcase(p1))
-    norm_p2 = os.path.normpath(os.path.normcase(p2))
-    return norm_p1 == norm_p2
-
-
 def _to_bytes(s):
     return s.encode('utf8')
 
@@ -309,24 +293,9 @@ class easy_install(Command):
             self.script_dir = self.install_scripts
         # default --record from the install command
         self.set_undefined_options('install', ('record', 'record'))
-        # Should this be moved to the if statement below? It's not used
-        # elsewhere
-        normpath = map(normalize_path, sys.path)
         self.all_site_dirs = get_site_dirs()
-        if self.site_dirs is not None:
-            site_dirs = [
-                os.path.expanduser(s.strip()) for s in
-                self.site_dirs.split(',')
-            ]
-            for d in site_dirs:
-                if not os.path.isdir(d):
-                    log.warn("%s (in --site-dirs) does not exist", d)
-                elif normalize_path(d) not in normpath:
-                    raise DistutilsOptionError(
-                        d + " (in --site-dirs) is not on sys.path"
-                    )
-                else:
-                    self.all_site_dirs.append(normalize_path(d))
+        self.all_site_dirs.extend(self._process_site_dirs(self.site_dirs))
+
         if not self.editable:
             self.check_site_dir()
         self.index_url = self.index_url or "https://pypi.org/simple/"
@@ -355,15 +324,7 @@ class easy_install(Command):
         if not self.no_find_links:
             self.package_index.add_find_links(self.find_links)
         self.set_undefined_options('install_lib', ('optimize', 'optimize'))
-        if not isinstance(self.optimize, int):
-            try:
-                self.optimize = int(self.optimize)
-                if not (0 <= self.optimize <= 2):
-                    raise ValueError
-            except ValueError as e:
-                raise DistutilsOptionError(
-                    "--optimize must be 0, 1, or 2"
-                ) from e
+        self.optimize = self._validate_optimize(self.optimize)
 
         if self.editable and not self.build_directory:
             raise DistutilsArgError(
@@ -375,6 +336,39 @@ class easy_install(Command):
 
         self.outputs = []
 
+    @staticmethod
+    def _process_site_dirs(site_dirs):
+        if site_dirs is None:
+            return
+
+        normpath = map(normalize_path, sys.path)
+        site_dirs = [
+            os.path.expanduser(s.strip()) for s in
+            site_dirs.split(',')
+        ]
+        for d in site_dirs:
+            if not os.path.isdir(d):
+                log.warn("%s (in --site-dirs) does not exist", d)
+            elif normalize_path(d) not in normpath:
+                raise DistutilsOptionError(
+                    d + " (in --site-dirs) is not on sys.path"
+                )
+            else:
+                yield normalize_path(d)
+
+    @staticmethod
+    def _validate_optimize(value):
+        try:
+            value = int(value)
+            if value not in range(3):
+                raise ValueError
+        except ValueError as e:
+            raise DistutilsOptionError(
+                "--optimize must be 0, 1, or 2"
+            ) from e
+
+        return value
+
     def _fix_install_dir_for_user_site(self):
         """
         Fix the install_dir if "--user" was used.
@@ -928,7 +922,9 @@ class easy_install(Command):
             ensure_directory(destination)
 
         dist = self.egg_distribution(egg_path)
-        if not samefile(egg_path, destination):
+        if not (
+            os.path.exists(destination) and os.path.samefile(egg_path, destination)
+        ):
             if os.path.isdir(destination) and not os.path.islink(destination):
                 dir_util.remove_tree(destination, dry_run=self.dry_run)
             elif os.path.exists(destination):
@@ -1659,14 +1655,14 @@ class PthDistributions(Environment):
         if new_path:
             self.paths.append(dist.location)
             self.dirty = True
-        Environment.add(self, dist)
+        super().add(dist)
 
     def remove(self, dist):
         """Remove `dist` from the distribution map"""
         while dist.location in self.paths:
             self.paths.remove(dist.location)
             self.dirty = True
-        Environment.remove(self, dist)
+        super().remove(dist)
 
     def make_relative(self, path):
         npath, last = os.path.split(normalize_path(path))
index f429f56..a548000 100644 (file)
@@ -17,8 +17,10 @@ import itertools
 import functools
 import http.client
 import urllib.parse
+import warnings
 
 from .._importlib import metadata
+from .. import SetuptoolsDeprecationWarning
 
 from .upload import upload
 
@@ -89,6 +91,12 @@ class upload_docs(upload):
             zip_file.close()
 
     def run(self):
+        warnings.warn(
+            "upload_docs is deprecated and will be removed in a future "
+            "version. Use tools like httpie or curl instead.",
+            SetuptoolsDeprecationWarning,
+        )
+
         # Run sub commands
         for cmd_name in self.get_sub_commands():
             self.run_command(cmd_name)
index e825785..b55996f 100644 (file)
@@ -28,7 +28,8 @@ from distutils.util import rfc822_escape
 
 from setuptools.extern import packaging
 from setuptools.extern import ordered_set
-from setuptools.extern.more_itertools import unique_everseen, always_iterable
+from setuptools.extern.more_itertools import unique_everseen
+from setuptools.extern import nspektr
 
 from ._importlib import metadata
 
@@ -40,7 +41,7 @@ from setuptools import windows_support
 from setuptools.monkey import get_unpatched
 from setuptools.config import parse_configuration
 import pkg_resources
-from setuptools.extern.packaging import version, requirements
+from setuptools.extern.packaging import version
 from . import _reqs
 from . import _entry_points
 
@@ -876,25 +877,10 @@ class Distribution(_Distribution):
         Given an entry point, ensure that any declared extras for
         its distribution are installed.
         """
-        reqs = {
-            req
-            for req in map(requirements.Requirement, always_iterable(ep.dist.requires))
-            for extra in ep.extras
-            if extra in req.extras
-        }
-        missing = itertools.filterfalse(self._is_installed, reqs)
-        for req in missing:
+        for req in nspektr.missing(ep):
             # fetch_build_egg expects pkg_resources.Requirement
             self.fetch_build_egg(pkg_resources.Requirement(str(req)))
 
-    def _is_installed(self, req):
-        try:
-            dist = metadata.distribution(req.name)
-        except metadata.PackageNotFoundError:
-            return False
-        found_ver = packaging.version.Version(dist.version())
-        return found_ver in req.specifier
-
     def get_egg_cache_dir(self):
         egg_cache_dir = os.path.join(os.curdir, '.eggs')
         if not os.path.exists(egg_cache_dir):
index 98235a4..7907faf 100644 (file)
@@ -71,6 +71,6 @@ class VendorImporter:
 
 names = (
     'packaging', 'pyparsing', 'ordered_set', 'more_itertools', 'importlib_metadata',
-    'zipp', 'importlib_resources', 'jaraco', 'typing_extensions',
+    'zipp', 'importlib_resources', 'jaraco', 'typing_extensions', 'nspektr',
 )
 VendorImporter(__name__, names, 'setuptools._vendor').install()
index 051e523..14881d2 100644 (file)
@@ -680,8 +680,7 @@ class PackageIndex(Environment):
             # Make sure the file has been downloaded to the temp dir.
             if os.path.dirname(filename) != tmpdir:
                 dst = os.path.join(tmpdir, basename)
-                from setuptools.command.easy_install import samefile
-                if not samefile(filename, dst):
+                if not (os.path.exists(dst) and os.path.samefile(filename, dst)):
                     shutil.copy2(filename, dst)
                     filename = dst
 
index a0c0ec6..bcf2960 100644 (file)
@@ -18,6 +18,19 @@ class VirtualEnv(jaraco.envs.VirtualEnv):
     def run(self, cmd, *args, **kwargs):
         cmd = [self.exe(cmd[0])] + cmd[1:]
         kwargs = {"cwd": self.root, **kwargs}  # Allow overriding
+        # In some environments (eg. downstream distro packaging), where:
+        # - tox isn't used to run tests and
+        # - PYTHONPATH is set to point to a specific setuptools codebase and
+        # - no custom env is explicitly set by a test
+        # PYTHONPATH will leak into the spawned processes.
+        # In that case tests look for module in the wrong place (on PYTHONPATH).
+        # Unless the test sets its own special env, pass a copy of the existing
+        # environment with removed PYTHONPATH to the subprocesses.
+        if "env" not in kwargs:
+            env = dict(os.environ)
+            if "PYTHONPATH" in env:
+                del env["PYTHONPATH"]
+            kwargs["env"] = env
         return subprocess.check_output(cmd, *args, **kwargs)
 
 
index 7599e65..25ab49f 100644 (file)
@@ -1,6 +1,8 @@
+import os
 import contextlib
 import sys
 import subprocess
+from pathlib import Path
 
 import pytest
 import path
@@ -64,6 +66,9 @@ def sample_project(tmp_path):
 
 @pytest.fixture(scope="session")
 def setuptools_sdist(tmp_path_factory, request):
+    if os.getenv("PRE_BUILT_SETUPTOOLS_SDIST"):
+        return Path(os.getenv("PRE_BUILT_SETUPTOOLS_SDIST")).resolve()
+
     with contexts.session_locked_tmp_dir(
             request, tmp_path_factory, "sdist_build") as tmp:
         dist = next(tmp.glob("*.tar.gz"), None)
@@ -79,6 +84,9 @@ def setuptools_sdist(tmp_path_factory, request):
 
 @pytest.fixture(scope="session")
 def setuptools_wheel(tmp_path_factory, request):
+    if os.getenv("PRE_BUILT_SETUPTOOLS_WHEEL"):
+        return Path(os.getenv("PRE_BUILT_SETUPTOOLS_WHEEL")).resolve()
+
     with contexts.session_locked_tmp_dir(
             request, tmp_path_factory, "wheel_build") as tmp:
         dist = next(tmp.glob("*.whl"), None)
@@ -98,7 +106,18 @@ def venv(tmp_path, setuptools_wheel):
     env = environment.VirtualEnv()
     env.root = path.Path(tmp_path / 'venv')
     env.req = str(setuptools_wheel)
-    return env.create()
+    # In some environments (eg. downstream distro packaging),
+    # where tox isn't used to run tests and PYTHONPATH is set to point to
+    # a specific setuptools codebase, PYTHONPATH will leak into the spawned
+    # processes.
+    # env.create() should install the just created setuptools
+    # wheel, but it doesn't if it finds another existing matching setuptools
+    # installation present on PYTHONPATH:
+    # `setuptools is already installed with the same version as the provided
+    # wheel. Use --force-reinstall to force an installation of the wheel.`
+    # This prevents leaking PYTHONPATH to the created environment.
+    with contexts.environment(PYTHONPATH=None):
+        return env.create()
 
 
 @pytest.fixture
index 9270aa7..c4cdda0 100644 (file)
@@ -1,4 +1,5 @@
 import os
+import sys
 import shutil
 import signal
 import tarfile
@@ -14,6 +15,14 @@ from .textwrap import DALS
 
 
 TIMEOUT = int(os.getenv("TIMEOUT_BACKEND_TEST", "180"))  # in seconds
+IS_PYPY = '__pypy__' in sys.builtin_module_names
+
+
+pytestmark = pytest.mark.skipif(
+    sys.platform == "win32" and IS_PYPY,
+    reason="The combination of PyPy + Windows + pytest-xdist + ProcessPoolExecutor "
+    "is flaky and problematic"
+)
 
 
 class BuildBackendBase:
@@ -44,6 +53,10 @@ class BuildBackend(BuildBackendBase):
                 self.pool.shutdown(wait=False)  # doesn't stop already running processes
                 self._kill(pid)
                 pytest.xfail(f"Backend did not respond before timeout ({TIMEOUT} s)")
+            except (futures.process.BrokenProcessPool, MemoryError, OSError):
+                if IS_PYPY:
+                    pytest.xfail("PyPy frequently fails tests with ProcessPoolExector")
+                raise
 
         return method
 
diff --git a/setuptools/tests/test_sphinx_upload_docs.py b/setuptools/tests/test_sphinx_upload_docs.py
deleted file mode 100644 (file)
index f24077f..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-import pytest
-
-from jaraco import path
-
-from setuptools.command.upload_docs import upload_docs
-from setuptools.dist import Distribution
-
-
-@pytest.fixture
-def sphinx_doc_sample_project(tmpdir_cwd):
-    path.build({
-        'setup.py': 'from setuptools import setup; setup()',
-        'build': {
-            'docs': {
-                'conf.py': 'project="test"',
-                'index.rst': ".. toctree::\
-                    :maxdepth: 2\
-                    :caption: Contents:",
-            },
-        },
-    })
-
-
-@pytest.mark.usefixtures('sphinx_doc_sample_project')
-class TestSphinxUploadDocs:
-    def test_sphinx_doc(self):
-        params = dict(
-            packages=['test'],
-        )
-        dist = Distribution(params)
-
-        cmd = upload_docs(dist)
-
-        cmd.initialize_options()
-        assert cmd.upload_dir is None
-        assert cmd.has_sphinx() is True
-        cmd.finalize_options()
diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py
deleted file mode 100644 (file)
index 68977a5..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-import os
-import zipfile
-import contextlib
-
-import pytest
-from jaraco import path
-
-from setuptools.command.upload_docs import upload_docs
-from setuptools.dist import Distribution
-
-from .textwrap import DALS
-from . import contexts
-
-
-@pytest.fixture
-def sample_project(tmpdir_cwd):
-    path.build({
-        'setup.py': DALS("""
-            from setuptools import setup
-
-            setup()
-            """),
-        'build': {
-            'index.html': 'Hello world.',
-            'empty': {},
-        }
-    })
-
-
-@pytest.mark.usefixtures('sample_project')
-@pytest.mark.usefixtures('user_override')
-class TestUploadDocsTest:
-    def test_create_zipfile(self):
-        """
-        Ensure zipfile creation handles common cases, including a folder
-        containing an empty folder.
-        """
-
-        dist = Distribution()
-
-        cmd = upload_docs(dist)
-        cmd.target_dir = cmd.upload_dir = 'build'
-        with contexts.tempdir() as tmp_dir:
-            tmp_file = os.path.join(tmp_dir, 'foo.zip')
-            zip_file = cmd.create_zipfile(tmp_file)
-
-            assert zipfile.is_zipfile(tmp_file)
-
-            with contextlib.closing(zipfile.ZipFile(tmp_file)) as zip_file:
-                assert zip_file.namelist() == ['index.html']
-
-    def test_build_multipart(self):
-        data = dict(
-            a="foo",
-            b="bar",
-            file=('file.txt', b'content'),
-        )
-        body, content_type = upload_docs._build_multipart(data)
-        assert 'form-data' in content_type
-        assert "b'" not in content_type
-        assert 'b"' not in content_type
-        assert isinstance(body, bytes)
-        assert b'foo' in body
-        assert b'content' in body
index 0ba8964..6535854 100644 (file)
@@ -1,7 +1,8 @@
 import os
 import sys
-import itertools
 import subprocess
+from urllib.request import urlopen
+from urllib.error import URLError
 
 import pathlib
 
@@ -31,56 +32,39 @@ def test_clean_env_install(venv_without_setuptools, setuptools_wheel):
     venv_without_setuptools.run(cmd)
 
 
-def _get_pip_versions():
-    # This fixture will attempt to detect if tests are being run without
-    # network connectivity and if so skip some tests
-
-    network = True
+def access_pypi():
+    # Detect if tests are being run without connectivity
     if not os.environ.get('NETWORK_REQUIRED', False):  # pragma: nocover
         try:
-            from urllib.request import urlopen
-            from urllib.error import URLError
-        except ImportError:
-            from urllib2 import urlopen, URLError  # Python 2.7 compat
-
-        try:
             urlopen('https://pypi.org', timeout=1)
         except URLError:
             # No network, disable most of these tests
-            network = False
+            return False
 
-    def mark(param, *marks):
-        if not isinstance(param, type(pytest.param(''))):
-            param = pytest.param(param)
-        return param._replace(marks=param.marks + marks)
+    return True
 
-    def skip_network(param):
-        return param if network else mark(param, pytest.mark.skip(reason="no network"))
 
-    network_versions = [
-        mark('pip<20', pytest.mark.xfail(reason='pypa/pip#6599')),
+@pytest.mark.skipif(
+    'platform.python_implementation() == "PyPy"',
+    reason="https://github.com/pypa/setuptools/pull/2865#issuecomment-965834995",
+)
+@pytest.mark.skipif(not access_pypi(), reason="no network")
+# ^-- Even when it is not necessary to install a different version of `pip`
+#     the build process will still try to download `wheel`, see #3147 and #2986.
+@pytest.mark.parametrize(
+    'pip_version',
+    [
+        None,
+        pytest.param('pip<20', marks=pytest.mark.xfail(reason='pypa/pip#6599')),
         'pip<20.1',
         'pip<21',
         'pip<22',
-        mark(
+        pytest.param(
             'https://github.com/pypa/pip/archive/main.zip',
-            pytest.mark.xfail(reason='#2975'),
+            marks=pytest.mark.xfail(reason='#2975'),
         ),
     ]
-
-    versions = itertools.chain(
-        [None],
-        map(skip_network, network_versions)
-    )
-
-    return list(versions)
-
-
-@pytest.mark.skipif(
-    'platform.python_implementation() == "PyPy"',
-    reason="https://github.com/pypa/setuptools/pull/2865#issuecomment-965834995",
 )
-@pytest.mark.parametrize('pip_version', _get_pip_versions())
 def test_pip_upgrade_from_source(pip_version, venv_without_setuptools,
                                  setuptools_wheel, setuptools_sdist):
     """
index e4f6554..5a4df5d 100644 (file)
@@ -42,6 +42,7 @@ def update_changelog():
     cmd = [
         sys.executable, '-m',
         'towncrier',
+        'build',
         '--version', get_version(),
         '--yes',
     ]
index 8a122ad..cd15adb 100644 (file)
@@ -89,6 +89,16 @@ def rewrite_more_itertools(pkg_files: Path):
     more_file.write_text(text)
 
 
+def rewrite_nspektr(pkg_files: Path, new_root):
+    for file in pkg_files.glob('*.py'):
+        text = file.read_text()
+        text = re.sub(r' (more_itertools)', rf' {new_root}.\1', text)
+        text = re.sub(r' (jaraco\.\w+)', rf' {new_root}.\1', text)
+        text = re.sub(r' (packaging)', rf' {new_root}.\1', text)
+        text = re.sub(r' (importlib_metadata)', rf' {new_root}.\1', text)
+        file.write_text(text)
+
+
 def clean(vendor):
     """
     Remove all files out of the vendor directory except the meta
@@ -133,6 +143,7 @@ def update_setuptools():
     rewrite_importlib_resources(vendor / 'importlib_resources', 'setuptools.extern')
     rewrite_importlib_metadata(vendor / 'importlib_metadata', 'setuptools.extern')
     rewrite_more_itertools(vendor / "more_itertools")
+    rewrite_nspektr(vendor / "nspektr", 'setuptools.extern')
 
 
 __name__ == '__main__' and update_vendored()
diff --git a/tox.ini b/tox.ini
index 6b587e2..a56ea24 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -14,6 +14,8 @@ usedevelop = True
 extras = testing
 passenv =
        SETUPTOOLS_USE_DISTUTILS
+       PRE_BUILT_SETUPTOOLS_WHEEL
+       PRE_BUILT_SETUPTOOLS_SDIST
        TIMEOUT_BACKEND_TEST  # timeout (in seconds) for test_build_meta
        windir  # required for test_pkg_resources
        # honor git config in pytest-perf