Imported Upstream version 60.6.0 upstream/60.6.0
authorJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:02:44 +0000 (17:02 +0900)
committerJinWang An <jinwang.an@samsung.com>
Mon, 27 Mar 2023 08:02:44 +0000 (17:02 +0900)
51 files changed:
.bumpversion.cfg
.codecov.yml
.github/ISSUE_TEMPLATE/bug-report.yml
.github/ISSUE_TEMPLATE/config.yml
.github/workflows/main.yml
CHANGES.rst
README.rst
changelog.d/2897.docs.rst [new file with mode: 0644]
changelog.d/3034.docs.rst [new file with mode: 0644]
changelog.d/3056.docs.rst [new file with mode: 0644]
docs/build_meta.rst
docs/conf.py
docs/deprecated/distutils-legacy.rst
docs/deprecated/functionalities.rst
docs/development/developer-guide.rst
docs/setuptools.rst
docs/userguide/declarative_config.rst
docs/userguide/dependency_management.rst
docs/userguide/distribution.rst
docs/userguide/quickstart.rst
pkg_resources/__init__.py
setup.cfg
setuptools/__init__.py
setuptools/_distutils/_msvccompiler.py
setuptools/_distutils/bcppcompiler.py
setuptools/_distutils/command/bdist_msi.py
setuptools/_distutils/command/check.py
setuptools/_distutils/command/install.py
setuptools/_distutils/cygwinccompiler.py
setuptools/_distutils/msvc9compiler.py
setuptools/_distutils/msvccompiler.py
setuptools/_distutils/spawn.py
setuptools/_distutils/tests/test_config.py
setuptools/_distutils/tests/test_install.py
setuptools/_distutils/tests/test_util.py
setuptools/_distutils/util.py
setuptools/command/easy_install.py
setuptools/dist.py
setuptools/extension.py
setuptools/logging.py
setuptools/package_index.py
setuptools/tests/requirements.txt [deleted file]
setuptools/tests/test_bdist_egg.py
setuptools/tests/test_build_py.py
setuptools/tests/test_develop.py
setuptools/tests/test_dist.py
setuptools/tests/test_easy_install.py
setuptools/tests/test_logging.py [new file with mode: 0644]
setuptools/tests/test_sphinx_upload_docs.py
setuptools/tests/test_test.py
setuptools/tests/test_upload_docs.py

index 6534cde93c237711217c0b0f8b6d5e679bd22f83..baf5b08857b3409774e6ca3b744dd58457948cee 100644 (file)
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 60.5.4
+current_version = 60.6.0
 commit = True
 tag = True
 
index 7510dfc65cdaee7737c905b9925889a8d663c74a..51b248baddb578c5b629075895bc5fbdef1e2e04 100644 (file)
@@ -2,4 +2,5 @@ comment: false
 coverage:
   status:
     project:
-      threshold: 0.5%
+      default:
+        threshold: 0.5%
index 73911ec8a5b77373a84d554df9eec01132548f11..672acd18857b3f85cf53c4bbd8d37b6f2fa61699 100644 (file)
@@ -115,15 +115,4 @@ body:
   validations:
     required: true
 
-
-- type: checkboxes
-  attributes:
-    label: Code of Conduct
-    description: |
-      Read the [PSF Code of Conduct][CoC] first.
-
-      [CoC]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
-    options:
-    - label: I agree to follow the PSF Code of Conduct
-      required: true
 ...
index dde102ca1160b95e20d5bb2643e189ada2fbf260..ebc2d3399e6a485c5163db47d87372facbed04a3 100644 (file)
@@ -1,5 +1,3 @@
-# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
-blank_issues_enabled: false  # default: true
 contact_links:
 - name: ðŸ¤” Have questions or need support?
   url: https://github.com/pypa/setuptools/discussions
@@ -9,7 +7,6 @@ contact_links:
   about: |
     Please ask typical Q&A here: general ideas for Python packaging,
     questions about structuring projects and so on
-- name: >-
-    ðŸ’¬ IRC: #pypa @ Freenode
-  url: https://webchat.freenode.net/#pypa
+- name: ðŸ’¬ Discord (chat)
+  url: https://discord.com/invite/pypa
   about: Chat with devs
index 6ae4a264be838b1f77d020223b8e28bb76f25200..821cf883502a52e3971b56c0833566324ff95e04 100644 (file)
@@ -42,7 +42,6 @@ jobs:
             ${{ matrix.python }}
 
   test_cygwin:
-    if: ${{ false }}  # failing #3016
     strategy:
       matrix:
         distutils:
@@ -53,13 +52,24 @@ jobs:
       SETUPTOOLS_USE_DISTUTILS: ${{ matrix.distutils }}
     steps:
       - uses: actions/checkout@v2
-      - name: Install Cygwin with Python and tox
+      - name: Install Cygwin with Python
+        uses: cygwin/cygwin-install-action@v1
+        with:
+          platform: x86_64
+          packages: >-
+            git,
+            gcc-core,
+            python38,
+            python38-devel,
+            python38-pip
+      - name: Install tox
+        shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -leo pipefail -o igncr {0}
         run: |
-          choco install git gcc-core python38-devel python38-pip --source cygwin
-          C:\\tools\\cygwin\\bin\\bash -l -x -c 'python3.8 -m pip install tox'
+          python3.8 -m pip install tox
       - name: Run tests
+        shell: C:\cygwin\bin\env.exe CYGWIN_NOWINPATH=1 CHERE_INVOKING=1 C:\cygwin\bin\bash.exe -leo pipefail -o igncr {0}
         run: |
-          C:\\tools\\cygwin\\bin\\bash -l -x -c 'cd $(cygpath -u "$GITHUB_WORKSPACE") && tox -- --cov-report xml'
+          tox -- --cov-report xml
 
   integration-test:
     strategy:
index e9f8a70f87c4d2f6b0c0ceda69a8225b370fd788..a77f5395b05da87e1e44948953de79e0a35258f8 100644 (file)
@@ -1,10 +1,24 @@
+v60.6.0
+-------
+
+
+Changes
+^^^^^^^
+* #3043: Merge with pypa/distutils@bb018f1ac3 including consolidated behavior in sysconfig.get_platform (pypa/distutils#104).
+* #3057: Don't include optional ``Home-page`` in metadata if no ``url`` is specified. -- by :user:`cdce8p`
+* #3062: Merge with pypa/distutils@b53a824ec3 including improved support for lib directories on non-x64 Windows builds.
+
+Misc
+^^^^
+* #3054: Used Py3 syntax ``super().__init__()`` -- by :user:`imba-tjd`
+
+
 v60.5.4
 -------
 
 
 Misc
 ^^^^
-* #NNN: 
 * #3009: Remove filtering of distutils warnings.
 * #3031: Suppress distutils replacement when building or testing CPython.
 
index 7ea2b70ebec82614d10b32c038ea1e8f2ec41ce8..fe2e749eb0fad55249a393ba9dfba71f9ea66263 100644 (file)
@@ -40,8 +40,8 @@ See the `Installation Instructions
 User's Guide for instructions on installing, upgrading, and uninstalling
 Setuptools.
 
-Questions and comments should be directed to the `distutils-sig
-mailing list <http://mail.python.org/pipermail/distutils-sig/>`_.
+Questions and comments should be directed to `GitHub Discussions
+<https://github.com/pypa/setuptools/discussions>`_.
 Bug reports and especially tested patches may be
 submitted directly to the `bug tracker
 <https://github.com/pypa/setuptools/issues>`_.
@@ -51,7 +51,7 @@ Code of Conduct
 ===============
 
 Everyone interacting in the setuptools project's codebases, issue trackers,
-chat rooms, and mailing lists is expected to follow the
+chat rooms, and fora is expected to follow the
 `PSF Code of Conduct <https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md>`_.
 
 
diff --git a/changelog.d/2897.docs.rst b/changelog.d/2897.docs.rst
new file mode 100644 (file)
index 0000000..763a39b
--- /dev/null
@@ -0,0 +1,4 @@
+Added documentation about wrapping ``setuptools.build_meta`` in a in-tree
+custom backend. This is a :pep:`517`-compliant way of dynamically specifying
+build dependencies (e.g. when platform, OS and other markers are not enough)
+-- by :user:`abravalheri`.
diff --git a/changelog.d/3034.docs.rst b/changelog.d/3034.docs.rst
new file mode 100644 (file)
index 0000000..6106e0f
--- /dev/null
@@ -0,0 +1,4 @@
+Replaced occurrences of the defunct distutils-sig mailing list with pointers
+to GitHub Discussions.
+-- by :user:`ashemedai`
+
diff --git a/changelog.d/3056.docs.rst b/changelog.d/3056.docs.rst
new file mode 100644 (file)
index 0000000..c3de4e9
--- /dev/null
@@ -0,0 +1,2 @@
+The documentation has stopped suggesting to add ``wheel`` to
+:pep:`517` requirements -- by :user:`webknjaz`
index 27df70a2fefc3bd1c9436e2af098cad2734604d8..1337bddbfb932dbf72e8ce8a0d74b818039f4ede 100644 (file)
@@ -9,29 +9,29 @@ Python packaging has come `a long way <https://bernat.tech/posts/pep-517-518/>`_
 
 The traditional ``setuptools`` way of packaging Python modules
 uses a ``setup()`` function within the ``setup.py`` script. Commands such as
-``python setup.py bdist`` or ``python setup.py bdist_wheel`` generate a 
-distribution bundle and ``python setup.py install`` installs the distribution. 
-This interface makes it difficult to choose other packaging tools without an 
+``python setup.py bdist`` or ``python setup.py bdist_wheel`` generate a
+distribution bundle and ``python setup.py install`` installs the distribution.
+This interface makes it difficult to choose other packaging tools without an
 overhaul. Because ``setup.py`` scripts allowed for arbitrary execution, it
 proved difficult to provide a reliable user experience across environments
 and history.
 
 `PEP 517 <https://www.python.org/dev/peps/pep-0517/>`_ therefore came to
-rescue and specified a new standard to 
+rescue and specified a new standard to
 package and distribute Python modules. Under PEP 517:
 
     a ``pyproject.toml`` file is used to specify what program to use
-    for generating distribution. 
+    for generating distribution.
 
-    Then, two functions provided by the program, ``build_wheel(directory: str)`` 
-    and ``build_sdist(directory: str)`` create the distribution bundle at the 
-    specified ``directory``. The program is free to use its own configuration 
-    script or extend the ``.toml`` file. 
+    Then, two functions provided by the program, ``build_wheel(directory: str)``
+    and ``build_sdist(directory: str)`` create the distribution bundle at the
+    specified ``directory``. The program is free to use its own configuration
+    script or extend the ``.toml`` file.
 
     Lastly, ``pip install *.whl`` or ``pip install *.tar.gz`` does the actual
     installation. If ``*.whl`` is available, ``pip`` will go ahead and copy
     the files into ``site-packages`` directory. If not, ``pip`` will look at
-    ``pyproject.toml`` and decide what program to use to 'build from source' 
+    ``pyproject.toml`` and decide what program to use to 'build from source'
     (the default is ``setuptools``)
 
 With this standard, switching between packaging tools becomes a lot easier. ``build_meta``
@@ -48,17 +48,18 @@ scripts, a ``pyproject.toml`` file and a ``setup.cfg`` file::
         setup.cfg
         meowpkg/__init__.py
 
-The pyproject.toml file is required to specify the build system (i.e. what is 
-being used to package your scripts and install from source). To use it with 
+The pyproject.toml file is required to specify the build system (i.e. what is
+being used to package your scripts and install from source). To use it with
 setuptools, the content would be::
 
     [build-system]
-    requires = ["setuptools", "wheel"]
+    requires = ["setuptools"]
     build-backend = "setuptools.build_meta"
 
 The ``setuptools`` package implements the ``build_sdist``
 command and the ``wheel`` package implements the ``build_wheel``
-command; both are required to be compliant with PEP 517.
+command; the latter is a dependency of the former
+exposed via :pep:`517` hooks.
 
 Use ``setuptools``' :ref:`declarative config <declarative config>` to
 specify the package information::
@@ -67,7 +68,7 @@ specify the package information::
     name = meowpkg
     version = 0.0.1
     description = a package that meows
-    
+
     [options]
     packages = find:
 
@@ -77,7 +78,7 @@ Now generate the distribution. To build the package, use
     $ pip install -q build
     $ python -m build
 
-And now it's done! The ``.whl`` file  and ``.tar.gz`` can then be distributed 
+And now it's done! The ``.whl`` file  and ``.tar.gz`` can then be distributed
 and installed::
 
     dist/
@@ -89,3 +90,85 @@ and installed::
 or::
 
     $ pip install dist/meowpkg-0.0.1.tar.gz
+
+Dynamic build dependencies and other ``build_meta`` tweaks
+----------------------------------------------------------
+
+With the changes introduced by :pep:`517` and :pep:`518`, the
+``setup_requires`` configuration field was made deprecated in ``setup.cfg`` and
+``setup.py``, in favour of directly listing build dependencies in the
+``requires`` field of the ``build-system`` table of ``pyproject.toml``.
+This approach has a series of advantages and gives package managers and
+installers the ability to inspect in advance the build requirements and
+perform a series of optimisations.
+
+However some package authors might still need to dynamically inspect the final
+users machine before deciding these requirements. One way of doing that, as
+specified by :pep:`517`, is to "tweak" ``setuptools.build_meta`` by using a
+:pep:`in-tree backend <517#in-tree-build-backends>`.
+
+.. tip:: Before implementing a *in-tree* backend, have a look on
+   :pep:`PEP 508 <508#environment-markers>`. Most of the times, dependencies
+   with **environment markers** are enough to differentiate operating systems
+   and platforms.
+
+If you add the following configuration to your ``pyprojec.toml``:
+
+
+.. code-block:: toml
+
+    [build-system]
+    requires = ["setuptools", "wheel"]
+    build-backend = "backend"
+    backend-path = ["_custom_build"]
+
+
+then you should be able to implement a thin wrapper around ``build_meta`` in
+the ``_custom_build/backend.py`` file, as shown in the following example:
+
+.. code-block:: python
+
+    from setuptools import build_meta as _orig
+
+    prepare_metadata_for_build_wheel = _orig.prepare_metadata_for_build_wheel
+    build_wheel = _orig.build_wheel
+    build_sdist = _orig.build_sdist
+
+
+    def get_requires_for_build_wheel(self, config_settings=None):
+        return _orig.get_requires_for_build_wheel(config_settings) + [...]
+
+
+    def get_requires_for_build_sdist(self, config_settings=None):
+        return _orig.get_requires_for_build_sdist(config_settings) + [...]
+
+
+Note that you can override any of the functions specified in :pep:`PEP 517
+<517#build-backend-interface>`, not only the ones responsible for gathering
+requirements.
+
+.. important:: Make sure your backend script is included in the :doc:`source
+   distribution </userguide/distribution>`, otherwise the build will fail.
+   This can be done by using a SCM_/VCS_ plugin (like :pypi:`setuptools-scm`
+   and :pypi:`setuptools-svn`), or by correctly setting up :ref:`MANIFEST.in
+   <manifest>`.
+
+   If this is the first time you are using a customised backend, please have a
+   look on the generated ``.tar.gz`` and ``.whl``.
+   On POSIX systems that can be done with ``tar -tf dist/*.tar.gz``
+   and ``unzip -l dist/*.whl``.
+   On Windows systems you can rename the ``.whl`` to ``.zip`` to be able to
+   inspect it on the file explorer, and use the same ``tar`` command in a
+   command prompt (alternativelly there are GUI programs like `7-zip`_ that
+   handle ``.tar.gz``).
+
+   In general the backend script should be present in the ``.tar.gz`` (so the
+   project can be build from the source) but not in the ``.whl`` (otherwise the
+   backend script would end up being distributed alongside your package).
+   See ":doc:`/userguide/package_discovery`" for more details about package
+   files.
+
+
+.. _SCM: https://en.wikipedia.org/wiki/Software_configuration_management
+.. _VCS: https://en.wikipedia.org/wiki/Version_control
+.. _7-zip: https://www.7-zip.org
index 1fb2771612400b027a0498760d36b11109f70899..bfd45a69d255139addb0375e54b25360ed5fe850 100644 (file)
@@ -96,6 +96,7 @@ github_url = 'https://github.com'
 github_sponsors_url = f'{github_url}/sponsors'
 extlinks = {
     'user': (f'{github_sponsors_url}/%s', '@'),  # noqa: WPS323
+    'pypi': ('https://pypi.org/project/%s', '%s'),
 }
 extensions += ['sphinx.ext.extlinks']
 
index 94104fe8b3937345f2dc8e53082b1b517d66d601..148dc25932c82c3112680ed2a886799d681546f5 100644 (file)
@@ -5,7 +5,7 @@ Setuptools and the PyPA have a `stated goal <https://github.com/pypa/packaging-p
 
 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:
 
-       SETUPTOOLS_USE_DISTUTILS=local
+    SETUPTOOLS_USE_DISTUTILS=local
 
 This behavior is planned to become the default.
 
index c6ea83b387d17cb92739d7e9b4c2442eb2945179..7213c5d676203d06eea9f51efa69f807d5bf5816 100644 (file)
@@ -30,4 +30,4 @@ invoked via symlinks.  They *must* be invoked using their original filename, in
 order to ensure that, once running, ``pkg_resources`` will know what project
 and version is in use.  The header script will check this and exit with an
 error if the ``.egg`` file has been renamed or is invoked via a symlink that
-changes its base name.
\ No newline at end of file
+changes its base name.
index f29c1a80e480a8f6fd6310eb6faf04575b3857ac..d2cf15928b913e921441568858f149c5f6b683b3 100644 (file)
@@ -25,12 +25,12 @@ Setuptools is maintained primarily in GitHub at `this home
 Python Packaging Authority (PyPA) with several core contributors. All bugs
 for Setuptools are filed and the canonical source is maintained in GitHub.
 
-User support and discussions are done through the issue tracker (for specific)
-issues, through the `distutils-sig mailing list <https://mail.python.org/mailman3/lists/distutils-sig.python.org/>`_, or on IRC (Freenode) at
-#pypa.
+User support and discussions are done through
+`GitHub Discussions <https://github.com/pypa/setuptools/discussions>`_,
+or the issue tracker (for specific issues).
 
-Discussions about development happen on the distutils-sig mailing list or on
-`Gitter <https://gitter.im/pypa/setuptools>`_.
+Discussions about development happen on GitHub Discussions or
+the ``setuptools`` channel on `PyPA Discord <https://discord.com/invite/pypa>`_.
 
 -----------------
 Authoring Tickets
@@ -125,12 +125,9 @@ cannot declare dependencies other than through
 ``setuptools/_vendor/vendored.txt`` and
 ``pkg_resources/_vendor/vendored.txt``.
 
-All the dependencies specified in these files are "vendorized" using Paver_, a
-simple Python-based project scripting and task running tool.
+All the dependencies specified in these files are "vendorized" using a
+simple Python script ``tools/vendor.py``.
 
-To refresh the dependencies, you can run the following command (defined in
-``pavement.py``)::
+To refresh the dependencies, run the following command::
 
-    $ paver update_vendored
-
-.. _Paver: https://pythonhosted.org/Paver/
+    $ tox -e vendor
index c5a89adc2fbfa0c63375408d8ab01b898c0d7377..d0fb9a9cec9161e0abd0380fa1ffec49c7743b34 100644 (file)
@@ -201,13 +201,13 @@ As a consequence, the resulting dictionary will include no such options.
 
 
 
-Mailing List and Bug Tracker
-============================
+Forum and Bug Tracker
+=====================
 
-Please use the `distutils-sig mailing list`_ for questions and discussion about
+Please use `GitHub Discussions`_ for questions and discussion about
 setuptools, and the `setuptools bug tracker`_ ONLY for issues you have
-confirmed via the list are actual bugs, and which you have reduced to a minimal
+confirmed via the forum are actual bugs, and which you have reduced to a minimal
 set of steps to reproduce.
 
-.. _distutils-sig mailing list: http://mail.python.org/pipermail/distutils-sig/
+.. _GitHub Discussions: https://github.com/pypa/setuptools/discussions
 .. _setuptools bug tracker: https://github.com/pypa/setuptools/
index d1c25df1973b0455bafab0b0beffc2999a42bc9a..6f41d92b9ac39c075a57070fb26228159c91c581 100644 (file)
@@ -222,10 +222,10 @@ data_files               section                              40.6.0          [#
 
 .. [#opt-1] In the ``package_data`` section, a key named with a single asterisk
    (``*``) refers to all packages, in lieu of the empty string used in ``setup.py``.
+
 .. [#opt-2] In the ``extras_require`` section, values are parsed as ``list-semi``.
    This implies that in order to include markers, they **must** be *dangling*:
+
    .. code-block:: ini
 
       [options.extras_require]
index 9c29dbd5e20f74d4b1832df308a32ac497b57e52..ea2fc5563dbf88157430801871b61261d7b0ebef 100644 (file)
@@ -28,7 +28,7 @@ other two types of dependency keyword, this one is specified in your
 .. code-block:: ini
 
     [build-system]
-    requires = ["setuptools", "wheel"]
+    requires = ["setuptools"]
     #...
 
 .. note::
index 2872dacd6b97b9f36d552493bdbfb5402eed1d3c..db0f1a5f596ff1a97fc80b9711d36932b0986acc 100644 (file)
@@ -162,7 +162,7 @@ Specifying Your Project's Version
 ---------------------------------
 
 Setuptools can work well with most versioning schemes. Over the years,
-setuptools has tried to closely follow the 
+setuptools has tried to closely follow the
 `PEP 440 <https://www.python.org/dev/peps/pep-0440/>`_ scheme, but it
 also supports legacy versions. There are, however, a
 few special things to watch out for, in order to ensure that setuptools and
index 4c62c6dff2beddac624f859a5b8855e8ed0e164f..203d620473e58c098d377792cabce3e59bf7ea1b 100644 (file)
@@ -32,7 +32,7 @@ package your project:
 .. code-block:: toml
 
     [build-system]
-    requires = ["setuptools", "wheel"]
+    requires = ["setuptools"]
     build-backend = "setuptools.build_meta"
 
 Then, you will need a ``setup.cfg`` or ``setup.py`` to specify your package
@@ -96,7 +96,7 @@ to specify to properly package your project.
 Automatic package discovery
 ===========================
 For simple projects, it's usually easy enough to manually add packages to
-the ``packages`` keyword in ``setup.cfg``.  However, for very large projects, 
+the ``packages`` keyword in ``setup.cfg``.  However, for very large projects,
 it can be a big burden to keep the package list updated. ``setuptools``
 therefore provides two convenient tools to ease the burden: :literal:`find:\ ` and
 :literal:`find_namespace:\ `. To use it in your project:
@@ -189,9 +189,9 @@ Development mode
 
 .. tip::
 
-       Prior to :ref:`pip v21.1 <pip:v21-1>`, a ``setup.py`` script was
-       required to be compatible with development mode. With late
-       versions of pip, any project may be installed in this mode.
+    Prior to :ref:`pip v21.1 <pip:v21-1>`, a ``setup.py`` script was
+    required to be compatible with development mode. With late
+    versions of pip, any project may be installed in this mode.
 
 ``setuptools`` allows you to install a package without copying any files
 to your interpreter directory (e.g. the ``site-packages`` directory).
index 9cc6b0a4d99c41183ef3cced09e5c9ea09dafec3..93db52d213f64eb95a7a77584e25148b697bfe4a 100644 (file)
@@ -1581,7 +1581,7 @@ class EggProvider(NullProvider):
     """Provider based on a virtual filesystem"""
 
     def __init__(self, module):
-        NullProvider.__init__(self, module)
+        super().__init__(module)
         self._setup_prefix()
 
     def _setup_prefix(self):
@@ -1701,7 +1701,7 @@ class ZipProvider(EggProvider):
     _zip_manifests = MemoizedZipManifests()
 
     def __init__(self, module):
-        EggProvider.__init__(self, module)
+        super().__init__(module)
         self.zip_pre = self.loader.archive + os.sep
 
     def _zipinfo_name(self, fspath):
@@ -2404,7 +2404,20 @@ def _nonblank(str):
 
 @functools.singledispatch
 def yield_lines(iterable):
-    """Yield valid lines of a string or iterable"""
+    r"""
+    Yield valid lines of a string or iterable.
+
+    >>> list(yield_lines(''))
+    []
+    >>> list(yield_lines(['foo', 'bar']))
+    ['foo', 'bar']
+    >>> list(yield_lines('foo\nbar'))
+    ['foo', 'bar']
+    >>> list(yield_lines('\nfoo\n#bar\nbaz #comment'))
+    ['foo', 'baz #comment']
+    >>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n']))
+    ['foo', 'bar', 'baz', 'bing']
+    """
     return itertools.chain.from_iterable(map(yield_lines, iterable))
 
 
@@ -3079,26 +3092,61 @@ def issue_warning(*args, **kw):
     warnings.warn(stacklevel=level + 1, *args, **kw)
 
 
-def parse_requirements(strs):
-    """Yield ``Requirement`` objects for each specification in `strs`
+def drop_comment(line):
+    """
+    Drop comments.
 
-    `strs` must be a string, or a (possibly-nested) iterable thereof.
+    >>> drop_comment('foo # bar')
+    'foo'
+
+    A hash without a space may be in a URL.
+
+    >>> drop_comment('http://example.com/foo#bar')
+    'http://example.com/foo#bar'
     """
-    # create a steppable iterator, so we can handle \-continuations
-    lines = iter(yield_lines(strs))
+    return line.partition(' #')[0]
+
+
+def join_continuation(lines):
+    r"""
+    Join lines continued by a trailing backslash.
 
-    for line in lines:
-        # Drop comments -- a hash without a space may be in a URL.
-        if ' #' in line:
-            line = line[:line.find(' #')]
-        # If there is a line continuation, drop it, and append the next line.
-        if line.endswith('\\'):
-            line = line[:-2].strip()
+    >>> list(join_continuation(['foo \\', 'bar', 'baz']))
+    ['foobar', 'baz']
+    >>> list(join_continuation(['foo \\', 'bar', 'baz']))
+    ['foobar', 'baz']
+    >>> list(join_continuation(['foo \\', 'bar \\', 'baz']))
+    ['foobarbaz']
+
+    Not sure why, but...
+    The character preceeding the backslash is also elided.
+
+    >>> list(join_continuation(['goo\\', 'dly']))
+    ['godly']
+
+    A terrible idea, but...
+    If no line is available to continue, suppress the lines.
+
+    >>> list(join_continuation(['foo', 'bar\\', 'baz\\']))
+    ['foo']
+    """
+    lines = iter(lines)
+    for item in lines:
+        while item.endswith('\\'):
             try:
-                line += next(lines)
+                item = item[:-2].strip() + next(lines)
             except StopIteration:
                 return
-        yield Requirement(line)
+        yield item
+
+
+def parse_requirements(strs):
+    """
+    Yield ``Requirement`` objects for each specification in `strs`.
+
+    `strs` must be a string, or a (possibly-nested) iterable thereof.
+    """
+    return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs))))
 
 
 class RequirementParseError(packaging.requirements.InvalidRequirement):
index b83d3763ebc382bd057452b0ff102b08fa3ed934..957c7ad21b414b665bd51fce7be63b5b83fa86c2 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = setuptools
-version = 60.5.4
+version = 60.6.0
 author = Python Packaging Authority
 author_email = distutils-sig@python.org
 description = Easily download, build, install, upgrade, and uninstall Python packages
@@ -65,6 +65,7 @@ testing =
        jaraco.path>=3.2.0
        build[virtualenv]
        filelock>=3.4.0
+       pip_run>=8.8
 
 testing-integration =
        pytest
index 43d1c96eb28b97580f7d4878c680a852e929ba7a..06991b65d7548dc592c4e87030691af610ea849f 100644 (file)
@@ -132,7 +132,7 @@ def _install_setup_requires(attrs):
         def __init__(self, attrs):
             _incl = 'dependency_links', 'setup_requires'
             filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}
-            distutils.core.Distribution.__init__(self, filtered)
+            super().__init__(filtered)
 
         def finalize_options(self):
             """
@@ -171,7 +171,7 @@ class Command(_Command):
         Construct the command for dist, updating
         vars(self) with any keyword parameters.
         """
-        _Command.__init__(self, dist)
+        super().__init__(dist)
         vars(self).update(kw)
 
     def _ensure_stringlike(self, option, what, default=None):
index c41ea9ae30b6f3d753c3fb71d2766e7be263df0b..f2f801c552a4132a5067af5fb9eb0a8641debd47 100644 (file)
@@ -203,7 +203,7 @@ class MSVCCompiler(CCompiler) :
 
 
     def __init__(self, verbose=0, dry_run=0, force=0):
-        CCompiler.__init__ (self, verbose, dry_run, force)
+        super().__init__(verbose, dry_run, force)
         # target platform (.plat_name is consistent with 'bdist')
         self.plat_name = None
         self.initialized = False
index 071fea5d038cb0425a962a8b6bea55a9c158dd5d..2eb6d2e9568e0989914f9d1bcd9f085ac38a2936 100644 (file)
@@ -55,7 +55,7 @@ class BCPPCompiler(CCompiler) :
                   dry_run=0,
                   force=0):
 
-        CCompiler.__init__ (self, verbose, dry_run, force)
+        super().__init__(verbose, dry_run, force)
 
         # These executables are assumed to all be in the path.
         # Borland doesn't seem to use any special registry settings to
index 0863a1883e72058a8701a946c644276f047f837e..15259532415a5d1c4b18e5e09b775667b28f8b8e 100644 (file)
@@ -27,7 +27,7 @@ class PyDialog(Dialog):
     def __init__(self, *args, **kw):
         """Dialog(database, name, x, y, w, h, attributes, title, first,
         default, cancel, bitmap=true)"""
-        Dialog.__init__(self, *args)
+        super().__init__(*args)
         ruler = self.h - 36
         bmwidth = 152*ruler/328
         #if kw.get("bitmap", True):
index ada250064678ee3ddfbc29244a45ca64d527659c..525540b6cc4ab61c377068f4aeb3c0a448aabba7 100644 (file)
@@ -17,7 +17,7 @@ try:
         def __init__(self, source, report_level, halt_level, stream=None,
                      debug=0, encoding='ascii', error_handler='replace'):
             self.messages = []
-            Reporter.__init__(self, source, report_level, halt_level, stream,
+            super().__init__(source, report_level, halt_level, stream,
                               debug, encoding, error_handler)
 
         def system_message(self, level, message, *children, **kwargs):
index 0587ccd01719cfd66f94e5c1dafa93a85d36be4c..41c17d8a7f4ad4c120a87e386c8797f41b6e30ad 100644 (file)
@@ -68,8 +68,8 @@ if HAS_USER_SITE:
     INSTALL_SCHEMES['nt_user'] = {
         'purelib': '{usersite}',
         'platlib': '{usersite}',
-        'headers': '{userbase}/{implementation}{py_version_nodot}/Include/{dist_name}',
-        'scripts': '{userbase}/{implementation}{py_version_nodot}/Scripts',
+        'headers': '{userbase}/{implementation}{py_version_nodot_plat}/Include/{dist_name}',
+        'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts',
         'data'   : '{userbase}',
         }
 
@@ -412,12 +412,18 @@ class install(Command):
             'implementation': _get_implementation(),
         }
 
+        # vars for compatibility on older Pythons
+        compat_vars = dict(
+            # Python 3.9 and earlier
+            py_version_nodot_plat=getattr(sys, 'winver', '').replace('.', ''),
+        )
+
         if HAS_USER_SITE:
             local_vars['userbase'] = self.install_userbase
             local_vars['usersite'] = self.install_usersite
 
         self.config_vars = _collections.DictStack(
-            [sysconfig.get_config_vars(), local_vars])
+            [compat_vars, sysconfig.get_config_vars(), local_vars])
 
         self.expand_basedirs()
 
index fd082f6d2789737ec0c96bdde7a033e2c1a3b9ab..c5c86d8f074c02bc4323adcfd7fce960fd3e90ee 100644 (file)
@@ -108,7 +108,7 @@ class CygwinCCompiler(UnixCCompiler):
 
     def __init__(self, verbose=0, dry_run=0, force=0):
 
-        UnixCCompiler.__init__(self, verbose, dry_run, force)
+        super().__init__(verbose, dry_run, force)
 
         status, details = check_config_h()
         self.debug_print("Python's GCC status: %s (details: %s)" %
@@ -268,7 +268,7 @@ class Mingw32CCompiler(CygwinCCompiler):
 
     def __init__(self, verbose=0, dry_run=0, force=0):
 
-        CygwinCCompiler.__init__ (self, verbose, dry_run, force)
+        super().__init__ (verbose, dry_run, force)
 
         shared_option = "-shared"
 
index 14d137752d22df0876c2d07085e29df3f8981322..6b6273836e41a8704e9142e5d700ed517b136e15 100644 (file)
@@ -324,7 +324,7 @@ class MSVCCompiler(CCompiler) :
     exe_extension = '.exe'
 
     def __init__(self, verbose=0, dry_run=0, force=0):
-        CCompiler.__init__ (self, verbose, dry_run, force)
+        super().__init__(verbose, dry_run, force)
         self.__version = VERSION
         self.__root = r"Software\Microsoft\VisualStudio"
         # self.__macros = MACROS
index 2d447b857d34c8e8ed1e16b48e7d1d6a7e5c293a..e1367b891856b8a4dc4d4e00f46a77d1be6d7aff 100644 (file)
@@ -228,7 +228,7 @@ class MSVCCompiler(CCompiler) :
     exe_extension = '.exe'
 
     def __init__(self, verbose=0, dry_run=0, force=0):
-        CCompiler.__init__ (self, verbose, dry_run, force)
+        super().__init__(verbose, dry_run, force)
         self.__version = get_build_version()
         self.__arch = get_build_architecture()
         if self.__arch == "Intel":
index 6e1c89f1f235b29809bfacb6df2cf00f2215a47f..b2d10e39d32854b390db40760b52bb05e89028e7 100644 (file)
@@ -10,7 +10,7 @@ import sys
 import os
 import subprocess
 
-from distutils.errors import DistutilsPlatformError, DistutilsExecError
+from distutils.errors import DistutilsExecError
 from distutils.debug import DEBUG
 from distutils import log
 
index 8ab70efb161cbd87e0bd95291f2c78e3b2a19a16..27bd9d4435c2378cbd2b358740aed1cbb6e1c99b 100644 (file)
@@ -66,7 +66,7 @@ class BasePyPIRCCommandTestCase(support.TempdirManager,
 
         class command(PyPIRCCommand):
             def __init__(self, dist):
-                PyPIRCCommand.__init__(self, dist)
+                super().__init__(dist)
             def initialize_options(self):
                 pass
             finalize_options = initialize_options
index 75770b05e60deefd4bd6db2c8b24fe8ea58ee33a..5dbc06b0902d53beae6f179b2aa4ad2744241aac 100644 (file)
@@ -81,7 +81,9 @@ class InstallTestCase(support.TempdirManager,
         install_module.USER_SITE = self.user_site
 
         def _expanduser(path):
-            return self.tmpdir
+            if path.startswith('~'):
+                return os.path.normpath(self.tmpdir + path[1:])
+            return path
         self.old_expand = os.path.expanduser
         os.path.expanduser = _expanduser
 
@@ -122,6 +124,17 @@ class InstallTestCase(support.TempdirManager,
         self.assertIn('userbase', cmd.config_vars)
         self.assertIn('usersite', cmd.config_vars)
 
+        actual_headers = os.path.relpath(cmd.install_headers, self.user_base)
+        if os.name == 'nt':
+            site_path = os.path.relpath(
+                os.path.dirname(self.old_user_site), self.old_user_base)
+            include = os.path.join(site_path, 'Include')
+        else:
+            include = sysconfig.get_python_inc(0, '')
+        expect_headers = os.path.join(include, 'xx')
+
+        self.assertEqual(os.path.normcase(actual_headers), os.path.normcase(expect_headers))
+
     def test_handle_extra_path(self):
         dist = Distribution({'name': 'xx', 'extra_path': 'path,dirs'})
         cmd = install(dist)
index 12469e3de3f7da21b18abe8299698a5f9230ab7d..2738388ea029b2988df6d5ed2537e19e92ccfb17 100644 (file)
@@ -2,6 +2,7 @@
 import os
 import sys
 import unittest
+import sysconfig as stdlib_sysconfig
 from copy import copy
 from test.support import run_unittest
 from unittest import mock
@@ -10,12 +11,10 @@ from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError
 from distutils.util import (get_platform, convert_path, change_root,
                             check_environ, split_quoted, strtobool,
                             rfc822_escape, byte_compile,
-                            grok_environment_error)
+                            grok_environment_error, get_host_platform)
 from distutils import util # used to patch _environ_checked
-from distutils.sysconfig import get_config_vars
 from distutils import sysconfig
 from distutils.tests import support
-import _osx_support
 
 class UtilTestCase(support.EnvironGuard, unittest.TestCase):
 
@@ -63,110 +62,26 @@ class UtilTestCase(support.EnvironGuard, unittest.TestCase):
     def _get_uname(self):
         return self._uname
 
-    def test_get_platform(self):
-
-        # windows XP, 32bits
-        os.name = 'nt'
-        sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) '
-                       '[MSC v.1310 32 bit (Intel)]')
-        sys.platform = 'win32'
-        self.assertEqual(get_platform(), 'win32')
-
-        # windows XP, amd64
-        os.name = 'nt'
-        sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) '
-                       '[MSC v.1310 32 bit (Amd64)]')
-        sys.platform = 'win32'
-        self.assertEqual(get_platform(), 'win-amd64')
-
-        # macbook
-        os.name = 'posix'
-        sys.version = ('2.5 (r25:51918, Sep 19 2006, 08:49:13) '
-                       '\n[GCC 4.0.1 (Apple Computer, Inc. build 5341)]')
-        sys.platform = 'darwin'
-        self._set_uname(('Darwin', 'macziade', '8.11.1',
-                   ('Darwin Kernel Version 8.11.1: '
-                    'Wed Oct 10 18:23:28 PDT 2007; '
-                    'root:xnu-792.25.20~1/RELEASE_I386'), 'i386'))
-        _osx_support._remove_original_values(get_config_vars())
-        get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
-
-        get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g '
-                                       '-fwrapv -O3 -Wall -Wstrict-prototypes')
-
-        cursize = sys.maxsize
-        sys.maxsize = (2 ** 31)-1
-        try:
-            self.assertEqual(get_platform(), 'macosx-10.3-i386')
-        finally:
-            sys.maxsize = cursize
-
-        # macbook with fat binaries (fat, universal or fat64)
-        _osx_support._remove_original_values(get_config_vars())
-        get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.4'
-        get_config_vars()['CFLAGS'] = ('-arch ppc -arch i386 -isysroot '
-                                       '/Developer/SDKs/MacOSX10.4u.sdk  '
-                                       '-fno-strict-aliasing -fno-common '
-                                       '-dynamic -DNDEBUG -g -O3')
-
-        self.assertEqual(get_platform(), 'macosx-10.4-fat')
-
-        _osx_support._remove_original_values(get_config_vars())
-        os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.1'
-        self.assertEqual(get_platform(), 'macosx-10.4-fat')
-
+    def test_get_host_platform(self):
+        with unittest.mock.patch('os.name', 'nt'):
+             with unittest.mock.patch('sys.version', '... [... (ARM64)]'):
+                self.assertEqual(get_host_platform(), 'win-arm64')
+             with unittest.mock.patch('sys.version', '... [... (ARM)]'):
+                self.assertEqual(get_host_platform(), 'win-arm32')
 
-        _osx_support._remove_original_values(get_config_vars())
-        get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch i386 -isysroot '
-                                       '/Developer/SDKs/MacOSX10.4u.sdk  '
-                                       '-fno-strict-aliasing -fno-common '
-                                       '-dynamic -DNDEBUG -g -O3')
+        with unittest.mock.patch('sys.version_info', (3, 9, 0, 'final', 0)):
+            self.assertEqual(get_host_platform(), stdlib_sysconfig.get_platform())
 
-        self.assertEqual(get_platform(), 'macosx-10.4-intel')
-
-        _osx_support._remove_original_values(get_config_vars())
-        get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc -arch i386 -isysroot '
-                                       '/Developer/SDKs/MacOSX10.4u.sdk  '
-                                       '-fno-strict-aliasing -fno-common '
-                                       '-dynamic -DNDEBUG -g -O3')
-        self.assertEqual(get_platform(), 'macosx-10.4-fat3')
-
-        _osx_support._remove_original_values(get_config_vars())
-        get_config_vars()['CFLAGS'] = ('-arch ppc64 -arch x86_64 -arch ppc -arch i386 -isysroot '
-                                       '/Developer/SDKs/MacOSX10.4u.sdk  '
-                                       '-fno-strict-aliasing -fno-common '
-                                       '-dynamic -DNDEBUG -g -O3')
-        self.assertEqual(get_platform(), 'macosx-10.4-universal')
-
-        _osx_support._remove_original_values(get_config_vars())
-        get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc64 -isysroot '
-                                       '/Developer/SDKs/MacOSX10.4u.sdk  '
-                                       '-fno-strict-aliasing -fno-common '
-                                       '-dynamic -DNDEBUG -g -O3')
-
-        self.assertEqual(get_platform(), 'macosx-10.4-fat64')
-
-        for arch in ('ppc', 'i386', 'x86_64', 'ppc64'):
-            _osx_support._remove_original_values(get_config_vars())
-            get_config_vars()['CFLAGS'] = ('-arch %s -isysroot '
-                                           '/Developer/SDKs/MacOSX10.4u.sdk  '
-                                           '-fno-strict-aliasing -fno-common '
-                                           '-dynamic -DNDEBUG -g -O3'%(arch,))
-
-            self.assertEqual(get_platform(), 'macosx-10.4-%s'%(arch,))
-
-
-        # linux debian sarge
-        os.name = 'posix'
-        sys.version = ('2.3.5 (#1, Jul  4 2007, 17:28:59) '
-                       '\n[GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)]')
-        sys.platform = 'linux2'
-        self._set_uname(('Linux', 'aglae', '2.6.21.1dedibox-r7',
-                    '#1 Mon Apr 30 17:25:38 CEST 2007', 'i686'))
-
-        self.assertEqual(get_platform(), 'linux-i686')
-
-        # XXX more platforms to tests here
+    def test_get_platform(self):
+        with unittest.mock.patch('os.name', 'nt'):
+            with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x86'}):
+                 self.assertEqual(get_platform(), 'win32')
+            with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'x64'}):
+                 self.assertEqual(get_platform(), 'win-amd64')
+            with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm'}):
+                 self.assertEqual(get_platform(), 'win-arm32')
+            with unittest.mock.patch.dict('os.environ', {'VSCMD_ARG_TGT_ARCH': 'arm64'}):
+                 self.assertEqual(get_platform(), 'win-arm64')
 
     def test_convert_path(self):
         # linux/mac
index ac6d446d681a0231892493fba10dfda450d6d71e..6d506d7e31cbc34994a1660644d0ee93170e1164 100644 (file)
@@ -9,6 +9,7 @@ import re
 import importlib.util
 import string
 import sys
+import sysconfig
 from distutils.errors import DistutilsPlatformError
 from distutils.dep_util import newer
 from distutils.spawn import spawn
@@ -20,82 +21,29 @@ from .py35compat import _optim_args_from_interpreter_flags
 def get_host_platform():
     """Return a string that identifies the current platform.  This is used mainly to
     distinguish platform-specific build directories and platform-specific built
-    distributions.  Typically includes the OS name and version and the
-    architecture (as supplied by 'os.uname()'), although the exact information
-    included depends on the OS; eg. on Linux, the kernel version isn't
-    particularly important.
-
-    Examples of returned values:
-       linux-i586
-       linux-alpha (?)
-       solaris-2.6-sun4u
-
-    Windows will return one of:
-       win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
-       win32 (all others - specifically, sys.platform is returned)
-
-    For other non-POSIX platforms, currently just returns 'sys.platform'.
-
+    distributions.
     """
-    if os.name == 'nt':
-        if 'amd64' in sys.version.lower():
-            return 'win-amd64'
-        if '(arm)' in sys.version.lower():
-            return 'win-arm32'
-        if '(arm64)' in sys.version.lower():
-            return 'win-arm64'
-        return sys.platform
-
-    # Set for cross builds explicitly
-    if "_PYTHON_HOST_PLATFORM" in os.environ:
-        return os.environ["_PYTHON_HOST_PLATFORM"]
-
-    if os.name != "posix" or not hasattr(os, 'uname'):
-        # XXX what about the architecture? NT is Intel or Alpha,
-        # Mac OS is M68k or PPC, etc.
-        return sys.platform
-
-    # Try to distinguish various flavours of Unix
-
-    (osname, host, release, version, machine) = os.uname()
-
-    # Convert the OS name to lowercase, remove '/' characters, and translate
-    # spaces (for "Power Macintosh")
-    osname = osname.lower().replace('/', '')
-    machine = machine.replace(' ', '_')
-    machine = machine.replace('/', '-')
-
-    if osname[:5] == "linux":
-        # At least on Linux/Intel, 'machine' is the processor --
-        # i386, etc.
-        # XXX what about Alpha, SPARC, etc?
-        return  "%s-%s" % (osname, machine)
-    elif osname[:5] == "sunos":
-        if release[0] >= "5":           # SunOS 5 == Solaris 2
-            osname = "solaris"
-            release = "%d.%s" % (int(release[0]) - 3, release[2:])
-            # We can't use "platform.architecture()[0]" because a
-            # bootstrap problem. We use a dict to get an error
-            # if some suspicious happens.
-            bitness = {2147483647:"32bit", 9223372036854775807:"64bit"}
-            machine += ".%s" % bitness[sys.maxsize]
-        # fall through to standard osname-release-machine representation
-    elif osname[:3] == "aix":
-        from .py38compat import aix_platform
-        return aix_platform(osname, version, release)
-    elif osname[:6] == "cygwin":
-        osname = "cygwin"
-        rel_re = re.compile (r'[\d.]+', re.ASCII)
-        m = rel_re.match(release)
-        if m:
-            release = m.group()
-    elif osname[:6] == "darwin":
-        import _osx_support, distutils.sysconfig
-        osname, release, machine = _osx_support.get_platform_osx(
-                                        distutils.sysconfig.get_config_vars(),
-                                        osname, release, machine)
-
-    return "%s-%s-%s" % (osname, release, machine)
+
+    # We initially exposed platforms as defined in Python 3.9
+    # even with older Python versions when distutils was split out.
+    # Now that we delegate to stdlib sysconfig we need to restore this
+    # in case anyone has started to depend on it.
+
+    if sys.version_info < (3, 8):
+        if os.name == 'nt':
+            if '(arm)' in sys.version.lower():
+                return 'win-arm32'
+            if '(arm64)' in sys.version.lower():
+                return 'win-arm64'
+
+    if sys.version_info < (3, 9):
+        if os.name == "posix" and hasattr(os, 'uname'):
+            osname, host, release, version, machine = os.uname()
+            if osname[:3] == "aix":
+                from .py38compat import aix_platform
+                return aix_platform(osname, version, release)
+
+    return sysconfig.get_platform()
 
 def get_platform():
     if os.name == 'nt':
index 514719dedcd7abb267573250023252b379412a76..e25090b840505e2cb2e9ca85856f5fe3916d669e 100644 (file)
@@ -260,6 +260,12 @@ class easy_install(Command):
                 'implementation': install._get_implementation(),
             })
 
+        # pypa/distutils#113 Python 3.9 compat
+        self.config_vars.setdefault(
+            'py_version_nodot_plat',
+            getattr(sys, 'windir', '').replace('.', ''),
+        )
+
         if site.ENABLE_USER_SITE:
             self.config_vars['userbase'] = self.install_userbase
             self.config_vars['usersite'] = self.install_usersite
@@ -1577,7 +1583,7 @@ class PthDistributions(Environment):
         self.sitedirs = list(map(normalize_path, sitedirs))
         self.basedir = normalize_path(os.path.dirname(self.filename))
         self._load()
-        Environment.__init__(self, [], None, None)
+        super().__init__([], None, None)
         for path in yield_lines(self.paths):
             list(map(self.add, find_distributions(path, True)))
 
index 37a10d1dcd92e9ab8c1409173332f9b76d82c9ae..f4a56b0e42661bd6ffc383fbe3291f3ff4087763 100644 (file)
@@ -113,13 +113,9 @@ def read_pkg_file(self, file):
     self.author_email = _read_field_from_msg(msg, 'author-email')
     self.maintainer_email = None
     self.url = _read_field_from_msg(msg, 'home-page')
+    self.download_url = _read_field_from_msg(msg, 'download-url')
     self.license = _read_field_unescaped_from_msg(msg, 'license')
 
-    if 'download-url' in msg:
-        self.download_url = _read_field_from_msg(msg, 'download-url')
-    else:
-        self.download_url = None
-
     self.long_description = _read_field_unescaped_from_msg(msg, 'description')
     if (
         self.long_description is None and
@@ -171,9 +167,10 @@ def write_pkg_file(self, file):  # noqa: C901  # is too complex (14)  # FIXME
     write_field('Name', self.get_name())
     write_field('Version', self.get_version())
     write_field('Summary', single_line(self.get_description()))
-    write_field('Home-page', self.get_url())
 
     optional_fields = (
+        ('Home-page', 'url'),
+        ('Download-URL', 'download_url'),
         ('Author', 'author'),
         ('Author-email', 'author_email'),
         ('Maintainer', 'maintainer'),
@@ -187,8 +184,6 @@ def write_pkg_file(self, file):  # noqa: C901  # is too complex (14)  # FIXME
 
     license = rfc822_escape(self.get_license())
     write_field('License', license)
-    if self.download_url:
-        write_field('Download-URL', self.download_url)
     for project_url in self.project_urls.items():
         write_field('Project-URL', '%s, %s' % project_url)
 
@@ -472,6 +467,19 @@ class Distribution(_Distribution):
         )
         self._finalize_requires()
 
+    def _validate_metadata(self):
+        required = {"name"}
+        provided = {
+            key
+            for key in vars(self.metadata)
+            if getattr(self.metadata, key, None) is not None
+        }
+        missing = required - provided
+
+        if missing:
+            msg = f"Required package metadata is missing: {missing}"
+            raise DistutilsSetupError(msg)
+
     def _set_metadata_defaults(self, attrs):
         """
         Fill-in missing metadata fields not supported by distutils.
index 1820722a494b1744a406e364bc3dc3aefc7dd059..f696c9c1ac69f58deb43f64e9c3c29f3c7271559 100644 (file)
@@ -34,7 +34,7 @@ class Extension(_Extension):
         # The *args is needed for compatibility as calls may use positional
         # arguments. py_limited_api may be set only via keyword.
         self.py_limited_api = kw.pop("py_limited_api", False)
-        _Extension.__init__(self, name, sources, *args, **kw)
+        super().__init__(name, sources, *args, **kw)
 
     def _convert_pyx_sources_to_lang(self):
         """
index dbead6e664b3972cbfa7926e9c127bec425176d2..15b57613f6d3e9cbdd455fcea6e98729935b99c1 100644 (file)
@@ -24,6 +24,12 @@ def configure():
         format="{message}", style='{', handlers=handlers, level=logging.DEBUG)
     monkey.patch_func(set_threshold, distutils.log, 'set_threshold')
 
+    # For some reason `distutils.log` module is getting cached in `distutils.dist`
+    # and then loaded again when patched,
+    # implying: id(distutils.log) != id(distutils.dist.log).
+    # Make sure the same module object is used everywhere:
+    distutils.dist.log = distutils.log
+
 
 def set_threshold(level):
     logging.root.setLevel(level*10)
index 270e7f3c91bb1a4e21502d9450471d658eb57c49..051e523a570858f9160581387d4c467627e48a9b 100644 (file)
@@ -285,7 +285,7 @@ class PackageIndex(Environment):
             self, index_url="https://pypi.org/simple/", hosts=('*',),
             ca_bundle=None, verify_ssl=True, *args, **kw
     ):
-        Environment.__init__(self, *args, **kw)
+        super().__init__(*args, **kw)
         self.index_url = index_url + "/" [:not index_url.endswith('/')]
         self.scanned_urls = {}
         self.fetched_urls = {}
@@ -1002,7 +1002,7 @@ class PyPIConfig(configparser.RawConfigParser):
         Load from ~/.pypirc
         """
         defaults = dict.fromkeys(['username', 'password', 'repository'], '')
-        configparser.RawConfigParser.__init__(self, defaults)
+        super().__init__(defaults)
 
         rc = os.path.join(os.path.expanduser('~'), '.pypirc')
         if os.path.exists(rc):
diff --git a/setuptools/tests/requirements.txt b/setuptools/tests/requirements.txt
deleted file mode 100644 (file)
index b2d84a9..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-mock
-pytest-flake8
-flake8-2020; python_version>="3.6"
-virtualenv>=13.0.0
-pytest-virtualenv>=1.2.7
-pytest>=3.7
-wheel
-coverage>=4.5.1
-pytest-cov>=2.5.1
-paver; python_version>="3.6"
-futures; python_version=="2.7"
-pip>=19.1 # For proper file:// URLs support.
-jaraco.envs
-sphinx
index fb5b90b1a3846aa90ff9a11e5feb8b817faba5b8..67f788cca35c3f9674822bf32c483bd71c8bcae3 100644 (file)
@@ -13,7 +13,7 @@ from . import contexts
 SETUP_PY = """\
 from setuptools import setup
 
-setup(name='foo', py_modules=['hi'])
+setup(py_modules=['hi'])
 """
 
 
@@ -52,7 +52,6 @@ class Test:
         dist = Distribution(dict(
             script_name='setup.py',
             script_args=['bdist_egg', '--exclude-source-files'],
-            name='foo',
             py_modules=['hi'],
         ))
         with contexts.quiet():
index 78a31ac49e2e9c8c9d4c20a79c044548c82a305e..19c8b780b85c907b4c86c10b328292e260d93a40 100644 (file)
@@ -18,7 +18,6 @@ def test_directories_in_package_data_glob(tmpdir_cwd):
         script_name='setup.py',
         script_args=['build_py'],
         packages=[''],
-        name='foo',
         package_data={'': ['path/*']},
     ))
     os.makedirs('path/subpath')
@@ -40,7 +39,6 @@ def test_read_only(tmpdir_cwd):
         script_args=['build_py'],
         packages=['pkg'],
         package_data={'pkg': ['data.dat']},
-        name='pkg',
     ))
     os.makedirs('pkg')
     open('pkg/__init__.py', 'w').close()
@@ -70,7 +68,6 @@ def test_executable_data(tmpdir_cwd):
         script_args=['build_py'],
         packages=['pkg'],
         package_data={'pkg': ['run-me']},
-        name='pkg',
     ))
     os.makedirs('pkg')
     open('pkg/__init__.py', 'w').close()
index 1aeb7ffeaf7cbf1f57d6a0f6258a79b7e43fd0a1..c52072ac1e93f85d1eead839a9bb3f5427a85f93 100644 (file)
@@ -6,11 +6,11 @@ import sys
 import subprocess
 import platform
 import pathlib
-import textwrap
 
 from setuptools.command import test
 
 import pytest
+import pip_run.launch
 
 from setuptools.command.develop import develop
 from setuptools.dist import Distribution
@@ -166,21 +166,6 @@ class TestNamespaces:
         with test.test.paths_on_pythonpath([str(target)]):
             subprocess.check_call(pkg_resources_imp)
 
-    @staticmethod
-    def install_workaround(site_packages):
-        site_packages.mkdir(parents=True)
-        sc = site_packages / 'sitecustomize.py'
-        sc.write_text(
-            textwrap.dedent(
-                """
-            import site
-            import pathlib
-            here = pathlib.Path(__file__).parent
-            site.addsitedir(str(here))
-            """
-            ).lstrip()
-        )
-
     @pytest.mark.xfail(
         platform.python_implementation() == 'PyPy',
         reason="Workaround fails on PyPy (why?)",
@@ -190,7 +175,6 @@ class TestNamespaces:
         Editable install to a prefix should be discoverable.
         """
         prefix = tmp_path / 'prefix'
-        prefix.mkdir()
 
         # figure out where pip will likely install the package
         site_packages = prefix / next(
@@ -198,9 +182,10 @@ class TestNamespaces:
             for path in sys.path
             if 'site-packages' in path and path.startswith(sys.prefix)
         )
+        site_packages.mkdir(parents=True)
 
-        # install the workaround
-        self.install_workaround(site_packages)
+        # install workaround
+        pip_run.launch.inject_sitecustomize(str(site_packages))
 
         env = dict(os.environ, PYTHONPATH=str(site_packages))
         cmd = [
index c4279f0bc4f67f21b6954d2fa4507cb5a7b9bc02..4980f2c3cebeef7ff962da1e9f649fb721f2f966 100644 (file)
@@ -374,3 +374,8 @@ def test_check_specifier():
 )
 def test_rfc822_unescape(content, result):
     assert (result or content) == rfc822_unescape(rfc822_escape(content))
+
+
+def test_metadata_name():
+    with pytest.raises(DistutilsSetupError, match='missing.*name'):
+        Distribution()._validate_metadata()
index 83ce7f45293d11b31c567b4cd7d1b970d3d55415..5831b26757a4bab859d2b6a0930f4140193080a9 100644 (file)
@@ -64,7 +64,7 @@ class FakeDist:
 SETUP_PY = DALS("""
     from setuptools import setup
 
-    setup(name='foo')
+    setup()
     """)
 
 
diff --git a/setuptools/tests/test_logging.py b/setuptools/tests/test_logging.py
new file mode 100644 (file)
index 0000000..a5ddd56
--- /dev/null
@@ -0,0 +1,36 @@
+import logging
+
+import pytest
+
+
+setup_py = """\
+from setuptools import setup
+
+setup(
+    name="test_logging",
+    version="0.0"
+)
+"""
+
+
+@pytest.mark.parametrize(
+    "flag, expected_level", [("--dry-run", "INFO"), ("--verbose", "DEBUG")]
+)
+def test_verbosity_level(tmp_path, monkeypatch, flag, expected_level):
+    """Make sure the correct verbosity level is set (issue #3038)"""
+    import setuptools  # noqa: Import setuptools to monkeypatch distutils
+    import distutils  # <- load distutils after all the patches take place
+
+    logger = logging.Logger(__name__)
+    monkeypatch.setattr(logging, "root", logger)
+    unset_log_level = logger.getEffectiveLevel()
+    assert logging.getLevelName(unset_log_level) == "NOTSET"
+
+    setup_script = tmp_path / "setup.py"
+    setup_script.write_text(setup_py)
+    dist = distutils.core.run_setup(setup_script, stop_after="init")
+    dist.script_args = [flag, "sdist"]
+    dist.parse_command_line()  # <- where the log level is set
+    log_level = logger.getEffectiveLevel()
+    log_level_name = logging.getLevelName(log_level)
+    assert log_level_name == expected_level
index cc5b8293bf48bbc3e71399aae02d26470ae27f83..f24077fd06fa4ba9fd014bba22ba2592da4c8328 100644 (file)
@@ -25,7 +25,6 @@ def sphinx_doc_sample_project(tmpdir_cwd):
 class TestSphinxUploadDocs:
     def test_sphinx_doc(self):
         params = dict(
-            name='foo',
             packages=['test'],
         )
         dist = Distribution(params)
index 8b8d9e6c4e4eee6e9b9960ae45f09503a0b1e600..530474d7a234e9eccc96ca1cbc7e768e1c54a329 100644 (file)
@@ -10,7 +10,6 @@ from .textwrap import DALS
 @pytest.mark.usefixtures('tmpdir_cwd')
 def test_tests_are_run_once(capfd):
     params = dict(
-        name='foo',
         packages=['dummy'],
     )
     files = {
index 55978aadc70e9f255d7a84934c8e66aaba1f13ec..68977a5d2812f01acf060cb4f68ff55e45e670da 100644 (file)
@@ -18,7 +18,7 @@ def sample_project(tmpdir_cwd):
         'setup.py': DALS("""
             from setuptools import setup
 
-            setup(name='foo')
+            setup()
             """),
         'build': {
             'index.html': 'Hello world.',