summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2019-12-29 12:47:31 -0500
committerJason R. Coombs <jaraco@jaraco.com>2019-12-29 12:47:31 -0500
commita5b66a8581b758aff763765857359ef540631202 (patch)
tree9404571514fbe550c98a939bd4458d97e1bf1dd2
parentd53e024af2f5d8f3a4a36588c3dc004d156bc830 (diff)
parente6bdf25f6ab5bf4d32b0f9affa0ab98ea35f3a29 (diff)
downloadpython-setuptools-git-a5b66a8581b758aff763765857359ef540631202.tar.gz
Merge branch 'master' into feature/include-pyproject.toml
-rw-r--r--.bumpversion.cfg7
-rw-r--r--.travis.yml37
-rw-r--r--CHANGES.rst163
-rw-r--r--MANIFEST.in1
-rw-r--r--README.rst29
-rw-r--r--appveyor.yml3
-rw-r--r--changelog.d/1499.change.rst1
-rw-r--r--changelog.d/1544.change.rst1
-rw-r--r--changelog.d/1551.breaking.rst1
-rw-r--r--changelog.d/1625.change.rst1
-rw-r--r--changelog.d/1927.change.rst1
-rw-r--r--conftest.py1
-rw-r--r--docs/_templates/indexsidebar.html16
-rw-r--r--docs/conf.py2
-rw-r--r--docs/developer-guide.txt14
-rw-r--r--docs/development.txt2
-rw-r--r--docs/easy_install.txt4
-rw-r--r--docs/ez_setup.txt195
-rw-r--r--docs/formats.txt7
-rw-r--r--docs/pkg_resources.txt18
-rw-r--r--docs/requirements.txt2
-rw-r--r--docs/setuptools.txt329
-rw-r--r--launcher.c1
-rw-r--r--pkg_resources/__init__.py143
-rw-r--r--pkg_resources/api_tests.txt2
-rw-r--r--pkg_resources/tests/data/my-test-package-source/setup.cfg0
-rw-r--r--pkg_resources/tests/data/my-test-package-source/setup.py6
-rw-r--r--pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO10
-rw-r--r--pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt7
-rw-r--r--pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt1
-rw-r--r--pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt1
-rw-r--r--pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe1
-rw-r--r--pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.eggbin0 -> 843 bytes
-rw-r--r--pkg_resources/tests/test_find_distributions.py58
-rw-r--r--pkg_resources/tests/test_pkg_resources.py140
-rw-r--r--pkg_resources/tests/test_resources.py19
-rw-r--r--pyproject.toml3
-rw-r--r--pytest.ini7
-rw-r--r--setup.cfg48
-rwxr-xr-xsetup.py56
-rw-r--r--setuptools/__init__.py35
-rw-r--r--setuptools/_imp.py73
-rw-r--r--setuptools/_vendor/ordered_set.py488
-rw-r--r--setuptools/_vendor/packaging/__about__.py14
-rw-r--r--setuptools/_vendor/packaging/__init__.py20
-rw-r--r--setuptools/_vendor/packaging/_compat.py7
-rw-r--r--setuptools/_vendor/packaging/_structures.py4
-rw-r--r--setuptools/_vendor/packaging/markers.py91
-rw-r--r--setuptools/_vendor/packaging/requirements.py41
-rw-r--r--setuptools/_vendor/packaging/specifiers.py71
-rw-r--r--setuptools/_vendor/packaging/tags.py404
-rw-r--r--setuptools/_vendor/packaging/utils.py43
-rw-r--r--setuptools/_vendor/packaging/version.py149
-rw-r--r--setuptools/_vendor/vendored.txt3
-rw-r--r--setuptools/build_meta.py252
-rw-r--r--setuptools/command/__init__.py3
-rw-r--r--setuptools/command/bdist_egg.py2
-rw-r--r--setuptools/command/build_ext.py10
-rw-r--r--setuptools/command/easy_install.py17
-rw-r--r--setuptools/command/egg_info.py1
-rw-r--r--setuptools/command/install.py2
-rw-r--r--setuptools/command/install_lib.py6
-rw-r--r--setuptools/command/register.py22
-rw-r--r--setuptools/command/sdist.py62
-rw-r--r--setuptools/command/test.py13
-rw-r--r--setuptools/command/upload.py195
-rw-r--r--setuptools/config.py3
-rw-r--r--setuptools/depends.py48
-rw-r--r--setuptools/dist.py237
-rw-r--r--setuptools/errors.py16
-rw-r--r--setuptools/extern/__init__.py2
-rw-r--r--setuptools/glibc.py86
-rw-r--r--setuptools/installer.py150
-rw-r--r--setuptools/msvc.py1022
-rw-r--r--setuptools/package_index.py26
-rw-r--r--setuptools/pep425tags.py319
-rw-r--r--setuptools/py27compat.py32
-rw-r--r--setuptools/py31compat.py4
-rw-r--r--setuptools/py33compat.py6
-rw-r--r--setuptools/py34compat.py13
-rw-r--r--setuptools/py36compat.py82
-rw-r--r--setuptools/tests/environment.py2
-rw-r--r--setuptools/tests/files.py9
-rw-r--r--setuptools/tests/server.py33
-rw-r--r--setuptools/tests/test_bdist_egg.py2
-rw-r--r--setuptools/tests/test_build_clib.py9
-rw-r--r--setuptools/tests/test_build_ext.py70
-rw-r--r--setuptools/tests/test_build_meta.py406
-rw-r--r--setuptools/tests/test_build_py.py10
-rw-r--r--setuptools/tests/test_config.py115
-rw-r--r--setuptools/tests/test_depends.py18
-rw-r--r--setuptools/tests/test_dist.py82
-rw-r--r--setuptools/tests/test_easy_install.py279
-rw-r--r--setuptools/tests/test_egg_info.py299
-rw-r--r--setuptools/tests/test_find_packages.py11
-rw-r--r--setuptools/tests/test_glibc.py52
-rw-r--r--setuptools/tests/test_install_scripts.py6
-rw-r--r--setuptools/tests/test_integration.py31
-rw-r--r--setuptools/tests/test_manifest.py133
-rw-r--r--setuptools/tests/test_msvc.py5
-rw-r--r--setuptools/tests/test_namespaces.py1
-rw-r--r--setuptools/tests/test_packageindex.py19
-rw-r--r--setuptools/tests/test_pep425tags.py168
-rw-r--r--setuptools/tests/test_register.py43
-rw-r--r--setuptools/tests/test_sandbox.py3
-rw-r--r--setuptools/tests/test_setopt.py36
-rw-r--r--setuptools/tests/test_setuptools.py9
-rw-r--r--setuptools/tests/test_test.py61
-rw-r--r--setuptools/tests/test_upload.py211
-rw-r--r--setuptools/tests/test_virtualenv.py73
-rw-r--r--setuptools/tests/test_wheel.py39
-rw-r--r--setuptools/tests/test_windows_wrappers.py13
-rw-r--r--setuptools/wheel.py25
-rw-r--r--tests/manual_test.py100
-rw-r--r--tests/requirements.txt3
-rw-r--r--tools/tox_pip.py32
-rw-r--r--tox.ini37
117 files changed, 4958 insertions, 2899 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
new file mode 100644
index 00000000..8a9f4435
--- /dev/null
+++ b/.bumpversion.cfg
@@ -0,0 +1,7 @@
+[bumpversion]
+current_version = 42.0.2
+commit = True
+tag = True
+
+[bumpversion:file:setup.cfg]
+
diff --git a/.travis.yml b/.travis.yml
index d1febccb..b3a6556d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,4 @@
-dist: trusty
+dist: xenial
language: python
jobs:
@@ -14,36 +14,20 @@ jobs:
env: DISABLE_COVERAGE=1
- python: 3.4
- python: 3.5
- - &default_py
- python: 3.6
+ - python: 3.6
+ - python: 3.7
- &latest_py3
- python: 3.7
- dist: xenial
+ python: 3.8
- <<: *latest_py3
env: LANG=C
- python: 3.8-dev
- dist: xenial
- env: DISABLE_COVERAGE=1 # Ignore invalid coverage data.
- - <<: *default_py
- stage: deploy (to PyPI for tagged commits)
+ - <<: *latest_py3
+ env: TOXENV=docs DISABLE_COVERAGE=1
+ - <<: *latest_py3
+ stage: deploy
if: tag IS present
- install: skip
- script: skip
- after_success: true
- before_deploy:
- - python bootstrap.py
- - "! grep pyc setuptools.egg-info/SOURCES.txt"
- deploy:
- provider: pypi
- on:
- tags: true
- all_branches: true
- user: jaraco
- password:
- secure: tfWrsQMH2bHrWjqnP+08IX1WlkbW94Q30f4d7lCyhWS1FIf/jBDx4jrEILNfMxQ1NCwuBRje5sihj1Ow0BFf0vVrkaeff2IdvnNDEGFduMejaEQJL3s3QrLfpiAvUbtqwyWaHfAdGfk48PovDKTx0ZTvXZKYGXZhxGCYSlG2CE6Y6RDvnEl6Tk8e+LqUohkcSOwxrRwUoyxSnUaavdGohXxDT8MJlfWOXgr2u+KsRrriZqp3l6Fdsnk4IGvy6pXpy42L1HYQyyVu9XyJilR2JTbC6eCp5f8p26093m1Qas49+t6vYb0VLqQe12dO+Jm3v4uztSS5pPQzS7PFyjEYd2Rdb6ijsdbsy1074S4q7G9Sz+T3RsPUwYEJ07lzez8cxP64dtj5j94RL8m35A1Fb1OE8hHN+4c1yLG1gudfXbem+fUhi2eqhJrzQo5vsvDv1xS5x5GIS5ZHgKHCsWcW1Tv+dsFkrhaup3uU6VkOuc9UN+7VPsGEY7NvquGpTm8O1CnGJRzuJg6nbYRGj8ORwDpI0KmrExx6akV92P72fMC/I5TCgbSQSZn370H3Jj40gz1SM30WAli9M+wFHFd4ddMVY65yxj0NLmrP+m1tvnWdKtNh/RHuoW92d9/UFtiA5IhMf1/3djfsjBq6S9NT1uaLkVkTttqrPYJ7hOql8+g=
- distributions: release
- skip_cleanup: true
- skip_upload_docs: true
+ script: tox -e release
+ after_success: skip
cache: pip
@@ -63,6 +47,7 @@ install:
- "! grep pyc setuptools.egg-info/SOURCES.txt"
script:
+ - export NETWORK_REQUIRED=1
- |
( # Run testsuite.
if [ -z "$DISABLE_COVERAGE" ]
diff --git a/CHANGES.rst b/CHANGES.rst
index 48a176a8..81abbe59 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,156 @@
+v42.0.2
+-------
+
+* #1921: Fix support for easy_install's ``find-links`` option in ``setup.cfg``.
+* #1922: Build dependencies (setup_requires and tests_require) now install transitive dependencies indicated by extras.
+
+
+v42.0.1
+-------
+
+* #1918: Fix regression in handling wheels compatibility tags.
+
+
+v42.0.0
+-------
+
+* #1830, #1909: Mark the easy_install script and setuptools command as deprecated, and use `pip <https://pip.pypa.io/en/stable/>`_ when available to fetch/build wheels for missing ``setup_requires``/``tests_require`` requirements, with the following differences in behavior:
+ * support for ``python_requires``
+ * better support for wheels (proper handling of priority with respect to PEP 425 tags)
+ * PEP 517/518 support
+ * eggs are not supported
+ * no support for the ``allow_hosts`` easy_install option (``index_url``/``find_links`` are still honored)
+ * pip environment variables are honored (and take precedence over easy_install options)
+* #1898: Removed the "upload" and "register" commands in favor of `twine <https://pypi.org/p/twine>`_.
+* #1767: Add support for the ``license_files`` option in ``setup.cfg`` to automatically
+ include multiple license files in a source distribution.
+* #1829: Update handling of wheels compatibility tags:
+ * add support for manylinux2010
+ * fix use of removed 'm' ABI flag in Python 3.8 on Windows
+* #1861: Fix empty namespace package installation from wheel.
+* #1877: Setuptools now exposes a new entry point hook "setuptools.finalize_distribution_options", enabling plugins like `setuptools_scm <https://pypi.org/project/setuptools_scm>`_ to configure options on the distribution at finalization time.
+
+
+v41.6.0
+-------
+
+* #479: Replace usage of deprecated ``imp`` module with local re-implementation in ``setuptools._imp``.
+
+
+v41.5.1
+-------
+
+* #1891: Fix code for detecting Visual Studio's version on Windows under Python 2.
+
+
+v41.5.0
+-------
+
+* #1811: Improve Visual C++ 14.X support, mainly for Visual Studio 2017 and 2019.
+* #1814: Fix ``pkg_resources.Requirement`` hash/equality implementation: take PEP 508 direct URL into account.
+* #1824: Fix tests when running under ``python3.10``.
+* #1878: Formally deprecated the ``test`` command, with the recommendation that users migrate to ``tox``.
+* #1860: Update documentation to mention the egg format is not supported by pip and dependency links support was dropped starting with pip 19.0.
+* #1862: Drop ez_setup documentation: deprecated for some time (last updated in 2016), and still relying on easy_install (deprecated too).
+* #1868: Drop most documentation references to (deprecated) EasyInstall.
+* #1884: Added a trove classifier to document support for Python 3.8.
+* #1886: Added Python 3.8 release to the Travis test matrix.
+
+
+v41.4.0
+-------
+
+* #1847: In declarative config, now traps errors when invalid ``python_requires`` values are supplied.
+
+
+v41.3.0
+-------
+
+* #1690: When storing extras, rely on OrderedSet to retain order of extras as indicated by the packager, which will also be deterministic on Python 2.7 (with PYTHONHASHSEED unset) and Python 3.6+.
+* #1858: Fixed failing integration test triggered by 'long_description_content_type' in packaging.
+
+
+v41.2.0
+-------
+
+* #479: Remove some usage of the deprecated ``imp`` module.
+* #1565: Changed html_sidebars from string to list of string as per
+ https://www.sphinx-doc.org/en/master/changes.html#id58
+
+
+v41.1.0
+-------
+
+* #1697: Moved most of the constants from setup.py to setup.cfg
+* #1749: Fixed issue with the PEP 517 backend where building a source distribution would fail if any tarball existed in the destination directory.
+* #1750: Fixed an issue with PEP 517 backend where wheel builds would fail if the destination directory did not already exist.
+* #1756: Force metadata-version >= 1.2. when project urls are present.
+* #1769: Improve ``package_data`` check: ensure the dictionary values are lists/tuples of strings.
+* #1788: Changed compatibility fallback logic for ``html.unescape`` to avoid accessing ``HTMLParser.unescape`` when not necessary. ``HTMLParser.unescape`` is deprecated and will be removed in Python 3.9.
+* #1790: Added the file path to the error message when a ``UnicodeDecodeError`` occurs while reading a metadata file.
+* #1776: Use license classifiers rather than the license field.
+
+
+v41.0.1
+-------
+
+* #1671: Fixed issue with the PEP 517 backend that prevented building a wheel when the ``dist/`` directory contained existing ``.whl`` files.
+* #1709: In test.paths_on_python_path, avoid adding unnecessary duplicates to the PYTHONPATH.
+* #1741: In package_index, now honor "current directory" during a checkout of git and hg repositories under Windows
+
+
+v41.0.0
+-------
+
+* #1735: When parsing setup.cfg files, setuptools now requires the files to be encoded as UTF-8. Any other encoding will lead to a UnicodeDecodeError. This change removes support for specifying an encoding using a 'coding: ' directive in the header of the file, a feature that was introduces in 40.7. Given the recent release of the aforementioned feature, it is assumed that few if any projects are utilizing the feature to specify an encoding other than UTF-8.
+
+
+v40.9.0
+-------
+
+* #1675: Added support for ``setup.cfg``-only projects when using the ``setuptools.build_meta`` backend. Projects that have enabled PEP 517 no longer need to have a ``setup.py`` and can use the purely declarative ``setup.cfg`` configuration file instead.
+* #1720: Added support for ``pkg_resources.parse_requirements``-style requirements in ``setup_requires`` when ``setup.py`` is invoked from the ``setuptools.build_meta`` build backend.
+* #1664: Added the path to the ``PKG-INFO`` or ``METADATA`` file in the exception
+ text when the ``Version:`` header can't be found.
+* #1705: Removed some placeholder documentation sections referring to deprecated features.
+
+
+v40.8.0
+-------
+
+* #1652: Added the ``build_meta:__legacy__`` backend, a "compatibility mode" PEP 517 backend that can be used as the default when ``build-backend`` is left unspecified in ``pyproject.toml``.
+* #1635: Resource paths are passed to ``pkg_resources.resource_string`` and similar no longer accept paths that traverse parents, that begin with a leading ``/``. Violations of this expectation raise DeprecationWarnings and will become errors. Additionally, any paths that are absolute on Windows are strictly disallowed and will raise ValueErrors.
+* #1536: ``setuptools`` will now automatically include licenses if ``setup.cfg`` contains a ``license_file`` attribute, unless this file is manually excluded inside ``MANIFEST.in``.
+
+
+v40.7.3
+-------
+
+* #1670: In package_index, revert to using a copy of splituser from Python 3.8. Attempts to use ``urllib.parse.urlparse`` led to problems as reported in #1663 and #1668. This change serves as an alternative to #1499 and fixes #1668.
+
+
+v40.7.2
+-------
+
+* #1666: Restore port in URL handling in package_index.
+
+
+v40.7.1
+-------
+
+* #1660: On Python 2, when reading config files, downcast options from text to bytes to satisfy distutils expectations.
+
+
+v40.7.0
+-------
+
+* #1551: File inputs for the `license` field in `setup.cfg` files now explicitly raise an error.
+* #1180: Add support for non-ASCII in setup.cfg (#1062). Add support for native strings on some parameters (#1136).
+* #1499: ``setuptools.package_index`` no longer relies on the deprecated ``urllib.parse.splituser`` per Python #27485.
+* #1544: Added tests for PackageIndex.download (for git URLs).
+* #1625: In PEP 517 build_meta builder, ensure that sdists are built as gztar per the spec.
+
+
v40.6.3
-------
@@ -1444,8 +1597,8 @@ v20.6.0
19.7
----
-* `Off-project PR <https://github.com/jaraco/setuptools/pull/32>`_:
- For FreeBSD, also honor root certificates from ca_root_nss.
+* Off-project PR: `0dcee79 <https://github.com/pypa/setuptools/commit/0dcee791dfdcfacddaaec79b29f30a347a147413>`_ and `f9bd9b9 <https://github.com/pypa/setuptools/commit/f9bd9b9f5df54ef5a0bf8d16c3a889ab8c640580>`_
+ For FreeBSD, also `honor root certificates from ca_root_nss <https://github.com/pypa/setuptools/commit/3ae46c30225eb46e1f5aada1a19e88b79f04dc72>`_.
19.6.2
------
@@ -1609,9 +1762,9 @@ v20.6.0
now logged when pkg_resources is imported on Python 3.2 or earlier
Python 3 versions.
* `Add support for python_platform_implementation environment marker
- <https://github.com/jaraco/setuptools/pull/28>`_.
+ <https://github.com/pypa/setuptools/commit/94416707fd59a65f4a8f7f70541d6b3fc018b626>`_.
* `Fix dictionary mutation during iteration
- <https://github.com/jaraco/setuptools/pull/29>`_.
+ <https://github.com/pypa/setuptools/commit/57ebfa41e0f96b97e599ecd931b7ae8a143e096e>`_.
18.4
----
@@ -1971,7 +2124,7 @@ process to fail and PyPI uploads no longer accept files for 13.0.
---
* Prefer vendored packaging library `as recommended
- <https://github.com/jaraco/setuptools/commit/170657b68f4b92e7e1bf82f5e19a831f5744af67#commitcomment-9109448>`_.
+ <https://github.com/pypa/setuptools/commit/170657b68f4b92e7e1bf82f5e19a831f5744af67>`_.
9.0.1
-----
diff --git a/MANIFEST.in b/MANIFEST.in
index 9cce3c90..16d60e5f 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -12,3 +12,4 @@ include launcher.c
include msvc-build-launcher.cmd
include pytest.ini
include tox.ini
+exclude pyproject.toml # Temporary workaround for #1644.
diff --git a/README.rst b/README.rst
index 0454f2ed..da0549a9 100644
--- a/README.rst
+++ b/README.rst
@@ -1,23 +1,23 @@
.. image:: https://img.shields.io/pypi/v/setuptools.svg
:target: https://pypi.org/project/setuptools
-.. image:: https://readthedocs.org/projects/setuptools/badge/?version=latest
+.. image:: https://img.shields.io/readthedocs/setuptools/latest.svg
:target: https://setuptools.readthedocs.io
-.. image:: https://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20build%20%40%20Travis%20CI
+.. image:: https://img.shields.io/travis/pypa/setuptools/master.svg?label=Linux%20CI&logo=travis&logoColor=white
:target: https://travis-ci.org/pypa/setuptools
-.. image:: https://img.shields.io/appveyor/ci/pypa/setuptools/master.svg?label=Windows%20build%20%40%20Appveyor
+.. image:: https://img.shields.io/appveyor/ci/pypa/setuptools/master.svg?label=Windows%20CI&logo=appveyor&logoColor=white
:target: https://ci.appveyor.com/project/pypa/setuptools/branch/master
-.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg
+.. image:: https://img.shields.io/codecov/c/github/pypa/setuptools/master.svg?logo=codecov&logoColor=white
:target: https://codecov.io/gh/pypa/setuptools
-.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg
-
-.. image:: https://tidelift.com/badges/github/pypa/setuptools
+.. image:: https://tidelift.com/badges/github/pypa/setuptools?style=flat
:target: https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme
+.. image:: https://img.shields.io/pypi/pyversions/setuptools.svg
+
See the `Installation Instructions
<https://packaging.python.org/installing/>`_ in the Python Packaging
User's Guide for instructions on installing, upgrading, and uninstalling
@@ -29,9 +29,22 @@ Bug reports and especially tested patches may be
submitted directly to the `bug tracker
<https://github.com/pypa/setuptools/issues>`_.
+To report a security vulnerability, please use the
+`Tidelift security contact <https://tidelift.com/security>`_.
+Tidelift will coordinate the fix and disclosure.
+
+
+For Enterprise
+==============
+
+Available as part of the Tidelift Subscription.
+
+Setuptools and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.
+
+`Learn more <https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=referral&utm_campaign=github>`_.
Code of Conduct
----------------
+===============
Everyone interacting in the setuptools project's codebases, issue trackers,
chat rooms, and mailing lists is expected to follow the
diff --git a/appveyor.yml b/appveyor.yml
index ef4a9f7e..08818069 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -3,6 +3,7 @@ clone_depth: 50
environment:
APPVEYOR: True
+ NETWORK_REQUIRED: True
CODECOV_ENV: APPVEYOR_JOB_NAME
matrix:
@@ -25,7 +26,7 @@ cache:
test_script:
- python --version
- python -m pip install --disable-pip-version-check --upgrade pip setuptools wheel
- - pip install --upgrade tox tox-venv
+ - pip install --upgrade tox tox-venv virtualenv
- pip freeze --all
- python bootstrap.py
- tox -- --cov
diff --git a/changelog.d/1499.change.rst b/changelog.d/1499.change.rst
deleted file mode 100644
index 10e4db68..00000000
--- a/changelog.d/1499.change.rst
+++ /dev/null
@@ -1 +0,0 @@
-``setuptools.package_index`` no longer relies on the deprecated ``urllib.parse.splituser`` per Python #27485.
diff --git a/changelog.d/1544.change.rst b/changelog.d/1544.change.rst
deleted file mode 100644
index 748b64e1..00000000
--- a/changelog.d/1544.change.rst
+++ /dev/null
@@ -1 +0,0 @@
-Added tests for PackageIndex.download (for git URLs).
diff --git a/changelog.d/1551.breaking.rst b/changelog.d/1551.breaking.rst
deleted file mode 100644
index c0e477ce..00000000
--- a/changelog.d/1551.breaking.rst
+++ /dev/null
@@ -1 +0,0 @@
-File inputs for the `license` field in `setup.cfg` files now explicitly raise an error.
diff --git a/changelog.d/1625.change.rst b/changelog.d/1625.change.rst
deleted file mode 100644
index 9125ac0c..00000000
--- a/changelog.d/1625.change.rst
+++ /dev/null
@@ -1 +0,0 @@
-In PEP 517 build_meta builder, ensure that sdists are built as gztar per the spec.
diff --git a/changelog.d/1927.change.rst b/changelog.d/1927.change.rst
new file mode 100644
index 00000000..3b293d63
--- /dev/null
+++ b/changelog.d/1927.change.rst
@@ -0,0 +1 @@
+Setuptools once again declares 'setuptools' in the ``build-system.requires`` and adds PEP 517 build support by declaring itself as the ``build-backend``. It additionally specifies ``build-system.backend-path`` to rely on itself for those builders that support it.
diff --git a/conftest.py b/conftest.py
index 0d7b274c..1746bfb5 100644
--- a/conftest.py
+++ b/conftest.py
@@ -19,6 +19,7 @@ collect_ignore = [
if sys.version_info < (3,):
collect_ignore.append('setuptools/lib2to3_ex.py')
+ collect_ignore.append('setuptools/_imp.py')
if sys.version_info < (3, 6):
diff --git a/docs/_templates/indexsidebar.html b/docs/_templates/indexsidebar.html
index b8c6148e..d803b8a3 100644
--- a/docs/_templates/indexsidebar.html
+++ b/docs/_templates/indexsidebar.html
@@ -1,3 +1,10 @@
+<h3 class="donation">For Enterprise</h3>
+
+<p>
+Professionally-supported {{ project }} is available with the
+<a href="https://tidelift.com/subscription/pkg/pypi-{{ project }}?utm_source=pypi-{{ project }}&utm_medium=referral">Tidelift Subscription</a>.
+</p>
+
<h3>Download</h3>
<p>Current version: <b>{{ version }}</b></p>
@@ -5,11 +12,4 @@
<h3>Questions? Suggestions? Contributions?</h3>
-<p>Visit the <a href="https://github.com/pypa/setuptools">Setuptools project page</a> </p>
-
-<h3 class="donation">Professional support</h3>
-
-<p>
-Professionally-supported {{ project }} is available with the
-<a href="https://tidelift.com/subscription/pkg/pypi-setuptools?utm_source=pypi-setuptools&utm_medium=readme">Tidelift Subscription</a>.
-</p>
+<p>Visit the <a href="{{ package_url }}">Project page</a> </p>
diff --git a/docs/conf.py b/docs/conf.py
index c7eb6d3f..cbd19fb4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -69,7 +69,7 @@ html_theme_path = ['_theme']
html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
-html_sidebars = {'index': 'indexsidebar.html'}
+html_sidebars = {'index': ['relations.html', 'sourcelink.html', 'indexsidebar.html', 'searchbox.html']}
# If false, no module index is generated.
html_use_modindex = False
diff --git a/docs/developer-guide.txt b/docs/developer-guide.txt
index a5942c8b..d145fba1 100644
--- a/docs/developer-guide.txt
+++ b/docs/developer-guide.txt
@@ -137,3 +137,17 @@ To build the docs locally, use tox::
.. _Sphinx: http://www.sphinx-doc.org/en/master/
.. _published documentation: https://setuptools.readthedocs.io/en/latest/
+
+---------------------
+Vendored Dependencies
+---------------------
+
+Setuptools has some dependencies, but due to `bootstrapping issues
+<https://github.com/pypa/setuptools/issues/980>`, those dependencies
+cannot be declared as they won't be resolved soon enough to build
+setuptools from source. Eventually, this limitation may be lifted as
+PEP 517/518 reach ubiquitous adoption, but for now, Setuptools
+cannot declare dependencies other than through
+``setuptools/_vendor/vendored.txt`` and
+``pkg_reosurces/_vendor/vendored.txt`` and refreshed by way of
+``paver update_vendored`` (pavement.py).
diff --git a/docs/development.txt b/docs/development.txt
index 455f038a..28e653fe 100644
--- a/docs/development.txt
+++ b/docs/development.txt
@@ -7,7 +7,7 @@ Authority (PyPA) and led by Jason R. Coombs.
This document describes the process by which Setuptools is developed.
This document assumes the reader has some passing familiarity with
-*using* setuptools, the ``pkg_resources`` module, and EasyInstall. It
+*using* setuptools, the ``pkg_resources`` module, and pip. It
does not attempt to explain basic concepts like inter-project
dependencies, nor does it contain detailed lexical syntax for most
file formats. Neither does it explain concepts like "namespace
diff --git a/docs/easy_install.txt b/docs/easy_install.txt
index aa11f890..544b9efd 100644
--- a/docs/easy_install.txt
+++ b/docs/easy_install.txt
@@ -317,8 +317,8 @@ Note that instead of changing your ``PATH`` to include the Python scripts
directory, you can also retarget the installation location for scripts so they
go on a directory that's already on the ``PATH``. For more information see
`Command-Line Options`_ and `Configuration Files`_. During installation,
-pass command line options (such as ``--script-dir``) to
-``ez_setup.py`` to control where ``easy_install.exe`` will be installed.
+pass command line options (such as ``--script-dir``) to control where
+scripts will be installed.
Windows Executable Launcher
diff --git a/docs/ez_setup.txt b/docs/ez_setup.txt
deleted file mode 100644
index 0126fee3..00000000
--- a/docs/ez_setup.txt
+++ /dev/null
@@ -1,195 +0,0 @@
-:orphan:
-
-``ez_setup`` distribution guide
-===============================
-
-Using ``setuptools``... Without bundling it!
----------------------------------------------
-
-.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
-
-.. _ez_setup.py: https://bootstrap.pypa.io/ez_setup.py
-
-.. _EasyInstall Installation Instructions: easy_install.html
-
-.. _Custom Installation Locations: easy_install.html
-
-Your users might not have ``setuptools`` installed on their machines, or even
-if they do, it might not be the right version. Fixing this is easy; just
-download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
-script. (Be sure to add it to your revision control system, too.) Then add
-these two lines to the very top of your setup script, before the script imports
-anything from setuptools:
-
-.. code-block:: python
-
- import ez_setup
- ez_setup.use_setuptools()
-
-That's it. The ``ez_setup`` module will automatically download a matching
-version of ``setuptools`` from PyPI, if it isn't present on the target system.
-Whenever you install an updated version of setuptools, you should also update
-your projects' ``ez_setup.py`` files, so that a matching version gets installed
-on the target machine(s).
-
-(By the way, if you need to distribute a specific version of ``setuptools``,
-you can specify the exact version and base download URL as parameters to the
-``use_setuptools()`` function. See the function's docstring for details.)
-
-
-What Your Users Should Know
----------------------------
-
-In general, a setuptools-based project looks just like any distutils-based
-project -- as long as your users have an internet connection and are installing
-to ``site-packages``, that is. But for some users, these conditions don't
-apply, and they may become frustrated if this is their first encounter with
-a setuptools-based project. To keep these users happy, you should review the
-following topics in your project's installation instructions, if they are
-relevant to your project and your target audience isn't already familiar with
-setuptools and ``easy_install``.
-
-Network Access
- If your project is using ``ez_setup``, you should inform users of the
- need to either have network access, or to preinstall the correct version of
- setuptools using the `EasyInstall installation instructions`_. Those
- instructions also have tips for dealing with firewalls as well as how to
- manually download and install setuptools.
-
-Custom Installation Locations
- You should inform your users that if they are installing your project to
- somewhere other than the main ``site-packages`` directory, they should
- first install setuptools using the instructions for `Custom Installation
- Locations`_, before installing your project.
-
-Your Project's Dependencies
- If your project depends on other projects that may need to be downloaded
- from PyPI or elsewhere, you should list them in your installation
- instructions, or tell users how to find out what they are. While most
- users will not need this information, any users who don't have unrestricted
- internet access may have to find, download, and install the other projects
- manually. (Note, however, that they must still install those projects
- using ``easy_install``, or your project will not know they are installed,
- and your setup script will try to download them again.)
-
- If you want to be especially friendly to users with limited network access,
- you may wish to build eggs for your project and its dependencies, making
- them all available for download from your site, or at least create a page
- with links to all of the needed eggs. In this way, users with limited
- network access can manually download all the eggs to a single directory,
- then use the ``-f`` option of ``easy_install`` to specify the directory
- to find eggs in. Users who have full network access can just use ``-f``
- with the URL of your download page, and ``easy_install`` will find all the
- needed eggs using your links directly. This is also useful when your
- target audience isn't able to compile packages (e.g. most Windows users)
- and your package or some of its dependencies include C code.
-
-Revision Control System Users and Co-Developers
- Users and co-developers who are tracking your in-development code using
- a revision control system should probably read this manual's sections
- regarding such development. Alternately, you may wish to create a
- quick-reference guide containing the tips from this manual that apply to
- your particular situation. For example, if you recommend that people use
- ``setup.py develop`` when tracking your in-development code, you should let
- them know that this needs to be run after every update or commit.
-
- Similarly, if you remove modules or data files from your project, you
- should remind them to run ``setup.py clean --all`` and delete any obsolete
- ``.pyc`` or ``.pyo``. (This tip applies to the distutils in general, not
- just setuptools, but not everybody knows about them; be kind to your users
- by spelling out your project's best practices rather than leaving them
- guessing.)
-
-Creating System Packages
- Some users want to manage all Python packages using a single package
- manager, and sometimes that package manager isn't ``easy_install``!
- Setuptools currently supports ``bdist_rpm``, ``bdist_wininst``, and
- ``bdist_dumb`` formats for system packaging. If a user has a locally-
- installed "bdist" packaging tool that internally uses the distutils
- ``install`` command, it should be able to work with ``setuptools``. Some
- examples of "bdist" formats that this should work with include the
- ``bdist_nsi`` and ``bdist_msi`` formats for Windows.
-
- However, packaging tools that build binary distributions by running
- ``setup.py install`` on the command line or as a subprocess will require
- modification to work with setuptools. They should use the
- ``--single-version-externally-managed`` option to the ``install`` command,
- combined with the standard ``--root`` or ``--record`` options.
- See the `install command`_ documentation below for more details. The
- ``bdist_deb`` command is an example of a command that currently requires
- this kind of patching to work with setuptools.
-
- Please note that building system packages may require you to install
- some system software, for example ``bdist_rpm`` requires the ``rpmbuild``
- command to be installed.
-
- If you or your users have a problem building a usable system package for
- your project, please report the problem via the mailing list so that
- either the "bdist" tool in question or setuptools can be modified to
- resolve the issue.
-
-Your users might not have ``setuptools`` installed on their machines, or even
-if they do, it might not be the right version. Fixing this is easy; just
-download `ez_setup.py`_, and put it in the same directory as your ``setup.py``
-script. (Be sure to add it to your revision control system, too.) Then add
-these two lines to the very top of your setup script, before the script imports
-anything from setuptools:
-
-.. code-block:: python
-
- import ez_setup
- ez_setup.use_setuptools()
-
-That's it. The ``ez_setup`` module will automatically download a matching
-version of ``setuptools`` from PyPI, if it isn't present on the target system.
-Whenever you install an updated version of setuptools, you should also update
-your projects' ``ez_setup.py`` files, so that a matching version gets installed
-on the target machine(s).
-
-(By the way, if you need to distribute a specific version of ``setuptools``,
-you can specify the exact version and base download URL as parameters to the
-``use_setuptools()`` function. See the function's docstring for details.)
-
-.. _install command:
-
-``install`` - Run ``easy_install`` or old-style installation
-============================================================
-
-The setuptools ``install`` command is basically a shortcut to run the
-``easy_install`` command on the current project. However, for convenience
-in creating "system packages" of setuptools-based projects, you can also
-use this option:
-
-``--single-version-externally-managed``
- This boolean option tells the ``install`` command to perform an "old style"
- installation, with the addition of an ``.egg-info`` directory so that the
- installed project will still have its metadata available and operate
- normally. If you use this option, you *must* also specify the ``--root``
- or ``--record`` options (or both), because otherwise you will have no way
- to identify and remove the installed files.
-
-This option is automatically in effect when ``install`` is invoked by another
-distutils command, so that commands like ``bdist_wininst`` and ``bdist_rpm``
-will create system packages of eggs. It is also automatically in effect if
-you specify the ``--root`` option.
-
-
-``install_egg_info`` - Install an ``.egg-info`` directory in ``site-packages``
-==============================================================================
-
-Setuptools runs this command as part of ``install`` operations that use the
-``--single-version-externally-managed`` options. You should not invoke it
-directly; it is documented here for completeness and so that distutils
-extensions such as system package builders can make use of it. This command
-has only one option:
-
-``--install-dir=DIR, -d DIR``
- The parent directory where the ``.egg-info`` directory will be placed.
- Defaults to the same as the ``--install-dir`` option specified for the
- ``install_lib`` command, which is usually the system ``site-packages``
- directory.
-
-This command assumes that the ``egg_info`` command has been given valid options
-via the command line or ``setup.cfg``, as it will invoke the ``egg_info``
-command and use its options to locate the project's source ``.egg-info``
-directory.
diff --git a/docs/formats.txt b/docs/formats.txt
index a182eb99..6c0456de 100644
--- a/docs/formats.txt
+++ b/docs/formats.txt
@@ -299,11 +299,8 @@ specified by the ``setup_requires`` parameter to the Distribution.
A list of dependency URLs, one per line, as specified using the
``dependency_links`` keyword to ``setup()``. These may be direct
download URLs, or the URLs of web pages containing direct download
-links, and will be used by EasyInstall to find dependencies, as though
-the user had manually provided them via the ``--find-links`` command
-line option. Please see the setuptools manual and EasyInstall manual
-for more information on specifying this option, and for information on
-how EasyInstall processes ``--find-links`` URLs.
+links. Please see the setuptools manual for more information on
+specifying this option.
``depends.txt`` -- Obsolete, do not create!
diff --git a/docs/pkg_resources.txt b/docs/pkg_resources.txt
index 0c9fb5f2..b887a923 100644
--- a/docs/pkg_resources.txt
+++ b/docs/pkg_resources.txt
@@ -245,8 +245,8 @@ abbreviation for ``pkg_resources.working_set.require()``:
interactive interpreter hacking than for production use. If you're creating
an actual library or application, it's strongly recommended that you create
a "setup.py" script using ``setuptools``, and declare all your requirements
- there. That way, tools like EasyInstall can automatically detect what
- requirements your package has, and deal with them accordingly.
+ there. That way, tools like pip can automatically detect what requirements
+ your package has, and deal with them accordingly.
Note that calling ``require('SomePackage')`` will not install
``SomePackage`` if it isn't already present. If you need to do this, you
@@ -611,9 +611,9 @@ Requirements Parsing
or activation of both Report-O-Rama and any libraries it needs in order to
provide PDF support. For example, you could use::
- easy_install.py Report-O-Rama[PDF]
+ pip install Report-O-Rama[PDF]
- To install the necessary packages using the EasyInstall program, or call
+ To install the necessary packages using pip, or call
``pkg_resources.require('Report-O-Rama[PDF]')`` to add the necessary
distributions to sys.path at runtime.
@@ -1132,8 +1132,8 @@ relative to the root of the identified distribution; i.e. its first path
segment will be treated as a peer of the top-level modules or packages in the
distribution.
-Note that resource names must be ``/``-separated paths and cannot be absolute
-(i.e. no leading ``/``) or contain relative names like ``".."``. Do *not* use
+Note that resource names must be ``/``-separated paths rooted at the package,
+cannot contain relative names like ``".."``, and cannot be absolute. Do *not* use
``os.path`` routines to manipulate resource paths, as they are *not* filesystem
paths.
@@ -1843,9 +1843,9 @@ History
because it isn't necessarily a filesystem path (and hasn't been for some
time now). The ``location`` of ``Distribution`` objects in the filesystem
should always be normalized using ``pkg_resources.normalize_path()``; all
- of the setuptools and EasyInstall code that generates distributions from
- the filesystem (including ``Distribution.from_filename()``) ensure this
- invariant, but if you use a more generic API like ``Distribution()`` or
+ of the setuptools' code that generates distributions from the filesystem
+ (including ``Distribution.from_filename()``) ensure this invariant, but if
+ you use a more generic API like ``Distribution()`` or
``Distribution.from_location()`` you should take care that you don't
create a distribution with an un-normalized filesystem path.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index c6d594e8..bc27165b 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,5 @@
sphinx!=1.8.0
rst.linker>=1.9
-jaraco.packaging>=3.2
+jaraco.packaging>=6.1
setuptools>=34
diff --git a/docs/setuptools.txt b/docs/setuptools.txt
index cf166e9c..c109e673 100644
--- a/docs/setuptools.txt
+++ b/docs/setuptools.txt
@@ -8,23 +8,10 @@ distribute Python packages, especially ones that have dependencies on other
packages.
Packages built and distributed using ``setuptools`` look to the user like
-ordinary Python packages based on the ``distutils``. Your users don't need to
-install or even know about setuptools in order to use them, and you don't
-have to include the entire setuptools package in your distributions. By
-including just a single `bootstrap module`_ (a 12K .py file), your package will
-automatically download and install ``setuptools`` if the user is building your
-package from source and doesn't have a suitable version already installed.
-
-.. _bootstrap module: https://bootstrap.pypa.io/ez_setup.py
+ordinary Python packages based on the ``distutils``.
Feature Highlights:
-* Automatically find/download/install/upgrade dependencies at build time using
- the `EasyInstall tool <easy_install.html>`_,
- which supports downloading via HTTP, FTP, Subversion, and SourceForge, and
- automatically scans web pages linked from PyPI to find download links. (It's
- the closest thing to CPAN currently available for Python.)
-
* Create `Python Eggs <http://peak.telecommunity.com/DevCenter/PythonEggs>`_ -
a single-file importable distribution format
@@ -62,8 +49,6 @@ Feature Highlights:
.. contents:: **Table of Contents**
-.. _ez_setup.py: `bootstrap module`_
-
-----------------
Developer's Guide
@@ -73,10 +58,6 @@ Developer's Guide
Installing ``setuptools``
=========================
-.. _EasyInstall Installation Instructions: easy_install.html
-
-.. _Custom Installation Locations: easy_install.html
-
.. _Installing Packages: https://packaging.python.org/tutorials/installing-packages/
To install the latest version of setuptools, use::
@@ -136,16 +117,18 @@ dependencies, and perhaps some data files and scripts::
author="Me",
author_email="me@example.com",
description="This is an Example Package",
- license="PSF",
keywords="hello world example examples",
url="http://example.com/HelloWorld/", # project home page, if any
project_urls={
"Bug Tracker": "https://bugs.example.com/HelloWorld/",
"Documentation": "https://docs.example.com/HelloWorld/",
"Source Code": "https://code.example.com/HelloWorld/",
- }
+ },
+ classifiers=[
+ 'License :: OSI Approved :: Python Software Foundation License'
+ ]
- # could also include long_description, download_url, classifiers, etc.
+ # could also include long_description, download_url, etc.
)
In the sections that follow, we'll explain what most of these ``setup()``
@@ -158,7 +141,7 @@ Specifying Your Project's Version
Setuptools can work well with most versioning schemes; there are, however, a
few special things to watch out for, in order to ensure that setuptools and
-EasyInstall can always tell what version of your package is newer than another
+other tools can always tell what version of your package is newer than another
version. Knowing these things will also help you correctly specify what
versions of other projects your project depends on.
@@ -299,11 +282,11 @@ unless you need the associated ``setuptools`` feature.
``setup_requires``
A string or list of strings specifying what other distributions need to
be present in order for the *setup script* to run. ``setuptools`` will
- attempt to obtain these (even going so far as to download them using
- ``EasyInstall``) before processing the rest of the setup script or commands.
- This argument is needed if you are using distutils extensions as part of
- your build process; for example, extensions that process setup() arguments
- and turn them into EGG-INFO metadata files.
+ attempt to obtain these (using pip if available) before processing the
+ rest of the setup script or commands. This argument is needed if you
+ are using distutils extensions as part of your build process; for
+ example, extensions that process setup() arguments and turn them into
+ EGG-INFO metadata files.
(Note: projects listed in ``setup_requires`` will NOT be automatically
installed on the system where the setup script is being run. They are
@@ -316,8 +299,7 @@ unless you need the associated ``setuptools`` feature.
A list of strings naming URLs to be searched when satisfying dependencies.
These links will be used if needed to install packages specified by
``setup_requires`` or ``tests_require``. They will also be written into
- the egg's metadata for use by tools like EasyInstall to use when installing
- an ``.egg`` file.
+ the egg's metadata for use during install by tools that support them.
``namespace_packages``
A list of strings naming the project's "namespace packages". A namespace
@@ -344,16 +326,19 @@ unless you need the associated ``setuptools`` feature.
specified test suite, e.g. via ``setup.py test``. See the section on the
`test`_ command below for more details.
+ New in 41.5.0: Deprecated the test command.
+
``tests_require``
If your project's tests need one or more additional packages besides those
needed to install it, you can use this option to specify them. It should
be a string or list of strings specifying what other distributions need to
be present for the package's tests to run. When you run the ``test``
- command, ``setuptools`` will attempt to obtain these (even going
- so far as to download them using ``EasyInstall``). Note that these
- required projects will *not* be installed on the system where the tests
- are run, but only downloaded to the project's setup directory if they're
- not already installed locally.
+ command, ``setuptools`` will attempt to obtain these (using pip if
+ available). Note that these required projects will *not* be installed on
+ the system where the tests are run, but only downloaded to the project's setup
+ directory if they're not already installed locally.
+
+ New in 41.5.0: Deprecated the test command.
.. _test_loader:
@@ -378,6 +363,8 @@ unless you need the associated ``setuptools`` feature.
as long as you use the ``tests_require`` option to ensure that the package
containing the loader class is available when the ``test`` command is run.
+ New in 41.5.0: Deprecated the test command.
+
``eager_resources``
A list of strings naming resources that should be extracted together, if
any of them is needed, or if any C extensions included in the project are
@@ -550,11 +537,12 @@ script called ``baz``, you might do something like this::
)
When this project is installed on non-Windows platforms (using "setup.py
-install", "setup.py develop", or by using EasyInstall), a set of ``foo``,
-``bar``, and ``baz`` scripts will be installed that import ``main_func`` and
-``some_func`` from the specified modules. The functions you specify are called
-with no arguments, and their return value is passed to ``sys.exit()``, so you
-can return an errorlevel or message to print to stderr.
+install", "setup.py develop", or with pip), a set of ``foo``, ``bar``,
+and ``baz`` scripts will be installed that import ``main_func`` and
+``some_func`` from the specified modules. The functions you specify are
+called with no arguments, and their return value is passed to
+``sys.exit()``, so you can return an errorlevel or message to print to
+stderr.
On Windows, a set of ``foo.exe``, ``bar.exe``, and ``baz.exe`` launchers are
created, alongside a set of ``foo.py``, ``bar.py``, and ``baz.pyw`` files. The
@@ -594,10 +582,6 @@ Python must be available via the ``PATH`` environment variable, under its
"long" name. That is, if the egg is built for Python 2.3, there must be a
``python2.3`` executable present in a directory on ``PATH``.
-This feature is primarily intended to support ez_setup the installation of
-setuptools itself on non-Windows platforms, but may also be useful for other
-projects as well.
-
IMPORTANT NOTE: Eggs with an "eggsecutable" header cannot be renamed, or
invoked via symlinks. They *must* be invoked using their original filename, in
order to ensure that, once running, ``pkg_resources`` will know what project
@@ -611,7 +595,7 @@ Declaring Dependencies
``setuptools`` supports automatically installing dependencies when a package is
installed, and including information about dependencies in Python Eggs (so that
-package management tools like EasyInstall can use the information).
+package management tools like pip can use the information).
``setuptools`` and ``pkg_resources`` use a common syntax for specifying a
project's required dependencies. This syntax consists of a project's PyPI
@@ -650,10 +634,9 @@ requirement in a string, each requirement must begin on a new line.
This has three effects:
-1. When your project is installed, either by using EasyInstall, ``setup.py
- install``, or ``setup.py develop``, all of the dependencies not already
- installed will be located (via PyPI), downloaded, built (if necessary),
- and installed.
+1. When your project is installed, either by using pip, ``setup.py install``,
+ or ``setup.py develop``, all of the dependencies not already installed will
+ be located (via PyPI), downloaded, built (if necessary), and installed.
2. Any scripts in your project will be installed with wrappers that verify
the availability of the specified dependencies at runtime, and ensure that
@@ -673,6 +656,10 @@ using ``setup.py develop``.)
Dependencies that aren't in PyPI
--------------------------------
+.. warning::
+ Dependency links support has been dropped by pip starting with version
+ 19.0 (released 2019-01-22).
+
If your project depends on packages that don't exist on PyPI, you may still be
able to depend on them, as long as they are available for download as:
@@ -723,9 +710,8 @@ This will do a checkout (or a clone, in Git and Mercurial parlance) to a
temporary folder and run ``setup.py bdist_egg``.
The ``dependency_links`` option takes the form of a list of URL strings. For
-example, the below will cause EasyInstall to search the specified page for
-eggs or source distributions, if the package's dependencies aren't already
-installed::
+example, this will cause a search of the specified page for eggs or source
+distributions, if the package's dependencies aren't already installed::
setup(
...
@@ -765,7 +751,7 @@ names of "extra" features, to strings or lists of strings describing those
features' requirements. These requirements will *not* be automatically
installed unless another package depends on them (directly or indirectly) by
including the desired "extras" in square brackets after the associated project
-name. (Or if the extras were listed in a requirement spec on the EasyInstall
+name. (Or if the extras were listed in a requirement spec on the "pip install"
command line.)
Extras can be used by a project's `entry points`_ to specify dynamic
@@ -1180,13 +1166,12 @@ preferred way of working (as opposed to using a common independent staging area
or the site-packages directory).
To do this, use the ``setup.py develop`` command. It works very similarly to
-``setup.py install`` or the EasyInstall tool, except that it doesn't actually
-install anything. Instead, it creates a special ``.egg-link`` file in the
-deployment directory, that links to your project's source code. And, if your
-deployment directory is Python's ``site-packages`` directory, it will also
-update the ``easy-install.pth`` file to include your project's source code,
-thereby making it available on ``sys.path`` for all programs using that Python
-installation.
+``setup.py install``, except that it doesn't actually install anything.
+Instead, it creates a special ``.egg-link`` file in the deployment directory,
+that links to your project's source code. And, if your deployment directory is
+Python's ``site-packages`` directory, it will also update the
+``easy-install.pth`` file to include your project's source code, thereby making
+it available on ``sys.path`` for all programs using that Python installation.
If you have enabled the ``use_2to3`` flag, then of course the ``.egg-link``
will not link directly to your source code when run under Python 3, since
@@ -1223,7 +1208,7 @@ the quoted part.
Distributing a ``setuptools``-based project
===========================================
-Detailed instructions to distribute a setuptools project can be found at
+Detailed instructions to distribute a setuptools project can be found at
`Packaging project tutorials`_.
.. _Packaging project tutorials: https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives
@@ -1239,7 +1224,7 @@ setup.py is located::
This will generate distribution archives in the `dist` directory.
-Before you upload the generated archives make sure you're registered on
+Before you upload the generated archives make sure you're registered on
https://test.pypi.org/account/register/. You will also need to verify your email
to be able to upload any packages.
You should install twine to be able to upload packages::
@@ -1257,20 +1242,6 @@ To install your newly uploaded package ``example_pkg``, you can use pip::
If you have issues at any point, please refer to `Packaging project tutorials`_
for clarification.
-Distributing legacy ``setuptools`` projects using ez_setup.py
--------------------------------------------------------------
-
-.. warning:: **ez_setup** is deprecated in favor of PIP with **PEP-518** support.
-
-Distributing packages using the legacy ``ez_setup.py`` and ``easy_install`` is
-deprecated in favor of PIP. Please consider migrating to using pip and twine based
-distribution.
-
-However, if you still have any ``ez_setup`` based packages, documentation for
-ez_setup based distributions can be found at `ez_setup distribution guide`_.
-
-.. _ez_setup distribution guide: ez_setup.html
-
Setting the ``zip_safe`` flag
-----------------------------
@@ -1306,20 +1277,14 @@ you've checked over all the warnings it issued, and you are either satisfied it
doesn't work, you can always change it to ``False``, which will force
``setuptools`` to install your project as a directory rather than as a zipfile.
-Of course, the end-user can still override either decision, if they are using
-EasyInstall to install your package. And, if you want to override for testing
-purposes, you can just run ``setup.py easy_install --zip-ok .`` or ``setup.py
-easy_install --always-unzip .`` in your project directory. to install the
-package as a zipfile or directory, respectively.
-
In the future, as we gain more experience with different packages and become
more satisfied with the robustness of the ``pkg_resources`` runtime, the
"zip safety" analysis may become less conservative. However, we strongly
recommend that you determine for yourself whether your project functions
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`` or ``EasyInstall`` to
-try to guess whether your project can work as a zipfile.
+flag, so that it will not be necessary for ``bdist_egg`` to try to guess
+whether your project can work as a zipfile.
.. _Namespace Packages:
@@ -1433,9 +1398,9 @@ to generate a daily build or snapshot for. See the section below on the
(Also, before you release your project, be sure to see the section above on
`Specifying Your Project's Version`_ for more information about how pre- and
-post-release tags affect how setuptools and EasyInstall interpret version
-numbers. This is important in order to make sure that dependency processing
-tools will know which versions of your project are newer than others.)
+post-release tags affect how version numbers are interpreted. This is
+important in order to make sure that dependency processing tools will know
+which versions of your project are newer than others.)
Finally, if you are creating builds frequently, and either building them in a
downloadable location or are copying them to a distribution server, you should
@@ -1491,58 +1456,6 @@ all practical purposes, you'll probably use only the ``--formats`` option, if
you use any option at all.
-Making your package available for EasyInstall
----------------------------------------------
-
-There may be reasons why you don't want to upload distributions to
-PyPI, and just want your existing distributions (or perhaps a Subversion
-checkout) to be used instead.
-
-There are three ``setup()`` arguments that affect EasyInstall:
-
-``url`` and ``download_url``
- These become links on your project's PyPI page. EasyInstall will examine
- them to see if they link to a package ("primary links"), or whether they are
- HTML pages. If they're HTML pages, EasyInstall scans all HREF's on the
- page for primary links
-
-``long_description``
- EasyInstall will check any URLs contained in this argument to see if they
- are primary links.
-
-A URL is considered a "primary link" if it is a link to a .tar.gz, .tgz, .zip,
-.egg, .egg.zip, .tar.bz2, or .exe file, or if it has an ``#egg=project`` or
-``#egg=project-version`` fragment identifier attached to it. EasyInstall
-attempts to determine a project name and optional version number from the text
-of a primary link *without* downloading it. When it has found all the primary
-links, EasyInstall will select the best match based on requested version,
-platform compatibility, and other criteria.
-
-So, if your ``url`` or ``download_url`` point either directly to a downloadable
-source distribution, or to HTML page(s) that have direct links to such, then
-EasyInstall will be able to locate downloads automatically. If you want to
-make Subversion checkouts available, then you should create links with either
-``#egg=project`` or ``#egg=project-version`` added to the URL. You should
-replace ``project`` and ``version`` with the values they would have in an egg
-filename. (Be sure to actually generate an egg and then use the initial part
-of the filename, rather than trying to guess what the escaped form of the
-project name and version number will be.)
-
-Note that Subversion checkout links are of lower precedence than other kinds
-of distributions, so EasyInstall will not select a Subversion checkout for
-downloading unless it has a version included in the ``#egg=`` suffix, and
-it's a higher version than EasyInstall has seen in any other links for your
-project.
-
-As a result, it's a common practice to use mark checkout URLs with a version of
-"dev" (i.e., ``#egg=projectname-dev``), so that users can do something like
-this::
-
- easy_install --editable projectname==dev
-
-in order to check out the in-development version of ``projectname``.
-
-
Making "Official" (Non-Snapshot) Releases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1687,6 +1600,9 @@ file locations.
``bdist_egg`` - Create a Python Egg for the project
===================================================
+.. warning::
+ **eggs** are deprecated in favor of wheels, and not supported by pip.
+
This command generates a Python Egg (``.egg`` file) for the project. Python
Eggs are the preferred binary distribution format for EasyInstall, because they
are cross-platform (for "pure" packages), directly importable, and contain
@@ -1794,9 +1710,9 @@ Here are some of the options that the ``develop`` command accepts. Note that
they affect the project's dependencies as well as the project itself, so if you
have dependencies that need to be installed and you use ``--exclude-scripts``
(for example), the dependencies' scripts will not be installed either! For
-this reason, you may want to use EasyInstall to install the project's
-dependencies before using the ``develop`` command, if you need finer control
-over the installation options for dependencies.
+this reason, you may want to use pip to install the project's dependencies
+before using the ``develop`` command, if you need finer control over the
+installation options for dependencies.
``--uninstall, -u``
Un-deploy the current project. You may use the ``--install-dir`` or ``-d``
@@ -1806,10 +1722,10 @@ over the installation options for dependencies.
staging area is Python's ``site-packages`` directory.
Note that this option currently does *not* uninstall script wrappers! You
- must uninstall them yourself, or overwrite them by using EasyInstall to
- activate a different version of the package. You can also avoid installing
- script wrappers in the first place, if you use the ``--exclude-scripts``
- (aka ``-x``) option when you run ``develop`` to deploy the project.
+ must uninstall them yourself, or overwrite them by using pip to install a
+ different version of the package. You can also avoid installing script
+ wrappers in the first place, if you use the ``--exclude-scripts`` (aka
+ ``-x``) option when you run ``develop`` to deploy the project.
``--multi-version, -m``
"Multi-version" mode. Specifying this option prevents ``develop`` from
@@ -1818,8 +1734,8 @@ over the installation options for dependencies.
removed upon successful deployment. In multi-version mode, no specific
version of the package is available for importing, unless you use
``pkg_resources.require()`` to put it on ``sys.path``, or you are running
- a wrapper script generated by ``setuptools`` or EasyInstall. (In which
- case the wrapper script calls ``require()`` for you.)
+ a wrapper script generated by ``setuptools``. (In which case the wrapper
+ script calls ``require()`` for you.)
Note that if you install to a directory other than ``site-packages``,
this option is automatically in effect, because ``.pth`` files can only be
@@ -1872,25 +1788,6 @@ files), the ``develop`` command will use them as defaults, unless you override
them in a ``[develop]`` section or on the command line.
-``easy_install`` - Find and install packages
-============================================
-
-This command runs the `EasyInstall tool
-<easy_install.html>`_ for you. It is exactly
-equivalent to running the ``easy_install`` command. All command line arguments
-following this command are consumed and not processed further by the distutils,
-so this must be the last command listed on the command line. Please see
-the EasyInstall documentation for the options reference and usage examples.
-Normally, there is no reason to use this command via the command line, as you
-can just use ``easy_install`` directly. It's only listed here so that you know
-it's a distutils command, which means that you can:
-
-* create command aliases that use it,
-* create distutils extensions that invoke it as a subcommand, and
-* configure options for it in your ``setup.cfg`` or other distutils config
- files.
-
-
.. _egg_info:
``egg_info`` - Create egg metadata and set build tags
@@ -1949,9 +1846,9 @@ added in the following order:
(Note: Because these options modify the version number used for source and
binary distributions of your project, you should first make sure that you know
how the resulting version numbers will be interpreted by automated tools
-like EasyInstall. See the section above on `Specifying Your Project's
-Version`_ for an explanation of pre- and post-release tags, as well as tips on
-how to choose and verify a versioning scheme for your your project.)
+like pip. See the section above on `Specifying Your Project's Version`_ for an
+explanation of pre- and post-release tags, as well as tips on how to choose and
+verify a versioning scheme for your your project.)
For advanced uses, there is one other option that can be set, to change the
location of the project's ``.egg-info`` directory. Commands that need to find
@@ -2133,6 +2030,11 @@ distutils configuration file the option will be added to (or removed from).
``test`` - Build package and run a unittest suite
=================================================
+.. warning::
+ ``test`` is deprecated and will be removed in a future version. Users
+ looking for a generic test entry point independent of test runner are
+ encouraged to use `tox <https://tox.readthedocs.io>`_.
+
When doing test-driven development, or running automated builds that need
testing before they are deployed for downloading or use, it's often useful
to be able to run a project's unit tests without actually deploying the project
@@ -2178,22 +2080,21 @@ available:
If you did not set a ``test_suite`` in your ``setup()`` call, and do not
provide a ``--test-suite`` option, an error will occur.
+New in 41.5.0: Deprecated the test command.
+
.. _upload:
``upload`` - Upload source and/or egg distributions to PyPI
===========================================================
-.. warning::
- **upload** is deprecated in favor of using `twine
- <https://pypi.org/p/twine>`_
-
-The ``upload`` command is implemented and `documented
-<https://docs.python.org/3.1/distutils/uploading.html>`_
-in distutils.
+The ``upload`` command was deprecated in version 40.0 and removed in version
+42.0. Use `twine <https://pypi.org/p/twine>`_ instead.
-New in 20.1: Added keyring support.
-New in 40.0: Deprecated the upload command.
+For more information on the current best practices in uploading your packages
+to PyPI, see the Python Packaging User Guide's "Packaging Python Projects"
+tutorial specifically the section on `uploading the distribution archives
+<https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives>`_.
-----------------------------------------
@@ -2234,6 +2135,7 @@ boilerplate code in some cases.
license = BSD 3-Clause License
classifiers =
Framework :: Django
+ License :: OSI Approved :: BSD License
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
@@ -2371,6 +2273,8 @@ maintainer str
maintainer_email maintainer-email str
classifiers classifier file:, list-comma
license str
+license_file str
+license_files list-comma
description summary file:, str
long_description long-description file:, str
long_description_content_type str 38.6.0
@@ -2412,7 +2316,7 @@ tests_require list-semi
include_package_data bool
packages find:, find_namespace:, list-comma
package_dir dict
-package_data section
+package_data section (1)
exclude_package_data section
namespace_packages list-comma
py_modules list-comma
@@ -2429,6 +2333,10 @@ data_files dict 40.6.0
**find_namespace directive** - The ``find_namespace:`` directive is supported since Python >=3.3.
+Notes:
+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`.
+
Configuration API
=================
@@ -2511,6 +2419,10 @@ script defines entry points for them!
Adding ``setup()`` Arguments
----------------------------
+.. warning:: Adding arguments to setup is discouraged as such arguments
+ are only supported through imperative execution and not supported through
+ declarative config.
+
Sometimes, your commands may need additional arguments to the ``setup()``
call. You can enable this by defining entry points in the
``distutils.setup_keywords`` group. For example, if you wanted a ``setup()``
@@ -2562,6 +2474,25 @@ script using your extension lists your project in its ``setup_requires``
argument.
+Customizing Distribution Options
+--------------------------------
+
+Plugins may wish to extend or alter the options on a Distribution object to
+suit the purposes of that project. For example, a tool that infers the
+``Distribution.version`` from SCM-metadata may need to hook into the
+option finalization. To enable this feature, Setuptools offers an entry
+point "setuptools.finalize_distribution_options". That entry point must
+be a callable taking one argument (the Distribution instance).
+
+If the callable has an ``.order`` property, that value will be used to
+determine the order in which the hook is called. Lower numbers are called
+first and the default is zero (0).
+
+Plugins may read, alter, and set properties on the distribution, but each
+plugin is encouraged to load the configuration/settings for their behavior
+independently.
+
+
Adding new EGG-INFO Files
-------------------------
@@ -2678,42 +2609,6 @@ A few important points for writing revision control file finders:
inform the user of the missing program(s).
-Subclassing ``Command``
------------------------
-
-Sorry, this section isn't written yet, and neither is a lot of what's below
-this point.
-
-XXX
-
-
-Reusing ``setuptools`` Code
-===========================
-
-``ez_setup``
-------------
-
-XXX
-
-
-``setuptools.archive_util``
----------------------------
-
-XXX
-
-
-``setuptools.sandbox``
-----------------------
-
-XXX
-
-
-``setuptools.package_index``
-----------------------------
-
-XXX
-
-
Mailing List and Bug Tracker
============================
diff --git a/launcher.c b/launcher.c
index be69f0c6..23ef3ac2 100644
--- a/launcher.c
+++ b/launcher.c
@@ -37,6 +37,7 @@
#include <windows.h>
#include <tchar.h>
#include <fcntl.h>
+#include <process.h>
int child_pid=0;
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 6ca68daa..51fb1192 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -39,6 +39,8 @@ import tempfile
import textwrap
import itertools
import inspect
+import ntpath
+import posixpath
from pkgutil import get_importer
try:
@@ -331,7 +333,7 @@ class UnknownExtra(ResolutionError):
_provider_factories = {}
-PY_MAJOR = sys.version[:3]
+PY_MAJOR = '{}.{}'.format(*sys.version_info)
EGG_DIST = 3
BINARY_DIST = 2
SOURCE_DIST = 1
@@ -1401,14 +1403,30 @@ class NullProvider:
def has_resource(self, resource_name):
return self._has(self._fn(self.module_path, resource_name))
+ def _get_metadata_path(self, name):
+ return self._fn(self.egg_info, name)
+
def has_metadata(self, name):
- return self.egg_info and self._has(self._fn(self.egg_info, name))
+ if not self.egg_info:
+ return self.egg_info
+
+ path = self._get_metadata_path(name)
+ return self._has(path)
def get_metadata(self, name):
if not self.egg_info:
return ""
- value = self._get(self._fn(self.egg_info, name))
- return value.decode('utf-8') if six.PY3 else value
+ path = self._get_metadata_path(name)
+ value = self._get(path)
+ if six.PY2:
+ return value
+ try:
+ return value.decode('utf-8')
+ except UnicodeDecodeError as exc:
+ # Include the path in the error message to simplify
+ # troubleshooting, and without changing the exception type.
+ exc.reason += ' in {} file at path: {}'.format(name, path)
+ raise
def get_metadata_lines(self, name):
return yield_lines(self.get_metadata(name))
@@ -1466,10 +1484,86 @@ class NullProvider:
)
def _fn(self, base, resource_name):
+ self._validate_resource_path(resource_name)
if resource_name:
return os.path.join(base, *resource_name.split('/'))
return base
+ @staticmethod
+ def _validate_resource_path(path):
+ """
+ Validate the resource paths according to the docs.
+ https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access
+
+ >>> warned = getfixture('recwarn')
+ >>> warnings.simplefilter('always')
+ >>> vrp = NullProvider._validate_resource_path
+ >>> vrp('foo/bar.txt')
+ >>> bool(warned)
+ False
+ >>> vrp('../foo/bar.txt')
+ >>> bool(warned)
+ True
+ >>> warned.clear()
+ >>> vrp('/foo/bar.txt')
+ >>> bool(warned)
+ True
+ >>> vrp('foo/../../bar.txt')
+ >>> bool(warned)
+ True
+ >>> warned.clear()
+ >>> vrp('foo/f../bar.txt')
+ >>> bool(warned)
+ False
+
+ Windows path separators are straight-up disallowed.
+ >>> vrp(r'\\foo/bar.txt')
+ Traceback (most recent call last):
+ ...
+ ValueError: Use of .. or absolute path in a resource path \
+is not allowed.
+
+ >>> vrp(r'C:\\foo/bar.txt')
+ Traceback (most recent call last):
+ ...
+ ValueError: Use of .. or absolute path in a resource path \
+is not allowed.
+
+ Blank values are allowed
+
+ >>> vrp('')
+ >>> bool(warned)
+ False
+
+ Non-string values are not.
+
+ >>> vrp(None)
+ Traceback (most recent call last):
+ ...
+ AttributeError: ...
+ """
+ invalid = (
+ os.path.pardir in path.split(posixpath.sep) or
+ posixpath.isabs(path) or
+ ntpath.isabs(path)
+ )
+ if not invalid:
+ return
+
+ msg = "Use of .. or absolute path in a resource path is not allowed."
+
+ # Aggressively disallow Windows absolute paths
+ if ntpath.isabs(path) and not posixpath.isabs(path):
+ raise ValueError(msg)
+
+ # for compatibility, warn; in future
+ # raise ValueError(msg)
+ warnings.warn(
+ msg[:-1] + " and will raise exceptions in a future release.",
+ DeprecationWarning,
+ stacklevel=4,
+ )
+
def _get(self, path):
if hasattr(self.loader, 'get_data'):
return self.loader.get_data(path)
@@ -1790,6 +1884,9 @@ class FileMetadata(EmptyProvider):
def __init__(self, path):
self.path = path
+ def _get_metadata_path(self, name):
+ return self.path
+
def has_metadata(self, name):
return name == 'PKG-INFO' and os.path.isfile(self.path)
@@ -1888,7 +1985,7 @@ def find_eggs_in_zip(importer, path_item, only=False):
if only:
# don't yield nested distros
return
- for subitem in metadata.resource_listdir('/'):
+ for subitem in metadata.resource_listdir(''):
if _is_egg_path(subitem):
subpath = os.path.join(path_item, subitem)
dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
@@ -2583,10 +2680,14 @@ class Distribution:
try:
return self._version
except AttributeError:
- version = _version_from_file(self._get_metadata(self.PKG_INFO))
+ version = self._get_version()
if version is None:
- tmpl = "Missing 'Version:' header and/or %s file"
- raise ValueError(tmpl % self.PKG_INFO, self)
+ path = self._get_metadata_path_for_display(self.PKG_INFO)
+ msg = (
+ "Missing 'Version:' header and/or {} file at path: {}"
+ ).format(self.PKG_INFO, path)
+ raise ValueError(msg, self)
+
return version
@property
@@ -2644,11 +2745,34 @@ class Distribution:
)
return deps
+ def _get_metadata_path_for_display(self, name):
+ """
+ Return the path to the given metadata file, if available.
+ """
+ try:
+ # We need to access _get_metadata_path() on the provider object
+ # directly rather than through this class's __getattr__()
+ # since _get_metadata_path() is marked private.
+ path = self._provider._get_metadata_path(name)
+
+ # Handle exceptions e.g. in case the distribution's metadata
+ # provider doesn't support _get_metadata_path().
+ except Exception:
+ return '[could not detect]'
+
+ return path
+
def _get_metadata(self, name):
if self.has_metadata(name):
for line in self.get_metadata_lines(name):
yield line
+ def _get_version(self):
+ lines = self._get_metadata(self.PKG_INFO)
+ version = _version_from_file(lines)
+
+ return version
+
def activate(self, path=None, replace=False):
"""Ensure distribution is importable on `path` (default=sys.path)"""
if path is None:
@@ -2867,7 +2991,7 @@ class EggInfoDistribution(Distribution):
take an extra step and try to get the version number from
the metadata file itself instead of the filename.
"""
- md_version = _version_from_file(self._get_metadata(self.PKG_INFO))
+ md_version = self._get_version()
if md_version:
self._version = md_version
return self
@@ -2985,6 +3109,7 @@ class Requirement(packaging.requirements.Requirement):
self.extras = tuple(map(safe_extra, self.extras))
self.hashCmp = (
self.key,
+ self.url,
self.specifier,
frozenset(self.extras),
str(self.marker) if self.marker else None,
diff --git a/pkg_resources/api_tests.txt b/pkg_resources/api_tests.txt
index 0a75170e..7ae5a038 100644
--- a/pkg_resources/api_tests.txt
+++ b/pkg_resources/api_tests.txt
@@ -36,7 +36,7 @@ Distributions have various introspectable attributes::
>>> dist.version
'0.9'
- >>> dist.py_version == sys.version[:3]
+ >>> dist.py_version == '{}.{}'.format(*sys.version_info)
True
>>> print(dist.platform)
diff --git a/pkg_resources/tests/data/my-test-package-source/setup.cfg b/pkg_resources/tests/data/my-test-package-source/setup.cfg
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package-source/setup.cfg
diff --git a/pkg_resources/tests/data/my-test-package-source/setup.py b/pkg_resources/tests/data/my-test-package-source/setup.py
new file mode 100644
index 00000000..fe80d28f
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package-source/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+setuptools.setup(
+ name="my-test-package",
+ version="1.0",
+ zip_safe=True,
+)
diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO
new file mode 100644
index 00000000..7328e3f7
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: my-test-package
+Version: 1.0
+Summary: UNKNOWN
+Home-page: UNKNOWN
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt
new file mode 100644
index 00000000..3c4ee167
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/SOURCES.txt
@@ -0,0 +1,7 @@
+setup.cfg
+setup.py
+my_test_package.egg-info/PKG-INFO
+my_test_package.egg-info/SOURCES.txt
+my_test_package.egg-info/dependency_links.txt
+my_test_package.egg-info/top_level.txt
+my_test_package.egg-info/zip-safe \ No newline at end of file
diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/top_level.txt
@@ -0,0 +1 @@
+
diff --git a/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package_unpacked-egg/my_test_package-1.0-py3.7.egg/EGG-INFO/zip-safe
@@ -0,0 +1 @@
+
diff --git a/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg b/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg
new file mode 100644
index 00000000..5115b895
--- /dev/null
+++ b/pkg_resources/tests/data/my-test-package_zipped-egg/my_test_package-1.0-py3.7.egg
Binary files differ
diff --git a/pkg_resources/tests/test_find_distributions.py b/pkg_resources/tests/test_find_distributions.py
index d735c590..f9594422 100644
--- a/pkg_resources/tests/test_find_distributions.py
+++ b/pkg_resources/tests/test_find_distributions.py
@@ -1,17 +1,9 @@
-import subprocess
-import sys
-
+import py
import pytest
import pkg_resources
-SETUP_TEMPLATE = """
-import setuptools
-setuptools.setup(
- name="my-test-package",
- version="1.0",
- zip_safe=True,
-)
-""".lstrip()
+
+TESTS_DATA_DIR = py.path.local(__file__).dirpath('data')
class TestFindDistributions:
@@ -21,46 +13,22 @@ class TestFindDistributions:
target_dir = tmpdir.mkdir('target')
# place a .egg named directory in the target that is not an egg:
target_dir.mkdir('not.an.egg')
- return str(target_dir)
-
- @pytest.fixture
- def project_dir(self, tmpdir):
- project_dir = tmpdir.mkdir('my-test-package')
- (project_dir / "setup.py").write(SETUP_TEMPLATE)
- return str(project_dir)
+ return target_dir
def test_non_egg_dir_named_egg(self, target_dir):
- dists = pkg_resources.find_distributions(target_dir)
+ dists = pkg_resources.find_distributions(str(target_dir))
assert not list(dists)
- def test_standalone_egg_directory(self, project_dir, target_dir):
- # install this distro as an unpacked egg:
- args = [
- sys.executable,
- '-c', 'from setuptools.command.easy_install import main; main()',
- '-mNx',
- '-d', target_dir,
- '--always-unzip',
- project_dir,
- ]
- subprocess.check_call(args)
- dists = pkg_resources.find_distributions(target_dir)
+ def test_standalone_egg_directory(self, target_dir):
+ (TESTS_DATA_DIR / 'my-test-package_unpacked-egg').copy(target_dir)
+ dists = pkg_resources.find_distributions(str(target_dir))
assert [dist.project_name for dist in dists] == ['my-test-package']
- dists = pkg_resources.find_distributions(target_dir, only=True)
+ dists = pkg_resources.find_distributions(str(target_dir), only=True)
assert not list(dists)
- def test_zipped_egg(self, project_dir, target_dir):
- # install this distro as an unpacked egg:
- args = [
- sys.executable,
- '-c', 'from setuptools.command.easy_install import main; main()',
- '-mNx',
- '-d', target_dir,
- '--zip-ok',
- project_dir,
- ]
- subprocess.check_call(args)
- dists = pkg_resources.find_distributions(target_dir)
+ def test_zipped_egg(self, target_dir):
+ (TESTS_DATA_DIR / 'my-test-package_zipped-egg').copy(target_dir)
+ dists = pkg_resources.find_distributions(str(target_dir))
assert [dist.project_name for dist in dists] == ['my-test-package']
- dists = pkg_resources.find_distributions(target_dir, only=True)
+ dists = pkg_resources.find_distributions(str(target_dir), only=True)
assert not list(dists)
diff --git a/pkg_resources/tests/test_pkg_resources.py b/pkg_resources/tests/test_pkg_resources.py
index 416f9aff..5960868a 100644
--- a/pkg_resources/tests/test_pkg_resources.py
+++ b/pkg_resources/tests/test_pkg_resources.py
@@ -17,6 +17,8 @@ try:
except ImportError:
import mock
+from pkg_resources import DistInfoDistribution, Distribution, EggInfoDistribution
+from setuptools.extern import six
from pkg_resources.extern.six.moves import map
from pkg_resources.extern.six import text_type, string_types
@@ -93,7 +95,6 @@ class TestZipProvider:
expected_root = ['data.dat', 'mod.py', 'subdir']
assert sorted(zp.resource_listdir('')) == expected_root
- assert sorted(zp.resource_listdir('/')) == expected_root
expected_subdir = ['data2.dat', 'mod2.py']
assert sorted(zp.resource_listdir('subdir')) == expected_subdir
@@ -106,7 +107,6 @@ class TestZipProvider:
zp2 = pkg_resources.ZipProvider(mod2)
assert sorted(zp2.resource_listdir('')) == expected_subdir
- assert sorted(zp2.resource_listdir('/')) == expected_subdir
assert zp2.resource_listdir('subdir') == []
assert zp2.resource_listdir('subdir/') == []
@@ -192,6 +192,142 @@ class TestResourceManager:
subprocess.check_call(cmd)
+def make_test_distribution(metadata_path, metadata):
+ """
+ Make a test Distribution object, and return it.
+
+ :param metadata_path: the path to the metadata file that should be
+ created. This should be inside a distribution directory that should
+ also be created. For example, an argument value might end with
+ "<project>.dist-info/METADATA".
+ :param metadata: the desired contents of the metadata file, as bytes.
+ """
+ dist_dir = os.path.dirname(metadata_path)
+ os.mkdir(dist_dir)
+ with open(metadata_path, 'wb') as f:
+ f.write(metadata)
+ dists = list(pkg_resources.distributions_from_metadata(dist_dir))
+ dist, = dists
+
+ return dist
+
+
+def test_get_metadata__bad_utf8(tmpdir):
+ """
+ Test a metadata file with bytes that can't be decoded as utf-8.
+ """
+ filename = 'METADATA'
+ # Convert the tmpdir LocalPath object to a string before joining.
+ metadata_path = os.path.join(str(tmpdir), 'foo.dist-info', filename)
+ # Encode a non-ascii string with the wrong encoding (not utf-8).
+ metadata = 'née'.encode('iso-8859-1')
+ dist = make_test_distribution(metadata_path, metadata=metadata)
+
+ if six.PY2:
+ # In Python 2, get_metadata() doesn't do any decoding.
+ actual = dist.get_metadata(filename)
+ assert actual == metadata
+ return
+
+ # Otherwise, we are in the Python 3 case.
+ with pytest.raises(UnicodeDecodeError) as excinfo:
+ dist.get_metadata(filename)
+
+ exc = excinfo.value
+ actual = str(exc)
+ expected = (
+ # The error message starts with "'utf-8' codec ..." However, the
+ # spelling of "utf-8" can vary (e.g. "utf8") so we don't include it
+ "codec can't decode byte 0xe9 in position 1: "
+ 'invalid continuation byte in METADATA file at path: '
+ )
+ assert expected in actual, 'actual: {}'.format(actual)
+ assert actual.endswith(metadata_path), 'actual: {}'.format(actual)
+
+
+# TODO: remove this in favor of Path.touch() when Python 2 is dropped.
+def touch_file(path):
+ """
+ Create an empty file.
+ """
+ with open(path, 'w'):
+ pass
+
+
+def make_distribution_no_version(tmpdir, basename):
+ """
+ Create a distribution directory with no file containing the version.
+ """
+ # Convert the LocalPath object to a string before joining.
+ dist_dir = os.path.join(str(tmpdir), basename)
+ os.mkdir(dist_dir)
+ # Make the directory non-empty so distributions_from_metadata()
+ # will detect it and yield it.
+ touch_file(os.path.join(dist_dir, 'temp.txt'))
+
+ dists = list(pkg_resources.distributions_from_metadata(dist_dir))
+ assert len(dists) == 1
+ dist, = dists
+
+ return dist, dist_dir
+
+
+@pytest.mark.parametrize(
+ 'suffix, expected_filename, expected_dist_type',
+ [
+ ('egg-info', 'PKG-INFO', EggInfoDistribution),
+ ('dist-info', 'METADATA', DistInfoDistribution),
+ ],
+)
+def test_distribution_version_missing(tmpdir, suffix, expected_filename,
+ expected_dist_type):
+ """
+ Test Distribution.version when the "Version" header is missing.
+ """
+ basename = 'foo.{}'.format(suffix)
+ dist, dist_dir = make_distribution_no_version(tmpdir, basename)
+
+ expected_text = (
+ "Missing 'Version:' header and/or {} file at path: "
+ ).format(expected_filename)
+ metadata_path = os.path.join(dist_dir, expected_filename)
+
+ # Now check the exception raised when the "version" attribute is accessed.
+ with pytest.raises(ValueError) as excinfo:
+ dist.version
+
+ err = str(excinfo.value)
+ # Include a string expression after the assert so the full strings
+ # will be visible for inspection on failure.
+ assert expected_text in err, str((expected_text, err))
+
+ # Also check the args passed to the ValueError.
+ msg, dist = excinfo.value.args
+ assert expected_text in msg
+ # Check that the message portion contains the path.
+ assert metadata_path in msg, str((metadata_path, msg))
+ assert type(dist) == expected_dist_type
+
+
+def test_distribution_version_missing_undetected_path():
+ """
+ Test Distribution.version when the "Version" header is missing and
+ the path can't be detected.
+ """
+ # Create a Distribution object with no metadata argument, which results
+ # in an empty metadata provider.
+ dist = Distribution('/foo')
+ with pytest.raises(ValueError) as excinfo:
+ dist.version
+
+ msg, dist = excinfo.value.args
+ expected = (
+ "Missing 'Version:' header and/or PKG-INFO file at path: "
+ '[could not detect]'
+ )
+ assert msg == expected
+
+
class TestDeepVersionLookupDistutils:
@pytest.fixture
def env(self, tmpdir):
diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py
index 86afcf74..93fa7114 100644
--- a/pkg_resources/tests/test_resources.py
+++ b/pkg_resources/tests/test_resources.py
@@ -116,7 +116,7 @@ class TestDistro:
self.checkFooPkg(d)
d = Distribution("/some/path")
- assert d.py_version == sys.version[:3]
+ assert d.py_version == '{}.{}'.format(*sys.version_info)
assert d.platform is None
def testDistroParse(self):
@@ -520,6 +520,11 @@ class TestRequirements:
assert r1 == r2
assert str(r1) == str(r2)
assert str(r2) == "Twisted==1.2c1,>=1.2"
+ assert (
+ Requirement("Twisted")
+ !=
+ Requirement("Twisted @ https://localhost/twisted.zip")
+ )
def testBasicContains(self):
r = Requirement("Twisted>=1.2")
@@ -546,11 +551,23 @@ class TestRequirements:
==
hash((
"twisted",
+ None,
packaging.specifiers.SpecifierSet(">=1.2"),
frozenset(["foo", "bar"]),
None
))
)
+ assert (
+ hash(Requirement.parse("Twisted @ https://localhost/twisted.zip"))
+ ==
+ hash((
+ "twisted",
+ "https://localhost/twisted.zip",
+ packaging.specifiers.SpecifierSet(),
+ frozenset(),
+ None
+ ))
+ )
def testVersionEquality(self):
r1 = Requirement.parse("foo==0.3a2")
diff --git a/pyproject.toml b/pyproject.toml
index 4ef804ec..f0fd8521 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,7 @@
[build-system]
-requires = ["wheel"]
+requires = ["setuptools >= 40.8", "wheel"]
build-backend = "setuptools.build_meta"
+backend-path = ["."]
[tool.towncrier]
package = "setuptools"
diff --git a/pytest.ini b/pytest.ini
index 1c5b6b09..0bc1ec01 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,7 +1,10 @@
[pytest]
-addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -rsxX
-norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern .*
+addopts=--doctest-modules --doctest-glob=pkg_resources/api_tests.txt -r sxX
+norecursedirs=dist build *.egg setuptools/extern pkg_resources/extern pkg_resources/tests/data tools .*
flake8-ignore =
setuptools/site-patch.py F821
setuptools/py*compat.py F811
doctest_optionflags=ELLIPSIS ALLOW_UNICODE
+filterwarnings =
+ # https://github.com/pypa/setuptools/issues/1823
+ ignore:bdist_wininst command is deprecated
diff --git a/setup.cfg b/setup.cfg
index dd404469..68b49d73 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,8 +1,3 @@
-[bumpversion]
-current_version = 40.6.3
-commit = True
-tag = True
-
[egg_info]
tag_build = .post
tag_date = 1
@@ -23,6 +18,47 @@ formats = zip
universal = 1
[metadata]
+name = setuptools
+version = 42.0.2
+description = Easily download, build, install, upgrade, and uninstall Python packages
+author = Python Packaging Authority
+author_email = distutils-sig@python.org
+long_description = file: README.rst
+long_description_content_type = text/x-rst; charset=UTF-8
license_file = LICENSE
+keywords = CPAN PyPI distutils eggs package management
+url = https://github.com/pypa/setuptools
+project_urls =
+ Documentation = https://setuptools.readthedocs.io/
+classifiers =
+ Development Status :: 5 - Production/Stable
+ Intended Audience :: Developers
+ License :: OSI Approved :: MIT License
+ Operating System :: OS Independent
+ Programming Language :: Python :: 2
+ Programming Language :: Python :: 2.7
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.4
+ Programming Language :: Python :: 3.5
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
+ Topic :: Software Development :: Libraries :: Python Modules
+ Topic :: System :: Archiving :: Packaging
+ Topic :: System :: Systems Administration
+ Topic :: Utilities
+
+[options]
+zip_safe = True
+python_requires = >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
+py_modules = easy_install
+packages = find:
+
+[options.packages.find]
+exclude = *.tests
-[bumpversion:file:setup.py]
+[options.extras_require]
+ssl =
+ wincertstore==0.2; sys_platform=='win32'
+certs =
+ certifi==2016.9.26
diff --git a/setup.py b/setup.py
index 67d5691d..277b6640 100755
--- a/setup.py
+++ b/setup.py
@@ -3,10 +3,8 @@
Distutils setup file, used to install or test 'setuptools'
"""
-import io
import os
import sys
-import textwrap
import setuptools
@@ -46,13 +44,9 @@ def _gen_console_scripts():
if any(os.environ.get(var) not in (None, "", "0") for var in var_names):
return
tmpl = "easy_install-{shortver} = setuptools.command.easy_install:main"
- yield tmpl.format(shortver=sys.version[:3])
+ yield tmpl.format(shortver='{}.{}'.format(*sys.version_info))
-readme_path = os.path.join(here, 'README.rst')
-with io.open(readme_path, encoding='utf-8') as readme_file:
- long_description = readme_file.read()
-
package_data = dict(
setuptools=['script (dev).tmpl', 'script.tmpl', 'site-patch.py'],
)
@@ -88,31 +82,20 @@ def pypi_link(pkg_filename):
setup_params = dict(
- name="setuptools",
- version="40.6.3",
- description=(
- "Easily download, build, install, upgrade, and uninstall "
- "Python packages"
- ),
- author="Python Packaging Authority",
- author_email="distutils-sig@python.org",
- long_description=long_description,
- long_description_content_type='text/x-rst; charset=UTF-8',
- keywords="CPAN PyPI distutils eggs package management",
- url="https://github.com/pypa/setuptools",
- project_urls={
- "Documentation": "https://setuptools.readthedocs.io/",
- },
src_root=None,
- packages=setuptools.find_packages(exclude=['*.tests']),
package_data=package_data,
- py_modules=['easy_install'],
- zip_safe=True,
entry_points={
"distutils.commands": [
"%(cmd)s = setuptools.command.%(cmd)s:%(cmd)s" % locals()
for cmd in read_commands()
],
+ "setuptools.finalize_distribution_options": [
+ "parent_finalize = setuptools.dist:_Distribution.finalize_options",
+ "features = setuptools.dist:Distribution._finalize_feature_opts",
+ "keywords = setuptools.dist:Distribution._finalize_setup_keywords",
+ "2to3_doctests = "
+ "setuptools.dist:Distribution._finalize_2to3_doctests",
+ ],
"distutils.setup_keywords": [
"eager_resources = setuptools.dist:assert_string_list",
"namespace_packages = setuptools.dist:check_nsp",
@@ -153,28 +136,6 @@ setup_params = dict(
"setuptools.installation":
['eggsecutable = setuptools.command.easy_install:bootstrap'],
},
- classifiers=textwrap.dedent("""
- Development Status :: 5 - Production/Stable
- Intended Audience :: Developers
- License :: OSI Approved :: MIT License
- Operating System :: OS Independent
- Programming Language :: Python :: 2
- Programming Language :: Python :: 2.7
- Programming Language :: Python :: 3
- Programming Language :: Python :: 3.4
- Programming Language :: Python :: 3.5
- Programming Language :: Python :: 3.6
- Programming Language :: Python :: 3.7
- Topic :: Software Development :: Libraries :: Python Modules
- Topic :: System :: Archiving :: Packaging
- Topic :: System :: Systems Administration
- Topic :: Utilities
- """).strip().splitlines(),
- python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
- extras_require={
- "ssl:sys_platform=='win32'": "wincertstore==0.2",
- "certs": "certifi==2016.9.26",
- },
dependency_links=[
pypi_link(
'certifi-2016.9.26.tar.gz#md5=baa81e951a29958563689d868ef1064d',
@@ -183,7 +144,6 @@ setup_params = dict(
'wincertstore-0.2.zip#md5=ae728f2f007185648d0c7a8679b361e2',
),
],
- scripts=[],
setup_requires=[
] + wheel,
)
diff --git a/setuptools/__init__.py b/setuptools/__init__.py
index e438036a..a71b2bbd 100644
--- a/setuptools/__init__.py
+++ b/setuptools/__init__.py
@@ -5,12 +5,14 @@ import sys
import functools
import distutils.core
import distutils.filelist
+import re
+from distutils.errors import DistutilsOptionError
from distutils.util import convert_path
from fnmatch import fnmatchcase
from ._deprecation_warning import SetuptoolsDeprecationWarning
-from setuptools.extern.six import PY3
+from setuptools.extern.six import PY3, string_types
from setuptools.extern.six.moves import filter, map
import setuptools.version
@@ -161,6 +163,37 @@ class Command(_Command):
_Command.__init__(self, dist)
vars(self).update(kw)
+ def _ensure_stringlike(self, option, what, default=None):
+ val = getattr(self, option)
+ if val is None:
+ setattr(self, option, default)
+ return default
+ elif not isinstance(val, string_types):
+ raise DistutilsOptionError("'%s' must be a %s (got `%s`)"
+ % (option, what, val))
+ return val
+
+ def ensure_string_list(self, option):
+ r"""Ensure that 'option' is a list of strings. If 'option' is
+ currently a string, we split it either on /,\s*/ or /\s+/, so
+ "foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
+ ["foo", "bar", "baz"].
+ """
+ val = getattr(self, option)
+ if val is None:
+ return
+ elif isinstance(val, string_types):
+ setattr(self, option, re.split(r',\s*|\s+', val))
+ else:
+ if isinstance(val, list):
+ ok = all(isinstance(v, string_types) for v in val)
+ else:
+ ok = False
+ if not ok:
+ raise DistutilsOptionError(
+ "'%s' must be a list of strings (got %r)"
+ % (option, val))
+
def reinitialize_command(self, command, reinit_subcommands=0, **kw):
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
vars(cmd).update(kw)
diff --git a/setuptools/_imp.py b/setuptools/_imp.py
new file mode 100644
index 00000000..a3cce9b2
--- /dev/null
+++ b/setuptools/_imp.py
@@ -0,0 +1,73 @@
+"""
+Re-implementation of find_module and get_frozen_object
+from the deprecated imp module.
+"""
+
+import os
+import importlib.util
+import importlib.machinery
+
+from .py34compat import module_from_spec
+
+
+PY_SOURCE = 1
+PY_COMPILED = 2
+C_EXTENSION = 3
+C_BUILTIN = 6
+PY_FROZEN = 7
+
+
+def find_module(module, paths=None):
+ """Just like 'imp.find_module()', but with package support"""
+ spec = importlib.util.find_spec(module, paths)
+ if spec is None:
+ raise ImportError("Can't find %s" % module)
+ if not spec.has_location and hasattr(spec, 'submodule_search_locations'):
+ spec = importlib.util.spec_from_loader('__init__.py', spec.loader)
+
+ kind = -1
+ file = None
+ static = isinstance(spec.loader, type)
+ if spec.origin == 'frozen' or static and issubclass(
+ spec.loader, importlib.machinery.FrozenImporter):
+ kind = PY_FROZEN
+ path = None # imp compabilty
+ suffix = mode = '' # imp compability
+ elif spec.origin == 'built-in' or static and issubclass(
+ spec.loader, importlib.machinery.BuiltinImporter):
+ kind = C_BUILTIN
+ path = None # imp compabilty
+ suffix = mode = '' # imp compability
+ elif spec.has_location:
+ path = spec.origin
+ suffix = os.path.splitext(path)[1]
+ mode = 'r' if suffix in importlib.machinery.SOURCE_SUFFIXES else 'rb'
+
+ if suffix in importlib.machinery.SOURCE_SUFFIXES:
+ kind = PY_SOURCE
+ elif suffix in importlib.machinery.BYTECODE_SUFFIXES:
+ kind = PY_COMPILED
+ elif suffix in importlib.machinery.EXTENSION_SUFFIXES:
+ kind = C_EXTENSION
+
+ if kind in {PY_SOURCE, PY_COMPILED}:
+ file = open(path, mode)
+ else:
+ path = None
+ suffix = mode = ''
+
+ return file, path, (suffix, mode, kind)
+
+
+def get_frozen_object(module, paths=None):
+ spec = importlib.util.find_spec(module, paths)
+ if not spec:
+ raise ImportError("Can't find %s" % module)
+ return spec.loader.get_code(module)
+
+
+def get_module(module, paths, info):
+ spec = importlib.util.find_spec(module, paths)
+ if not spec:
+ raise ImportError("Can't find %s" % module)
+ return module_from_spec(spec)
diff --git a/setuptools/_vendor/ordered_set.py b/setuptools/_vendor/ordered_set.py
new file mode 100644
index 00000000..14876000
--- /dev/null
+++ b/setuptools/_vendor/ordered_set.py
@@ -0,0 +1,488 @@
+"""
+An OrderedSet is a custom MutableSet that remembers its order, so that every
+entry has an index that can be looked up.
+
+Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger,
+and released under the MIT license.
+"""
+import itertools as it
+from collections import deque
+
+try:
+ # Python 3
+ from collections.abc import MutableSet, Sequence
+except ImportError:
+ # Python 2.7
+ from collections import MutableSet, Sequence
+
+SLICE_ALL = slice(None)
+__version__ = "3.1"
+
+
+def is_iterable(obj):
+ """
+ Are we being asked to look up a list of things, instead of a single thing?
+ We check for the `__iter__` attribute so that this can cover types that
+ don't have to be known by this module, such as NumPy arrays.
+
+ Strings, however, should be considered as atomic values to look up, not
+ iterables. The same goes for tuples, since they are immutable and therefore
+ valid entries.
+
+ We don't need to check for the Python 2 `unicode` type, because it doesn't
+ have an `__iter__` attribute anyway.
+ """
+ return (
+ hasattr(obj, "__iter__")
+ and not isinstance(obj, str)
+ and not isinstance(obj, tuple)
+ )
+
+
+class OrderedSet(MutableSet, Sequence):
+ """
+ An OrderedSet is a custom MutableSet that remembers its order, so that
+ every entry has an index that can be looked up.
+
+ Example:
+ >>> OrderedSet([1, 1, 2, 3, 2])
+ OrderedSet([1, 2, 3])
+ """
+
+ def __init__(self, iterable=None):
+ self.items = []
+ self.map = {}
+ if iterable is not None:
+ self |= iterable
+
+ def __len__(self):
+ """
+ Returns the number of unique elements in the ordered set
+
+ Example:
+ >>> len(OrderedSet([]))
+ 0
+ >>> len(OrderedSet([1, 2]))
+ 2
+ """
+ return len(self.items)
+
+ def __getitem__(self, index):
+ """
+ Get the item at a given index.
+
+ If `index` is a slice, you will get back that slice of items, as a
+ new OrderedSet.
+
+ If `index` is a list or a similar iterable, you'll get a list of
+ items corresponding to those indices. This is similar to NumPy's
+ "fancy indexing". The result is not an OrderedSet because you may ask
+ for duplicate indices, and the number of elements returned should be
+ the number of elements asked for.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset[1]
+ 2
+ """
+ if isinstance(index, slice) and index == SLICE_ALL:
+ return self.copy()
+ elif is_iterable(index):
+ return [self.items[i] for i in index]
+ elif hasattr(index, "__index__") or isinstance(index, slice):
+ result = self.items[index]
+ if isinstance(result, list):
+ return self.__class__(result)
+ else:
+ return result
+ else:
+ raise TypeError("Don't know how to index an OrderedSet by %r" % index)
+
+ def copy(self):
+ """
+ Return a shallow copy of this object.
+
+ Example:
+ >>> this = OrderedSet([1, 2, 3])
+ >>> other = this.copy()
+ >>> this == other
+ True
+ >>> this is other
+ False
+ """
+ return self.__class__(self)
+
+ def __getstate__(self):
+ if len(self) == 0:
+ # The state can't be an empty list.
+ # We need to return a truthy value, or else __setstate__ won't be run.
+ #
+ # This could have been done more gracefully by always putting the state
+ # in a tuple, but this way is backwards- and forwards- compatible with
+ # previous versions of OrderedSet.
+ return (None,)
+ else:
+ return list(self)
+
+ def __setstate__(self, state):
+ if state == (None,):
+ self.__init__([])
+ else:
+ self.__init__(state)
+
+ def __contains__(self, key):
+ """
+ Test if the item is in this ordered set
+
+ Example:
+ >>> 1 in OrderedSet([1, 3, 2])
+ True
+ >>> 5 in OrderedSet([1, 3, 2])
+ False
+ """
+ return key in self.map
+
+ def add(self, key):
+ """
+ Add `key` as an item to this OrderedSet, then return its index.
+
+ If `key` is already in the OrderedSet, return the index it already
+ had.
+
+ Example:
+ >>> oset = OrderedSet()
+ >>> oset.append(3)
+ 0
+ >>> print(oset)
+ OrderedSet([3])
+ """
+ if key not in self.map:
+ self.map[key] = len(self.items)
+ self.items.append(key)
+ return self.map[key]
+
+ append = add
+
+ def update(self, sequence):
+ """
+ Update the set with the given iterable sequence, then return the index
+ of the last element inserted.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset.update([3, 1, 5, 1, 4])
+ 4
+ >>> print(oset)
+ OrderedSet([1, 2, 3, 5, 4])
+ """
+ item_index = None
+ try:
+ for item in sequence:
+ item_index = self.add(item)
+ except TypeError:
+ raise ValueError(
+ "Argument needs to be an iterable, got %s" % type(sequence)
+ )
+ return item_index
+
+ def index(self, key):
+ """
+ Get the index of a given entry, raising an IndexError if it's not
+ present.
+
+ `key` can be an iterable of entries that is not a string, in which case
+ this returns a list of indices.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset.index(2)
+ 1
+ """
+ if is_iterable(key):
+ return [self.index(subkey) for subkey in key]
+ return self.map[key]
+
+ # Provide some compatibility with pd.Index
+ get_loc = index
+ get_indexer = index
+
+ def pop(self):
+ """
+ Remove and return the last element from the set.
+
+ Raises KeyError if the set is empty.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset.pop()
+ 3
+ """
+ if not self.items:
+ raise KeyError("Set is empty")
+
+ elem = self.items[-1]
+ del self.items[-1]
+ del self.map[elem]
+ return elem
+
+ def discard(self, key):
+ """
+ Remove an element. Do not raise an exception if absent.
+
+ The MutableSet mixin uses this to implement the .remove() method, which
+ *does* raise an error when asked to remove a non-existent item.
+
+ Example:
+ >>> oset = OrderedSet([1, 2, 3])
+ >>> oset.discard(2)
+ >>> print(oset)
+ OrderedSet([1, 3])
+ >>> oset.discard(2)
+ >>> print(oset)
+ OrderedSet([1, 3])
+ """
+ if key in self:
+ i = self.map[key]
+ del self.items[i]
+ del self.map[key]
+ for k, v in self.map.items():
+ if v >= i:
+ self.map[k] = v - 1
+
+ def clear(self):
+ """
+ Remove all items from this OrderedSet.
+ """
+ del self.items[:]
+ self.map.clear()
+
+ def __iter__(self):
+ """
+ Example:
+ >>> list(iter(OrderedSet([1, 2, 3])))
+ [1, 2, 3]
+ """
+ return iter(self.items)
+
+ def __reversed__(self):
+ """
+ Example:
+ >>> list(reversed(OrderedSet([1, 2, 3])))
+ [3, 2, 1]
+ """
+ return reversed(self.items)
+
+ def __repr__(self):
+ if not self:
+ return "%s()" % (self.__class__.__name__,)
+ return "%s(%r)" % (self.__class__.__name__, list(self))
+
+ def __eq__(self, other):
+ """
+ Returns true if the containers have the same items. If `other` is a
+ Sequence, then order is checked, otherwise it is ignored.
+
+ Example:
+ >>> oset = OrderedSet([1, 3, 2])
+ >>> oset == [1, 3, 2]
+ True
+ >>> oset == [1, 2, 3]
+ False
+ >>> oset == [2, 3]
+ False
+ >>> oset == OrderedSet([3, 2, 1])
+ False
+ """
+ # In Python 2 deque is not a Sequence, so treat it as one for
+ # consistent behavior with Python 3.
+ if isinstance(other, (Sequence, deque)):
+ # Check that this OrderedSet contains the same elements, in the
+ # same order, as the other object.
+ return list(self) == list(other)
+ try:
+ other_as_set = set(other)
+ except TypeError:
+ # If `other` can't be converted into a set, it's not equal.
+ return False
+ else:
+ return set(self) == other_as_set
+
+ def union(self, *sets):
+ """
+ Combines all unique items.
+ Each items order is defined by its first appearance.
+
+ Example:
+ >>> oset = OrderedSet.union(OrderedSet([3, 1, 4, 1, 5]), [1, 3], [2, 0])
+ >>> print(oset)
+ OrderedSet([3, 1, 4, 5, 2, 0])
+ >>> oset.union([8, 9])
+ OrderedSet([3, 1, 4, 5, 2, 0, 8, 9])
+ >>> oset | {10}
+ OrderedSet([3, 1, 4, 5, 2, 0, 10])
+ """
+ cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
+ containers = map(list, it.chain([self], sets))
+ items = it.chain.from_iterable(containers)
+ return cls(items)
+
+ def __and__(self, other):
+ # the parent implementation of this is backwards
+ return self.intersection(other)
+
+ def intersection(self, *sets):
+ """
+ Returns elements in common between all sets. Order is defined only
+ by the first set.
+
+ Example:
+ >>> oset = OrderedSet.intersection(OrderedSet([0, 1, 2, 3]), [1, 2, 3])
+ >>> print(oset)
+ OrderedSet([1, 2, 3])
+ >>> oset.intersection([2, 4, 5], [1, 2, 3, 4])
+ OrderedSet([2])
+ >>> oset.intersection()
+ OrderedSet([1, 2, 3])
+ """
+ cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
+ if sets:
+ common = set.intersection(*map(set, sets))
+ items = (item for item in self if item in common)
+ else:
+ items = self
+ return cls(items)
+
+ def difference(self, *sets):
+ """
+ Returns all elements that are in this set but not the others.
+
+ Example:
+ >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]))
+ OrderedSet([1, 3])
+ >>> OrderedSet([1, 2, 3]).difference(OrderedSet([2]), OrderedSet([3]))
+ OrderedSet([1])
+ >>> OrderedSet([1, 2, 3]) - OrderedSet([2])
+ OrderedSet([1, 3])
+ >>> OrderedSet([1, 2, 3]).difference()
+ OrderedSet([1, 2, 3])
+ """
+ cls = self.__class__
+ if sets:
+ other = set.union(*map(set, sets))
+ items = (item for item in self if item not in other)
+ else:
+ items = self
+ return cls(items)
+
+ def issubset(self, other):
+ """
+ Report whether another set contains this set.
+
+ Example:
+ >>> OrderedSet([1, 2, 3]).issubset({1, 2})
+ False
+ >>> OrderedSet([1, 2, 3]).issubset({1, 2, 3, 4})
+ True
+ >>> OrderedSet([1, 2, 3]).issubset({1, 4, 3, 5})
+ False
+ """
+ if len(self) > len(other): # Fast check for obvious cases
+ return False
+ return all(item in other for item in self)
+
+ def issuperset(self, other):
+ """
+ Report whether this set contains another set.
+
+ Example:
+ >>> OrderedSet([1, 2]).issuperset([1, 2, 3])
+ False
+ >>> OrderedSet([1, 2, 3, 4]).issuperset({1, 2, 3})
+ True
+ >>> OrderedSet([1, 4, 3, 5]).issuperset({1, 2, 3})
+ False
+ """
+ if len(self) < len(other): # Fast check for obvious cases
+ return False
+ return all(item in self for item in other)
+
+ def symmetric_difference(self, other):
+ """
+ Return the symmetric difference of two OrderedSets as a new set.
+ That is, the new set will contain all elements that are in exactly
+ one of the sets.
+
+ Their order will be preserved, with elements from `self` preceding
+ elements from `other`.
+
+ Example:
+ >>> this = OrderedSet([1, 4, 3, 5, 7])
+ >>> other = OrderedSet([9, 7, 1, 3, 2])
+ >>> this.symmetric_difference(other)
+ OrderedSet([4, 5, 9, 2])
+ """
+ cls = self.__class__ if isinstance(self, OrderedSet) else OrderedSet
+ diff1 = cls(self).difference(other)
+ diff2 = cls(other).difference(self)
+ return diff1.union(diff2)
+
+ def _update_items(self, items):
+ """
+ Replace the 'items' list of this OrderedSet with a new one, updating
+ self.map accordingly.
+ """
+ self.items = items
+ self.map = {item: idx for (idx, item) in enumerate(items)}
+
+ def difference_update(self, *sets):
+ """
+ Update this OrderedSet to remove items from one or more other sets.
+
+ Example:
+ >>> this = OrderedSet([1, 2, 3])
+ >>> this.difference_update(OrderedSet([2, 4]))
+ >>> print(this)
+ OrderedSet([1, 3])
+
+ >>> this = OrderedSet([1, 2, 3, 4, 5])
+ >>> this.difference_update(OrderedSet([2, 4]), OrderedSet([1, 4, 6]))
+ >>> print(this)
+ OrderedSet([3, 5])
+ """
+ items_to_remove = set()
+ for other in sets:
+ items_to_remove |= set(other)
+ self._update_items([item for item in self.items if item not in items_to_remove])
+
+ def intersection_update(self, other):
+ """
+ Update this OrderedSet to keep only items in another set, preserving
+ their order in this set.
+
+ Example:
+ >>> this = OrderedSet([1, 4, 3, 5, 7])
+ >>> other = OrderedSet([9, 7, 1, 3, 2])
+ >>> this.intersection_update(other)
+ >>> print(this)
+ OrderedSet([1, 3, 7])
+ """
+ other = set(other)
+ self._update_items([item for item in self.items if item in other])
+
+ def symmetric_difference_update(self, other):
+ """
+ Update this OrderedSet to remove items from another set, then
+ add items from the other set that were not present in this set.
+
+ Example:
+ >>> this = OrderedSet([1, 4, 3, 5, 7])
+ >>> other = OrderedSet([9, 7, 1, 3, 2])
+ >>> this.symmetric_difference_update(other)
+ >>> print(this)
+ OrderedSet([4, 5, 9, 2])
+ """
+ items_to_add = [item for item in other if item not in self]
+ items_to_remove = set(other)
+ self._update_items(
+ [item for item in self.items if item not in items_to_remove] + items_to_add
+ )
diff --git a/setuptools/_vendor/packaging/__about__.py b/setuptools/_vendor/packaging/__about__.py
index 95d330ef..dc95138d 100644
--- a/setuptools/_vendor/packaging/__about__.py
+++ b/setuptools/_vendor/packaging/__about__.py
@@ -4,18 +4,24 @@
from __future__ import absolute_import, division, print_function
__all__ = [
- "__title__", "__summary__", "__uri__", "__version__", "__author__",
- "__email__", "__license__", "__copyright__",
+ "__title__",
+ "__summary__",
+ "__uri__",
+ "__version__",
+ "__author__",
+ "__email__",
+ "__license__",
+ "__copyright__",
]
__title__ = "packaging"
__summary__ = "Core utilities for Python packages"
__uri__ = "https://github.com/pypa/packaging"
-__version__ = "16.8"
+__version__ = "19.2"
__author__ = "Donald Stufft and individual contributors"
__email__ = "donald@stufft.io"
__license__ = "BSD or Apache License, Version 2.0"
-__copyright__ = "Copyright 2014-2016 %s" % __author__
+__copyright__ = "Copyright 2014-2019 %s" % __author__
diff --git a/setuptools/_vendor/packaging/__init__.py b/setuptools/_vendor/packaging/__init__.py
index 5ee62202..a0cf67df 100644
--- a/setuptools/_vendor/packaging/__init__.py
+++ b/setuptools/_vendor/packaging/__init__.py
@@ -4,11 +4,23 @@
from __future__ import absolute_import, division, print_function
from .__about__ import (
- __author__, __copyright__, __email__, __license__, __summary__, __title__,
- __uri__, __version__
+ __author__,
+ __copyright__,
+ __email__,
+ __license__,
+ __summary__,
+ __title__,
+ __uri__,
+ __version__,
)
__all__ = [
- "__title__", "__summary__", "__uri__", "__version__", "__author__",
- "__email__", "__license__", "__copyright__",
+ "__title__",
+ "__summary__",
+ "__uri__",
+ "__version__",
+ "__author__",
+ "__email__",
+ "__license__",
+ "__copyright__",
]
diff --git a/setuptools/_vendor/packaging/_compat.py b/setuptools/_vendor/packaging/_compat.py
index 210bb80b..25da473c 100644
--- a/setuptools/_vendor/packaging/_compat.py
+++ b/setuptools/_vendor/packaging/_compat.py
@@ -12,9 +12,9 @@ PY3 = sys.version_info[0] == 3
# flake8: noqa
if PY3:
- string_types = str,
+ string_types = (str,)
else:
- string_types = basestring,
+ string_types = (basestring,)
def with_metaclass(meta, *bases):
@@ -27,4 +27,5 @@ def with_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
- return type.__new__(metaclass, 'temporary_class', (), {})
+
+ return type.__new__(metaclass, "temporary_class", (), {})
diff --git a/setuptools/_vendor/packaging/_structures.py b/setuptools/_vendor/packaging/_structures.py
index ccc27861..68dcca63 100644
--- a/setuptools/_vendor/packaging/_structures.py
+++ b/setuptools/_vendor/packaging/_structures.py
@@ -5,7 +5,6 @@ from __future__ import absolute_import, division, print_function
class Infinity(object):
-
def __repr__(self):
return "Infinity"
@@ -33,11 +32,11 @@ class Infinity(object):
def __neg__(self):
return NegativeInfinity
+
Infinity = Infinity()
class NegativeInfinity(object):
-
def __repr__(self):
return "-Infinity"
@@ -65,4 +64,5 @@ class NegativeInfinity(object):
def __neg__(self):
return Infinity
+
NegativeInfinity = NegativeInfinity()
diff --git a/setuptools/_vendor/packaging/markers.py b/setuptools/_vendor/packaging/markers.py
index 031332a3..4bdfdb24 100644
--- a/setuptools/_vendor/packaging/markers.py
+++ b/setuptools/_vendor/packaging/markers.py
@@ -17,8 +17,11 @@ from .specifiers import Specifier, InvalidSpecifier
__all__ = [
- "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName",
- "Marker", "default_environment",
+ "InvalidMarker",
+ "UndefinedComparison",
+ "UndefinedEnvironmentName",
+ "Marker",
+ "default_environment",
]
@@ -42,7 +45,6 @@ class UndefinedEnvironmentName(ValueError):
class Node(object):
-
def __init__(self, value):
self.value = value
@@ -57,62 +59,52 @@ class Node(object):
class Variable(Node):
-
def serialize(self):
return str(self)
class Value(Node):
-
def serialize(self):
return '"{0}"'.format(self)
class Op(Node):
-
def serialize(self):
return str(self)
VARIABLE = (
- L("implementation_version") |
- L("platform_python_implementation") |
- L("implementation_name") |
- L("python_full_version") |
- L("platform_release") |
- L("platform_version") |
- L("platform_machine") |
- L("platform_system") |
- L("python_version") |
- L("sys_platform") |
- L("os_name") |
- L("os.name") | # PEP-345
- L("sys.platform") | # PEP-345
- L("platform.version") | # PEP-345
- L("platform.machine") | # PEP-345
- L("platform.python_implementation") | # PEP-345
- L("python_implementation") | # undocumented setuptools legacy
- L("extra")
+ L("implementation_version")
+ | L("platform_python_implementation")
+ | L("implementation_name")
+ | L("python_full_version")
+ | L("platform_release")
+ | L("platform_version")
+ | L("platform_machine")
+ | L("platform_system")
+ | L("python_version")
+ | L("sys_platform")
+ | L("os_name")
+ | L("os.name")
+ | L("sys.platform") # PEP-345
+ | L("platform.version") # PEP-345
+ | L("platform.machine") # PEP-345
+ | L("platform.python_implementation") # PEP-345
+ | L("python_implementation") # PEP-345
+ | L("extra") # undocumented setuptools legacy
)
ALIASES = {
- 'os.name': 'os_name',
- 'sys.platform': 'sys_platform',
- 'platform.version': 'platform_version',
- 'platform.machine': 'platform_machine',
- 'platform.python_implementation': 'platform_python_implementation',
- 'python_implementation': 'platform_python_implementation'
+ "os.name": "os_name",
+ "sys.platform": "sys_platform",
+ "platform.version": "platform_version",
+ "platform.machine": "platform_machine",
+ "platform.python_implementation": "platform_python_implementation",
+ "python_implementation": "platform_python_implementation",
}
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
VERSION_CMP = (
- L("===") |
- L("==") |
- L(">=") |
- L("<=") |
- L("!=") |
- L("~=") |
- L(">") |
- L("<")
+ L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<")
)
MARKER_OP = VERSION_CMP | L("not in") | L("in")
@@ -152,8 +144,11 @@ def _format_marker(marker, first=True):
# where the single item is itself it's own list. In that case we want skip
# the rest of this function so that we don't get extraneous () on the
# outside.
- if (isinstance(marker, list) and len(marker) == 1 and
- isinstance(marker[0], (list, tuple))):
+ if (
+ isinstance(marker, list)
+ and len(marker) == 1
+ and isinstance(marker[0], (list, tuple))
+ ):
return _format_marker(marker[0])
if isinstance(marker, list):
@@ -239,20 +234,20 @@ def _evaluate_markers(markers, environment):
def format_full_version(info):
- version = '{0.major}.{0.minor}.{0.micro}'.format(info)
+ version = "{0.major}.{0.minor}.{0.micro}".format(info)
kind = info.releaselevel
- if kind != 'final':
+ if kind != "final":
version += kind[0] + str(info.serial)
return version
def default_environment():
- if hasattr(sys, 'implementation'):
+ if hasattr(sys, "implementation"):
iver = format_full_version(sys.implementation.version)
implementation_name = sys.implementation.name
else:
- iver = '0'
- implementation_name = ''
+ iver = "0"
+ implementation_name = ""
return {
"implementation_name": implementation_name,
@@ -264,19 +259,19 @@ def default_environment():
"platform_version": platform.version(),
"python_full_version": platform.python_version(),
"platform_python_implementation": platform.python_implementation(),
- "python_version": platform.python_version()[:3],
+ "python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform,
}
class Marker(object):
-
def __init__(self, marker):
try:
self._markers = _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e:
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
- marker, marker[e.loc:e.loc + 8])
+ marker, marker[e.loc : e.loc + 8]
+ )
raise InvalidMarker(err_str)
def __str__(self):
diff --git a/setuptools/_vendor/packaging/requirements.py b/setuptools/_vendor/packaging/requirements.py
index 5b493416..8a0c2cb9 100644
--- a/setuptools/_vendor/packaging/requirements.py
+++ b/setuptools/_vendor/packaging/requirements.py
@@ -38,8 +38,8 @@ IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
NAME = IDENTIFIER("name")
EXTRA = IDENTIFIER
-URI = Regex(r'[^ ]+')("url")
-URL = (AT + URI)
+URI = Regex(r"[^ ]+")("url")
+URL = AT + URI
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
@@ -48,28 +48,31 @@ VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE)
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
-VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE),
- joinString=",", adjacent=False)("_raw_spec")
+VERSION_MANY = Combine(
+ VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
+)("_raw_spec")
_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
-_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '')
+_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
MARKER_EXPR.setParseAction(
- lambda s, l, t: Marker(s[t._original_start:t._original_end])
+ lambda s, l, t: Marker(s[t._original_start : t._original_end])
)
-MARKER_SEPERATOR = SEMICOLON
-MARKER = MARKER_SEPERATOR + MARKER_EXPR
+MARKER_SEPARATOR = SEMICOLON
+MARKER = MARKER_SEPARATOR + MARKER_EXPR
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
URL_AND_MARKER = URL + Optional(MARKER)
-NAMED_REQUIREMENT = \
- NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
+NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
+# setuptools.extern.pyparsing isn't thread safe during initialization, so we do it eagerly, see
+# issue #104
+REQUIREMENT.parseString("x[]")
class Requirement(object):
@@ -90,15 +93,21 @@ class Requirement(object):
req = REQUIREMENT.parseString(requirement_string)
except ParseException as e:
raise InvalidRequirement(
- "Invalid requirement, parse error at \"{0!r}\"".format(
- requirement_string[e.loc:e.loc + 8]))
+ 'Parse error at "{0!r}": {1}'.format(
+ requirement_string[e.loc : e.loc + 8], e.msg
+ )
+ )
self.name = req.name
if req.url:
parsed_url = urlparse.urlparse(req.url)
- if not (parsed_url.scheme and parsed_url.netloc) or (
- not parsed_url.scheme and not parsed_url.netloc):
- raise InvalidRequirement("Invalid URL given")
+ if parsed_url.scheme == "file":
+ if urlparse.urlunparse(parsed_url) != req.url:
+ raise InvalidRequirement("Invalid URL given")
+ elif not (parsed_url.scheme and parsed_url.netloc) or (
+ not parsed_url.scheme and not parsed_url.netloc
+ ):
+ raise InvalidRequirement("Invalid URL: {0}".format(req.url))
self.url = req.url
else:
self.url = None
@@ -117,6 +126,8 @@ class Requirement(object):
if self.url:
parts.append("@ {0}".format(self.url))
+ if self.marker:
+ parts.append(" ")
if self.marker:
parts.append("; {0}".format(self.marker))
diff --git a/setuptools/_vendor/packaging/specifiers.py b/setuptools/_vendor/packaging/specifiers.py
index 7f5a76cf..743576a0 100644
--- a/setuptools/_vendor/packaging/specifiers.py
+++ b/setuptools/_vendor/packaging/specifiers.py
@@ -19,7 +19,6 @@ class InvalidSpecifier(ValueError):
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)):
-
@abc.abstractmethod
def __str__(self):
"""
@@ -84,10 +83,7 @@ class _IndividualSpecifier(BaseSpecifier):
if not match:
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
- self._spec = (
- match.group("operator").strip(),
- match.group("version").strip(),
- )
+ self._spec = (match.group("operator").strip(), match.group("version").strip())
# Store whether or not this Specifier should accept prereleases
self._prereleases = prereleases
@@ -99,11 +95,7 @@ class _IndividualSpecifier(BaseSpecifier):
else ""
)
- return "<{0}({1!r}{2})>".format(
- self.__class__.__name__,
- str(self),
- pre,
- )
+ return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
def __str__(self):
return "{0}{1}".format(*self._spec)
@@ -194,11 +186,12 @@ class _IndividualSpecifier(BaseSpecifier):
# If our version is a prerelease, and we were not set to allow
# prereleases, then we'll store it for later incase nothing
# else matches this specifier.
- if (parsed_version.is_prerelease and not
- (prereleases or self.prereleases)):
+ if parsed_version.is_prerelease and not (
+ prereleases or self.prereleases
+ ):
found_prereleases.append(version)
# Either this is not a prerelease, or we should have been
- # accepting prereleases from the begining.
+ # accepting prereleases from the beginning.
else:
yielded = True
yield version
@@ -213,8 +206,7 @@ class _IndividualSpecifier(BaseSpecifier):
class LegacySpecifier(_IndividualSpecifier):
- _regex_str = (
- r"""
+ _regex_str = r"""
(?P<operator>(==|!=|<=|>=|<|>))
\s*
(?P<version>
@@ -225,10 +217,8 @@ class LegacySpecifier(_IndividualSpecifier):
# them, and a comma since it's a version separator.
)
"""
- )
- _regex = re.compile(
- r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
+ _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"==": "equal",
@@ -269,13 +259,13 @@ def _require_version_compare(fn):
if not isinstance(prospective, Version):
return False
return fn(self, prospective, spec)
+
return wrapped
class Specifier(_IndividualSpecifier):
- _regex_str = (
- r"""
+ _regex_str = r"""
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
(?P<version>
(?:
@@ -367,10 +357,8 @@ class Specifier(_IndividualSpecifier):
)
)
"""
- )
- _regex = re.compile(
- r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
+ _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE)
_operators = {
"~=": "compatible",
@@ -397,8 +385,7 @@ class Specifier(_IndividualSpecifier):
prefix = ".".join(
list(
itertools.takewhile(
- lambda x: (not x.startswith("post") and not
- x.startswith("dev")),
+ lambda x: (not x.startswith("post") and not x.startswith("dev")),
_version_split(spec),
)
)[:-1]
@@ -407,8 +394,9 @@ class Specifier(_IndividualSpecifier):
# Add the prefix notation to the end of our string
prefix += ".*"
- return (self._get_operator(">=")(prospective, spec) and
- self._get_operator("==")(prospective, prefix))
+ return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
+ prospective, prefix
+ )
@_require_version_compare
def _compare_equal(self, prospective, spec):
@@ -428,7 +416,7 @@ class Specifier(_IndividualSpecifier):
# Shorten the prospective version to be the same length as the spec
# so that we can determine if the specifier is a prefix of the
# prospective version or not.
- prospective = prospective[:len(spec)]
+ prospective = prospective[: len(spec)]
# Pad out our two sides with zeros so that they both equal the same
# length.
@@ -503,7 +491,7 @@ class Specifier(_IndividualSpecifier):
return False
# Ensure that we do not allow a local version of the version mentioned
- # in the specifier, which is techincally greater than, to match.
+ # in the specifier, which is technically greater than, to match.
if prospective.local is not None:
if Version(prospective.base_version) == Version(spec.base_version):
return False
@@ -567,27 +555,17 @@ def _pad_version(left, right):
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
# Get the rest of our versions
- left_split.append(left[len(left_split[0]):])
- right_split.append(right[len(right_split[0]):])
+ left_split.append(left[len(left_split[0]) :])
+ right_split.append(right[len(right_split[0]) :])
# Insert our padding
- left_split.insert(
- 1,
- ["0"] * max(0, len(right_split[0]) - len(left_split[0])),
- )
- right_split.insert(
- 1,
- ["0"] * max(0, len(left_split[0]) - len(right_split[0])),
- )
+ left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
+ right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
- return (
- list(itertools.chain(*left_split)),
- list(itertools.chain(*right_split)),
- )
+ return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split)))
class SpecifierSet(BaseSpecifier):
-
def __init__(self, specifiers="", prereleases=None):
# Split on , to break each indidivual specifier into it's own item, and
# strip each item to remove leading/trailing whitespace.
@@ -721,10 +699,7 @@ class SpecifierSet(BaseSpecifier):
# given version is contained within all of them.
# Note: This use of all() here means that an empty set of specifiers
# will always return True, this is an explicit design decision.
- return all(
- s.contains(item, prereleases=prereleases)
- for s in self._specs
- )
+ return all(s.contains(item, prereleases=prereleases) for s in self._specs)
def filter(self, iterable, prereleases=None):
# Determine if we're forcing a prerelease or not, if we're not forcing
diff --git a/setuptools/_vendor/packaging/tags.py b/setuptools/_vendor/packaging/tags.py
new file mode 100644
index 00000000..ec9942f0
--- /dev/null
+++ b/setuptools/_vendor/packaging/tags.py
@@ -0,0 +1,404 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import
+
+import distutils.util
+
+try:
+ from importlib.machinery import EXTENSION_SUFFIXES
+except ImportError: # pragma: no cover
+ import imp
+
+ EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()]
+ del imp
+import platform
+import re
+import sys
+import sysconfig
+import warnings
+
+
+INTERPRETER_SHORT_NAMES = {
+ "python": "py", # Generic.
+ "cpython": "cp",
+ "pypy": "pp",
+ "ironpython": "ip",
+ "jython": "jy",
+}
+
+
+_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
+
+
+class Tag(object):
+
+ __slots__ = ["_interpreter", "_abi", "_platform"]
+
+ def __init__(self, interpreter, abi, platform):
+ self._interpreter = interpreter.lower()
+ self._abi = abi.lower()
+ self._platform = platform.lower()
+
+ @property
+ def interpreter(self):
+ return self._interpreter
+
+ @property
+ def abi(self):
+ return self._abi
+
+ @property
+ def platform(self):
+ return self._platform
+
+ def __eq__(self, other):
+ return (
+ (self.platform == other.platform)
+ and (self.abi == other.abi)
+ and (self.interpreter == other.interpreter)
+ )
+
+ def __hash__(self):
+ return hash((self._interpreter, self._abi, self._platform))
+
+ def __str__(self):
+ return "{}-{}-{}".format(self._interpreter, self._abi, self._platform)
+
+ def __repr__(self):
+ return "<{self} @ {self_id}>".format(self=self, self_id=id(self))
+
+
+def parse_tag(tag):
+ tags = set()
+ interpreters, abis, platforms = tag.split("-")
+ for interpreter in interpreters.split("."):
+ for abi in abis.split("."):
+ for platform_ in platforms.split("."):
+ tags.add(Tag(interpreter, abi, platform_))
+ return frozenset(tags)
+
+
+def _normalize_string(string):
+ return string.replace(".", "_").replace("-", "_")
+
+
+def _cpython_interpreter(py_version):
+ # TODO: Is using py_version_nodot for interpreter version critical?
+ return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1])
+
+
+def _cpython_abis(py_version):
+ abis = []
+ version = "{}{}".format(*py_version[:2])
+ debug = pymalloc = ucs4 = ""
+ with_debug = sysconfig.get_config_var("Py_DEBUG")
+ has_refcount = hasattr(sys, "gettotalrefcount")
+ # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
+ # extension modules is the best option.
+ # https://github.com/pypa/pip/issues/3383#issuecomment-173267692
+ has_ext = "_d.pyd" in EXTENSION_SUFFIXES
+ if with_debug or (with_debug is None and (has_refcount or has_ext)):
+ debug = "d"
+ if py_version < (3, 8):
+ with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC")
+ if with_pymalloc or with_pymalloc is None:
+ pymalloc = "m"
+ if py_version < (3, 3):
+ unicode_size = sysconfig.get_config_var("Py_UNICODE_SIZE")
+ if unicode_size == 4 or (
+ unicode_size is None and sys.maxunicode == 0x10FFFF
+ ):
+ ucs4 = "u"
+ elif debug:
+ # Debug builds can also load "normal" extension modules.
+ # We can also assume no UCS-4 or pymalloc requirement.
+ abis.append("cp{version}".format(version=version))
+ abis.insert(
+ 0,
+ "cp{version}{debug}{pymalloc}{ucs4}".format(
+ version=version, debug=debug, pymalloc=pymalloc, ucs4=ucs4
+ ),
+ )
+ return abis
+
+
+def _cpython_tags(py_version, interpreter, abis, platforms):
+ for abi in abis:
+ for platform_ in platforms:
+ yield Tag(interpreter, abi, platform_)
+ for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms):
+ yield tag
+ for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms):
+ yield tag
+ # PEP 384 was first implemented in Python 3.2.
+ for minor_version in range(py_version[1] - 1, 1, -1):
+ for platform_ in platforms:
+ interpreter = "cp{major}{minor}".format(
+ major=py_version[0], minor=minor_version
+ )
+ yield Tag(interpreter, "abi3", platform_)
+
+
+def _pypy_interpreter():
+ return "pp{py_major}{pypy_major}{pypy_minor}".format(
+ py_major=sys.version_info[0],
+ pypy_major=sys.pypy_version_info.major,
+ pypy_minor=sys.pypy_version_info.minor,
+ )
+
+
+def _generic_abi():
+ abi = sysconfig.get_config_var("SOABI")
+ if abi:
+ return _normalize_string(abi)
+ else:
+ return "none"
+
+
+def _pypy_tags(py_version, interpreter, abi, platforms):
+ for tag in (Tag(interpreter, abi, platform) for platform in platforms):
+ yield tag
+ for tag in (Tag(interpreter, "none", platform) for platform in platforms):
+ yield tag
+
+
+def _generic_tags(interpreter, py_version, abi, platforms):
+ for tag in (Tag(interpreter, abi, platform) for platform in platforms):
+ yield tag
+ if abi != "none":
+ tags = (Tag(interpreter, "none", platform_) for platform_ in platforms)
+ for tag in tags:
+ yield tag
+
+
+def _py_interpreter_range(py_version):
+ """
+ Yield Python versions in descending order.
+
+ After the latest version, the major-only version will be yielded, and then
+ all following versions up to 'end'.
+ """
+ yield "py{major}{minor}".format(major=py_version[0], minor=py_version[1])
+ yield "py{major}".format(major=py_version[0])
+ for minor in range(py_version[1] - 1, -1, -1):
+ yield "py{major}{minor}".format(major=py_version[0], minor=minor)
+
+
+def _independent_tags(interpreter, py_version, platforms):
+ """
+ Return the sequence of tags that are consistent across implementations.
+
+ The tags consist of:
+ - py*-none-<platform>
+ - <interpreter>-none-any
+ - py*-none-any
+ """
+ for version in _py_interpreter_range(py_version):
+ for platform_ in platforms:
+ yield Tag(version, "none", platform_)
+ yield Tag(interpreter, "none", "any")
+ for version in _py_interpreter_range(py_version):
+ yield Tag(version, "none", "any")
+
+
+def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER):
+ if not is_32bit:
+ return arch
+
+ if arch.startswith("ppc"):
+ return "ppc"
+
+ return "i386"
+
+
+def _mac_binary_formats(version, cpu_arch):
+ formats = [cpu_arch]
+ if cpu_arch == "x86_64":
+ if version < (10, 4):
+ return []
+ formats.extend(["intel", "fat64", "fat32"])
+
+ elif cpu_arch == "i386":
+ if version < (10, 4):
+ return []
+ formats.extend(["intel", "fat32", "fat"])
+
+ elif cpu_arch == "ppc64":
+ # TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
+ if version > (10, 5) or version < (10, 4):
+ return []
+ formats.append("fat64")
+
+ elif cpu_arch == "ppc":
+ if version > (10, 6):
+ return []
+ formats.extend(["fat32", "fat"])
+
+ formats.append("universal")
+ return formats
+
+
+def _mac_platforms(version=None, arch=None):
+ version_str, _, cpu_arch = platform.mac_ver()
+ if version is None:
+ version = tuple(map(int, version_str.split(".")[:2]))
+ if arch is None:
+ arch = _mac_arch(cpu_arch)
+ platforms = []
+ for minor_version in range(version[1], -1, -1):
+ compat_version = version[0], minor_version
+ binary_formats = _mac_binary_formats(compat_version, arch)
+ for binary_format in binary_formats:
+ platforms.append(
+ "macosx_{major}_{minor}_{binary_format}".format(
+ major=compat_version[0],
+ minor=compat_version[1],
+ binary_format=binary_format,
+ )
+ )
+ return platforms
+
+
+# From PEP 513.
+def _is_manylinux_compatible(name, glibc_version):
+ # Check for presence of _manylinux module.
+ try:
+ import _manylinux
+
+ return bool(getattr(_manylinux, name + "_compatible"))
+ except (ImportError, AttributeError):
+ # Fall through to heuristic check below.
+ pass
+
+ return _have_compatible_glibc(*glibc_version)
+
+
+def _glibc_version_string():
+ # Returns glibc version string, or None if not using glibc.
+ import ctypes
+
+ # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
+ # manpage says, "If filename is NULL, then the returned handle is for the
+ # main program". This way we can let the linker do the work to figure out
+ # which libc our process is actually using.
+ process_namespace = ctypes.CDLL(None)
+ try:
+ gnu_get_libc_version = process_namespace.gnu_get_libc_version
+ except AttributeError:
+ # Symbol doesn't exist -> therefore, we are not linked to
+ # glibc.
+ return None
+
+ # Call gnu_get_libc_version, which returns a string like "2.5"
+ gnu_get_libc_version.restype = ctypes.c_char_p
+ version_str = gnu_get_libc_version()
+ # py2 / py3 compatibility:
+ if not isinstance(version_str, str):
+ version_str = version_str.decode("ascii")
+
+ return version_str
+
+
+# Separated out from have_compatible_glibc for easier unit testing.
+def _check_glibc_version(version_str, required_major, minimum_minor):
+ # Parse string and check against requested version.
+ #
+ # We use a regexp instead of str.split because we want to discard any
+ # random junk that might come after the minor version -- this might happen
+ # in patched/forked versions of glibc (e.g. Linaro's version of glibc
+ # uses version strings like "2.20-2014.11"). See gh-3588.
+ m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
+ if not m:
+ warnings.warn(
+ "Expected glibc version with 2 components major.minor,"
+ " got: %s" % version_str,
+ RuntimeWarning,
+ )
+ return False
+ return (
+ int(m.group("major")) == required_major
+ and int(m.group("minor")) >= minimum_minor
+ )
+
+
+def _have_compatible_glibc(required_major, minimum_minor):
+ version_str = _glibc_version_string()
+ if version_str is None:
+ return False
+ return _check_glibc_version(version_str, required_major, minimum_minor)
+
+
+def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
+ linux = _normalize_string(distutils.util.get_platform())
+ if linux == "linux_x86_64" and is_32bit:
+ linux = "linux_i686"
+ manylinux_support = (
+ ("manylinux2014", (2, 17)), # CentOS 7 w/ glibc 2.17 (PEP 599)
+ ("manylinux2010", (2, 12)), # CentOS 6 w/ glibc 2.12 (PEP 571)
+ ("manylinux1", (2, 5)), # CentOS 5 w/ glibc 2.5 (PEP 513)
+ )
+ manylinux_support_iter = iter(manylinux_support)
+ for name, glibc_version in manylinux_support_iter:
+ if _is_manylinux_compatible(name, glibc_version):
+ platforms = [linux.replace("linux", name)]
+ break
+ else:
+ platforms = []
+ # Support for a later manylinux implies support for an earlier version.
+ platforms += [linux.replace("linux", name) for name, _ in manylinux_support_iter]
+ platforms.append(linux)
+ return platforms
+
+
+def _generic_platforms():
+ platform = _normalize_string(distutils.util.get_platform())
+ return [platform]
+
+
+def _interpreter_name():
+ name = platform.python_implementation().lower()
+ return INTERPRETER_SHORT_NAMES.get(name) or name
+
+
+def _generic_interpreter(name, py_version):
+ version = sysconfig.get_config_var("py_version_nodot")
+ if not version:
+ version = "".join(map(str, py_version[:2]))
+ return "{name}{version}".format(name=name, version=version)
+
+
+def sys_tags():
+ """
+ Returns the sequence of tag triples for the running interpreter.
+
+ The order of the sequence corresponds to priority order for the
+ interpreter, from most to least important.
+ """
+ py_version = sys.version_info[:2]
+ interpreter_name = _interpreter_name()
+ if platform.system() == "Darwin":
+ platforms = _mac_platforms()
+ elif platform.system() == "Linux":
+ platforms = _linux_platforms()
+ else:
+ platforms = _generic_platforms()
+
+ if interpreter_name == "cp":
+ interpreter = _cpython_interpreter(py_version)
+ abis = _cpython_abis(py_version)
+ for tag in _cpython_tags(py_version, interpreter, abis, platforms):
+ yield tag
+ elif interpreter_name == "pp":
+ interpreter = _pypy_interpreter()
+ abi = _generic_abi()
+ for tag in _pypy_tags(py_version, interpreter, abi, platforms):
+ yield tag
+ else:
+ interpreter = _generic_interpreter(interpreter_name, py_version)
+ abi = _generic_abi()
+ for tag in _generic_tags(interpreter, py_version, abi, platforms):
+ yield tag
+ for tag in _independent_tags(interpreter, py_version, platforms):
+ yield tag
diff --git a/setuptools/_vendor/packaging/utils.py b/setuptools/_vendor/packaging/utils.py
index 942387ce..88418786 100644
--- a/setuptools/_vendor/packaging/utils.py
+++ b/setuptools/_vendor/packaging/utils.py
@@ -5,6 +5,8 @@ from __future__ import absolute_import, division, print_function
import re
+from .version import InvalidVersion, Version
+
_canonicalize_regex = re.compile(r"[-_.]+")
@@ -12,3 +14,44 @@ _canonicalize_regex = re.compile(r"[-_.]+")
def canonicalize_name(name):
# This is taken from PEP 503.
return _canonicalize_regex.sub("-", name).lower()
+
+
+def canonicalize_version(version):
+ """
+ This is very similar to Version.__str__, but has one subtle differences
+ with the way it handles the release segment.
+ """
+
+ try:
+ version = Version(version)
+ except InvalidVersion:
+ # Legacy versions cannot be normalized
+ return version
+
+ parts = []
+
+ # Epoch
+ if version.epoch != 0:
+ parts.append("{0}!".format(version.epoch))
+
+ # Release segment
+ # NB: This strips trailing '.0's to normalize
+ parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release)))
+
+ # Pre-release
+ if version.pre is not None:
+ parts.append("".join(str(x) for x in version.pre))
+
+ # Post-release
+ if version.post is not None:
+ parts.append(".post{0}".format(version.post))
+
+ # Development release
+ if version.dev is not None:
+ parts.append(".dev{0}".format(version.dev))
+
+ # Local version segment
+ if version.local is not None:
+ parts.append("+{0}".format(version.local))
+
+ return "".join(parts)
diff --git a/setuptools/_vendor/packaging/version.py b/setuptools/_vendor/packaging/version.py
index 83b5ee8c..95157a1f 100644
--- a/setuptools/_vendor/packaging/version.py
+++ b/setuptools/_vendor/packaging/version.py
@@ -10,14 +10,11 @@ import re
from ._structures import Infinity
-__all__ = [
- "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"
-]
+__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
_Version = collections.namedtuple(
- "_Version",
- ["epoch", "release", "dev", "pre", "post", "local"],
+ "_Version", ["epoch", "release", "dev", "pre", "post", "local"]
)
@@ -40,7 +37,6 @@ class InvalidVersion(ValueError):
class _BaseVersion(object):
-
def __hash__(self):
return hash(self._key)
@@ -70,7 +66,6 @@ class _BaseVersion(object):
class LegacyVersion(_BaseVersion):
-
def __init__(self, version):
self._version = str(version)
self._key = _legacy_cmpkey(self._version)
@@ -90,6 +85,26 @@ class LegacyVersion(_BaseVersion):
return self._version
@property
+ def epoch(self):
+ return -1
+
+ @property
+ def release(self):
+ return None
+
+ @property
+ def pre(self):
+ return None
+
+ @property
+ def post(self):
+ return None
+
+ @property
+ def dev(self):
+ return None
+
+ @property
def local(self):
return None
@@ -101,13 +116,19 @@ class LegacyVersion(_BaseVersion):
def is_postrelease(self):
return False
+ @property
+ def is_devrelease(self):
+ return False
-_legacy_version_component_re = re.compile(
- r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
-)
+
+_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
_legacy_version_replacement_map = {
- "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
+ "pre": "c",
+ "preview": "c",
+ "-": "final-",
+ "rc": "c",
+ "dev": "@",
}
@@ -154,6 +175,7 @@ def _legacy_cmpkey(version):
return epoch, parts
+
# Deliberately not anchored to the start and end of the string, to make it
# easier for 3rd party code to reuse
VERSION_PATTERN = r"""
@@ -190,10 +212,7 @@ VERSION_PATTERN = r"""
class Version(_BaseVersion):
- _regex = re.compile(
- r"^\s*" + VERSION_PATTERN + r"\s*$",
- re.VERBOSE | re.IGNORECASE,
- )
+ _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
def __init__(self, version):
# Validate the version and parse it into pieces
@@ -205,18 +224,11 @@ class Version(_BaseVersion):
self._version = _Version(
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
release=tuple(int(i) for i in match.group("release").split(".")),
- pre=_parse_letter_version(
- match.group("pre_l"),
- match.group("pre_n"),
- ),
+ pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
post=_parse_letter_version(
- match.group("post_l"),
- match.group("post_n1") or match.group("post_n2"),
- ),
- dev=_parse_letter_version(
- match.group("dev_l"),
- match.group("dev_n"),
+ match.group("post_l"), match.group("post_n1") or match.group("post_n2")
),
+ dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
local=_parse_local_version(match.group("local")),
)
@@ -237,33 +249,58 @@ class Version(_BaseVersion):
parts = []
# Epoch
- if self._version.epoch != 0:
- parts.append("{0}!".format(self._version.epoch))
+ if self.epoch != 0:
+ parts.append("{0}!".format(self.epoch))
# Release segment
- parts.append(".".join(str(x) for x in self._version.release))
+ parts.append(".".join(str(x) for x in self.release))
# Pre-release
- if self._version.pre is not None:
- parts.append("".join(str(x) for x in self._version.pre))
+ if self.pre is not None:
+ parts.append("".join(str(x) for x in self.pre))
# Post-release
- if self._version.post is not None:
- parts.append(".post{0}".format(self._version.post[1]))
+ if self.post is not None:
+ parts.append(".post{0}".format(self.post))
# Development release
- if self._version.dev is not None:
- parts.append(".dev{0}".format(self._version.dev[1]))
+ if self.dev is not None:
+ parts.append(".dev{0}".format(self.dev))
# Local version segment
- if self._version.local is not None:
- parts.append(
- "+{0}".format(".".join(str(x) for x in self._version.local))
- )
+ if self.local is not None:
+ parts.append("+{0}".format(self.local))
return "".join(parts)
@property
+ def epoch(self):
+ return self._version.epoch
+
+ @property
+ def release(self):
+ return self._version.release
+
+ @property
+ def pre(self):
+ return self._version.pre
+
+ @property
+ def post(self):
+ return self._version.post[1] if self._version.post else None
+
+ @property
+ def dev(self):
+ return self._version.dev[1] if self._version.dev else None
+
+ @property
+ def local(self):
+ if self._version.local:
+ return ".".join(str(x) for x in self._version.local)
+ else:
+ return None
+
+ @property
def public(self):
return str(self).split("+", 1)[0]
@@ -272,27 +309,25 @@ class Version(_BaseVersion):
parts = []
# Epoch
- if self._version.epoch != 0:
- parts.append("{0}!".format(self._version.epoch))
+ if self.epoch != 0:
+ parts.append("{0}!".format(self.epoch))
# Release segment
- parts.append(".".join(str(x) for x in self._version.release))
+ parts.append(".".join(str(x) for x in self.release))
return "".join(parts)
@property
- def local(self):
- version_string = str(self)
- if "+" in version_string:
- return version_string.split("+", 1)[1]
-
- @property
def is_prerelease(self):
- return bool(self._version.dev or self._version.pre)
+ return self.dev is not None or self.pre is not None
@property
def is_postrelease(self):
- return bool(self._version.post)
+ return self.post is not None
+
+ @property
+ def is_devrelease(self):
+ return self.dev is not None
def _parse_letter_version(letter, number):
@@ -326,7 +361,7 @@ def _parse_letter_version(letter, number):
return letter, int(number)
-_local_version_seperators = re.compile(r"[\._-]")
+_local_version_separators = re.compile(r"[\._-]")
def _parse_local_version(local):
@@ -336,7 +371,7 @@ def _parse_local_version(local):
if local is not None:
return tuple(
part.lower() if not part.isdigit() else int(part)
- for part in _local_version_seperators.split(local)
+ for part in _local_version_separators.split(local)
)
@@ -347,12 +382,7 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# re-reverse it back into the correct order and make it a tuple and use
# that for our sorting key.
release = tuple(
- reversed(list(
- itertools.dropwhile(
- lambda x: x == 0,
- reversed(release),
- )
- ))
+ reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
)
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
@@ -385,9 +415,6 @@ def _cmpkey(epoch, release, pre, post, dev, local):
# - Numeric segments sort numerically
# - Shorter versions sort before longer versions when the prefixes
# match exactly
- local = tuple(
- (i, "") if isinstance(i, int) else (-Infinity, i)
- for i in local
- )
+ local = tuple((i, "") if isinstance(i, int) else (-Infinity, i) for i in local)
return epoch, release, pre, post, dev, local
diff --git a/setuptools/_vendor/vendored.txt b/setuptools/_vendor/vendored.txt
index 7d77863d..65183d9a 100644
--- a/setuptools/_vendor/vendored.txt
+++ b/setuptools/_vendor/vendored.txt
@@ -1,3 +1,4 @@
-packaging==16.8
+packaging==19.2
pyparsing==2.2.1
six==1.10.0
+ordered-set==3.1.1
diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py
index c883d92f..10c4b528 100644
--- a/setuptools/build_meta.py
+++ b/setuptools/build_meta.py
@@ -26,6 +26,7 @@ bug reports or API stability):
Again, this is not a formal definition! Just a "taste" of the module.
"""
+import io
import os
import sys
import tokenize
@@ -34,7 +35,18 @@ import contextlib
import setuptools
import distutils
+from setuptools.py31compat import TemporaryDirectory
+from pkg_resources import parse_requirements
+from pkg_resources.py31compat import makedirs
+
+__all__ = ['get_requires_for_build_sdist',
+ 'get_requires_for_build_wheel',
+ 'prepare_metadata_for_build_wheel',
+ 'build_wheel',
+ 'build_sdist',
+ '__legacy__',
+ 'SetupRequirementsError']
class SetupRequirementsError(BaseException):
def __init__(self, specifiers):
@@ -43,7 +55,9 @@ class SetupRequirementsError(BaseException):
class Distribution(setuptools.dist.Distribution):
def fetch_build_eggs(self, specifiers):
- raise SetupRequirementsError(specifiers)
+ specifier_list = list(map(str, parse_requirements(specifiers)))
+
+ raise SetupRequirementsError(specifier_list)
@classmethod
@contextlib.contextmanager
@@ -74,110 +88,170 @@ def _to_str(s):
return s
-def _run_setup(setup_script='setup.py'):
- # Note that we can reuse our build directory between calls
- # Correctness comes first, then optimization later
- __file__ = setup_script
- __name__ = '__main__'
- f = getattr(tokenize, 'open', open)(__file__)
- code = f.read().replace('\\r\\n', '\\n')
- f.close()
- exec(compile(code, __file__, 'exec'), locals())
-
-
-def _fix_config(config_settings):
- config_settings = config_settings or {}
- config_settings.setdefault('--global-option', [])
- return config_settings
-
-
-def _get_build_requires(config_settings, requirements):
- config_settings = _fix_config(config_settings)
-
- sys.argv = sys.argv[:1] + ['egg_info'] + \
- config_settings["--global-option"]
- try:
- with Distribution.patch():
- _run_setup()
- except SetupRequirementsError as e:
- requirements += e.specifiers
-
- return requirements
-
-
def _get_immediate_subdirectories(a_dir):
return [name for name in os.listdir(a_dir)
if os.path.isdir(os.path.join(a_dir, name))]
-def get_requires_for_build_wheel(config_settings=None):
- config_settings = _fix_config(config_settings)
- return _get_build_requires(config_settings, requirements=['wheel'])
-
-
-def get_requires_for_build_sdist(config_settings=None):
- config_settings = _fix_config(config_settings)
- return _get_build_requires(config_settings, requirements=[])
-
+def _file_with_extension(directory, extension):
+ matching = (
+ f for f in os.listdir(directory)
+ if f.endswith(extension)
+ )
+ file, = matching
+ return file
-def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
- sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', _to_str(metadata_directory)]
- _run_setup()
- dist_info_directory = metadata_directory
- while True:
- dist_infos = [f for f in os.listdir(dist_info_directory)
- if f.endswith('.dist-info')]
+def _open_setup_script(setup_script):
+ if not os.path.exists(setup_script):
+ # Supply a default setup.py
+ return io.StringIO(u"from setuptools import setup; setup()")
- if len(dist_infos) == 0 and \
- len(_get_immediate_subdirectories(dist_info_directory)) == 1:
- dist_info_directory = os.path.join(
- dist_info_directory, os.listdir(dist_info_directory)[0])
- continue
+ return getattr(tokenize, 'open', open)(setup_script)
- assert len(dist_infos) == 1
- break
- # PEP 517 requires that the .dist-info directory be placed in the
- # metadata_directory. To comply, we MUST copy the directory to the root
- if dist_info_directory != metadata_directory:
- shutil.move(
- os.path.join(dist_info_directory, dist_infos[0]),
- metadata_directory)
- shutil.rmtree(dist_info_directory, ignore_errors=True)
+class _BuildMetaBackend(object):
- return dist_infos[0]
+ def _fix_config(self, config_settings):
+ config_settings = config_settings or {}
+ config_settings.setdefault('--global-option', [])
+ return config_settings
+ def _get_build_requires(self, config_settings, requirements):
+ config_settings = self._fix_config(config_settings)
-def _file_with_extension(directory, extension):
- matching = (
- f for f in os.listdir(directory)
- if f.endswith(extension)
- )
- file, = matching
- return file
+ sys.argv = sys.argv[:1] + ['egg_info'] + \
+ config_settings["--global-option"]
+ try:
+ with Distribution.patch():
+ self.run_setup()
+ except SetupRequirementsError as e:
+ requirements += e.specifiers
+
+ return requirements
+
+ def run_setup(self, setup_script='setup.py'):
+ # Note that we can reuse our build directory between calls
+ # Correctness comes first, then optimization later
+ __file__ = setup_script
+ __name__ = '__main__'
+
+ with _open_setup_script(__file__) as f:
+ code = f.read().replace(r'\r\n', r'\n')
+
+ exec(compile(code, __file__, 'exec'), locals())
+
+ def get_requires_for_build_wheel(self, config_settings=None):
+ config_settings = self._fix_config(config_settings)
+ return self._get_build_requires(config_settings, requirements=['wheel'])
+
+ def get_requires_for_build_sdist(self, config_settings=None):
+ config_settings = self._fix_config(config_settings)
+ return self._get_build_requires(config_settings, requirements=[])
+
+ def prepare_metadata_for_build_wheel(self, metadata_directory,
+ config_settings=None):
+ sys.argv = sys.argv[:1] + ['dist_info', '--egg-base',
+ _to_str(metadata_directory)]
+ self.run_setup()
+
+ dist_info_directory = metadata_directory
+ while True:
+ dist_infos = [f for f in os.listdir(dist_info_directory)
+ if f.endswith('.dist-info')]
+
+ if (len(dist_infos) == 0 and
+ len(_get_immediate_subdirectories(dist_info_directory)) == 1):
+
+ dist_info_directory = os.path.join(
+ dist_info_directory, os.listdir(dist_info_directory)[0])
+ continue
+
+ assert len(dist_infos) == 1
+ break
+
+ # PEP 517 requires that the .dist-info directory be placed in the
+ # metadata_directory. To comply, we MUST copy the directory to the root
+ if dist_info_directory != metadata_directory:
+ shutil.move(
+ os.path.join(dist_info_directory, dist_infos[0]),
+ metadata_directory)
+ shutil.rmtree(dist_info_directory, ignore_errors=True)
+
+ return dist_infos[0]
+
+ def _build_with_temp_dir(self, setup_command, result_extension,
+ result_directory, config_settings):
+ config_settings = self._fix_config(config_settings)
+ result_directory = os.path.abspath(result_directory)
+
+ # Build in a temporary directory, then copy to the target.
+ makedirs(result_directory, exist_ok=True)
+ with TemporaryDirectory(dir=result_directory) as tmp_dist_dir:
+ sys.argv = (sys.argv[:1] + setup_command +
+ ['--dist-dir', tmp_dist_dir] +
+ config_settings["--global-option"])
+ self.run_setup()
+
+ result_basename = _file_with_extension(tmp_dist_dir, result_extension)
+ result_path = os.path.join(result_directory, result_basename)
+ if os.path.exists(result_path):
+ # os.rename will fail overwriting on non-Unix.
+ os.remove(result_path)
+ os.rename(os.path.join(tmp_dist_dir, result_basename), result_path)
+
+ return result_basename
+
+
+ def build_wheel(self, wheel_directory, config_settings=None,
+ metadata_directory=None):
+ return self._build_with_temp_dir(['bdist_wheel'], '.whl',
+ wheel_directory, config_settings)
+
+ def build_sdist(self, sdist_directory, config_settings=None):
+ return self._build_with_temp_dir(['sdist', '--formats', 'gztar'],
+ '.tar.gz', sdist_directory,
+ config_settings)
+
+
+class _BuildMetaLegacyBackend(_BuildMetaBackend):
+ """Compatibility backend for setuptools
+
+ This is a version of setuptools.build_meta that endeavors to maintain backwards
+ compatibility with pre-PEP 517 modes of invocation. It exists as a temporary
+ bridge between the old packaging mechanism and the new packaging mechanism,
+ and will eventually be removed.
+ """
+ def run_setup(self, setup_script='setup.py'):
+ # In order to maintain compatibility with scripts assuming that
+ # the setup.py script is in a directory on the PYTHONPATH, inject
+ # '' into sys.path. (pypa/setuptools#1642)
+ sys_path = list(sys.path) # Save the original path
+ script_dir = os.path.dirname(os.path.abspath(setup_script))
+ if script_dir not in sys.path:
+ sys.path.insert(0, script_dir)
-def build_wheel(wheel_directory, config_settings=None,
- metadata_directory=None):
- config_settings = _fix_config(config_settings)
- wheel_directory = os.path.abspath(wheel_directory)
- sys.argv = sys.argv[:1] + ['bdist_wheel'] + \
- config_settings["--global-option"]
- _run_setup()
- if wheel_directory != 'dist':
- shutil.rmtree(wheel_directory)
- shutil.copytree('dist', wheel_directory)
+ try:
+ super(_BuildMetaLegacyBackend,
+ self).run_setup(setup_script=setup_script)
+ finally:
+ # While PEP 517 frontends should be calling each hook in a fresh
+ # subprocess according to the standard (and thus it should not be
+ # strictly necessary to restore the old sys.path), we'll restore
+ # the original path so that the path manipulation does not persist
+ # within the hook after run_setup is called.
+ sys.path[:] = sys_path
- return _file_with_extension(wheel_directory, '.whl')
+# The primary backend
+_BACKEND = _BuildMetaBackend()
+get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel
+get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist
+prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
+build_wheel = _BACKEND.build_wheel
+build_sdist = _BACKEND.build_sdist
-def build_sdist(sdist_directory, config_settings=None):
- config_settings = _fix_config(config_settings)
- sdist_directory = os.path.abspath(sdist_directory)
- sys.argv = sys.argv[:1] + ['sdist', '--formats', 'gztar'] + \
- config_settings["--global-option"] + \
- ["--dist-dir", sdist_directory]
- _run_setup()
- return _file_with_extension(sdist_directory, '.tar.gz')
+# The legacy backend
+__legacy__ = _BuildMetaLegacyBackend()
diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py
index fe619e2e..743f5588 100644
--- a/setuptools/command/__init__.py
+++ b/setuptools/command/__init__.py
@@ -2,8 +2,7 @@ __all__ = [
'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts',
- 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib',
- 'dist_info',
+ 'bdist_wininst', 'upload_docs', 'build_clib', 'dist_info',
]
from distutils.command.bdist import bdist
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 9f8df917..98470f17 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -284,7 +284,7 @@ class bdist_egg(Command):
"or refer to a module" % (ep,)
)
- pyver = sys.version[:3]
+ pyver = '{}.{}'.format(*sys.version_info)
pkg = ep.module_name
full = '.'.join(ep.attrs)
base = ep.attrs[0]
diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py
index 60a8a32f..daa8e4fe 100644
--- a/setuptools/command/build_ext.py
+++ b/setuptools/command/build_ext.py
@@ -1,7 +1,6 @@
import os
import sys
import itertools
-import imp
from distutils.command.build_ext import build_ext as _du_build_ext
from distutils.file_util import copy_file
from distutils.ccompiler import new_compiler
@@ -12,6 +11,13 @@ from distutils import log
from setuptools.extension import Library
from setuptools.extern import six
+if six.PY2:
+ import imp
+
+ EXTENSION_SUFFIXES = [s for s, _, tp in imp.get_suffixes() if tp == imp.C_EXTENSION]
+else:
+ from importlib.machinery import EXTENSION_SUFFIXES
+
try:
# Attempt to use Cython for building extensions, if available
from Cython.Distutils.build_ext import build_ext as _build_ext
@@ -64,7 +70,7 @@ if_dl = lambda s: s if have_rtld else ''
def get_abi3_suffix():
"""Return the file extension for an abi3-compliant Extension()"""
- for suffix, _, _ in (s for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION):
+ for suffix in EXTENSION_SUFFIXES:
if '.abi3' in suffix: # Unix
return suffix
elif suffix == '.pyd': # Windows
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index 06c98271..09066f8c 100644
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -241,7 +241,7 @@ class easy_install(Command):
"""
Render the Setuptools version and installation details, then exit.
"""
- ver = sys.version[:3]
+ ver = '{}.{}'.format(*sys.version_info)
dist = get_distribution('setuptools')
tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})'
print(tmpl.format(**locals()))
@@ -410,7 +410,13 @@ class easy_install(Command):
]
self._expand_attrs(dirs)
- def run(self):
+ def run(self, show_deprecation=True):
+ if show_deprecation:
+ self.announce(
+ "WARNING: The easy_install command is deprecated "
+ "and will be removed in a future version."
+ , log.WARN,
+ )
if self.verbose != self.distribution.verbose:
log.set_verbosity(self.verbose)
try:
@@ -1180,8 +1186,7 @@ class easy_install(Command):
# to the setup.cfg file.
ei_opts = self.distribution.get_option_dict('easy_install').copy()
fetch_directives = (
- 'find_links', 'site_dirs', 'index_url', 'optimize',
- 'site_dirs', 'allow_hosts',
+ 'find_links', 'site_dirs', 'index_url', 'optimize', 'allow_hosts',
)
fetch_options = {}
for key, val in ei_opts.items():
@@ -1412,7 +1417,7 @@ def get_site_dirs():
os.path.join(
prefix,
"lib",
- "python" + sys.version[:3],
+ "python{}.{}".format(*sys.version_info),
"site-packages",
),
os.path.join(prefix, "lib", "site-python"),
@@ -1433,7 +1438,7 @@ def get_site_dirs():
home,
'Library',
'Python',
- sys.version[:3],
+ '{}.{}'.format(*sys.version_info),
'site-packages',
)
sitedirs.append(home_sp)
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index d9fe3da3..5d8f451e 100644
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -568,6 +568,7 @@ class manifest_maker(sdist):
def add_defaults(self):
sdist.add_defaults(self)
+ self.check_license()
self.filelist.append(self.template)
self.filelist.append(self.manifest)
rcfiles = list(walk_revctrl())
diff --git a/setuptools/command/install.py b/setuptools/command/install.py
index 31a5ddb5..72b9a3e4 100644
--- a/setuptools/command/install.py
+++ b/setuptools/command/install.py
@@ -114,7 +114,7 @@ class install(orig.install):
args.insert(0, setuptools.bootstrap_install_from)
cmd.args = args
- cmd.run()
+ cmd.run(show_deprecation=False)
setuptools.bootstrap_install_from = None
diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py
index 2b31c3e3..07d65933 100644
--- a/setuptools/command/install_lib.py
+++ b/setuptools/command/install_lib.py
@@ -1,5 +1,5 @@
import os
-import imp
+import sys
from itertools import product, starmap
import distutils.command.install_lib as orig
@@ -74,10 +74,10 @@ class install_lib(orig.install_lib):
yield '__init__.pyc'
yield '__init__.pyo'
- if not hasattr(imp, 'get_tag'):
+ if not hasattr(sys, 'implementation'):
return
- base = os.path.join('__pycache__', '__init__.' + imp.get_tag())
+ base = os.path.join('__pycache__', '__init__.' + sys.implementation.cache_tag)
yield base + '.pyc'
yield base + '.pyo'
yield base + '.opt-1.pyc'
diff --git a/setuptools/command/register.py b/setuptools/command/register.py
index 98bc0156..b8266b9a 100644
--- a/setuptools/command/register.py
+++ b/setuptools/command/register.py
@@ -1,18 +1,18 @@
from distutils import log
import distutils.command.register as orig
+from setuptools.errors import RemovedCommandError
+
class register(orig.register):
- __doc__ = orig.register.__doc__
+ """Formerly used to register packages on PyPI."""
def run(self):
- try:
- # Make sure that we are using valid current name/version info
- self.run_command('egg_info')
- orig.register.run(self)
- finally:
- self.announce(
- "WARNING: Registering is deprecated, use twine to "
- "upload instead (https://pypi.org/p/twine/)",
- log.WARN
- )
+ msg = (
+ "The register command has been removed, use twine to upload "
+ + "instead (https://pypi.org/p/twine)"
+ )
+
+ self.announce("ERROR: " + msg, log.ERROR)
+
+ raise RemovedCommandError(msg)
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index 40965a67..a851453f 100644
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -5,7 +5,7 @@ import sys
import io
import contextlib
-from setuptools.extern import six
+from setuptools.extern import six, ordered_set
from .py36compat import sdist_add_defaults
@@ -134,14 +134,27 @@ class sdist(sdist_add_defaults, orig.sdist):
if self.distribution.has_pure_modules():
build_py = self.get_finalized_command('build_py')
self.filelist.extend(build_py.get_source_files())
- # This functionality is incompatible with include_package_data, and
- # will in fact create an infinite recursion if include_package_data
- # is True. Use of include_package_data will imply that
- # distutils-style automatic handling of package_data is disabled
- if not self.distribution.include_package_data:
- for _, src_dir, _, filenames in build_py.data_files:
- self.filelist.extend([os.path.join(src_dir, filename)
- for filename in filenames])
+ self._add_data_files(self._safe_data_files(build_py))
+
+ def _safe_data_files(self, build_py):
+ """
+ Extracting data_files from build_py is known to cause
+ infinite recursion errors when `include_package_data`
+ is enabled, so suppress it in that case.
+ """
+ if self.distribution.include_package_data:
+ return ()
+ return build_py.data_files
+
+ def _add_data_files(self, data_files):
+ """
+ Add data files as found in build_py.data_files.
+ """
+ self.filelist.extend(
+ os.path.join(src_dir, name)
+ for _, src_dir, _, filenames in data_files
+ for name in filenames
+ )
def _add_defaults_data_files(self):
try:
@@ -206,3 +219,34 @@ class sdist(sdist_add_defaults, orig.sdist):
continue
self.filelist.append(line)
manifest.close()
+
+ def check_license(self):
+ """Checks if license_file' or 'license_files' is configured and adds any
+ valid paths to 'self.filelist'.
+ """
+
+ files = ordered_set.OrderedSet()
+
+ opts = self.distribution.get_option_dict('metadata')
+
+ # ignore the source of the value
+ _, license_file = opts.get('license_file', (None, None))
+
+ if license_file is None:
+ log.debug("'license_file' option was not specified")
+ else:
+ files.add(license_file)
+
+ try:
+ files.update(self.distribution.metadata.license_files)
+ except TypeError:
+ log.warn("warning: 'license_files' option is malformed")
+
+ for f in files:
+ if not os.path.exists(f):
+ log.warn(
+ "warning: Failed to find the configured license file '%s'",
+ f)
+ files.remove(f)
+
+ self.filelist.extend(files)
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index dde0118c..c148b38d 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -15,6 +15,7 @@ from pkg_resources import (resource_listdir, resource_exists, normalize_path,
working_set, _namespace_packages, evaluate_marker,
add_activation_listener, require, EntryPoint)
from setuptools import Command
+from .build_py import _unique_everseen
__metaclass__ = type
@@ -73,7 +74,7 @@ class NonDataProperty:
class test(Command):
"""Command to run unit tests after in-place build"""
- description = "run unit tests after in-place build"
+ description = "run unit tests after in-place build (deprecated)"
user_options = [
('test-module=', 'm', "Run 'test_suite' in specified module"),
@@ -186,7 +187,7 @@ class test(Command):
orig_pythonpath = os.environ.get('PYTHONPATH', nothing)
current_pythonpath = os.environ.get('PYTHONPATH', '')
try:
- prefix = os.pathsep.join(paths)
+ prefix = os.pathsep.join(_unique_everseen(paths))
to_join = filter(None, [prefix, current_pythonpath])
new_path = os.pathsep.join(to_join)
if new_path:
@@ -213,6 +214,14 @@ class test(Command):
return itertools.chain(ir_d, tr_d, er_d)
def run(self):
+ self.announce(
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox.",
+ log.WARN,
+ )
+
installed_dists = self.install_dists(self.distribution)
cmd = ' '.join(self._argv)
diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py
index 6db8888b..ec7f81e2 100644
--- a/setuptools/command/upload.py
+++ b/setuptools/command/upload.py
@@ -1,196 +1,17 @@
-import io
-import os
-import hashlib
-import getpass
-
-from base64 import standard_b64encode
-
from distutils import log
from distutils.command import upload as orig
-from distutils.spawn import spawn
-
-from distutils.errors import DistutilsError
-from setuptools.extern.six.moves.urllib.request import urlopen, Request
-from setuptools.extern.six.moves.urllib.error import HTTPError
-from setuptools.extern.six.moves.urllib.parse import urlparse
+from setuptools.errors import RemovedCommandError
class upload(orig.upload):
- """
- Override default upload behavior to obtain password
- in a variety of different ways.
- """
- def run(self):
- try:
- orig.upload.run(self)
- finally:
- self.announce(
- "WARNING: Uploading via this command is deprecated, use twine "
- "to upload instead (https://pypi.org/p/twine/)",
- log.WARN
- )
+ """Formerly used to upload packages to PyPI."""
- def finalize_options(self):
- orig.upload.finalize_options(self)
- self.username = (
- self.username or
- getpass.getuser()
- )
- # Attempt to obtain password. Short circuit evaluation at the first
- # sign of success.
- self.password = (
- self.password or
- self._load_password_from_keyring() or
- self._prompt_for_password()
+ def run(self):
+ msg = (
+ "The upload command has been removed, use twine to upload "
+ + "instead (https://pypi.org/p/twine)"
)
- def upload_file(self, command, pyversion, filename):
- # Makes sure the repository URL is compliant
- schema, netloc, url, params, query, fragments = \
- urlparse(self.repository)
- if params or query or fragments:
- raise AssertionError("Incompatible url %s" % self.repository)
-
- if schema not in ('http', 'https'):
- raise AssertionError("unsupported schema " + schema)
-
- # Sign if requested
- if self.sign:
- gpg_args = ["gpg", "--detach-sign", "-a", filename]
- if self.identity:
- gpg_args[2:2] = ["--local-user", self.identity]
- spawn(gpg_args,
- dry_run=self.dry_run)
-
- # Fill in the data - send all the meta-data in case we need to
- # register a new release
- with open(filename, 'rb') as f:
- content = f.read()
-
- meta = self.distribution.metadata
-
- data = {
- # action
- ':action': 'file_upload',
- 'protocol_version': '1',
-
- # identify release
- 'name': meta.get_name(),
- 'version': meta.get_version(),
-
- # file content
- 'content': (os.path.basename(filename), content),
- 'filetype': command,
- 'pyversion': pyversion,
- 'md5_digest': hashlib.md5(content).hexdigest(),
-
- # additional meta-data
- 'metadata_version': str(meta.get_metadata_version()),
- 'summary': meta.get_description(),
- 'home_page': meta.get_url(),
- 'author': meta.get_contact(),
- 'author_email': meta.get_contact_email(),
- 'license': meta.get_licence(),
- 'description': meta.get_long_description(),
- 'keywords': meta.get_keywords(),
- 'platform': meta.get_platforms(),
- 'classifiers': meta.get_classifiers(),
- 'download_url': meta.get_download_url(),
- # PEP 314
- 'provides': meta.get_provides(),
- 'requires': meta.get_requires(),
- 'obsoletes': meta.get_obsoletes(),
- }
-
- data['comment'] = ''
-
- if self.sign:
- data['gpg_signature'] = (os.path.basename(filename) + ".asc",
- open(filename+".asc", "rb").read())
-
- # set up the authentication
- user_pass = (self.username + ":" + self.password).encode('ascii')
- # The exact encoding of the authentication string is debated.
- # Anyway PyPI only accepts ascii for both username or password.
- auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
-
- # Build up the MIME payload for the POST data
- boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- sep_boundary = b'\r\n--' + boundary.encode('ascii')
- end_boundary = sep_boundary + b'--\r\n'
- body = io.BytesIO()
- for key, value in data.items():
- title = '\r\nContent-Disposition: form-data; name="%s"' % key
- # handle multiple entries for the same name
- if not isinstance(value, list):
- value = [value]
- for value in value:
- if type(value) is tuple:
- title += '; filename="%s"' % value[0]
- value = value[1]
- else:
- value = str(value).encode('utf-8')
- body.write(sep_boundary)
- body.write(title.encode('utf-8'))
- body.write(b"\r\n\r\n")
- body.write(value)
- body.write(end_boundary)
- body = body.getvalue()
-
- msg = "Submitting %s to %s" % (filename, self.repository)
- self.announce(msg, log.INFO)
-
- # build the Request
- headers = {
- 'Content-type': 'multipart/form-data; boundary=%s' % boundary,
- 'Content-length': str(len(body)),
- 'Authorization': auth,
- }
-
- request = Request(self.repository, data=body,
- headers=headers)
- # send the data
- try:
- result = urlopen(request)
- status = result.getcode()
- reason = result.msg
- except HTTPError as e:
- status = e.code
- reason = e.msg
- except OSError as e:
- self.announce(str(e), log.ERROR)
- raise
-
- if status == 200:
- self.announce('Server response (%s): %s' % (status, reason),
- log.INFO)
- if self.show_response:
- text = getattr(self, '_read_pypi_response',
- lambda x: None)(result)
- if text is not None:
- msg = '\n'.join(('-' * 75, text, '-' * 75))
- self.announce(msg, log.INFO)
- else:
- msg = 'Upload failed (%s): %s' % (status, reason)
- self.announce(msg, log.ERROR)
- raise DistutilsError(msg)
-
- def _load_password_from_keyring(self):
- """
- Attempt to load password from keyring. Suppress Exceptions.
- """
- try:
- keyring = __import__('keyring')
- return keyring.get_password(self.repository, self.username)
- except Exception:
- pass
-
- def _prompt_for_password(self):
- """
- Prompt for a password on the tty. Suppress Exceptions.
- """
- try:
- return getpass.getpass()
- except (Exception, KeyboardInterrupt):
- pass
+ self.announce("ERROR: " + msg, log.ERROR)
+ raise RemovedCommandError(msg)
diff --git a/setuptools/config.py b/setuptools/config.py
index b6626043..9b9a0c45 100644
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -12,6 +12,7 @@ from importlib import import_module
from distutils.errors import DistutilsOptionError, DistutilsFileError
from setuptools.extern.packaging.version import LegacyVersion, parse
+from setuptools.extern.packaging.specifiers import SpecifierSet
from setuptools.extern.six import string_types, PY3
@@ -482,6 +483,7 @@ class ConfigMetadataHandler(ConfigHandler):
'obsoletes': parse_list,
'classifiers': self._get_parser_compound(parse_file, parse_list),
'license': exclude_files_parser('license'),
+ 'license_files': parse_list,
'description': parse_file,
'long_description': parse_file,
'version': self._parse_version,
@@ -554,6 +556,7 @@ class ConfigOptionsHandler(ConfigHandler):
'packages': self._parse_packages,
'entry_points': self._parse_file,
'py_modules': parse_list,
+ 'python_requires': SpecifierSet,
}
def _parse_packages(self, value):
diff --git a/setuptools/depends.py b/setuptools/depends.py
index 45e7052d..a37675cb 100644
--- a/setuptools/depends.py
+++ b/setuptools/depends.py
@@ -1,11 +1,13 @@
import sys
-import imp
import marshal
+import contextlib
from distutils.version import StrictVersion
-from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN
from .py33compat import Bytecode
+from .py27compat import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE
+from . import py27compat
+
__all__ = [
'Require', 'find_module', 'get_module_constant', 'extract_constant'
@@ -15,7 +17,8 @@ __all__ = [
class Require:
"""A prerequisite to building or installing a distribution"""
- def __init__(self, name, requested_version, module, homepage='',
+ def __init__(
+ self, name, requested_version, module, homepage='',
attribute=None, format=None):
if format is None and requested_version is not None:
@@ -79,23 +82,15 @@ class Require:
return self.version_ok(version)
-def find_module(module, paths=None):
- """Just like 'imp.find_module()', but with package support"""
-
- parts = module.split('.')
-
- while parts:
- part = parts.pop(0)
- f, path, (suffix, mode, kind) = info = imp.find_module(part, paths)
-
- if kind == PKG_DIRECTORY:
- parts = parts or ['__init__']
- paths = [path]
-
- elif parts:
- raise ImportError("Can't find %r in %s" % (parts, module))
+def maybe_close(f):
+ @contextlib.contextmanager
+ def empty():
+ yield
+ return
+ if not f:
+ return empty()
- return info
+ return contextlib.closing(f)
def get_module_constant(module, symbol, default=-1, paths=None):
@@ -106,28 +101,23 @@ def get_module_constant(module, symbol, default=-1, paths=None):
constant. Otherwise, return 'default'."""
try:
- f, path, (suffix, mode, kind) = find_module(module, paths)
+ f, path, (suffix, mode, kind) = info = find_module(module, paths)
except ImportError:
# Module doesn't exist
return None
- try:
+ with maybe_close(f):
if kind == PY_COMPILED:
f.read(8) # skip magic & date
code = marshal.load(f)
elif kind == PY_FROZEN:
- code = imp.get_frozen_object(module)
+ code = py27compat.get_frozen_object(module, paths)
elif kind == PY_SOURCE:
code = compile(f.read(), path, 'exec')
else:
# Not something we can parse; we'll have to import it. :(
- if module not in sys.modules:
- imp.load_module(module, f, path, (suffix, mode, kind))
- return getattr(sys.modules[module], symbol, None)
-
- finally:
- if f:
- f.close()
+ imported = py27compat.get_module(module, paths, info)
+ return getattr(imported, symbol, None)
return extract_constant(code, symbol, default)
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 7062ae8d..1ba262ec 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
__all__ = ['Distribution']
+import io
+import sys
import re
import os
import warnings
@@ -9,9 +11,11 @@ import distutils.log
import distutils.core
import distutils.cmd
import distutils.dist
+from distutils.util import strtobool
+from distutils.debug import DEBUG
+from distutils.fancy_getopt import translate_longopt
import itertools
-
from collections import defaultdict
from email import message_from_file
@@ -23,6 +27,7 @@ from distutils.version import StrictVersion
from setuptools.extern import six
from setuptools.extern import packaging
+from setuptools.extern import ordered_set
from setuptools.extern.six.moves import map, filter, filterfalse
from . import SetuptoolsDeprecationWarning
@@ -32,7 +37,6 @@ from setuptools import windows_support
from setuptools.monkey import get_unpatched
from setuptools.config import parse_configuration
import pkg_resources
-from .py36compat import Distribution_parse_config_files
__import__('setuptools.extern.packaging.specifiers')
__import__('setuptools.extern.packaging.version')
@@ -51,7 +55,8 @@ def get_metadata_version(self):
mv = StrictVersion('2.1')
elif (self.maintainer is not None or
self.maintainer_email is not None or
- getattr(self, 'python_requires', None) is not None):
+ getattr(self, 'python_requires', None) is not None or
+ self.project_urls):
mv = StrictVersion('1.2')
elif (self.provides or self.requires or self.obsoletes or
self.classifiers or self.download_url):
@@ -130,7 +135,6 @@ def write_pkg_file(self, file):
def write_field(key, value):
file.write("%s: %s\n" % (key, value))
-
write_field('Metadata-Version', str(version))
write_field('Name', self.get_name())
write_field('Version', self.get_version())
@@ -210,8 +214,12 @@ def check_importable(dist, attr, value):
def assert_string_list(dist, attr, value):
- """Verify that value is a string list or None"""
+ """Verify that value is a string list"""
try:
+ # verify that value is a list or tuple to exclude unordered
+ # or single-use iterables
+ assert isinstance(value, (list, tuple))
+ # verify that elements of value are strings
assert ''.join(value) != value
except (TypeError, ValueError, AttributeError, AssertionError):
raise DistutilsSetupError(
@@ -304,20 +312,17 @@ def check_test_suite(dist, attr, value):
def check_package_data(dist, attr, value):
"""Verify that value is a dictionary of package names to glob lists"""
- if isinstance(value, dict):
- for k, v in value.items():
- if not isinstance(k, str):
- break
- try:
- iter(v)
- except TypeError:
- break
- else:
- return
- raise DistutilsSetupError(
- attr + " must be a dictionary mapping package names to lists of "
- "wildcard patterns"
- )
+ if not isinstance(value, dict):
+ raise DistutilsSetupError(
+ "{!r} must be a dictionary mapping package names to lists of "
+ "string wildcard patterns".format(attr))
+ for k, v in value.items():
+ if not isinstance(k, six.string_types):
+ raise DistutilsSetupError(
+ "keys of {!r} dict must be strings (got {!r})"
+ .format(attr, k)
+ )
+ assert_string_list(dist, 'values of {!r} dict'.format(attr), v)
def check_packages(dist, attr, value):
@@ -332,7 +337,7 @@ def check_packages(dist, attr, value):
_Distribution = get_unpatched(distutils.core.Distribution)
-class Distribution(Distribution_parse_config_files, _Distribution):
+class Distribution(_Distribution):
"""Distribution with support for features, tests, and package data
This is an enhanced version of 'distutils.dist.Distribution' that
@@ -403,7 +408,8 @@ class Distribution(Distribution_parse_config_files, _Distribution):
_DISTUTILS_UNSUPPORTED_METADATA = {
'long_description_content_type': None,
'project_urls': dict,
- 'provides_extras': set,
+ 'provides_extras': ordered_set.OrderedSet,
+ 'license_files': ordered_set.OrderedSet,
}
_patched_dist = None
@@ -556,12 +562,141 @@ class Distribution(Distribution_parse_config_files, _Distribution):
req.marker = None
return req
+ def _parse_config_files(self, filenames=None):
+ """
+ Adapted from distutils.dist.Distribution.parse_config_files,
+ this method provides the same functionality in subtly-improved
+ ways.
+ """
+ from setuptools.extern.six.moves.configparser import ConfigParser
+
+ # Ignore install directory options if we have a venv
+ if six.PY3 and sys.prefix != sys.base_prefix:
+ ignore_options = [
+ 'install-base', 'install-platbase', 'install-lib',
+ 'install-platlib', 'install-purelib', 'install-headers',
+ 'install-scripts', 'install-data', 'prefix', 'exec-prefix',
+ 'home', 'user', 'root']
+ else:
+ ignore_options = []
+
+ ignore_options = frozenset(ignore_options)
+
+ if filenames is None:
+ filenames = self.find_config_files()
+
+ if DEBUG:
+ self.announce("Distribution.parse_config_files():")
+
+ parser = ConfigParser()
+ for filename in filenames:
+ with io.open(filename, encoding='utf-8') as reader:
+ if DEBUG:
+ self.announce(" reading {filename}".format(**locals()))
+ (parser.read_file if six.PY3 else parser.readfp)(reader)
+ for section in parser.sections():
+ options = parser.options(section)
+ opt_dict = self.get_option_dict(section)
+
+ for opt in options:
+ if opt != '__name__' and opt not in ignore_options:
+ val = self._try_str(parser.get(section, opt))
+ opt = opt.replace('-', '_')
+ opt_dict[opt] = (filename, val)
+
+ # Make the ConfigParser forget everything (so we retain
+ # the original filenames that options come from)
+ parser.__init__()
+
+ # If there was a "global" section in the config file, use it
+ # to set Distribution options.
+
+ if 'global' in self.command_options:
+ for (opt, (src, val)) in self.command_options['global'].items():
+ alias = self.negative_opt.get(opt)
+ try:
+ if alias:
+ setattr(self, alias, not strtobool(val))
+ elif opt in ('verbose', 'dry_run'): # ugh!
+ setattr(self, opt, strtobool(val))
+ else:
+ setattr(self, opt, val)
+ except ValueError as msg:
+ raise DistutilsOptionError(msg)
+
+ @staticmethod
+ def _try_str(val):
+ """
+ On Python 2, much of distutils relies on string values being of
+ type 'str' (bytes) and not unicode text. If the value can be safely
+ encoded to bytes using the default encoding, prefer that.
+
+ Why the default encoding? Because that value can be implicitly
+ decoded back to text if needed.
+
+ Ref #1653
+ """
+ if six.PY3:
+ return val
+ try:
+ return val.encode()
+ except UnicodeEncodeError:
+ pass
+ return val
+
+ def _set_command_options(self, command_obj, option_dict=None):
+ """
+ Set the options for 'command_obj' from 'option_dict'. Basically
+ this means copying elements of a dictionary ('option_dict') to
+ attributes of an instance ('command').
+
+ 'command_obj' must be a Command instance. If 'option_dict' is not
+ supplied, uses the standard option dictionary for this command
+ (from 'self.command_options').
+
+ (Adopted from distutils.dist.Distribution._set_command_options)
+ """
+ command_name = command_obj.get_command_name()
+ if option_dict is None:
+ option_dict = self.get_option_dict(command_name)
+
+ if DEBUG:
+ self.announce(" setting options for '%s' command:" % command_name)
+ for (option, (source, value)) in option_dict.items():
+ if DEBUG:
+ self.announce(" %s = %s (from %s)" % (option, value,
+ source))
+ try:
+ bool_opts = [translate_longopt(o)
+ for o in command_obj.boolean_options]
+ except AttributeError:
+ bool_opts = []
+ try:
+ neg_opt = command_obj.negative_opt
+ except AttributeError:
+ neg_opt = {}
+
+ try:
+ is_string = isinstance(value, six.string_types)
+ if option in neg_opt and is_string:
+ setattr(command_obj, neg_opt[option], not strtobool(value))
+ elif option in bool_opts and is_string:
+ setattr(command_obj, option, strtobool(value))
+ elif hasattr(command_obj, option):
+ setattr(command_obj, option, value)
+ else:
+ raise DistutilsOptionError(
+ "error in %s: command '%s' has no such option '%s'"
+ % (source, command_name, option))
+ except ValueError as msg:
+ raise DistutilsOptionError(msg)
+
def parse_config_files(self, filenames=None, ignore_option_errors=False):
"""Parses configuration files from various levels
and loads configuration.
"""
- _Distribution.parse_config_files(self, filenames=filenames)
+ self._parse_config_files(filenames=filenames)
parse_configuration(self, self.command_options,
ignore_option_errors=ignore_option_errors)
@@ -590,15 +725,28 @@ class Distribution(Distribution_parse_config_files, _Distribution):
return resolved_dists
def finalize_options(self):
- _Distribution.finalize_options(self)
- if self.features:
- self._set_global_opts_from_features()
+ """
+ Allow plugins to apply arbitrary operations to the
+ distribution. Each hook may optionally define a 'order'
+ to influence the order of execution. Smaller numbers
+ go first and the default is 0.
+ """
+ hook_key = 'setuptools.finalize_distribution_options'
+
+ def by_order(hook):
+ return getattr(hook, 'order', 0)
+ eps = pkg_resources.iter_entry_points(hook_key)
+ for ep in sorted(eps, key=by_order):
+ ep.load()(self)
+ def _finalize_setup_keywords(self):
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
value = getattr(self, ep.name, None)
if value is not None:
ep.require(installer=self.fetch_build_egg)
ep.load()(self, ep.name, value)
+
+ def _finalize_2to3_doctests(self):
if getattr(self, 'convert_2to3_doctests', None):
# XXX may convert to set here when we can rely on set being builtin
self.convert_2to3_doctests = [
@@ -625,36 +773,15 @@ class Distribution(Distribution_parse_config_files, _Distribution):
def fetch_build_egg(self, req):
"""Fetch an egg needed for building"""
- from setuptools.command.easy_install import easy_install
- dist = self.__class__({'script_args': ['easy_install']})
- opts = dist.get_option_dict('easy_install')
- opts.clear()
- opts.update(
- (k, v)
- for k, v in self.get_option_dict('easy_install').items()
- if k in (
- # don't use any other settings
- 'find_links', 'site_dirs', 'index_url',
- 'optimize', 'site_dirs', 'allow_hosts',
- ))
- if self.dependency_links:
- links = self.dependency_links[:]
- if 'find_links' in opts:
- links = opts['find_links'][1] + links
- opts['find_links'] = ('setup', links)
- install_dir = self.get_egg_cache_dir()
- cmd = easy_install(
- dist, args=["x"], install_dir=install_dir,
- exclude_scripts=True,
- always_copy=False, build_directory=None, editable=False,
- upgrade=False, multi_version=True, no_report=True, user=False
- )
- cmd.ensure_finalized()
- return cmd.easy_install(req)
+ from setuptools.installer import fetch_build_egg
+ return fetch_build_egg(self, req)
- def _set_global_opts_from_features(self):
+ def _finalize_feature_opts(self):
"""Add --with-X/--without-X options based on optional features"""
+ if not self.features:
+ return
+
go = []
no = self.negative_opt.copy()
@@ -747,7 +874,7 @@ class Distribution(Distribution_parse_config_files, _Distribution):
def include(self, **attrs):
"""Add items to distribution that are named in keyword arguments
- For example, 'dist.exclude(py_modules=["x"])' would add 'x' to
+ For example, 'dist.include(py_modules=["x"])' would add 'x' to
the distribution's 'py_modules' attribute, if it was not already
there.
@@ -965,7 +1092,6 @@ class Distribution(Distribution_parse_config_files, _Distribution):
return _Distribution.handle_display_options(self, option_order)
# Stdout may be StringIO (e.g. in tests)
- import io
if not isinstance(sys.stdout, io.TextIOWrapper):
return _Distribution.handle_display_options(self, option_order)
@@ -1144,4 +1270,5 @@ class Feature:
class DistDeprecationWarning(SetuptoolsDeprecationWarning):
- """Class for warning about deprecations in dist in setuptools. Not ignored by default, unlike DeprecationWarning."""
+ """Class for warning about deprecations in dist in
+ setuptools. Not ignored by default, unlike DeprecationWarning."""
diff --git a/setuptools/errors.py b/setuptools/errors.py
new file mode 100644
index 00000000..2701747f
--- /dev/null
+++ b/setuptools/errors.py
@@ -0,0 +1,16 @@
+"""setuptools.errors
+
+Provides exceptions used by setuptools modules.
+"""
+
+from distutils.errors import DistutilsError
+
+
+class RemovedCommandError(DistutilsError, RuntimeError):
+ """Error used for commands that have been removed in setuptools.
+
+ Since ``setuptools`` is built on ``distutils``, simply removing a command
+ from ``setuptools`` will make the behavior fall back to ``distutils``; this
+ error is raised if a command exists in ``distutils`` but has been actively
+ removed in ``setuptools``.
+ """
diff --git a/setuptools/extern/__init__.py b/setuptools/extern/__init__.py
index cb2fa329..e8c616f9 100644
--- a/setuptools/extern/__init__.py
+++ b/setuptools/extern/__init__.py
@@ -69,5 +69,5 @@ class VendorImporter:
sys.meta_path.append(self)
-names = 'six', 'packaging', 'pyparsing',
+names = 'six', 'packaging', 'pyparsing', 'ordered_set',
VendorImporter(__name__, names, 'setuptools._vendor').install()
diff --git a/setuptools/glibc.py b/setuptools/glibc.py
deleted file mode 100644
index a134591c..00000000
--- a/setuptools/glibc.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# This file originally from pip:
-# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/utils/glibc.py
-from __future__ import absolute_import
-
-import ctypes
-import re
-import warnings
-
-
-def glibc_version_string():
- "Returns glibc version string, or None if not using glibc."
-
- # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
- # manpage says, "If filename is NULL, then the returned handle is for the
- # main program". This way we can let the linker do the work to figure out
- # which libc our process is actually using.
- process_namespace = ctypes.CDLL(None)
- try:
- gnu_get_libc_version = process_namespace.gnu_get_libc_version
- except AttributeError:
- # Symbol doesn't exist -> therefore, we are not linked to
- # glibc.
- return None
-
- # Call gnu_get_libc_version, which returns a string like "2.5"
- gnu_get_libc_version.restype = ctypes.c_char_p
- version_str = gnu_get_libc_version()
- # py2 / py3 compatibility:
- if not isinstance(version_str, str):
- version_str = version_str.decode("ascii")
-
- return version_str
-
-
-# Separated out from have_compatible_glibc for easier unit testing
-def check_glibc_version(version_str, required_major, minimum_minor):
- # Parse string and check against requested version.
- #
- # We use a regexp instead of str.split because we want to discard any
- # random junk that might come after the minor version -- this might happen
- # in patched/forked versions of glibc (e.g. Linaro's version of glibc
- # uses version strings like "2.20-2014.11"). See gh-3588.
- m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
- if not m:
- warnings.warn("Expected glibc version with 2 components major.minor,"
- " got: %s" % version_str, RuntimeWarning)
- return False
- return (int(m.group("major")) == required_major and
- int(m.group("minor")) >= minimum_minor)
-
-
-def have_compatible_glibc(required_major, minimum_minor):
- version_str = glibc_version_string()
- if version_str is None:
- return False
- return check_glibc_version(version_str, required_major, minimum_minor)
-
-
-# platform.libc_ver regularly returns completely nonsensical glibc
-# versions. E.g. on my computer, platform says:
-#
-# ~$ python2.7 -c 'import platform; print(platform.libc_ver())'
-# ('glibc', '2.7')
-# ~$ python3.5 -c 'import platform; print(platform.libc_ver())'
-# ('glibc', '2.9')
-#
-# But the truth is:
-#
-# ~$ ldd --version
-# ldd (Debian GLIBC 2.22-11) 2.22
-#
-# This is unfortunate, because it means that the linehaul data on libc
-# versions that was generated by pip 8.1.2 and earlier is useless and
-# misleading. Solution: instead of using platform, use our code that actually
-# works.
-def libc_ver():
- """Try to determine the glibc version
-
- Returns a tuple of strings (lib, version) which default to empty strings
- in case the lookup fails.
- """
- glibc_version = glibc_version_string()
- if glibc_version is None:
- return ("", "")
- else:
- return ("glibc", glibc_version)
diff --git a/setuptools/installer.py b/setuptools/installer.py
new file mode 100644
index 00000000..9f8be2ef
--- /dev/null
+++ b/setuptools/installer.py
@@ -0,0 +1,150 @@
+import glob
+import os
+import subprocess
+import sys
+from distutils import log
+from distutils.errors import DistutilsError
+
+import pkg_resources
+from setuptools.command.easy_install import easy_install
+from setuptools.extern import six
+from setuptools.wheel import Wheel
+
+from .py31compat import TemporaryDirectory
+
+
+def _fixup_find_links(find_links):
+ """Ensure find-links option end-up being a list of strings."""
+ if isinstance(find_links, six.string_types):
+ return find_links.split()
+ assert isinstance(find_links, (tuple, list))
+ return find_links
+
+
+def _legacy_fetch_build_egg(dist, req):
+ """Fetch an egg needed for building.
+
+ Legacy path using EasyInstall.
+ """
+ tmp_dist = dist.__class__({'script_args': ['easy_install']})
+ opts = tmp_dist.get_option_dict('easy_install')
+ opts.clear()
+ opts.update(
+ (k, v)
+ for k, v in dist.get_option_dict('easy_install').items()
+ if k in (
+ # don't use any other settings
+ 'find_links', 'site_dirs', 'index_url',
+ 'optimize', 'site_dirs', 'allow_hosts',
+ ))
+ if dist.dependency_links:
+ links = dist.dependency_links[:]
+ if 'find_links' in opts:
+ links = _fixup_find_links(opts['find_links'][1]) + links
+ opts['find_links'] = ('setup', links)
+ install_dir = dist.get_egg_cache_dir()
+ cmd = easy_install(
+ tmp_dist, args=["x"], install_dir=install_dir,
+ exclude_scripts=True,
+ always_copy=False, build_directory=None, editable=False,
+ upgrade=False, multi_version=True, no_report=True, user=False
+ )
+ cmd.ensure_finalized()
+ return cmd.easy_install(req)
+
+
+def fetch_build_egg(dist, req):
+ """Fetch an egg needed for building.
+
+ Use pip/wheel to fetch/build a wheel."""
+ # Check pip is available.
+ try:
+ pkg_resources.get_distribution('pip')
+ except pkg_resources.DistributionNotFound:
+ dist.announce(
+ 'WARNING: The pip package is not available, falling back '
+ 'to EasyInstall for handling setup_requires/test_requires; '
+ 'this is deprecated and will be removed in a future version.'
+ , log.WARN
+ )
+ return _legacy_fetch_build_egg(dist, req)
+ # Warn if wheel is not.
+ try:
+ pkg_resources.get_distribution('wheel')
+ except pkg_resources.DistributionNotFound:
+ dist.announce('WARNING: The wheel package is not available.', log.WARN)
+ # Ignore environment markers; if supplied, it is required.
+ req = strip_marker(req)
+ # Take easy_install options into account, but do not override relevant
+ # pip environment variables (like PIP_INDEX_URL or PIP_QUIET); they'll
+ # take precedence.
+ opts = dist.get_option_dict('easy_install')
+ if 'allow_hosts' in opts:
+ raise DistutilsError('the `allow-hosts` option is not supported '
+ 'when using pip to install requirements.')
+ if 'PIP_QUIET' in os.environ or 'PIP_VERBOSE' in os.environ:
+ quiet = False
+ else:
+ quiet = True
+ if 'PIP_INDEX_URL' in os.environ:
+ index_url = None
+ elif 'index_url' in opts:
+ index_url = opts['index_url'][1]
+ else:
+ index_url = None
+ if 'find_links' in opts:
+ find_links = _fixup_find_links(opts['find_links'][1])[:]
+ else:
+ find_links = []
+ if dist.dependency_links:
+ find_links.extend(dist.dependency_links)
+ eggs_dir = os.path.realpath(dist.get_egg_cache_dir())
+ environment = pkg_resources.Environment()
+ for egg_dist in pkg_resources.find_distributions(eggs_dir):
+ if egg_dist in req and environment.can_add(egg_dist):
+ return egg_dist
+ with TemporaryDirectory() as tmpdir:
+ cmd = [
+ sys.executable, '-m', 'pip',
+ '--disable-pip-version-check',
+ 'wheel', '--no-deps',
+ '-w', tmpdir,
+ ]
+ if quiet:
+ cmd.append('--quiet')
+ if index_url is not None:
+ cmd.extend(('--index-url', index_url))
+ if find_links is not None:
+ for link in find_links:
+ cmd.extend(('--find-links', link))
+ # If requirement is a PEP 508 direct URL, directly pass
+ # the URL to pip, as `req @ url` does not work on the
+ # command line.
+ if req.url:
+ cmd.append(req.url)
+ else:
+ cmd.append(str(req))
+ try:
+ subprocess.check_call(cmd)
+ except subprocess.CalledProcessError as e:
+ raise DistutilsError(str(e))
+ wheel = Wheel(glob.glob(os.path.join(tmpdir, '*.whl'))[0])
+ dist_location = os.path.join(eggs_dir, wheel.egg_name())
+ wheel.install_as_egg(dist_location)
+ dist_metadata = pkg_resources.PathMetadata(
+ dist_location, os.path.join(dist_location, 'EGG-INFO'))
+ dist = pkg_resources.Distribution.from_filename(
+ dist_location, metadata=dist_metadata)
+ return dist
+
+
+def strip_marker(req):
+ """
+ Return a new requirement without the environment marker to avoid
+ calling pip with something like `babel; extra == "i18n"`, which
+ would always be ignored.
+ """
+ # create a copy to avoid mutating the input
+ req = pkg_resources.Requirement.parse(str(req))
+ req.marker = None
+ return req
diff --git a/setuptools/msvc.py b/setuptools/msvc.py
index b9c472f1..2ffe1c81 100644
--- a/setuptools/msvc.py
+++ b/setuptools/msvc.py
@@ -11,13 +11,18 @@ Microsoft Visual C++ 9.0:
Microsoft Visual C++ 10.0:
Microsoft Windows SDK 7.1 (x86, x64, ia64)
-Microsoft Visual C++ 14.0:
+Microsoft Visual C++ 14.X:
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
- Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
+ Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64)
+
+This may also support compilers shipped with compatible Visual Studio versions.
"""
-import os
+import json
+from io import open
+from os import listdir, pathsep
+from os.path import join, isfile, isdir, dirname
import sys
import platform
import itertools
@@ -30,12 +35,9 @@ from .monkey import get_unpatched
if platform.system() == 'Windows':
from setuptools.extern.six.moves import winreg
- safe_env = os.environ
+ from os import environ
else:
- """
- Mock winreg and environ so the module can be imported
- on this platform.
- """
+ # Mock winreg and environ so the module can be imported on this platform.
class winreg:
HKEY_USERS = None
@@ -43,7 +45,7 @@ else:
HKEY_LOCAL_MACHINE = None
HKEY_CLASSES_ROOT = None
- safe_env = dict()
+ environ = dict()
_msvc9_suppress_errors = (
# msvc9compiler isn't available on some platforms
@@ -63,15 +65,13 @@ except _msvc9_suppress_errors:
def msvc9_find_vcvarsall(version):
"""
Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone
- compiler build for Python (VCForPython). Fall back to original behavior
- when the standalone compiler is not available.
+ compiler build for Python
+ (VCForPython / Microsoft Visual C++ Compiler for Python 2.7).
- Redirect the path of "vcvarsall.bat".
+ Fall back to original behavior when the standalone compiler is not
+ available.
- Known supported compilers
- -------------------------
- Microsoft Visual C++ 9.0:
- Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
+ Redirect the path of "vcvarsall.bat".
Parameters
----------
@@ -80,24 +80,25 @@ def msvc9_find_vcvarsall(version):
Return
------
- vcvarsall.bat path: str
+ str
+ vcvarsall.bat path
"""
- VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
- key = VC_BASE % ('', version)
+ vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
+ key = vc_base % ('', version)
try:
# Per-user installs register the compiler path here
productdir = Reg.get_value(key, "installdir")
except KeyError:
try:
# All-user installs on a 64-bit system register here
- key = VC_BASE % ('Wow6432Node\\', version)
+ key = vc_base % ('Wow6432Node\\', version)
productdir = Reg.get_value(key, "installdir")
except KeyError:
productdir = None
if productdir:
- vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat")
- if os.path.isfile(vcvarsall):
+ vcvarsall = join(productdir, "vcvarsall.bat")
+ if isfile(vcvarsall):
return vcvarsall
return get_unpatched(msvc9_find_vcvarsall)(version)
@@ -106,20 +107,10 @@ def msvc9_find_vcvarsall(version):
def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
"""
Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
- compilers.
+ Microsoft Visual C++ 9.0 and 10.0 compilers.
Set environment without use of "vcvarsall.bat".
- Known supported compilers
- -------------------------
- Microsoft Visual C++ 9.0:
- Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
- Microsoft Windows SDK 6.1 (x86, x64, ia64)
- Microsoft Windows SDK 7.0 (x86, x64, ia64)
-
- Microsoft Visual C++ 10.0:
- Microsoft Windows SDK 7.1 (x86, x64, ia64)
-
Parameters
----------
ver: float
@@ -129,9 +120,10 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
Return
------
- environment: dict
+ dict
+ environment
"""
- # Try to get environement from vcvarsall.bat (Classical way)
+ # Try to get environment from vcvarsall.bat (Classical way)
try:
orig = get_unpatched(msvc9_query_vcvarsall)
return orig(ver, arch, *args, **kwargs)
@@ -153,17 +145,10 @@ def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
def msvc14_get_vc_env(plat_spec):
"""
Patched "distutils._msvccompiler._get_vc_env" for support extra
- compilers.
+ Microsoft Visual C++ 14.X compilers.
Set environment without use of "vcvarsall.bat".
- Known supported compilers
- -------------------------
- Microsoft Visual C++ 14.0:
- Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
- Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
- Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
-
Parameters
----------
plat_spec: str
@@ -171,7 +156,8 @@ def msvc14_get_vc_env(plat_spec):
Return
------
- environment: dict
+ dict
+ environment
"""
# Try to get environment from vcvarsall.bat (Classical way)
try:
@@ -217,9 +203,9 @@ def _augment_exception(exc, version, arch=''):
if version == 9.0:
if arch.lower().find('ia64') > -1:
# For VC++ 9.0, if IA64 support is needed, redirect user
- # to Windows SDK 7.0
- message += ' Get it with "Microsoft Windows SDK 7.0": '
- message += msdownload % 3138
+ # to Windows SDK 7.0.
+ # Note: No download link available from Microsoft.
+ message += ' Get it with "Microsoft Windows SDK 7.0"'
else:
# For VC++ 9.0 redirect user to Vc++ for Python 2.7 :
# This redirection link is maintained by Microsoft.
@@ -230,8 +216,8 @@ def _augment_exception(exc, version, arch=''):
message += ' Get it with "Microsoft Windows SDK 7.1": '
message += msdownload % 8279
elif version >= 14.0:
- # For VC++ 14.0 Redirect user to Visual C++ Build Tools
- message += (' Get it with "Microsoft Visual C++ Build Tools": '
+ # For VC++ 14.X Redirect user to latest Visual C++ Build Tools
+ message += (' Get it with "Build Tools for Visual Studio": '
r'https://visualstudio.microsoft.com/downloads/')
exc.args = (message, )
@@ -239,26 +225,50 @@ def _augment_exception(exc, version, arch=''):
class PlatformInfo:
"""
- Current and Target Architectures informations.
+ Current and Target Architectures information.
Parameters
----------
arch: str
Target architecture.
"""
- current_cpu = safe_env.get('processor_architecture', '').lower()
+ current_cpu = environ.get('processor_architecture', '').lower()
def __init__(self, arch):
self.arch = arch.lower().replace('x64', 'amd64')
@property
def target_cpu(self):
+ """
+ Return Target CPU architecture.
+
+ Return
+ ------
+ str
+ Target CPU
+ """
return self.arch[self.arch.find('_') + 1:]
def target_is_x86(self):
+ """
+ Return True if target CPU is x86 32 bits..
+
+ Return
+ ------
+ bool
+ CPU is x86 32 bits
+ """
return self.target_cpu == 'x86'
def current_is_x86(self):
+ """
+ Return True if current CPU is x86 32 bits..
+
+ Return
+ ------
+ bool
+ CPU is x86 32 bits
+ """
return self.current_cpu == 'x86'
def current_dir(self, hidex86=False, x64=False):
@@ -274,8 +284,8 @@ class PlatformInfo:
Return
------
- subfolder: str
- '\target', or '' (see hidex86 parameter)
+ str
+ subfolder: '\target', or '' (see hidex86 parameter)
"""
return (
'' if (self.current_cpu == 'x86' and hidex86) else
@@ -296,8 +306,8 @@ class PlatformInfo:
Return
------
- subfolder: str
- '\current', or '' (see hidex86 parameter)
+ str
+ subfolder: '\current', or '' (see hidex86 parameter)
"""
return (
'' if (self.target_cpu == 'x86' and hidex86) else
@@ -312,13 +322,13 @@ class PlatformInfo:
Parameters
----------
forcex86: bool
- Use 'x86' as current architecture even if current acritecture is
+ Use 'x86' as current architecture even if current architecture is
not x86.
Return
------
- subfolder: str
- '' if target architecture is current architecture,
+ str
+ subfolder: '' if target architecture is current architecture,
'\current_target' if not.
"""
current = 'x86' if forcex86 else self.current_cpu
@@ -330,7 +340,7 @@ class PlatformInfo:
class RegistryInfo:
"""
- Microsoft Visual Studio related registry informations.
+ Microsoft Visual Studio related registry information.
Parameters
----------
@@ -349,6 +359,11 @@ class RegistryInfo:
def visualstudio(self):
"""
Microsoft Visual Studio root registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
return 'VisualStudio'
@@ -356,27 +371,47 @@ class RegistryInfo:
def sxs(self):
"""
Microsoft Visual Studio SxS registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.visualstudio, 'SxS')
+ return join(self.visualstudio, 'SxS')
@property
def vc(self):
"""
Microsoft Visual C++ VC7 registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.sxs, 'VC7')
+ return join(self.sxs, 'VC7')
@property
def vs(self):
"""
Microsoft Visual Studio VS7 registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.sxs, 'VS7')
+ return join(self.sxs, 'VS7')
@property
def vc_for_python(self):
"""
Microsoft Visual C++ for Python registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
return r'DevDiv\VCForPython'
@@ -384,6 +419,11 @@ class RegistryInfo:
def microsoft_sdk(self):
"""
Microsoft SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
return 'Microsoft SDKs'
@@ -391,20 +431,35 @@ class RegistryInfo:
def windows_sdk(self):
"""
Microsoft Windows/Platform SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.microsoft_sdk, 'Windows')
+ return join(self.microsoft_sdk, 'Windows')
@property
def netfx_sdk(self):
"""
Microsoft .NET Framework SDK registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
- return os.path.join(self.microsoft_sdk, 'NETFXSDK')
+ return join(self.microsoft_sdk, 'NETFXSDK')
@property
def windows_kits_roots(self):
"""
Microsoft Windows Kits Roots registry key.
+
+ Return
+ ------
+ str
+ Registry key
"""
return r'Windows Kits\Installed Roots'
@@ -421,10 +476,11 @@ class RegistryInfo:
Return
------
- str: value
+ str
+ Registry key
"""
node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
- return os.path.join('Software', node64, 'Microsoft', key)
+ return join('Software', node64, 'Microsoft', key)
def lookup(self, key, name):
"""
@@ -439,18 +495,19 @@ class RegistryInfo:
Return
------
- str: value
+ str
+ value
"""
- KEY_READ = winreg.KEY_READ
+ key_read = winreg.KEY_READ
openkey = winreg.OpenKey
ms = self.microsoft
for hkey in self.HKEYS:
try:
- bkey = openkey(hkey, ms(key), 0, KEY_READ)
+ bkey = openkey(hkey, ms(key), 0, key_read)
except (OSError, IOError):
if not self.pi.current_is_x86():
try:
- bkey = openkey(hkey, ms(key, True), 0, KEY_READ)
+ bkey = openkey(hkey, ms(key, True), 0, key_read)
except (OSError, IOError):
continue
else:
@@ -463,7 +520,7 @@ class RegistryInfo:
class SystemInfo:
"""
- Microsoft Windows and Visual Studio related system inormations.
+ Microsoft Windows and Visual Studio related system information.
Parameters
----------
@@ -474,30 +531,52 @@ class SystemInfo:
"""
# Variables and properties in this class use originals CamelCase variables
- # names from Microsoft source files for more easy comparaison.
- WinDir = safe_env.get('WinDir', '')
- ProgramFiles = safe_env.get('ProgramFiles', '')
- ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles)
+ # names from Microsoft source files for more easy comparison.
+ WinDir = environ.get('WinDir', '')
+ ProgramFiles = environ.get('ProgramFiles', '')
+ ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)
def __init__(self, registry_info, vc_ver=None):
self.ri = registry_info
self.pi = self.ri.pi
- self.vc_ver = vc_ver or self._find_latest_available_vc_ver()
- def _find_latest_available_vc_ver(self):
- try:
- return self.find_available_vc_vers()[-1]
- except IndexError:
- err = 'No Microsoft Visual C++ version found'
- raise distutils.errors.DistutilsPlatformError(err)
+ self.known_vs_paths = self.find_programdata_vs_vers()
+
+ # Except for VS15+, VC version is aligned with VS version
+ self.vs_ver = self.vc_ver = (
+ vc_ver or self._find_latest_available_vs_ver())
+
+ def _find_latest_available_vs_ver(self):
+ """
+ Find the latest VC version
+
+ Return
+ ------
+ float
+ version
+ """
+ reg_vc_vers = self.find_reg_vs_vers()
+
+ if not (reg_vc_vers or self.known_vs_paths):
+ raise distutils.errors.DistutilsPlatformError(
+ 'No Microsoft Visual C++ version found')
+
+ vc_vers = set(reg_vc_vers)
+ vc_vers.update(self.known_vs_paths)
+ return sorted(vc_vers)[-1]
- def find_available_vc_vers(self):
+ def find_reg_vs_vers(self):
"""
- Find all available Microsoft Visual C++ versions.
+ Find Microsoft Visual Studio versions available in registry.
+
+ Return
+ ------
+ list of float
+ Versions
"""
ms = self.ri.microsoft
vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
- vc_vers = []
+ vs_vers = []
for hkey in self.ri.HKEYS:
for key in vckeys:
try:
@@ -508,49 +587,108 @@ class SystemInfo:
for i in range(values):
try:
ver = float(winreg.EnumValue(bkey, i)[0])
- if ver not in vc_vers:
- vc_vers.append(ver)
+ if ver not in vs_vers:
+ vs_vers.append(ver)
except ValueError:
pass
for i in range(subkeys):
try:
ver = float(winreg.EnumKey(bkey, i))
- if ver not in vc_vers:
- vc_vers.append(ver)
+ if ver not in vs_vers:
+ vs_vers.append(ver)
except ValueError:
pass
- return sorted(vc_vers)
+ return sorted(vs_vers)
+
+ def find_programdata_vs_vers(self):
+ r"""
+ Find Visual studio 2017+ versions from information in
+ "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".
+
+ Return
+ ------
+ dict
+ float version as key, path as value.
+ """
+ vs_versions = {}
+ instances_dir = \
+ r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'
+
+ try:
+ hashed_names = listdir(instances_dir)
+
+ except (OSError, IOError):
+ # Directory not exists with all Visual Studio versions
+ return vs_versions
+
+ for name in hashed_names:
+ try:
+ # Get VS installation path from "state.json" file
+ state_path = join(instances_dir, name, 'state.json')
+ with open(state_path, 'rt', encoding='utf-8') as state_file:
+ state = json.load(state_file)
+ vs_path = state['installationPath']
+
+ # Raises OSError if this VS installation does not contain VC
+ listdir(join(vs_path, r'VC\Tools\MSVC'))
+
+ # Store version and path
+ vs_versions[self._as_float_version(
+ state['installationVersion'])] = vs_path
+
+ except (OSError, IOError, KeyError):
+ # Skip if "state.json" file is missing or bad format
+ continue
+
+ return vs_versions
+
+ @staticmethod
+ def _as_float_version(version):
+ """
+ Return a string version as a simplified float version (major.minor)
+
+ Parameters
+ ----------
+ version: str
+ Version.
+
+ Return
+ ------
+ float
+ version
+ """
+ return float('.'.join(version.split('.')[:2]))
@property
def VSInstallDir(self):
"""
Microsoft Visual Studio directory.
+
+ Return
+ ------
+ str
+ path
"""
# Default path
- name = 'Microsoft Visual Studio %0.1f' % self.vc_ver
- default = os.path.join(self.ProgramFilesx86, name)
+ default = join(self.ProgramFilesx86,
+ 'Microsoft Visual Studio %0.1f' % self.vs_ver)
# Try to get path from registry, if fail use default path
- return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default
+ return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default
@property
def VCInstallDir(self):
"""
Microsoft Visual C++ directory.
- """
- self.VSInstallDir
-
- guess_vc = self._guess_vc() or self._guess_vc_legacy()
-
- # Try to get "VC++ for Python" path from registry as default path
- reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
- python_vc = self.ri.lookup(reg_path, 'installdir')
- default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc
- # Try to get path from registry, if fail use default path
- path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc
+ Return
+ ------
+ str
+ path
+ """
+ path = self._guess_vc() or self._guess_vc_legacy()
- if not os.path.isdir(path):
+ if not isdir(path):
msg = 'Microsoft Visual C++ directory not found'
raise distutils.errors.DistutilsPlatformError(msg)
@@ -558,186 +696,256 @@ class SystemInfo:
def _guess_vc(self):
"""
- Locate Visual C for 2017
+ Locate Visual C++ for VS2017+.
+
+ Return
+ ------
+ str
+ path
"""
- if self.vc_ver <= 14.0:
- return
+ if self.vs_ver <= 14.0:
+ return ''
+
+ try:
+ # First search in known VS paths
+ vs_dir = self.known_vs_paths[self.vs_ver]
+ except KeyError:
+ # Else, search with path from registry
+ vs_dir = self.VSInstallDir
+
+ guess_vc = join(vs_dir, r'VC\Tools\MSVC')
- default = r'VC\Tools\MSVC'
- guess_vc = os.path.join(self.VSInstallDir, default)
# Subdir with VC exact version as name
try:
- vc_exact_ver = os.listdir(guess_vc)[-1]
- return os.path.join(guess_vc, vc_exact_ver)
+ # Update the VC version with real one instead of VS version
+ vc_ver = listdir(guess_vc)[-1]
+ self.vc_ver = self._as_float_version(vc_ver)
+ return join(guess_vc, vc_ver)
except (OSError, IOError, IndexError):
- pass
+ return ''
def _guess_vc_legacy(self):
"""
- Locate Visual C for versions prior to 2017
+ Locate Visual C++ for versions prior to 2017.
+
+ Return
+ ------
+ str
+ path
"""
- default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver
- return os.path.join(self.ProgramFilesx86, default)
+ default = join(self.ProgramFilesx86,
+ r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver)
+
+ # Try to get "VC++ for Python" path from registry as default path
+ reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver)
+ python_vc = self.ri.lookup(reg_path, 'installdir')
+ default_vc = join(python_vc, 'VC') if python_vc else default
+
+ # Try to get path from registry, if fail use default path
+ return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc
@property
def WindowsSdkVersion(self):
"""
Microsoft Windows SDK versions for specified MSVC++ version.
- """
- if self.vc_ver <= 9.0:
- return ('7.0', '6.1', '6.0a')
- elif self.vc_ver == 10.0:
- return ('7.1', '7.0a')
- elif self.vc_ver == 11.0:
- return ('8.0', '8.0a')
- elif self.vc_ver == 12.0:
- return ('8.1', '8.1a')
- elif self.vc_ver >= 14.0:
- return ('10.0', '8.1')
+
+ Return
+ ------
+ tuple of str
+ versions
+ """
+ if self.vs_ver <= 9.0:
+ return '7.0', '6.1', '6.0a'
+ elif self.vs_ver == 10.0:
+ return '7.1', '7.0a'
+ elif self.vs_ver == 11.0:
+ return '8.0', '8.0a'
+ elif self.vs_ver == 12.0:
+ return '8.1', '8.1a'
+ elif self.vs_ver >= 14.0:
+ return '10.0', '8.1'
@property
def WindowsSdkLastVersion(self):
"""
- Microsoft Windows SDK last version
+ Microsoft Windows SDK last version.
+
+ Return
+ ------
+ str
+ version
"""
- return self._use_last_dir_name(os.path.join(
- self.WindowsSdkDir, 'lib'))
+ return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))
@property
def WindowsSdkDir(self):
"""
Microsoft Windows SDK directory.
+
+ Return
+ ------
+ str
+ path
"""
sdkdir = ''
for ver in self.WindowsSdkVersion:
# Try to get it from registry
- loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver)
+ loc = join(self.ri.windows_sdk, 'v%s' % ver)
sdkdir = self.ri.lookup(loc, 'installationfolder')
if sdkdir:
break
- if not sdkdir or not os.path.isdir(sdkdir):
+ if not sdkdir or not isdir(sdkdir):
# Try to get "VC++ for Python" version from registry
- path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
+ path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
install_base = self.ri.lookup(path, 'installdir')
if install_base:
- sdkdir = os.path.join(install_base, 'WinSDK')
- if not sdkdir or not os.path.isdir(sdkdir):
+ sdkdir = join(install_base, 'WinSDK')
+ if not sdkdir or not isdir(sdkdir):
# If fail, use default new path
for ver in self.WindowsSdkVersion:
intver = ver[:ver.rfind('.')]
- path = r'Microsoft SDKs\Windows Kits\%s' % (intver)
- d = os.path.join(self.ProgramFiles, path)
- if os.path.isdir(d):
+ path = r'Microsoft SDKs\Windows Kits\%s' % intver
+ d = join(self.ProgramFiles, path)
+ if isdir(d):
sdkdir = d
- if not sdkdir or not os.path.isdir(sdkdir):
+ if not sdkdir or not isdir(sdkdir):
# If fail, use default old path
for ver in self.WindowsSdkVersion:
path = r'Microsoft SDKs\Windows\v%s' % ver
- d = os.path.join(self.ProgramFiles, path)
- if os.path.isdir(d):
+ d = join(self.ProgramFiles, path)
+ if isdir(d):
sdkdir = d
if not sdkdir:
# If fail, use Platform SDK
- sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK')
+ sdkdir = join(self.VCInstallDir, 'PlatformSDK')
return sdkdir
@property
def WindowsSDKExecutablePath(self):
"""
Microsoft Windows SDK executable directory.
+
+ Return
+ ------
+ str
+ path
"""
# Find WinSDK NetFx Tools registry dir name
- if self.vc_ver <= 11.0:
+ if self.vs_ver <= 11.0:
netfxver = 35
arch = ''
else:
netfxver = 40
- hidex86 = True if self.vc_ver <= 12.0 else False
+ hidex86 = True if self.vs_ver <= 12.0 else False
arch = self.pi.current_dir(x64=True, hidex86=hidex86)
fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-'))
- # liste all possibles registry paths
+ # list all possibles registry paths
regpaths = []
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
for ver in self.NetFxSdkVersion:
- regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)]
+ regpaths += [join(self.ri.netfx_sdk, ver, fx)]
for ver in self.WindowsSdkVersion:
- regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
+ regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
# Return installation folder from the more recent path
for path in regpaths:
execpath = self.ri.lookup(path, 'installationfolder')
if execpath:
- break
- return execpath
+ return execpath
@property
def FSharpInstallDir(self):
"""
Microsoft Visual F# directory.
+
+ Return
+ ------
+ str
+ path
"""
- path = r'%0.1f\Setup\F#' % self.vc_ver
- path = os.path.join(self.ri.visualstudio, path)
+ path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver)
return self.ri.lookup(path, 'productdir') or ''
@property
def UniversalCRTSdkDir(self):
"""
Microsoft Universal CRT SDK directory.
+
+ Return
+ ------
+ str
+ path
"""
# Set Kit Roots versions for specified MSVC++ version
- if self.vc_ver >= 14.0:
- vers = ('10', '81')
- else:
- vers = ()
+ vers = ('10', '81') if self.vs_ver >= 14.0 else ()
# Find path of the more recent Kit
for ver in vers:
sdkdir = self.ri.lookup(self.ri.windows_kits_roots,
'kitsroot%s' % ver)
if sdkdir:
- break
- return sdkdir or ''
+ return sdkdir or ''
@property
def UniversalCRTSdkLastVersion(self):
"""
- Microsoft Universal C Runtime SDK last version
+ Microsoft Universal C Runtime SDK last version.
+
+ Return
+ ------
+ str
+ version
"""
- return self._use_last_dir_name(os.path.join(
- self.UniversalCRTSdkDir, 'lib'))
+ return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib'))
@property
def NetFxSdkVersion(self):
"""
Microsoft .NET Framework SDK versions.
+
+ Return
+ ------
+ tuple of str
+ versions
"""
- # Set FxSdk versions for specified MSVC++ version
- if self.vc_ver >= 14.0:
- return ('4.6.1', '4.6')
- else:
- return ()
+ # Set FxSdk versions for specified VS version
+ return (('4.7.2', '4.7.1', '4.7',
+ '4.6.2', '4.6.1', '4.6',
+ '4.5.2', '4.5.1', '4.5')
+ if self.vs_ver >= 14.0 else ())
@property
def NetFxSdkDir(self):
"""
Microsoft .NET Framework SDK directory.
+
+ Return
+ ------
+ str
+ path
"""
+ sdkdir = ''
for ver in self.NetFxSdkVersion:
- loc = os.path.join(self.ri.netfx_sdk, ver)
+ loc = join(self.ri.netfx_sdk, ver)
sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')
if sdkdir:
break
- return sdkdir or ''
+ return sdkdir
@property
def FrameworkDir32(self):
"""
Microsoft .NET Framework 32bit directory.
+
+ Return
+ ------
+ str
+ path
"""
# Default path
- guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework')
+ guess_fw = join(self.WinDir, r'Microsoft.NET\Framework')
# Try to get path from registry, if fail use default path
return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw
@@ -746,9 +954,14 @@ class SystemInfo:
def FrameworkDir64(self):
"""
Microsoft .NET Framework 64bit directory.
+
+ Return
+ ------
+ str
+ path
"""
# Default path
- guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64')
+ guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64')
# Try to get path from registry, if fail use default path
return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw
@@ -757,6 +970,11 @@ class SystemInfo:
def FrameworkVersion32(self):
"""
Microsoft .NET Framework 32bit versions.
+
+ Return
+ ------
+ tuple of str
+ versions
"""
return self._find_dot_net_versions(32)
@@ -764,6 +982,11 @@ class SystemInfo:
def FrameworkVersion64(self):
"""
Microsoft .NET Framework 64bit versions.
+
+ Return
+ ------
+ tuple of str
+ versions
"""
return self._find_dot_net_versions(64)
@@ -775,6 +998,11 @@ class SystemInfo:
----------
bits: int
Platform number of bits: 32 or 64.
+
+ Return
+ ------
+ tuple of str
+ versions
"""
# Find actual .NET version in registry
reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits)
@@ -782,18 +1010,17 @@ class SystemInfo:
ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''
# Set .NET versions for specified MSVC++ version
- if self.vc_ver >= 12.0:
- frameworkver = (ver, 'v4.0')
- elif self.vc_ver >= 10.0:
- frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver,
- 'v3.5')
- elif self.vc_ver == 9.0:
- frameworkver = ('v3.5', 'v2.0.50727')
- if self.vc_ver == 8.0:
- frameworkver = ('v3.0', 'v2.0.50727')
- return frameworkver
-
- def _use_last_dir_name(self, path, prefix=''):
+ if self.vs_ver >= 12.0:
+ return ver, 'v4.0'
+ elif self.vs_ver >= 10.0:
+ return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5'
+ elif self.vs_ver == 9.0:
+ return 'v3.5', 'v2.0.50727'
+ elif self.vs_ver == 8.0:
+ return 'v3.0', 'v2.0.50727'
+
+ @staticmethod
+ def _use_last_dir_name(path, prefix=''):
"""
Return name of the last dir in path or '' if no dir found.
@@ -802,12 +1029,17 @@ class SystemInfo:
path: str
Use dirs in this path
prefix: str
- Use only dirs startings by this prefix
+ Use only dirs starting by this prefix
+
+ Return
+ ------
+ str
+ name
"""
matching_dirs = (
dir_name
- for dir_name in reversed(os.listdir(path))
- if os.path.isdir(os.path.join(path, dir_name)) and
+ for dir_name in reversed(listdir(path))
+ if isdir(join(path, dir_name)) and
dir_name.startswith(prefix)
)
return next(matching_dirs, None) or ''
@@ -818,7 +1050,7 @@ class EnvironmentInfo:
Return environment variables for specified Microsoft Visual C++ version
and platform : Lib, Include, Path and libpath.
- This function is compatible with Microsoft Visual C++ 9.0 to 14.0.
+ This function is compatible with Microsoft Visual C++ 9.0 to 14.X.
Script created by analysing Microsoft environment configuration files like
"vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...
@@ -835,7 +1067,7 @@ class EnvironmentInfo:
"""
# Variables and properties in this class use originals CamelCase variables
- # names from Microsoft source files for more easy comparaison.
+ # names from Microsoft source files for more easy comparison.
def __init__(self, arch, vc_ver=None, vc_min_ver=0):
self.pi = PlatformInfo(arch)
@@ -847,204 +1079,254 @@ class EnvironmentInfo:
raise distutils.errors.DistutilsPlatformError(err)
@property
+ def vs_ver(self):
+ """
+ Microsoft Visual Studio.
+
+ Return
+ ------
+ float
+ version
+ """
+ return self.si.vs_ver
+
+ @property
def vc_ver(self):
"""
Microsoft Visual C++ version.
+
+ Return
+ ------
+ float
+ version
"""
return self.si.vc_ver
@property
def VSTools(self):
"""
- Microsoft Visual Studio Tools
+ Microsoft Visual Studio Tools.
+
+ Return
+ ------
+ list of str
+ paths
"""
paths = [r'Common7\IDE', r'Common7\Tools']
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']
paths += [r'Team Tools\Performance Tools']
paths += [r'Team Tools\Performance Tools%s' % arch_subdir]
- return [os.path.join(self.si.VSInstallDir, path) for path in paths]
+ return [join(self.si.VSInstallDir, path) for path in paths]
@property
def VCIncludes(self):
"""
- Microsoft Visual C++ & Microsoft Foundation Class Includes
+ Microsoft Visual C++ & Microsoft Foundation Class Includes.
+
+ Return
+ ------
+ list of str
+ paths
"""
- return [os.path.join(self.si.VCInstallDir, 'Include'),
- os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')]
+ return [join(self.si.VCInstallDir, 'Include'),
+ join(self.si.VCInstallDir, r'ATLMFC\Include')]
@property
def VCLibraries(self):
"""
- Microsoft Visual C++ & Microsoft Foundation Class Libraries
+ Microsoft Visual C++ & Microsoft Foundation Class Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver >= 15.0:
+ if self.vs_ver >= 15.0:
arch_subdir = self.pi.target_dir(x64=True)
else:
arch_subdir = self.pi.target_dir(hidex86=True)
paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir]
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
paths += [r'Lib\store%s' % arch_subdir]
- return [os.path.join(self.si.VCInstallDir, path) for path in paths]
+ return [join(self.si.VCInstallDir, path) for path in paths]
@property
def VCStoreRefs(self):
"""
- Microsoft Visual C++ store references Libraries
+ Microsoft Visual C++ store references Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0:
+ if self.vs_ver < 14.0:
return []
- return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')]
+ return [join(self.si.VCInstallDir, r'Lib\store\references')]
@property
def VCTools(self):
"""
- Microsoft Visual C++ Tools
+ Microsoft Visual C++ Tools.
+
+ Return
+ ------
+ list of str
+ paths
"""
si = self.si
- tools = [os.path.join(si.VCInstallDir, 'VCPackages')]
+ tools = [join(si.VCInstallDir, 'VCPackages')]
- forcex86 = True if self.vc_ver <= 10.0 else False
+ forcex86 = True if self.vs_ver <= 10.0 else False
arch_subdir = self.pi.cross_dir(forcex86)
if arch_subdir:
- tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
+ tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
- if self.vc_ver == 14.0:
+ if self.vs_ver == 14.0:
path = 'Bin%s' % self.pi.current_dir(hidex86=True)
- tools += [os.path.join(si.VCInstallDir, path)]
+ tools += [join(si.VCInstallDir, path)]
- elif self.vc_ver >= 15.0:
+ elif self.vs_ver >= 15.0:
host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else
r'bin\HostX64%s')
- tools += [os.path.join(
+ tools += [join(
si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))]
if self.pi.current_cpu != self.pi.target_cpu:
- tools += [os.path.join(
+ tools += [join(
si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))]
else:
- tools += [os.path.join(si.VCInstallDir, 'Bin')]
+ tools += [join(si.VCInstallDir, 'Bin')]
return tools
@property
def OSLibraries(self):
"""
- Microsoft Windows SDK Libraries
+ Microsoft Windows SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver <= 10.0:
+ if self.vs_ver <= 10.0:
arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
- return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
+ return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
else:
arch_subdir = self.pi.target_dir(x64=True)
- lib = os.path.join(self.si.WindowsSdkDir, 'lib')
+ lib = join(self.si.WindowsSdkDir, 'lib')
libver = self._sdk_subdir
- return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))]
+ return [join(lib, '%sum%s' % (libver , arch_subdir))]
@property
def OSIncludes(self):
"""
- Microsoft Windows SDK Include
+ Microsoft Windows SDK Include.
+
+ Return
+ ------
+ list of str
+ paths
"""
- include = os.path.join(self.si.WindowsSdkDir, 'include')
+ include = join(self.si.WindowsSdkDir, 'include')
- if self.vc_ver <= 10.0:
- return [include, os.path.join(include, 'gl')]
+ if self.vs_ver <= 10.0:
+ return [include, join(include, 'gl')]
else:
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
sdkver = self._sdk_subdir
else:
sdkver = ''
- return [os.path.join(include, '%sshared' % sdkver),
- os.path.join(include, '%sum' % sdkver),
- os.path.join(include, '%swinrt' % sdkver)]
+ return [join(include, '%sshared' % sdkver),
+ join(include, '%sum' % sdkver),
+ join(include, '%swinrt' % sdkver)]
@property
def OSLibpath(self):
"""
- Microsoft Windows SDK Libraries Paths
+ Microsoft Windows SDK Libraries Paths.
+
+ Return
+ ------
+ list of str
+ paths
"""
- ref = os.path.join(self.si.WindowsSdkDir, 'References')
+ ref = join(self.si.WindowsSdkDir, 'References')
libpath = []
- if self.vc_ver <= 9.0:
+ if self.vs_ver <= 9.0:
libpath += self.OSLibraries
- if self.vc_ver >= 11.0:
- libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')]
+ if self.vs_ver >= 11.0:
+ libpath += [join(ref, r'CommonConfiguration\Neutral')]
- if self.vc_ver >= 14.0:
+ if self.vs_ver >= 14.0:
libpath += [
ref,
- os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'),
- os.path.join(
- ref,
- 'Windows.Foundation.UniversalApiContract',
- '1.0.0.0',
- ),
- os.path.join(
- ref,
- 'Windows.Foundation.FoundationContract',
- '1.0.0.0',
- ),
- os.path.join(
- ref,
- 'Windows.Networking.Connectivity.WwanContract',
- '1.0.0.0',
- ),
- os.path.join(
- self.si.WindowsSdkDir,
- 'ExtensionSDKs',
- 'Microsoft.VCLibs',
- '%0.1f' % self.vc_ver,
- 'References',
- 'CommonConfiguration',
- 'neutral',
- ),
+ join(self.si.WindowsSdkDir, 'UnionMetadata'),
+ join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
+ join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
+ join(ref,'Windows.Networking.Connectivity.WwanContract',
+ '1.0.0.0'),
+ join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs',
+ '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration',
+ 'neutral'),
]
return libpath
@property
def SdkTools(self):
"""
- Microsoft Windows SDK Tools
+ Microsoft Windows SDK Tools.
+
+ Return
+ ------
+ list of str
+ paths
"""
return list(self._sdk_tools())
def _sdk_tools(self):
"""
- Microsoft Windows SDK Tools paths generator
+ Microsoft Windows SDK Tools paths generator.
+
+ Return
+ ------
+ generator of str
+ paths
"""
- if self.vc_ver < 15.0:
- bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86'
- yield os.path.join(self.si.WindowsSdkDir, bin_dir)
+ if self.vs_ver < 15.0:
+ bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
+ yield join(self.si.WindowsSdkDir, bin_dir)
if not self.pi.current_is_x86():
arch_subdir = self.pi.current_dir(x64=True)
path = 'Bin%s' % arch_subdir
- yield os.path.join(self.si.WindowsSdkDir, path)
+ yield join(self.si.WindowsSdkDir, path)
- if self.vc_ver == 10.0 or self.vc_ver == 11.0:
+ if self.vs_ver in (10.0, 11.0):
if self.pi.target_is_x86():
arch_subdir = ''
else:
arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir
- yield os.path.join(self.si.WindowsSdkDir, path)
+ yield join(self.si.WindowsSdkDir, path)
- elif self.vc_ver >= 15.0:
- path = os.path.join(self.si.WindowsSdkDir, 'Bin')
+ elif self.vs_ver >= 15.0:
+ path = join(self.si.WindowsSdkDir, 'Bin')
arch_subdir = self.pi.current_dir(x64=True)
sdkver = self.si.WindowsSdkLastVersion
- yield os.path.join(path, '%s%s' % (sdkver, arch_subdir))
+ yield join(path, '%s%s' % (sdkver, arch_subdir))
if self.si.WindowsSDKExecutablePath:
yield self.si.WindowsSDKExecutablePath
@@ -1052,7 +1334,12 @@ class EnvironmentInfo:
@property
def _sdk_subdir(self):
"""
- Microsoft Windows SDK version subdir
+ Microsoft Windows SDK version subdir.
+
+ Return
+ ------
+ str
+ subdir
"""
ucrtver = self.si.WindowsSdkLastVersion
return ('%s\\' % ucrtver) if ucrtver else ''
@@ -1060,22 +1347,32 @@ class EnvironmentInfo:
@property
def SdkSetup(self):
"""
- Microsoft Windows SDK Setup
+ Microsoft Windows SDK Setup.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver > 9.0:
+ if self.vs_ver > 9.0:
return []
- return [os.path.join(self.si.WindowsSdkDir, 'Setup')]
+ return [join(self.si.WindowsSdkDir, 'Setup')]
@property
def FxTools(self):
"""
- Microsoft .NET Framework Tools
+ Microsoft .NET Framework Tools.
+
+ Return
+ ------
+ list of str
+ paths
"""
pi = self.pi
si = self.si
- if self.vc_ver <= 10.0:
+ if self.vs_ver <= 10.0:
include32 = True
include64 = not pi.target_is_x86() and not pi.current_is_x86()
else:
@@ -1084,102 +1381,142 @@ class EnvironmentInfo:
tools = []
if include32:
- tools += [os.path.join(si.FrameworkDir32, ver)
+ tools += [join(si.FrameworkDir32, ver)
for ver in si.FrameworkVersion32]
if include64:
- tools += [os.path.join(si.FrameworkDir64, ver)
+ tools += [join(si.FrameworkDir64, ver)
for ver in si.FrameworkVersion64]
return tools
@property
def NetFxSDKLibraries(self):
"""
- Microsoft .Net Framework SDK Libraries
+ Microsoft .Net Framework SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
+ if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
return []
arch_subdir = self.pi.target_dir(x64=True)
- return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
+ return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
@property
def NetFxSDKIncludes(self):
"""
- Microsoft .Net Framework SDK Includes
+ Microsoft .Net Framework SDK Includes.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
+ if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
return []
- return [os.path.join(self.si.NetFxSdkDir, r'include\um')]
+ return [join(self.si.NetFxSdkDir, r'include\um')]
@property
def VsTDb(self):
"""
- Microsoft Visual Studio Team System Database
+ Microsoft Visual Studio Team System Database.
+
+ Return
+ ------
+ list of str
+ paths
"""
- return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
+ return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
@property
def MSBuild(self):
"""
- Microsoft Build Engine
+ Microsoft Build Engine.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 12.0:
+ if self.vs_ver < 12.0:
return []
- elif self.vc_ver < 15.0:
+ elif self.vs_ver < 15.0:
base_path = self.si.ProgramFilesx86
arch_subdir = self.pi.current_dir(hidex86=True)
else:
base_path = self.si.VSInstallDir
arch_subdir = ''
- path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir)
- build = [os.path.join(base_path, path)]
+ path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir)
+ build = [join(base_path, path)]
- if self.vc_ver >= 15.0:
+ if self.vs_ver >= 15.0:
# Add Roslyn C# & Visual Basic Compiler
- build += [os.path.join(base_path, path, 'Roslyn')]
+ build += [join(base_path, path, 'Roslyn')]
return build
@property
def HTMLHelpWorkshop(self):
"""
- Microsoft HTML Help Workshop
+ Microsoft HTML Help Workshop.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 11.0:
+ if self.vs_ver < 11.0:
return []
- return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
+ return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
@property
def UCRTLibraries(self):
"""
- Microsoft Universal C Runtime SDK Libraries
+ Microsoft Universal C Runtime SDK Libraries.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0:
+ if self.vs_ver < 14.0:
return []
arch_subdir = self.pi.target_dir(x64=True)
- lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib')
+ lib = join(self.si.UniversalCRTSdkDir, 'lib')
ucrtver = self._ucrt_subdir
- return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
+ return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
@property
def UCRTIncludes(self):
"""
- Microsoft Universal C Runtime SDK Include
+ Microsoft Universal C Runtime SDK Include.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 14.0:
+ if self.vs_ver < 14.0:
return []
- include = os.path.join(self.si.UniversalCRTSdkDir, 'include')
- return [os.path.join(include, '%sucrt' % self._ucrt_subdir)]
+ include = join(self.si.UniversalCRTSdkDir, 'include')
+ return [join(include, '%sucrt' % self._ucrt_subdir)]
@property
def _ucrt_subdir(self):
"""
- Microsoft Universal C Runtime SDK version subdir
+ Microsoft Universal C Runtime SDK version subdir.
+
+ Return
+ ------
+ str
+ subdir
"""
ucrtver = self.si.UniversalCRTSdkLastVersion
return ('%s\\' % ucrtver) if ucrtver else ''
@@ -1187,31 +1524,52 @@ class EnvironmentInfo:
@property
def FSharp(self):
"""
- Microsoft Visual F#
+ Microsoft Visual F#.
+
+ Return
+ ------
+ list of str
+ paths
"""
- if self.vc_ver < 11.0 and self.vc_ver > 12.0:
+ if 11.0 > self.vs_ver > 12.0:
return []
- return self.si.FSharpInstallDir
+ return [self.si.FSharpInstallDir]
@property
def VCRuntimeRedist(self):
"""
- Microsoft Visual C++ runtime redistribuable dll
- """
- arch_subdir = self.pi.target_dir(x64=True)
- if self.vc_ver < 15:
- redist_path = self.si.VCInstallDir
- vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
- else:
- redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist')
- vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
-
- # Visual Studio 2017 is still Visual C++ 14.0
- dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver
+ Microsoft Visual C++ runtime redistributable dll.
- vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver)
- return os.path.join(redist_path, vcruntime)
+ Return
+ ------
+ str
+ path
+ """
+ vcruntime = 'vcruntime%d0.dll' % self.vc_ver
+ arch_subdir = self.pi.target_dir(x64=True).strip('\\')
+
+ # Installation prefixes candidates
+ prefixes = []
+ tools_path = self.si.VCInstallDir
+ redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist'))
+ if isdir(redist_path):
+ # Redist version may not be exactly the same as tools
+ redist_path = join(redist_path, listdir(redist_path)[-1])
+ prefixes += [redist_path, join(redist_path, 'onecore')]
+
+ prefixes += [join(tools_path, 'redist')] # VS14 legacy path
+
+ # CRT directory
+ crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10),
+ # Sometime store in directory with VS version instead of VC
+ 'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10))
+
+ # vcruntime path
+ for prefix, crt_dir in itertools.product(prefixes, crt_dirs):
+ path = join(prefix, arch_subdir, crt_dir, vcruntime)
+ if isfile(path):
+ return path
def return_env(self, exists=True):
"""
@@ -1221,6 +1579,11 @@ class EnvironmentInfo:
----------
exists: bool
It True, only return existing paths.
+
+ Return
+ ------
+ dict
+ environment
"""
env = dict(
include=self._build_paths('include',
@@ -1254,7 +1617,7 @@ class EnvironmentInfo:
self.FSharp],
exists),
)
- if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist):
+ if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist):
env['py_vcruntime_redist'] = self.VCRuntimeRedist
return env
@@ -1265,20 +1628,35 @@ class EnvironmentInfo:
unique, extant, directories from those paths and from
the environment variable. Raise an error if no paths
are resolved.
+
+ Parameters
+ ----------
+ name: str
+ Environment variable name
+ spec_path_lists: list of str
+ Paths
+ exists: bool
+ It True, only return existing paths.
+
+ Return
+ ------
+ str
+ Pathsep-separated paths
"""
# flatten spec_path_lists
spec_paths = itertools.chain.from_iterable(spec_path_lists)
- env_paths = safe_env.get(name, '').split(os.pathsep)
+ env_paths = environ.get(name, '').split(pathsep)
paths = itertools.chain(spec_paths, env_paths)
- extant_paths = list(filter(os.path.isdir, paths)) if exists else paths
+ extant_paths = list(filter(isdir, paths)) if exists else paths
if not extant_paths:
msg = "%s environment variable is empty" % name.upper()
raise distutils.errors.DistutilsPlatformError(msg)
unique_paths = self._unique_everseen(extant_paths)
- return os.pathsep.join(unique_paths)
+ return pathsep.join(unique_paths)
# from Python docs
- def _unique_everseen(self, iterable, key=None):
+ @staticmethod
+ def _unique_everseen(iterable, key=None):
"""
List unique elements, preserving order.
Remember all elements ever seen.
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index 7e9517ce..f419d471 100644
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -46,7 +46,7 @@ __all__ = [
_SOCKET_TIMEOUT = 15
_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}"
-user_agent = _tmpl.format(py_major=sys.version[:3], setuptools=setuptools)
+user_agent = _tmpl.format(py_major='{}.{}'.format(*sys.version_info), setuptools=setuptools)
def parse_requirement_arg(spec):
@@ -850,16 +850,13 @@ class PackageIndex(Environment):
def _download_svn(self, url, filename):
warnings.warn("SVN download support is deprecated", UserWarning)
- def splituser(host):
- user, delim, host = host.rpartition('@')
- return user, host
url = url.split('#', 1)[0] # remove any fragment for svn's sake
creds = ''
if url.lower().startswith('svn:') and '@' in url:
scheme, netloc, path, p, q, f = urllib.parse.urlparse(url)
if not netloc and path.startswith('//') and '/' in path[2:]:
netloc, path = path[2:].split('/', 1)
- auth, host = splituser(netloc)
+ auth, host = _splituser(netloc)
if auth:
if ':' in auth:
user, pw = auth.split(':', 1)
@@ -900,7 +897,7 @@ class PackageIndex(Environment):
if rev is not None:
self.info("Checking out %s", rev)
- os.system("(cd %s && git checkout --quiet %s)" % (
+ os.system("git -C %s checkout --quiet %s" % (
filename,
rev,
))
@@ -916,7 +913,7 @@ class PackageIndex(Environment):
if rev is not None:
self.info("Updating to %s", rev)
- os.system("(cd %s && hg up -C -r %s -q)" % (
+ os.system("hg --cwd %s up -C -r %s -q" % (
filename,
rev,
))
@@ -1058,8 +1055,8 @@ def open_with_auth(url, opener=urllib.request.urlopen):
if netloc.endswith(':'):
raise http_client.InvalidURL("nonnumeric port: ''")
- if scheme in ('http', 'https') and parsed.username:
- auth = ':'.join((parsed.username, parsed.password))
+ if scheme in ('http', 'https'):
+ auth, address = _splituser(netloc)
else:
auth = None
@@ -1072,7 +1069,7 @@ def open_with_auth(url, opener=urllib.request.urlopen):
if auth:
auth = "Basic " + _encode_auth(auth)
- parts = scheme, parsed.hostname, path, params, query, frag
+ parts = scheme, address, path, params, query, frag
new_url = urllib.parse.urlunparse(parts)
request = urllib.request.Request(new_url)
request.add_header("Authorization", auth)
@@ -1086,13 +1083,20 @@ def open_with_auth(url, opener=urllib.request.urlopen):
# Put authentication info back into request URL if same host,
# so that links found on the page will work
s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url)
- if s2 == scheme and h2 == parsed.hostname:
+ if s2 == scheme and h2 == address:
parts = s2, netloc, path2, param2, query2, frag2
fp.url = urllib.parse.urlunparse(parts)
return fp
+# copy of urllib.parse._splituser from Python 3.8
+def _splituser(host):
+ """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
+ user, delim, host = host.rpartition('@')
+ return (user if delim else None), host
+
+
# adding a timeout to avoid freezing package_index
open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth)
diff --git a/setuptools/pep425tags.py b/setuptools/pep425tags.py
deleted file mode 100644
index 48745a29..00000000
--- a/setuptools/pep425tags.py
+++ /dev/null
@@ -1,319 +0,0 @@
-# This file originally from pip:
-# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/pep425tags.py
-"""Generate and work with PEP 425 Compatibility Tags."""
-from __future__ import absolute_import
-
-import distutils.util
-from distutils import log
-import platform
-import re
-import sys
-import sysconfig
-import warnings
-from collections import OrderedDict
-
-from .extern import six
-
-from . import glibc
-
-_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)')
-
-
-def get_config_var(var):
- try:
- return sysconfig.get_config_var(var)
- except IOError as e: # Issue #1074
- warnings.warn("{}".format(e), RuntimeWarning)
- return None
-
-
-def get_abbr_impl():
- """Return abbreviated implementation name."""
- if hasattr(sys, 'pypy_version_info'):
- pyimpl = 'pp'
- elif sys.platform.startswith('java'):
- pyimpl = 'jy'
- elif sys.platform == 'cli':
- pyimpl = 'ip'
- else:
- pyimpl = 'cp'
- return pyimpl
-
-
-def get_impl_ver():
- """Return implementation version."""
- impl_ver = get_config_var("py_version_nodot")
- if not impl_ver or get_abbr_impl() == 'pp':
- impl_ver = ''.join(map(str, get_impl_version_info()))
- return impl_ver
-
-
-def get_impl_version_info():
- """Return sys.version_info-like tuple for use in decrementing the minor
- version."""
- if get_abbr_impl() == 'pp':
- # as per https://github.com/pypa/pip/issues/2882
- return (sys.version_info[0], sys.pypy_version_info.major,
- sys.pypy_version_info.minor)
- else:
- return sys.version_info[0], sys.version_info[1]
-
-
-def get_impl_tag():
- """
- Returns the Tag for this specific implementation.
- """
- return "{}{}".format(get_abbr_impl(), get_impl_ver())
-
-
-def get_flag(var, fallback, expected=True, warn=True):
- """Use a fallback method for determining SOABI flags if the needed config
- var is unset or unavailable."""
- val = get_config_var(var)
- if val is None:
- if warn:
- log.debug("Config variable '%s' is unset, Python ABI tag may "
- "be incorrect", var)
- return fallback()
- return val == expected
-
-
-def get_abi_tag():
- """Return the ABI tag based on SOABI (if available) or emulate SOABI
- (CPython 2, PyPy)."""
- soabi = get_config_var('SOABI')
- impl = get_abbr_impl()
- if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'):
- d = ''
- m = ''
- u = ''
- if get_flag('Py_DEBUG',
- lambda: hasattr(sys, 'gettotalrefcount'),
- warn=(impl == 'cp')):
- d = 'd'
- if get_flag('WITH_PYMALLOC',
- lambda: impl == 'cp',
- warn=(impl == 'cp')):
- m = 'm'
- if get_flag('Py_UNICODE_SIZE',
- lambda: sys.maxunicode == 0x10ffff,
- expected=4,
- warn=(impl == 'cp' and
- six.PY2)) \
- and six.PY2:
- u = 'u'
- abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u)
- elif soabi and soabi.startswith('cpython-'):
- abi = 'cp' + soabi.split('-')[1]
- elif soabi:
- abi = soabi.replace('.', '_').replace('-', '_')
- else:
- abi = None
- return abi
-
-
-def _is_running_32bit():
- return sys.maxsize == 2147483647
-
-
-def get_platform():
- """Return our platform name 'win32', 'linux_x86_64'"""
- if sys.platform == 'darwin':
- # distutils.util.get_platform() returns the release based on the value
- # of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may
- # be significantly older than the user's current machine.
- release, _, machine = platform.mac_ver()
- split_ver = release.split('.')
-
- if machine == "x86_64" and _is_running_32bit():
- machine = "i386"
- elif machine == "ppc64" and _is_running_32bit():
- machine = "ppc"
-
- return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine)
-
- # XXX remove distutils dependency
- result = distutils.util.get_platform().replace('.', '_').replace('-', '_')
- if result == "linux_x86_64" and _is_running_32bit():
- # 32 bit Python program (running on a 64 bit Linux): pip should only
- # install and run 32 bit compiled extensions in that case.
- result = "linux_i686"
-
- return result
-
-
-def is_manylinux1_compatible():
- # Only Linux, and only x86-64 / i686
- if get_platform() not in {"linux_x86_64", "linux_i686"}:
- return False
-
- # Check for presence of _manylinux module
- try:
- import _manylinux
- return bool(_manylinux.manylinux1_compatible)
- except (ImportError, AttributeError):
- # Fall through to heuristic check below
- pass
-
- # Check glibc version. CentOS 5 uses glibc 2.5.
- return glibc.have_compatible_glibc(2, 5)
-
-
-def get_darwin_arches(major, minor, machine):
- """Return a list of supported arches (including group arches) for
- the given major, minor and machine architecture of a macOS machine.
- """
- arches = []
-
- def _supports_arch(major, minor, arch):
- # Looking at the application support for macOS versions in the chart
- # provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears
- # our timeline looks roughly like:
- #
- # 10.0 - Introduces ppc support.
- # 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64
- # and x86_64 support is CLI only, and cannot be used for GUI
- # applications.
- # 10.5 - Extends ppc64 and x86_64 support to cover GUI applications.
- # 10.6 - Drops support for ppc64
- # 10.7 - Drops support for ppc
- #
- # Given that we do not know if we're installing a CLI or a GUI
- # application, we must be conservative and assume it might be a GUI
- # application and behave as if ppc64 and x86_64 support did not occur
- # until 10.5.
- #
- # Note: The above information is taken from the "Application support"
- # column in the chart not the "Processor support" since I believe
- # that we care about what instruction sets an application can use
- # not which processors the OS supports.
- if arch == 'ppc':
- return (major, minor) <= (10, 5)
- if arch == 'ppc64':
- return (major, minor) == (10, 5)
- if arch == 'i386':
- return (major, minor) >= (10, 4)
- if arch == 'x86_64':
- return (major, minor) >= (10, 5)
- if arch in groups:
- for garch in groups[arch]:
- if _supports_arch(major, minor, garch):
- return True
- return False
-
- groups = OrderedDict([
- ("fat", ("i386", "ppc")),
- ("intel", ("x86_64", "i386")),
- ("fat64", ("x86_64", "ppc64")),
- ("fat32", ("x86_64", "i386", "ppc")),
- ])
-
- if _supports_arch(major, minor, machine):
- arches.append(machine)
-
- for garch in groups:
- if machine in groups[garch] and _supports_arch(major, minor, garch):
- arches.append(garch)
-
- arches.append('universal')
-
- return arches
-
-
-def get_supported(versions=None, noarch=False, platform=None,
- impl=None, abi=None):
- """Return a list of supported tags for each version specified in
- `versions`.
-
- :param versions: a list of string versions, of the form ["33", "32"],
- or None. The first version will be assumed to support our ABI.
- :param platform: specify the exact platform you want valid
- tags for, or None. If None, use the local system platform.
- :param impl: specify the exact implementation you want valid
- tags for, or None. If None, use the local interpreter impl.
- :param abi: specify the exact abi you want valid
- tags for, or None. If None, use the local interpreter abi.
- """
- supported = []
-
- # Versions must be given with respect to the preference
- if versions is None:
- versions = []
- version_info = get_impl_version_info()
- major = version_info[:-1]
- # Support all previous minor Python versions.
- for minor in range(version_info[-1], -1, -1):
- versions.append(''.join(map(str, major + (minor,))))
-
- impl = impl or get_abbr_impl()
-
- abis = []
-
- abi = abi or get_abi_tag()
- if abi:
- abis[0:0] = [abi]
-
- abi3s = set()
- import imp
- for suffix in imp.get_suffixes():
- if suffix[0].startswith('.abi'):
- abi3s.add(suffix[0].split('.', 2)[1])
-
- abis.extend(sorted(list(abi3s)))
-
- abis.append('none')
-
- if not noarch:
- arch = platform or get_platform()
- if arch.startswith('macosx'):
- # support macosx-10.6-intel on macosx-10.9-x86_64
- match = _osx_arch_pat.match(arch)
- if match:
- name, major, minor, actual_arch = match.groups()
- tpl = '{}_{}_%i_%s'.format(name, major)
- arches = []
- for m in reversed(range(int(minor) + 1)):
- for a in get_darwin_arches(int(major), m, actual_arch):
- arches.append(tpl % (m, a))
- else:
- # arch pattern didn't match (?!)
- arches = [arch]
- elif platform is None and is_manylinux1_compatible():
- arches = [arch.replace('linux', 'manylinux1'), arch]
- else:
- arches = [arch]
-
- # Current version, current API (built specifically for our Python):
- for abi in abis:
- for arch in arches:
- supported.append(('%s%s' % (impl, versions[0]), abi, arch))
-
- # abi3 modules compatible with older version of Python
- for version in versions[1:]:
- # abi3 was introduced in Python 3.2
- if version in {'31', '30'}:
- break
- for abi in abi3s: # empty set if not Python 3
- for arch in arches:
- supported.append(("%s%s" % (impl, version), abi, arch))
-
- # Has binaries, does not use the Python API:
- for arch in arches:
- supported.append(('py%s' % (versions[0][0]), 'none', arch))
-
- # No abi / arch, but requires our implementation:
- supported.append(('%s%s' % (impl, versions[0]), 'none', 'any'))
- # Tagged specifically as being cross-version compatible
- # (with just the major version specified)
- supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any'))
-
- # No abi / arch, generic Python
- for i, version in enumerate(versions):
- supported.append(('py%s' % (version,), 'none', 'any'))
- if i == 0:
- supported.append(('py%s' % (version[0]), 'none', 'any'))
-
- return supported
-
-
-implementation_tag = get_impl_tag()
diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py
index 2985011b..1d57360f 100644
--- a/setuptools/py27compat.py
+++ b/setuptools/py27compat.py
@@ -2,6 +2,7 @@
Compatibility Support for Python 2.7 and earlier
"""
+import sys
import platform
from setuptools.extern import six
@@ -26,3 +27,34 @@ linux_py2_ascii = (
rmtree_safe = str if linux_py2_ascii else lambda x: x
"""Workaround for http://bugs.python.org/issue24672"""
+
+
+try:
+ from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE
+ from ._imp import get_frozen_object, get_module
+except ImportError:
+ import imp
+ from imp import PY_COMPILED, PY_FROZEN, PY_SOURCE # noqa
+
+ def find_module(module, paths=None):
+ """Just like 'imp.find_module()', but with package support"""
+ parts = module.split('.')
+ while parts:
+ part = parts.pop(0)
+ f, path, (suffix, mode, kind) = info = imp.find_module(part, paths)
+
+ if kind == imp.PKG_DIRECTORY:
+ parts = parts or ['__init__']
+ paths = [path]
+
+ elif parts:
+ raise ImportError("Can't find %r in %s" % (parts, module))
+
+ return info
+
+ def get_frozen_object(module, paths):
+ return imp.get_frozen_object(module)
+
+ def get_module(module, paths, info):
+ imp.load_module(module, *info)
+ return sys.modules[module]
diff --git a/setuptools/py31compat.py b/setuptools/py31compat.py
index 1a0705ec..e1da7ee2 100644
--- a/setuptools/py31compat.py
+++ b/setuptools/py31compat.py
@@ -17,9 +17,9 @@ except ImportError:
errors on deletion.
"""
- def __init__(self):
+ def __init__(self, **kwargs):
self.name = None # Handle mkdtemp raising an exception
- self.name = tempfile.mkdtemp()
+ self.name = tempfile.mkdtemp(**kwargs)
def __enter__(self):
return self.name
diff --git a/setuptools/py33compat.py b/setuptools/py33compat.py
index 87cf5398..cb694436 100644
--- a/setuptools/py33compat.py
+++ b/setuptools/py33compat.py
@@ -52,4 +52,8 @@ class Bytecode_compat:
Bytecode = getattr(dis, 'Bytecode', Bytecode_compat)
-unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape)
+unescape = getattr(html, 'unescape', None)
+if unescape is None:
+ # HTMLParser.unescape is deprecated since Python 3.4, and will be removed
+ # from 3.9.
+ unescape = html_parser.HTMLParser().unescape
diff --git a/setuptools/py34compat.py b/setuptools/py34compat.py
new file mode 100644
index 00000000..3ad91722
--- /dev/null
+++ b/setuptools/py34compat.py
@@ -0,0 +1,13 @@
+import importlib
+
+try:
+ import importlib.util
+except ImportError:
+ pass
+
+
+try:
+ module_from_spec = importlib.util.module_from_spec
+except AttributeError:
+ def module_from_spec(spec):
+ return spec.loader.load_module(spec.name)
diff --git a/setuptools/py36compat.py b/setuptools/py36compat.py
deleted file mode 100644
index f5279696..00000000
--- a/setuptools/py36compat.py
+++ /dev/null
@@ -1,82 +0,0 @@
-import sys
-from distutils.errors import DistutilsOptionError
-from distutils.util import strtobool
-from distutils.debug import DEBUG
-
-
-class Distribution_parse_config_files:
- """
- Mix-in providing forward-compatibility for functionality to be
- included by default on Python 3.7.
-
- Do not edit the code in this class except to update functionality
- as implemented in distutils.
- """
- def parse_config_files(self, filenames=None):
- from configparser import ConfigParser
-
- # Ignore install directory options if we have a venv
- if sys.prefix != sys.base_prefix:
- ignore_options = [
- 'install-base', 'install-platbase', 'install-lib',
- 'install-platlib', 'install-purelib', 'install-headers',
- 'install-scripts', 'install-data', 'prefix', 'exec-prefix',
- 'home', 'user', 'root']
- else:
- ignore_options = []
-
- ignore_options = frozenset(ignore_options)
-
- if filenames is None:
- filenames = self.find_config_files()
-
- if DEBUG:
- self.announce("Distribution.parse_config_files():")
-
- parser = ConfigParser(interpolation=None)
- for filename in filenames:
- if DEBUG:
- self.announce(" reading %s" % filename)
- parser.read(filename)
- for section in parser.sections():
- options = parser.options(section)
- opt_dict = self.get_option_dict(section)
-
- for opt in options:
- if opt != '__name__' and opt not in ignore_options:
- val = parser.get(section,opt)
- opt = opt.replace('-', '_')
- opt_dict[opt] = (filename, val)
-
- # Make the ConfigParser forget everything (so we retain
- # the original filenames that options come from)
- parser.__init__()
-
- # If there was a "global" section in the config file, use it
- # to set Distribution options.
-
- if 'global' in self.command_options:
- for (opt, (src, val)) in self.command_options['global'].items():
- alias = self.negative_opt.get(opt)
- try:
- if alias:
- setattr(self, alias, not strtobool(val))
- elif opt in ('verbose', 'dry_run'): # ugh!
- setattr(self, opt, strtobool(val))
- else:
- setattr(self, opt, val)
- except ValueError as msg:
- raise DistutilsOptionError(msg)
-
-
-if sys.version_info < (3,):
- # Python 2 behavior is sufficient
- class Distribution_parse_config_files:
- pass
-
-
-if False:
- # When updated behavior is available upstream,
- # disable override here.
- class Distribution_parse_config_files:
- pass
diff --git a/setuptools/tests/environment.py b/setuptools/tests/environment.py
index c67898ca..bd3119ef 100644
--- a/setuptools/tests/environment.py
+++ b/setuptools/tests/environment.py
@@ -46,6 +46,8 @@ def run_setup_py(cmd, pypath=None, path=None,
cmd, stdout=_PIPE, stderr=_PIPE, shell=shell, env=env,
)
+ if isinstance(data_stream, tuple):
+ data_stream = slice(*data_stream)
data = proc.communicate()[data_stream]
except OSError:
return 1, ''
diff --git a/setuptools/tests/files.py b/setuptools/tests/files.py
index 465a6b41..bad2189d 100644
--- a/setuptools/tests/files.py
+++ b/setuptools/tests/files.py
@@ -6,10 +6,13 @@ import pkg_resources.py31compat
def build_files(file_defs, prefix=""):
"""
- Build a set of files/directories, as described by the file_defs dictionary.
+ Build a set of files/directories, as described by the
+ file_defs dictionary.
- Each key/value pair in the dictionary is interpreted as a filename/contents
- pair. If the contents value is a dictionary, a directory is created, and the
+ Each key/value pair in the dictionary is interpreted as
+ a filename/contents
+ pair. If the contents value is a dictionary, a directory
+ is created, and the
dictionary interpreted as the files within it, recursively.
For example:
diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py
index 35312120..8b17b081 100644
--- a/setuptools/tests/server.py
+++ b/setuptools/tests/server.py
@@ -1,10 +1,13 @@
"""Basic http server for tests to simulate PyPI or custom indexes
"""
+import os
import time
import threading
from setuptools.extern.six.moves import BaseHTTPServer, SimpleHTTPServer
+from setuptools.extern.six.moves.urllib_parse import urljoin
+from setuptools.extern.six.moves.urllib.request import pathname2url
class IndexServer(BaseHTTPServer.HTTPServer):
@@ -19,10 +22,11 @@ class IndexServer(BaseHTTPServer.HTTPServer):
s.stop()
"""
- def __init__(self, server_address=('', 0),
+ def __init__(
+ self, server_address=('', 0),
RequestHandlerClass=SimpleHTTPServer.SimpleHTTPRequestHandler):
- BaseHTTPServer.HTTPServer.__init__(self, server_address,
- RequestHandlerClass)
+ BaseHTTPServer.HTTPServer.__init__(
+ self, server_address, RequestHandlerClass)
self._run = True
def start(self):
@@ -56,10 +60,11 @@ class MockServer(BaseHTTPServer.HTTPServer, threading.Thread):
A simple HTTP Server that records the requests made to it.
"""
- def __init__(self, server_address=('', 0),
+ def __init__(
+ self, server_address=('', 0),
RequestHandlerClass=RequestRecorder):
- BaseHTTPServer.HTTPServer.__init__(self, server_address,
- RequestHandlerClass)
+ BaseHTTPServer.HTTPServer.__init__(
+ self, server_address, RequestHandlerClass)
threading.Thread.__init__(self)
self.setDaemon(True)
self.requests = []
@@ -68,5 +73,19 @@ class MockServer(BaseHTTPServer.HTTPServer, threading.Thread):
self.serve_forever()
@property
+ def netloc(self):
+ return 'localhost:%s' % self.server_port
+
+ @property
def url(self):
- return 'http://localhost:%(server_port)s/' % vars(self)
+ return 'http://%s/' % self.netloc
+
+
+def path_to_url(path, authority=None):
+ """ Convert a path to a file: URL. """
+ path = os.path.normpath(os.path.abspath(path))
+ base = 'file:'
+ if authority is not None:
+ base += '//' + authority
+ url = urljoin(base, pathname2url(path))
+ return url
diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py
index 54742aa6..fb5b90b1 100644
--- a/setuptools/tests/test_bdist_egg.py
+++ b/setuptools/tests/test_bdist_egg.py
@@ -42,7 +42,7 @@ class Test:
# let's see if we got our egg link at the right place
[content] = os.listdir('dist')
- assert re.match(r'foo-0.0.0-py[23].\d.egg$', content)
+ assert re.match(r'foo-0.0.0-py[23].\d+.egg$', content)
@pytest.mark.xfail(
os.environ.get('PYTHONDONTWRITEBYTECODE'),
diff --git a/setuptools/tests/test_build_clib.py b/setuptools/tests/test_build_clib.py
index aebcc350..3779e679 100644
--- a/setuptools/tests/test_build_clib.py
+++ b/setuptools/tests/test_build_clib.py
@@ -1,6 +1,4 @@
import pytest
-import os
-import shutil
import mock
from distutils.errors import DistutilsSetupError
@@ -40,13 +38,14 @@ class TestBuildCLib:
# with that out of the way, let's see if the crude dependency
# system works
cmd.compiler = mock.MagicMock(spec=cmd.compiler)
- mock_newer.return_value = ([],[])
+ mock_newer.return_value = ([], [])
obj_deps = {'': ('global.h',), 'example.c': ('example.h',)}
- libs = [('example', {'sources': ['example.c'] ,'obj_deps': obj_deps})]
+ libs = [('example', {'sources': ['example.c'], 'obj_deps': obj_deps})]
cmd.build_libraries(libs)
- assert [['example.c', 'global.h', 'example.h']] in mock_newer.call_args[0]
+ assert [['example.c', 'global.h', 'example.h']] in \
+ mock_newer.call_args[0]
assert not cmd.compiler.compile.called
assert cmd.compiler.create_static_lib.call_count == 1
diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py
index 60257154..3dc87ca3 100644
--- a/setuptools/tests/test_build_ext.py
+++ b/setuptools/tests/test_build_ext.py
@@ -8,6 +8,10 @@ from setuptools.command.build_ext import build_ext, get_abi3_suffix
from setuptools.dist import Distribution
from setuptools.extension import Extension
+from . import environment
+from .files import build_files
+from .textwrap import DALS
+
class TestBuildExt:
def test_get_ext_filename(self):
@@ -43,3 +47,69 @@ class TestBuildExt:
assert res.endswith('eggs.pyd')
else:
assert 'abi3' in res
+
+
+def test_build_ext_config_handling(tmpdir_cwd):
+ files = {
+ 'setup.py': DALS(
+ """
+ from setuptools import Extension, setup
+ setup(
+ name='foo',
+ version='0.0.0',
+ ext_modules=[Extension('foo', ['foo.c'])],
+ )
+ """),
+ 'foo.c': DALS(
+ """
+ #include "Python.h"
+
+ #if PY_MAJOR_VERSION >= 3
+
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "foo",
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ };
+
+ #define INITERROR return NULL
+
+ PyMODINIT_FUNC PyInit_foo(void)
+
+ #else
+
+ #define INITERROR return
+
+ void initfoo(void)
+
+ #endif
+ {
+ #if PY_MAJOR_VERSION >= 3
+ PyObject *module = PyModule_Create(&moduledef);
+ #else
+ PyObject *module = Py_InitModule("extension", NULL);
+ #endif
+ if (module == NULL)
+ INITERROR;
+ #if PY_MAJOR_VERSION >= 3
+ return module;
+ #endif
+ }
+ """),
+ 'setup.cfg': DALS(
+ """
+ [build]
+ build-base = foo_build
+ """),
+ }
+ build_files(files)
+ code, output = environment.run_setup_py(
+ cmd=['build'], data_stream=(0, 2),
+ )
+ assert code == 0, '\nSTDOUT:\n%s\nSTDERR:\n%s' % output
diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py
index 82b44c89..e1efe561 100644
--- a/setuptools/tests/test_build_meta.py
+++ b/setuptools/tests/test_build_meta.py
@@ -6,7 +6,6 @@ import tarfile
import pytest
-from setuptools.build_meta import build_sdist
from .files import build_files
from .textwrap import DALS
from . import py2_only
@@ -24,13 +23,12 @@ class BuildBackendBase:
self.env = env
self.backend_name = backend_name
-
class BuildBackend(BuildBackendBase):
"""PEP 517 Build Backend"""
def __init__(self, *args, **kwargs):
super(BuildBackend, self).__init__(*args, **kwargs)
- self.pool = futures.ProcessPoolExecutor()
+ self.pool = futures.ProcessPoolExecutor(max_workers=1)
def __getattr__(self, name):
"""Handles aribrary function invocations on the build backend."""
@@ -44,12 +42,24 @@ class BuildBackend(BuildBackendBase):
class BuildBackendCaller(BuildBackendBase):
+ def __init__(self, *args, **kwargs):
+ super(BuildBackendCaller, self).__init__(*args, **kwargs)
+
+ (self.backend_name, _,
+ self.backend_obj) = self.backend_name.partition(':')
+
def __call__(self, name, *args, **kw):
"""Handles aribrary function invocations on the build backend."""
os.chdir(self.cwd)
os.environ.update(self.env)
mod = importlib.import_module(self.backend_name)
- return getattr(mod, name)(*args, **kw)
+
+ if self.backend_obj:
+ backend = getattr(mod, self.backend_obj)
+ else:
+ backend = mod
+
+ return getattr(backend, name)(*args, **kw)
defns = [
@@ -100,139 +110,289 @@ defns = [
print('hello')
"""),
},
+ {
+ 'setup.cfg': DALS("""
+ [metadata]
+ name = foo
+ version='0.0.0'
+
+ [options]
+ py_modules=hello
+ setup_requires=six
+ """),
+ 'hello.py': DALS("""
+ def run():
+ print('hello')
+ """)
+ },
]
-@pytest.fixture(params=defns)
-def build_backend(tmpdir, request):
- build_files(request.param, prefix=str(tmpdir))
- with tmpdir.as_cwd():
- yield BuildBackend(cwd='.')
-
-
-def test_get_requires_for_build_wheel(build_backend):
- actual = build_backend.get_requires_for_build_wheel()
- expected = ['six', 'wheel']
- assert sorted(actual) == sorted(expected)
-
-
-def test_get_requires_for_build_sdist(build_backend):
- actual = build_backend.get_requires_for_build_sdist()
- expected = ['six']
- assert sorted(actual) == sorted(expected)
-
-
-def test_build_wheel(build_backend):
- dist_dir = os.path.abspath('pip-wheel')
- os.makedirs(dist_dir)
- wheel_name = build_backend.build_wheel(dist_dir)
-
- assert os.path.isfile(os.path.join(dist_dir, wheel_name))
-
-
-def test_build_sdist(build_backend):
- dist_dir = os.path.abspath('pip-sdist')
- os.makedirs(dist_dir)
- sdist_name = build_backend.build_sdist(dist_dir)
-
- assert os.path.isfile(os.path.join(dist_dir, sdist_name))
-
-
-def test_prepare_metadata_for_build_wheel(build_backend):
- dist_dir = os.path.abspath('pip-dist-info')
- os.makedirs(dist_dir)
-
- dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
-
- assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
-
-
-@py2_only
-def test_prepare_metadata_for_build_wheel_with_str(build_backend):
- dist_dir = os.path.abspath(str('pip-dist-info'))
- os.makedirs(dist_dir)
-
- dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
-
- assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
-
-
-def test_build_sdist_explicit_dist(build_backend):
- # explicitly specifying the dist folder should work
- # the folder sdist_directory and the ``--dist-dir`` can be the same
- dist_dir = os.path.abspath('dist')
- sdist_name = build_backend.build_sdist(dist_dir)
- assert os.path.isfile(os.path.join(dist_dir, sdist_name))
-
-
-def test_build_sdist_version_change(build_backend):
- sdist_into_directory = os.path.abspath("out_sdist")
- os.makedirs(sdist_into_directory)
-
- sdist_name = build_backend.build_sdist(sdist_into_directory)
- assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
-
- # if the setup.py changes subsequent call of the build meta
- # should still succeed, given the
- # sdist_directory the frontend specifies is empty
- with open(os.path.abspath("setup.py"), 'rt') as file_handler:
- content = file_handler.read()
- with open(os.path.abspath("setup.py"), 'wt') as file_handler:
- file_handler.write(
- content.replace("version='0.0.0'", "version='0.0.1'"))
-
- shutil.rmtree(sdist_into_directory)
- os.makedirs(sdist_into_directory)
-
- sdist_name = build_backend.build_sdist("out_sdist")
- assert os.path.isfile(
- os.path.join(os.path.abspath("out_sdist"), sdist_name))
-
-
-def test_build_sdist_setup_py_exists(tmpdir_cwd):
- # If build_sdist is called from a script other than setup.py,
- # ensure setup.py is include
- build_files(defns[0])
- targz_path = build_sdist("temp")
- with tarfile.open(os.path.join("temp", targz_path)) as tar:
- assert any('setup.py' in name for name in tar.getnames())
-
-
-def test_build_sdist_setup_py_manifest_excluded(tmpdir_cwd):
- # Ensure that MANIFEST.in can exclude setup.py
- files = {
- 'setup.py': DALS("""
- __import__('setuptools').setup(
- name='foo',
- version='0.0.0',
- py_modules=['hello']
- )"""),
- 'hello.py': '',
- 'MANIFEST.in': DALS("""
- exclude setup.py
- """)
- }
+class TestBuildMetaBackend:
+ backend_name = 'setuptools.build_meta'
+
+ def get_build_backend(self):
+ return BuildBackend(cwd='.', backend_name=self.backend_name)
+
+ @pytest.fixture(params=defns)
+ def build_backend(self, tmpdir, request):
+ build_files(request.param, prefix=str(tmpdir))
+ with tmpdir.as_cwd():
+ yield self.get_build_backend()
+
+ def test_get_requires_for_build_wheel(self, build_backend):
+ actual = build_backend.get_requires_for_build_wheel()
+ expected = ['six', 'wheel']
+ assert sorted(actual) == sorted(expected)
+
+ def test_get_requires_for_build_sdist(self, build_backend):
+ actual = build_backend.get_requires_for_build_sdist()
+ expected = ['six']
+ assert sorted(actual) == sorted(expected)
+
+ def test_build_wheel(self, build_backend):
+ dist_dir = os.path.abspath('pip-wheel')
+ os.makedirs(dist_dir)
+ wheel_name = build_backend.build_wheel(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, wheel_name))
+
+ @pytest.mark.parametrize('build_type', ('wheel', 'sdist'))
+ def test_build_with_existing_file_present(self, build_type, tmpdir_cwd):
+ # Building a sdist/wheel should still succeed if there's
+ # already a sdist/wheel in the destination directory.
+ files = {
+ 'setup.py': "from setuptools import setup\nsetup()",
+ 'VERSION': "0.0.1",
+ 'setup.cfg': DALS("""
+ [metadata]
+ name = foo
+ version = file: VERSION
+ """),
+ 'pyproject.toml': DALS("""
+ [build-system]
+ requires = ["setuptools", "wheel"]
+ build-backend = "setuptools.build_meta
+ """),
+ }
- build_files(files)
- targz_path = build_sdist("temp")
- with tarfile.open(os.path.join("temp", targz_path)) as tar:
- assert not any('setup.py' in name for name in tar.getnames())
+ build_files(files)
+ dist_dir = os.path.abspath('preexisting-' + build_type)
-def test_build_sdist_builds_targz_even_if_zip_indicated(tmpdir_cwd):
- files = {
+ build_backend = self.get_build_backend()
+ build_method = getattr(build_backend, 'build_' + build_type)
+
+ # Build a first sdist/wheel.
+ # Note: this also check the destination directory is
+ # successfully created if it does not exist already.
+ first_result = build_method(dist_dir)
+
+ # Change version.
+ with open("VERSION", "wt") as version_file:
+ version_file.write("0.0.2")
+
+ # Build a *second* sdist/wheel.
+ second_result = build_method(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, first_result))
+ assert first_result != second_result
+
+ # And if rebuilding the exact same sdist/wheel?
+ open(os.path.join(dist_dir, second_result), 'w').close()
+ third_result = build_method(dist_dir)
+ assert third_result == second_result
+ assert os.path.getsize(os.path.join(dist_dir, third_result)) > 0
+
+ def test_build_sdist(self, build_backend):
+ dist_dir = os.path.abspath('pip-sdist')
+ os.makedirs(dist_dir)
+ sdist_name = build_backend.build_sdist(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, sdist_name))
+
+ def test_prepare_metadata_for_build_wheel(self, build_backend):
+ dist_dir = os.path.abspath('pip-dist-info')
+ os.makedirs(dist_dir)
+
+ dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
+
+ @py2_only
+ def test_prepare_metadata_for_build_wheel_with_str(self, build_backend):
+ dist_dir = os.path.abspath(str('pip-dist-info'))
+ os.makedirs(dist_dir)
+
+ dist_info = build_backend.prepare_metadata_for_build_wheel(dist_dir)
+
+ assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
+
+ def test_build_sdist_explicit_dist(self, build_backend):
+ # explicitly specifying the dist folder should work
+ # the folder sdist_directory and the ``--dist-dir`` can be the same
+ dist_dir = os.path.abspath('dist')
+ sdist_name = build_backend.build_sdist(dist_dir)
+ assert os.path.isfile(os.path.join(dist_dir, sdist_name))
+
+ def test_build_sdist_version_change(self, build_backend):
+ sdist_into_directory = os.path.abspath("out_sdist")
+ os.makedirs(sdist_into_directory)
+
+ sdist_name = build_backend.build_sdist(sdist_into_directory)
+ assert os.path.isfile(os.path.join(sdist_into_directory, sdist_name))
+
+ # if the setup.py changes subsequent call of the build meta
+ # should still succeed, given the
+ # sdist_directory the frontend specifies is empty
+ setup_loc = os.path.abspath("setup.py")
+ if not os.path.exists(setup_loc):
+ setup_loc = os.path.abspath("setup.cfg")
+
+ with open(setup_loc, 'rt') as file_handler:
+ content = file_handler.read()
+ with open(setup_loc, 'wt') as file_handler:
+ file_handler.write(
+ content.replace("version='0.0.0'", "version='0.0.1'"))
+
+ shutil.rmtree(sdist_into_directory)
+ os.makedirs(sdist_into_directory)
+
+ sdist_name = build_backend.build_sdist("out_sdist")
+ assert os.path.isfile(
+ os.path.join(os.path.abspath("out_sdist"), sdist_name))
+
+ def test_build_sdist_setup_py_exists(self, tmpdir_cwd):
+ # If build_sdist is called from a script other than setup.py,
+ # ensure setup.py is included
+ build_files(defns[0])
+
+ build_backend = self.get_build_backend()
+ targz_path = build_backend.build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert any('setup.py' in name for name in tar.getnames())
+
+ def test_build_sdist_setup_py_manifest_excluded(self, tmpdir_cwd):
+ # Ensure that MANIFEST.in can exclude setup.py
+ files = {
+ 'setup.py': DALS("""
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello']
+ )"""),
+ 'hello.py': '',
+ 'MANIFEST.in': DALS("""
+ exclude setup.py
+ """)
+ }
+
+ build_files(files)
+
+ build_backend = self.get_build_backend()
+ targz_path = build_backend.build_sdist("temp")
+ with tarfile.open(os.path.join("temp", targz_path)) as tar:
+ assert not any('setup.py' in name for name in tar.getnames())
+
+ def test_build_sdist_builds_targz_even_if_zip_indicated(self, tmpdir_cwd):
+ files = {
+ 'setup.py': DALS("""
+ __import__('setuptools').setup(
+ name='foo',
+ version='0.0.0',
+ py_modules=['hello']
+ )"""),
+ 'hello.py': '',
+ 'setup.cfg': DALS("""
+ [sdist]
+ formats=zip
+ """)
+ }
+
+ build_files(files)
+
+ build_backend = self.get_build_backend()
+ build_backend.build_sdist("temp")
+
+ _relative_path_import_files = {
'setup.py': DALS("""
__import__('setuptools').setup(
name='foo',
- version='0.0.0',
+ version=__import__('hello').__version__,
py_modules=['hello']
)"""),
- 'hello.py': '',
+ 'hello.py': '__version__ = "0.0.0"',
'setup.cfg': DALS("""
[sdist]
formats=zip
""")
}
- build_files(files)
- build_sdist("temp")
+ def test_build_sdist_relative_path_import(self, tmpdir_cwd):
+ build_files(self._relative_path_import_files)
+ build_backend = self.get_build_backend()
+ with pytest.raises(ImportError):
+ build_backend.build_sdist("temp")
+
+ @pytest.mark.parametrize('setup_literal, requirements', [
+ ("'foo'", ['foo']),
+ ("['foo']", ['foo']),
+ (r"'foo\n'", ['foo']),
+ (r"'foo\n\n'", ['foo']),
+ ("['foo', 'bar']", ['foo', 'bar']),
+ (r"'# Has a comment line\nfoo'", ['foo']),
+ (r"'foo # Has an inline comment'", ['foo']),
+ (r"'foo \\\n >=3.0'", ['foo>=3.0']),
+ (r"'foo\nbar'", ['foo', 'bar']),
+ (r"'foo\nbar\n'", ['foo', 'bar']),
+ (r"['foo\n', 'bar\n']", ['foo', 'bar']),
+ ])
+ @pytest.mark.parametrize('use_wheel', [True, False])
+ def test_setup_requires(self, setup_literal, requirements, use_wheel,
+ tmpdir_cwd):
+
+ files = {
+ 'setup.py': DALS("""
+ from setuptools import setup
+
+ setup(
+ name="qux",
+ version="0.0.0",
+ py_modules=["hello.py"],
+ setup_requires={setup_literal},
+ )
+ """).format(setup_literal=setup_literal),
+ 'hello.py': DALS("""
+ def run():
+ print('hello')
+ """),
+ }
+
+ build_files(files)
+
+ build_backend = self.get_build_backend()
+
+ if use_wheel:
+ base_requirements = ['wheel']
+ get_requires = build_backend.get_requires_for_build_wheel
+ else:
+ base_requirements = []
+ get_requires = build_backend.get_requires_for_build_sdist
+
+ # Ensure that the build requirements are properly parsed
+ expected = sorted(base_requirements + requirements)
+ actual = get_requires()
+
+ assert expected == sorted(actual)
+
+
+class TestBuildMetaLegacyBackend(TestBuildMetaBackend):
+ backend_name = 'setuptools.build_meta:__legacy__'
+
+ # build_meta_legacy-specific tests
+ def test_build_sdist_relative_path_import(self, tmpdir_cwd):
+ # This must fail in build_meta, but must pass in build_meta_legacy
+ build_files(self._relative_path_import_files)
+
+ build_backend = self.get_build_backend()
+ build_backend.build_sdist("temp")
diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py
index cc701ae6..b3a99f56 100644
--- a/setuptools/tests/test_build_py.py
+++ b/setuptools/tests/test_build_py.py
@@ -1,17 +1,9 @@
import os
-import pytest
-
from setuptools.dist import Distribution
-@pytest.yield_fixture
-def tmpdir_as_cwd(tmpdir):
- with tmpdir.as_cwd():
- yield tmpdir
-
-
-def test_directories_in_package_data_glob(tmpdir_as_cwd):
+def test_directories_in_package_data_glob(tmpdir_cwd):
"""
Directories matching the glob in package_data should
not be included in the package data.
diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py
index 53b8a956..69d8d00d 100644
--- a/setuptools/tests/test_config.py
+++ b/setuptools/tests/test_config.py
@@ -1,3 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
import contextlib
import pytest
@@ -5,9 +8,11 @@ from distutils.errors import DistutilsOptionError, DistutilsFileError
from mock import patch
from setuptools.dist import Distribution, _Distribution
from setuptools.config import ConfigHandler, read_configuration
+from setuptools.extern.six.moves import configparser
from . import py2_only, py3_only
from .textwrap import DALS
+
class ErrConfigHandler(ConfigHandler):
"""Erroneous handler. Fails to implement required methods."""
@@ -18,12 +23,14 @@ def make_package_dir(name, base_dir, ns=False):
dir_package = dir_package.mkdir(dir_name)
init_file = None
if not ns:
- init_file = dir_package.join('__init__.py')
- init_file.write('')
+ init_file = dir_package.join('__init__.py')
+ init_file.write('')
return dir_package, init_file
-def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'):
+def fake_env(
+ tmpdir, setup_cfg, setup_py=None,
+ encoding='ascii', package_path='fake_package'):
if setup_py is None:
setup_py = (
@@ -33,7 +40,7 @@ def fake_env(tmpdir, setup_cfg, setup_py=None, package_path='fake_package'):
tmpdir.join('setup.py').write(setup_py)
config = tmpdir.join('setup.cfg')
- config.write(setup_cfg)
+ config.write(setup_cfg.encode(encoding), mode='wb')
package_dir, init_file = make_package_dir(package_path, tmpdir)
@@ -308,7 +315,7 @@ class TestMetadata:
tmpdir.join('fake_package', 'version.txt').write('1.2.3\n4.5.6\n')
with pytest.raises(DistutilsOptionError):
with get_dist(tmpdir) as dist:
- _ = dist.metadata.version
+ dist.metadata.version
def test_version_with_package_dir_simple(self, tmpdir):
@@ -428,6 +435,61 @@ class TestMetadata:
assert metadata.description == 'Some description'
assert metadata.requires == ['some', 'requirement']
+ def test_interpolation(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'description = %(message)s\n'
+ )
+ with pytest.raises(configparser.InterpolationMissingOptionError):
+ with get_dist(tmpdir):
+ pass
+
+ def test_non_ascii_1(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '[metadata]\n'
+ 'description = éàïôñ\n',
+ encoding='utf-8'
+ )
+ with get_dist(tmpdir):
+ pass
+
+ def test_non_ascii_3(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '\n'
+ '# -*- coding: invalid\n'
+ )
+ with get_dist(tmpdir):
+ pass
+
+ def test_non_ascii_4(self, tmpdir):
+ fake_env(
+ tmpdir,
+ '# -*- coding: utf-8\n'
+ '[metadata]\n'
+ 'description = éàïôñ\n',
+ encoding='utf-8'
+ )
+ with get_dist(tmpdir) as dist:
+ assert dist.metadata.description == 'éàïôñ'
+
+ def test_not_utf8(self, tmpdir):
+ """
+ Config files encoded not in UTF-8 will fail
+ """
+ fake_env(
+ tmpdir,
+ '# vim: set fileencoding=iso-8859-15 :\n'
+ '[metadata]\n'
+ 'description = éàïôñ\n',
+ encoding='iso-8859-15'
+ )
+ with pytest.raises(UnicodeDecodeError):
+ with get_dist(tmpdir):
+ pass
+
class TestOptions:
@@ -451,7 +513,7 @@ class TestOptions:
'tests_require = mock==0.7.2; pytest\n'
'setup_requires = docutils>=0.3; spack ==1.1, ==1.3; there\n'
'dependency_links = http://some.com/here/1, '
- 'http://some.com/there/2\n'
+ 'http://some.com/there/2\n'
'python_requires = >=1.0, !=2.8\n'
'py_modules = module1, module2\n'
)
@@ -659,7 +721,7 @@ class TestOptions:
dir_sub_two, _ = make_package_dir('sub_two', dir_package, ns=True)
with get_dist(tmpdir) as dist:
- assert set(dist.packages) == {
+ assert set(dist.packages) == {
'fake_package', 'fake_package.sub_two', 'fake_package.sub_one'
}
@@ -711,7 +773,7 @@ class TestOptions:
tmpdir,
'[options.entry_points]\n'
'group1 = point1 = pack.module:func, '
- '.point2 = pack.module2:func_rest [rest]\n'
+ '.point2 = pack.module2:func_rest [rest]\n'
'group2 = point3 = pack.module:func2\n'
)
@@ -757,7 +819,44 @@ class TestOptions:
]
assert sorted(dist.data_files) == sorted(expected)
+ def test_python_requires_simple(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS("""
+ [options]
+ python_requires=>=2.7
+ """),
+ )
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
+ def test_python_requires_compound(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS("""
+ [options]
+ python_requires=>=2.7,!=3.0.*
+ """),
+ )
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
+ def test_python_requires_invalid(self, tmpdir):
+ fake_env(
+ tmpdir,
+ DALS("""
+ [options]
+ python_requires=invalid
+ """),
+ )
+ with pytest.raises(Exception):
+ with get_dist(tmpdir) as dist:
+ dist.parse_config_files()
+
+
saved_dist_init = _Distribution.__init__
+
+
class TestExternalSetters:
# During creation of the setuptools Distribution() object, we call
# the init of the parent distutils Distribution object via
diff --git a/setuptools/tests/test_depends.py b/setuptools/tests/test_depends.py
index e0cfa880..bff1dfb1 100644
--- a/setuptools/tests/test_depends.py
+++ b/setuptools/tests/test_depends.py
@@ -5,12 +5,12 @@ from setuptools import depends
class TestGetModuleConstant:
- def test_basic(self):
- """
- Invoke get_module_constant on a module in
- the test package.
- """
- mod_name = 'setuptools.tests.mod_with_constant'
- val = depends.get_module_constant(mod_name, 'value')
- assert val == 'three, sir!'
- assert 'setuptools.tests.mod_with_constant' not in sys.modules
+ def test_basic(self):
+ """
+ Invoke get_module_constant on a module in
+ the test package.
+ """
+ mod_name = 'setuptools.tests.mod_with_constant'
+ val = depends.get_module_constant(mod_name, 'value')
+ assert val == 'three, sir!'
+ assert 'setuptools.tests.mod_with_constant' not in sys.modules
diff --git a/setuptools/tests/test_dist.py b/setuptools/tests/test_dist.py
index cf830b43..36237f24 100644
--- a/setuptools/tests/test_dist.py
+++ b/setuptools/tests/test_dist.py
@@ -3,7 +3,14 @@
from __future__ import unicode_literals
import io
-from setuptools.dist import DistDeprecationWarning, _get_unpatched
+import collections
+import re
+from distutils.errors import DistutilsSetupError
+from setuptools.dist import (
+ _get_unpatched,
+ check_package_data,
+ DistDeprecationWarning,
+)
from setuptools import Distribution
from setuptools.extern.six.moves.urllib.request import pathname2url
from setuptools.extern.six.moves.urllib_parse import urljoin
@@ -14,6 +21,7 @@ from .test_easy_install import make_nspkg_sdist
import pytest
+
def test_dist_fetch_build_egg(tmpdir):
"""
Check multiple calls to `Distribution.fetch_build_egg` work as expected.
@@ -90,30 +98,32 @@ def __read_test_cases():
'classifiers': [
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
- 'License :: OSI Approved :: MIT License'
- ]})),
+ 'License :: OSI Approved :: MIT License',
+ ]})),
('Metadata version 1.1: Download URL', merge_dicts(base_attrs, {
'download_url': 'https://example.com'
})),
('Metadata Version 1.2: Requires-Python', merge_dicts(base_attrs, {
'python_requires': '>=3.7'
})),
- pytest.param('Metadata Version 1.2: Project-Url',
+ pytest.param(
+ 'Metadata Version 1.2: Project-Url',
merge_dicts(base_attrs, {
'project_urls': {
'Foo': 'https://example.bar'
}
}), marks=pytest.mark.xfail(
reason="Issue #1578: project_urls not read"
- )),
+ )),
('Metadata Version 2.1: Long Description Content Type',
merge_dicts(base_attrs, {
'long_description_content_type': 'text/x-rst; charset=UTF-8'
})),
- pytest.param('Metadata Version 2.1: Provides Extra',
+ pytest.param(
+ 'Metadata Version 2.1: Provides Extra',
merge_dicts(base_attrs, {
'provides_extras': ['foo', 'bar']
- }), marks=pytest.mark.xfail(reason="provides_extras not read")),
+ }), marks=pytest.mark.xfail(reason="provides_extras not read")),
('Missing author, missing author e-mail',
{'name': 'foo', 'version': '1.0.0'}),
('Missing author',
@@ -260,3 +270,61 @@ def test_maintainer_author(name, attrs, tmpdir):
else:
line = '%s: %s' % (fkey, val)
assert line in pkg_lines_set
+
+
+def test_provides_extras_deterministic_order():
+ extras = collections.OrderedDict()
+ extras['a'] = ['foo']
+ extras['b'] = ['bar']
+ attrs = dict(extras_require=extras)
+ dist = Distribution(attrs)
+ assert dist.metadata.provides_extras == ['a', 'b']
+ attrs['extras_require'] = collections.OrderedDict(
+ reversed(list(attrs['extras_require'].items())))
+ dist = Distribution(attrs)
+ assert dist.metadata.provides_extras == ['b', 'a']
+
+
+CHECK_PACKAGE_DATA_TESTS = (
+ # Valid.
+ ({
+ '': ['*.txt', '*.rst'],
+ 'hello': ['*.msg'],
+ }, None),
+ # Not a dictionary.
+ ((
+ ('', ['*.txt', '*.rst']),
+ ('hello', ['*.msg']),
+ ), (
+ "'package_data' must be a dictionary mapping package"
+ " names to lists of string wildcard patterns"
+ )),
+ # Invalid key type.
+ ({
+ 400: ['*.txt', '*.rst'],
+ }, (
+ "keys of 'package_data' dict must be strings (got 400)"
+ )),
+ # Invalid value type.
+ ({
+ 'hello': str('*.msg'),
+ }, (
+ "\"values of 'package_data' dict\" must be a list of strings (got '*.msg')"
+ )),
+ # Invalid value type (generators are single use)
+ ({
+ 'hello': (x for x in "generator"),
+ }, (
+ "\"values of 'package_data' dict\" must be a list of strings "
+ "(got <generator object"
+ )),
+)
+
+
+@pytest.mark.parametrize('package_data, expected_message', CHECK_PACKAGE_DATA_TESTS)
+def test_check_package_data(package_data, expected_message):
+ if expected_message is None:
+ assert check_package_data(None, 'package_data', package_data) is None
+ else:
+ with pytest.raises(DistutilsSetupError, match=re.escape(expected_message)):
+ check_package_data(None, str('package_data'), package_data)
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
index 2cf65ae7..2be1be47 100644
--- a/setuptools/tests/test_easy_install.py
+++ b/setuptools/tests/test_easy_install.py
@@ -15,26 +15,29 @@ import distutils.errors
import io
import zipfile
import mock
-from setuptools.command.easy_install import EasyInstallDeprecationWarning, ScriptWriter, WindowsScriptWriter
import time
+
from setuptools.extern import six
-from setuptools.extern.six.moves import urllib
import pytest
from setuptools import sandbox
from setuptools.sandbox import run_setup
import setuptools.command.easy_install as ei
-from setuptools.command.easy_install import PthDistributions
+from setuptools.command.easy_install import (
+ EasyInstallDeprecationWarning, ScriptWriter, PthDistributions,
+ WindowsScriptWriter,
+)
from setuptools.command import easy_install as easy_install_pkg
from setuptools.dist import Distribution
from pkg_resources import normalize_path, working_set
from pkg_resources import Distribution as PRDistribution
-import setuptools.tests.server
+from setuptools.tests.server import MockServer, path_to_url
from setuptools.tests import fail_on_ascii
import pkg_resources
from . import contexts
+from .files import build_files
from .textwrap import DALS
__metaclass__ = type
@@ -287,7 +290,6 @@ class TestEasyInstallTest:
cmd.easy_install(sdist_script)
assert (target / 'mypkg_script').exists()
-
def test_dist_get_script_args_deprecated(self):
with pytest.warns(EasyInstallDeprecationWarning):
ScriptWriter.get_script_args(None, None)
@@ -304,6 +306,7 @@ class TestEasyInstallTest:
with pytest.warns(EasyInstallDeprecationWarning):
WindowsScriptWriter.get_writer()
+
@pytest.mark.filterwarnings('ignore:Unbuilt egg')
class TestPTHFileWriter:
def test_add_from_cwd_site_sets_dirty(self):
@@ -438,35 +441,40 @@ def distutils_package():
yield
+@pytest.fixture
+def mock_index():
+ # set up a server which will simulate an alternate package index.
+ p_index = MockServer()
+ if p_index.server_port == 0:
+ # Some platforms (Jython) don't find a port to which to bind,
+ # so skip test for them.
+ pytest.skip("could not find a valid port")
+ p_index.start()
+ return p_index
+
+
class TestDistutilsPackage:
def test_bdist_egg_available_on_distutils_pkg(self, distutils_package):
run_setup('setup.py', ['bdist_egg'])
class TestSetupRequires:
- def test_setup_requires_honors_fetch_params(self):
+
+ def test_setup_requires_honors_fetch_params(self, mock_index, monkeypatch):
"""
When easy_install installs a source distribution which specifies
setup_requires, it should honor the fetch parameters (such as
- allow-hosts, index-url, and find-links).
+ index-url, and find-links).
"""
- # set up a server which will simulate an alternate package index.
- p_index = setuptools.tests.server.MockServer()
- p_index.start()
- netloc = 1
- p_index_loc = urllib.parse.urlparse(p_index.url)[netloc]
- if p_index_loc.endswith(':0'):
- # Some platforms (Jython) don't find a port to which to bind,
- # so skip this test for them.
- return
+ monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
+ monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
with contexts.quiet():
# create an sdist that has a build-time dependency.
with TestSetupRequires.create_sdist() as dist_file:
with contexts.tempdir() as temp_install_dir:
with contexts.environment(PYTHONPATH=temp_install_dir):
ei_params = [
- '--index-url', p_index.url,
- '--allow-hosts', p_index_loc,
+ '--index-url', mock_index.url,
'--exclude-scripts',
'--install-dir', temp_install_dir,
dist_file,
@@ -476,10 +484,8 @@ class TestSetupRequires:
# fail because it doesn't exist.
with pytest.raises(SystemExit):
easy_install_pkg.main(ei_params)
- # there should have been two or three requests to the server
- # (three happens on Python 3.3a)
- assert 2 <= len(p_index.requests) <= 3
- assert p_index.requests[0].path == '/does-not-exist/'
+ # there should have been one requests to the server
+ assert [r.path for r in mock_index.requests] == ['/does-not-exist/']
@staticmethod
@contextlib.contextmanager
@@ -498,7 +504,9 @@ class TestSetupRequires:
version="1.0",
setup_requires = ['does-not-exist'],
)
- """))])
+ """)),
+ ('setup.cfg', ''),
+ ])
yield dist_path
use_setup_cfg = (
@@ -630,6 +638,194 @@ class TestSetupRequires:
assert len(lines) > 0
assert lines[-1].strip() == '42'
+ def test_setup_requires_honors_pip_env(self, mock_index, monkeypatch):
+ monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
+ monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
+ monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url)
+ with contexts.save_pkg_resources_state():
+ with contexts.tempdir() as temp_dir:
+ test_pkg = create_setup_requires_package(
+ temp_dir, 'python-xlib', '0.19',
+ setup_attrs=dict(dependency_links=[]))
+ test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
+ with open(test_setup_cfg, 'w') as fp:
+ fp.write(DALS(
+ '''
+ [easy_install]
+ index_url = https://pypi.org/legacy/
+ '''))
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ with pytest.raises(distutils.errors.DistutilsError):
+ run_setup(test_setup_py, [str('--version')])
+ assert len(mock_index.requests) == 1
+ assert mock_index.requests[0].path == '/python-xlib/'
+
+ def test_setup_requires_with_pep508_url(self, mock_index, monkeypatch):
+ monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
+ monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
+ monkeypatch.setenv(str('PIP_INDEX_URL'), mock_index.url)
+ with contexts.save_pkg_resources_state():
+ with contexts.tempdir() as temp_dir:
+ dep_sdist = os.path.join(temp_dir, 'dep.tar.gz')
+ make_trivial_sdist(dep_sdist, 'dependency', '42')
+ dep_url = path_to_url(dep_sdist, authority='localhost')
+ test_pkg = create_setup_requires_package(
+ temp_dir,
+ 'python-xlib', '0.19', # Ignored (overriden by setup_attrs).
+ setup_attrs=dict(setup_requires='dependency @ %s' % dep_url))
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ run_setup(test_setup_py, [str('--version')])
+ assert len(mock_index.requests) == 0
+
+ def test_setup_requires_with_allow_hosts(self, mock_index):
+ ''' The `allow-hosts` option in not supported anymore. '''
+ with contexts.save_pkg_resources_state():
+ with contexts.tempdir() as temp_dir:
+ test_pkg = os.path.join(temp_dir, 'test_pkg')
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
+ os.mkdir(test_pkg)
+ with open(test_setup_py, 'w') as fp:
+ fp.write(DALS(
+ '''
+ from setuptools import setup
+ setup(setup_requires='python-xlib')
+ '''))
+ with open(test_setup_cfg, 'w') as fp:
+ fp.write(DALS(
+ '''
+ [easy_install]
+ allow_hosts = *
+ '''))
+ with pytest.raises(distutils.errors.DistutilsError):
+ run_setup(test_setup_py, [str('--version')])
+ assert len(mock_index.requests) == 0
+
+ def test_setup_requires_with_python_requires(self, monkeypatch, tmpdir):
+ ''' Check `python_requires` is honored. '''
+ monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
+ monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
+ monkeypatch.setenv(str('PIP_NO_INDEX'), str('1'))
+ monkeypatch.setenv(str('PIP_VERBOSE'), str('1'))
+ dep_1_0_sdist = 'dep-1.0.tar.gz'
+ dep_1_0_url = path_to_url(str(tmpdir / dep_1_0_sdist))
+ dep_1_0_python_requires = '>=2.7'
+ make_python_requires_sdist(str(tmpdir / dep_1_0_sdist), 'dep', '1.0', dep_1_0_python_requires)
+ dep_2_0_sdist = 'dep-2.0.tar.gz'
+ dep_2_0_url = path_to_url(str(tmpdir / dep_2_0_sdist))
+ dep_2_0_python_requires = '!=' + '.'.join(map(str, sys.version_info[:2])) + '.*'
+ make_python_requires_sdist(str(tmpdir / dep_2_0_sdist), 'dep', '2.0', dep_2_0_python_requires)
+ index = tmpdir / 'index.html'
+ index.write_text(DALS(
+ '''
+ <!DOCTYPE html>
+ <html><head><title>Links for dep</title></head>
+ <body>
+ <h1>Links for dep</h1>
+ <a href="{dep_1_0_url}" data-requires-python="{dep_1_0_python_requires}">{dep_1_0_sdist}</a><br/>
+ <a href="{dep_2_0_url}" data-requires-python="{dep_2_0_python_requires}">{dep_2_0_sdist}</a><br/>
+ </body>
+ </html>
+ ''').format(
+ dep_1_0_url=dep_1_0_url,
+ dep_1_0_sdist=dep_1_0_sdist,
+ dep_1_0_python_requires=dep_1_0_python_requires,
+ dep_2_0_url=dep_2_0_url,
+ dep_2_0_sdist=dep_2_0_sdist,
+ dep_2_0_python_requires=dep_2_0_python_requires,
+ ), 'utf-8')
+ index_url = path_to_url(str(index))
+ with contexts.save_pkg_resources_state():
+ test_pkg = create_setup_requires_package(
+ str(tmpdir),
+ 'python-xlib', '0.19', # Ignored (overriden by setup_attrs).
+ setup_attrs=dict(setup_requires='dep', dependency_links=[index_url]))
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ run_setup(test_setup_py, [str('--version')])
+ eggs = list(map(str, pkg_resources.find_distributions(os.path.join(test_pkg, '.eggs'))))
+ assert eggs == ['dep 1.0']
+
+ @pytest.mark.parametrize('use_legacy_installer,with_dependency_links_in_setup_py',
+ itertools.product((False, True), (False, True)))
+ def test_setup_requires_with_find_links_in_setup_cfg(self, monkeypatch,
+ use_legacy_installer,
+ with_dependency_links_in_setup_py):
+ monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
+ monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
+ with contexts.save_pkg_resources_state():
+ with contexts.tempdir() as temp_dir:
+ make_trivial_sdist(os.path.join(temp_dir, 'python-xlib-42.tar.gz'), 'python-xlib', '42')
+ test_pkg = os.path.join(temp_dir, 'test_pkg')
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
+ os.mkdir(test_pkg)
+ with open(test_setup_py, 'w') as fp:
+ if with_dependency_links_in_setup_py:
+ dependency_links = [os.path.join(temp_dir, 'links')]
+ else:
+ dependency_links = []
+ fp.write(DALS(
+ '''
+ from setuptools import installer, setup
+ if {use_legacy_installer}:
+ installer.fetch_build_egg = installer._legacy_fetch_build_egg
+ setup(setup_requires='python-xlib==42',
+ dependency_links={dependency_links!r})
+ ''').format(use_legacy_installer=use_legacy_installer,
+ dependency_links=dependency_links))
+ with open(test_setup_cfg, 'w') as fp:
+ fp.write(DALS(
+ '''
+ [easy_install]
+ index_url = {index_url}
+ find_links = {find_links}
+ ''').format(index_url=os.path.join(temp_dir, 'index'),
+ find_links=temp_dir))
+ run_setup(test_setup_py, [str('--version')])
+
+ def test_setup_requires_with_transitive_extra_dependency(self, monkeypatch):
+ # Use case: installing a package with a build dependency on
+ # an already installed `dep[extra]`, which in turn depends
+ # on `extra_dep` (whose is not already installed).
+ with contexts.save_pkg_resources_state():
+ with contexts.tempdir() as temp_dir:
+ # Create source distribution for `extra_dep`.
+ make_trivial_sdist(os.path.join(temp_dir, 'extra_dep-1.0.tar.gz'), 'extra_dep', '1.0')
+ # Create source tree for `dep`.
+ dep_pkg = os.path.join(temp_dir, 'dep')
+ os.mkdir(dep_pkg)
+ build_files({
+ 'setup.py':
+ DALS("""
+ import setuptools
+ setuptools.setup(
+ name='dep', version='2.0',
+ extras_require={'extra': ['extra_dep']},
+ )
+ """),
+ 'setup.cfg': '',
+ }, prefix=dep_pkg)
+ # "Install" dep.
+ run_setup(os.path.join(dep_pkg, 'setup.py'), [str('dist_info')])
+ working_set.add_entry(dep_pkg)
+ # Create source tree for test package.
+ test_pkg = os.path.join(temp_dir, 'test_pkg')
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
+ os.mkdir(test_pkg)
+ with open(test_setup_py, 'w') as fp:
+ fp.write(DALS(
+ '''
+ from setuptools import installer, setup
+ setup(setup_requires='dep[extra]')
+ '''))
+ # Check...
+ monkeypatch.setenv(str('PIP_FIND_LINKS'), str(temp_dir))
+ monkeypatch.setenv(str('PIP_NO_INDEX'), str('1'))
+ monkeypatch.setenv(str('PIP_RETRIES'), str('0'))
+ monkeypatch.setenv(str('PIP_TIMEOUT'), str('0'))
+ run_setup(test_setup_py, [str('--version')])
+
def make_trivial_sdist(dist_path, distname, version):
"""
@@ -645,7 +841,9 @@ def make_trivial_sdist(dist_path, distname, version):
name=%r,
version=%r
)
- """ % (distname, version)))])
+ """ % (distname, version))),
+ ('setup.cfg', ''),
+ ])
def make_nspkg_sdist(dist_path, distname, version):
@@ -681,12 +879,29 @@ def make_nspkg_sdist(dist_path, distname, version):
make_sdist(dist_path, files)
+def make_python_requires_sdist(dist_path, distname, version, python_requires):
+ make_sdist(dist_path, [
+ ('setup.py', DALS("""\
+ import setuptools
+ setuptools.setup(
+ name={name!r},
+ version={version!r},
+ python_requires={python_requires!r},
+ )
+ """).format(name=distname, version=version,
+ python_requires=python_requires)),
+ ('setup.cfg', ''),
+ ])
+
+
def make_sdist(dist_path, files):
"""
Create a simple sdist tarball at dist_path, containing the files
listed in ``files`` as ``(filename, content)`` tuples.
"""
+ # Distributions with only one file don't play well with pip.
+ assert len(files) > 1
with tarfile.open(dist_path, 'w:gz') as dist:
for filename, content in files:
file_bytes = io.BytesIO(content.encode('utf-8'))
@@ -719,8 +934,8 @@ def create_setup_requires_package(path, distname='foobar', version='0.1',
test_pkg = os.path.join(path, 'test_pkg')
os.mkdir(test_pkg)
+ # setup.cfg
if use_setup_cfg:
- test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
options = []
metadata = []
for name in use_setup_cfg:
@@ -732,8 +947,7 @@ def create_setup_requires_package(path, distname='foobar', version='0.1',
if isinstance(value, (tuple, list)):
value = ';'.join(value)
section.append('%s: %s' % (name, value))
- with open(test_setup_cfg, 'w') as f:
- f.write(DALS(
+ test_setup_cfg_contents = DALS(
"""
[metadata]
{metadata}
@@ -743,16 +957,19 @@ def create_setup_requires_package(path, distname='foobar', version='0.1',
).format(
options='\n'.join(options),
metadata='\n'.join(metadata),
- ))
-
- test_setup_py = os.path.join(test_pkg, 'setup.py')
+ )
+ else:
+ test_setup_cfg_contents = ''
+ with open(os.path.join(test_pkg, 'setup.cfg'), 'w') as f:
+ f.write(test_setup_cfg_contents)
+ # setup.py
if setup_py_template is None:
setup_py_template = DALS("""\
import setuptools
setuptools.setup(**%r)
""")
- with open(test_setup_py, 'w') as f:
+ with open(os.path.join(test_pkg, 'setup.py'), 'w') as f:
f.write(setup_py_template % test_setup_attrs)
foobar_path = os.path.join(path, '%s-%s.tar.gz' % (distname, version))
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 979ff18e..0db204ba 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -1,4 +1,3 @@
-import datetime
import sys
import ast
import os
@@ -7,7 +6,9 @@ import re
import stat
import time
-from setuptools.command.egg_info import egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision
+from setuptools.command.egg_info import (
+ egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision,
+)
from setuptools.dist import Distribution
from setuptools.extern.six.moves import map
@@ -517,6 +518,253 @@ class TestEggInfo:
pkg_info_text = pkginfo_file.read()
assert 'Provides-Extra:' not in pkg_info_text
+ @pytest.mark.parametrize("files, license_in_sources", [
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE
+ """),
+ 'LICENSE': "Test license"
+ }, True), # with license
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = INVALID_LICENSE
+ """),
+ 'LICENSE': "Test license"
+ }, False), # with an invalid license
+ ({
+ 'setup.cfg': DALS("""
+ """),
+ 'LICENSE': "Test license"
+ }, False), # no license_file attribute
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE
+ """),
+ 'MANIFEST.in': "exclude LICENSE",
+ 'LICENSE': "Test license"
+ }, False) # license file is manually excluded
+ ])
+ def test_setup_cfg_license_file(
+ self, tmpdir_cwd, env, files, license_in_sources):
+ self._create_project()
+ build_files(files)
+
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)])
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+
+ with open(os.path.join(egg_info_dir, 'SOURCES.txt')) as sources_file:
+ sources_text = sources_file.read()
+
+ if license_in_sources:
+ assert 'LICENSE' in sources_text
+ else:
+ assert 'LICENSE' not in sources_text
+ assert 'INVALID_LICENSE' not in sources_text # for invalid license test
+
+ @pytest.mark.parametrize("files, incl_licenses, excl_licenses", [
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with licenses
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files = LICENSE-ABC, LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-ABC', 'LICENSE-XYZ'], []), # with commas
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-ABC'], ['LICENSE-XYZ']), # with one license
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # empty
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files = LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-XYZ'], ['LICENSE-ABC']), # on same line
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ INVALID_LICENSE
+ """),
+ 'LICENSE-ABC': "Test license"
+ }, ['LICENSE-ABC'], ['INVALID_LICENSE']), # with an invalid license
+ ({
+ 'setup.cfg': DALS("""
+ """),
+ 'LICENSE': "Test license"
+ }, [], ['LICENSE']), # no license_files attribute
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files = LICENSE
+ """),
+ 'MANIFEST.in': "exclude LICENSE",
+ 'LICENSE': "Test license"
+ }, [], ['LICENSE']), # license file is manually excluded
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """),
+ 'MANIFEST.in': "exclude LICENSE-XYZ",
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-ABC'], ['LICENSE-XYZ']) # subset is manually excluded
+ ])
+ def test_setup_cfg_license_files(
+ self, tmpdir_cwd, env, files, incl_licenses, excl_licenses):
+ self._create_project()
+ build_files(files)
+
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)])
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+
+ with open(os.path.join(egg_info_dir, 'SOURCES.txt')) as sources_file:
+ sources_lines = list(line.strip() for line in sources_file)
+
+ for lf in incl_licenses:
+ assert sources_lines.count(lf) == 1
+
+ for lf in excl_licenses:
+ assert sources_lines.count(lf) == 0
+
+ @pytest.mark.parametrize("files, incl_licenses, excl_licenses", [
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file =
+ license_files =
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # both empty
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file =
+ LICENSE-ABC
+ LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, [], ['LICENSE-ABC', 'LICENSE-XYZ']), # license_file is still singular
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ LICENSE-PQR
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-PQR': "PQR license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # combined
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-ABC
+ LICENSE-XYZ
+ LICENSE-PQR
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-PQR': "PQR license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-ABC', 'LICENSE-PQR', 'LICENSE-XYZ'], []), # duplicate license
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ """),
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-PQR': "PQR license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-ABC', 'LICENSE-XYZ'], ['LICENSE-PQR']), # combined subset
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-XYZ
+ LICENSE-PQR
+ """),
+ 'LICENSE-PQR': "Test license"
+ }, ['LICENSE-PQR'], ['LICENSE-ABC', 'LICENSE-XYZ']), # with invalid licenses
+ ({
+ 'setup.cfg': DALS("""
+ [metadata]
+ license_file = LICENSE-ABC
+ license_files =
+ LICENSE-PQR
+ LICENSE-XYZ
+ """),
+ 'MANIFEST.in': "exclude LICENSE-ABC\nexclude LICENSE-PQR",
+ 'LICENSE-ABC': "ABC license",
+ 'LICENSE-PQR': "PQR license",
+ 'LICENSE-XYZ': "XYZ license"
+ }, ['LICENSE-XYZ'], ['LICENSE-ABC', 'LICENSE-PQR']) # manually excluded
+ ])
+ def test_setup_cfg_license_file_license_files(
+ self, tmpdir_cwd, env, files, incl_licenses, excl_licenses):
+ self._create_project()
+ build_files(files)
+
+ environment.run_setup_py(
+ cmd=['egg_info'],
+ pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)])
+ )
+ egg_info_dir = os.path.join('.', 'foo.egg-info')
+
+ with open(os.path.join(egg_info_dir, 'SOURCES.txt')) as sources_file:
+ sources_lines = list(line.strip() for line in sources_file)
+
+ for lf in incl_licenses:
+ assert sources_lines.count(lf) == 1
+
+ for lf in excl_licenses:
+ assert sources_lines.count(lf) == 0
+
def test_long_description_content_type(self, tmpdir_cwd, env):
# Test that specifying a `long_description_content_type` keyword arg to
# the `setup` function results in writing a `Description-Content-Type`
@@ -572,6 +820,7 @@ class TestEggInfo:
assert expected_line in pkg_info_lines
expected_line = 'Project-URL: Link Two, https://example.com/two/'
assert expected_line in pkg_info_lines
+ assert 'Metadata-Version: 1.2' in pkg_info_lines
def test_python_requires_egg_info(self, tmpdir_cwd, env):
self._setup_script_with_requires(
@@ -651,3 +900,49 @@ class TestEggInfo:
def test_get_pkg_info_revision_deprecated(self):
pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision)
+
+ EGG_INFO_TESTS = (
+ # Check for issue #1136: invalid string type when
+ # reading declarative `setup.cfg` under Python 2.
+ {
+ 'setup.py': DALS(
+ """
+ from setuptools import setup
+ setup(
+ name="foo",
+ )
+ """),
+ 'setup.cfg': DALS(
+ """
+ [options]
+ package_dir =
+ = src
+ """),
+ 'src': {},
+ },
+ # Check Unicode can be used in `setup.py` under Python 2.
+ {
+ 'setup.py': DALS(
+ """
+ # -*- coding: utf-8 -*-
+ from __future__ import unicode_literals
+ from setuptools import setup, find_packages
+ setup(
+ name="foo",
+ package_dir={'': 'src'},
+ )
+ """),
+ 'src': {},
+ }
+ )
+
+ @pytest.mark.parametrize('package_files', EGG_INFO_TESTS)
+ def test_egg_info(self, tmpdir_cwd, env, package_files):
+ """
+ """
+ build_files(package_files)
+ code, data = environment.run_setup_py(
+ cmd=['egg_info'],
+ data_stream=1,
+ )
+ assert not code, data
diff --git a/setuptools/tests/test_find_packages.py b/setuptools/tests/test_find_packages.py
index b08f91c7..ab26b4f1 100644
--- a/setuptools/tests/test_find_packages.py
+++ b/setuptools/tests/test_find_packages.py
@@ -12,10 +12,10 @@ from . import py3_only
from setuptools.extern.six import PY3
from setuptools import find_packages
if PY3:
- from setuptools import find_namespace_packages
+ from setuptools import find_namespace_packages
-# modeled after CPython's test.support.can_symlink
+# modeled after CPython's test.support.can_symlink
def can_symlink():
TESTFN = tempfile.mktemp()
symlink_path = TESTFN + "can_symlink"
@@ -164,12 +164,14 @@ class TestFindPackages:
def test_pep420_ns_package_no_includes(self):
packages = find_namespace_packages(
self.dist_dir, exclude=['pkg.subpkg.assets'])
- self._assert_packages(packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])
+ self._assert_packages(
+ packages, ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg'])
@py3_only
def test_pep420_ns_package_no_includes_or_excludes(self):
packages = find_namespace_packages(self.dist_dir)
- expected = ['docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']
+ expected = [
+ 'docs', 'pkg', 'pkg.nspkg', 'pkg.subpkg', 'pkg.subpkg.assets']
self._assert_packages(packages, expected)
@py3_only
@@ -185,4 +187,3 @@ class TestFindPackages:
shutil.rmtree(os.path.join(self.dist_dir, 'pkg/subpkg/assets'))
packages = find_namespace_packages(self.dist_dir)
self._assert_packages(packages, ['pkg', 'pkg.nspkg', 'pkg.subpkg'])
-
diff --git a/setuptools/tests/test_glibc.py b/setuptools/tests/test_glibc.py
deleted file mode 100644
index 795fdc56..00000000
--- a/setuptools/tests/test_glibc.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import warnings
-
-import pytest
-
-from setuptools.glibc import check_glibc_version
-
-__metaclass__ = type
-
-
-@pytest.fixture(params=[
- "2.20",
- # used by "linaro glibc", see gh-3588
- "2.20-2014.11",
- # weird possibilities that I just made up
- "2.20+dev",
- "2.20-custom",
- "2.20.1",
- ])
-def two_twenty(request):
- return request.param
-
-
-@pytest.fixture(params=["asdf", "", "foo.bar"])
-def bad_string(request):
- return request.param
-
-
-class TestGlibc:
- def test_manylinux1_check_glibc_version(self, two_twenty):
- """
- Test that the check_glibc_version function is robust against weird
- glibc version strings.
- """
- assert check_glibc_version(two_twenty, 2, 15)
- assert check_glibc_version(two_twenty, 2, 20)
- assert not check_glibc_version(two_twenty, 2, 21)
- assert not check_glibc_version(two_twenty, 3, 15)
- assert not check_glibc_version(two_twenty, 1, 15)
-
- def test_bad_versions(self, bad_string):
- """
- For unparseable strings, warn and return False
- """
- with warnings.catch_warnings(record=True) as ws:
- warnings.filterwarnings("always")
- assert not check_glibc_version(bad_string, 2, 5)
- for w in ws:
- if "Expected glibc version with" in str(w.message):
- break
- else:
- # Didn't find the warning we were expecting
- assert False
diff --git a/setuptools/tests/test_install_scripts.py b/setuptools/tests/test_install_scripts.py
index 727ad65b..4338c792 100644
--- a/setuptools/tests/test_install_scripts.py
+++ b/setuptools/tests/test_install_scripts.py
@@ -64,7 +64,8 @@ class TestInstallScripts:
@pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only')
def test_executable_with_spaces_escaping_unix(self, tmpdir):
"""
- Ensure that shebang on Unix is not quoted, even when a value with spaces
+ Ensure that shebang on Unix is not quoted, even when
+ a value with spaces
is specified using --executable.
"""
expected = '#!%s\n' % self.unix_spaces_exe
@@ -77,7 +78,8 @@ class TestInstallScripts:
@pytest.mark.skipif(sys.platform != 'win32', reason='Windows only')
def test_executable_arg_escaping_win32(self, tmpdir):
"""
- Ensure that shebang on Windows is quoted when getting a path with spaces
+ Ensure that shebang on Windows is quoted when
+ getting a path with spaces
from --executable, that is itself properly quoted.
"""
expected = '#!"%s"\n' % self.win32_exe
diff --git a/setuptools/tests/test_integration.py b/setuptools/tests/test_integration.py
index 3a9a6c50..f1a27f8b 100644
--- a/setuptools/tests/test_integration.py
+++ b/setuptools/tests/test_integration.py
@@ -6,6 +6,11 @@ Try to install a few packages.
import glob
import os
import sys
+import re
+import subprocess
+import functools
+import tarfile
+import zipfile
from setuptools.extern.six.moves import urllib
import pytest
@@ -59,7 +64,7 @@ def install_context(request, tmpdir, monkeypatch):
monkeypatch.setattr('site.USER_BASE', user_base.strpath)
monkeypatch.setattr('site.USER_SITE', user_site.strpath)
monkeypatch.setattr('sys.path', sys.path + [install_dir.strpath])
- monkeypatch.setenv('PYTHONPATH', os.path.pathsep.join(sys.path))
+ monkeypatch.setenv(str('PYTHONPATH'), str(os.path.pathsep.join(sys.path)))
# Set up the command for performing the installation.
dist = Distribution()
@@ -114,15 +119,12 @@ def test_pyuri(install_context):
assert os.path.exists(os.path.join(pyuri.location, 'pyuri', 'uri.regex'))
-import re
-import subprocess
-import functools
-import tarfile, zipfile
+build_deps = ['appdirs', 'packaging', 'pyparsing', 'six']
-build_deps = ['appdirs', 'packaging', 'pyparsing', 'six']
@pytest.mark.parametrize("build_dep", build_deps)
-@pytest.mark.skipif(sys.version_info < (3, 6), reason='run only on late versions')
+@pytest.mark.skipif(
+ sys.version_info < (3, 6), reason='run only on late versions')
def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
"""
All setuptools build dependencies must build without
@@ -139,7 +141,9 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
allowed_unknowns = [
'test_suite',
'tests_require',
+ 'python_requires',
'install_requires',
+ 'long_description_content_type',
]
assert not match or match.group(1).strip('"\'') in allowed_unknowns
@@ -147,15 +151,18 @@ def test_build_deps_on_distutils(request, tmpdir_factory, build_dep):
def install(pkg_dir, install_dir):
with open(os.path.join(pkg_dir, 'setuptools.py'), 'w') as breaker:
breaker.write('raise ImportError()')
- cmd = [sys.executable, 'setup.py', 'install', '--prefix', install_dir]
- env = dict(os.environ, PYTHONPATH=pkg_dir)
- output = subprocess.check_output(cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT)
+ cmd = [sys.executable, 'setup.py', 'install', '--prefix', str(install_dir)]
+ env = dict(os.environ, PYTHONPATH=str(pkg_dir))
+ output = subprocess.check_output(
+ cmd, cwd=pkg_dir, env=env, stderr=subprocess.STDOUT)
return output.decode('utf-8')
def download_and_extract(request, req, target):
- cmd = [sys.executable, '-m', 'pip', 'download', '--no-deps',
- '--no-binary', ':all:', req]
+ cmd = [
+ sys.executable, '-m', 'pip', 'download', '--no-deps',
+ '--no-binary', ':all:', req,
+ ]
output = subprocess.check_output(cmd, encoding='utf-8')
filename = re.search('Saved (.*)', output).group(1)
request.addfinalizer(functools.partial(os.remove, filename))
diff --git a/setuptools/tests/test_manifest.py b/setuptools/tests/test_manifest.py
index 5edfbea0..2a0e9c86 100644
--- a/setuptools/tests/test_manifest.py
+++ b/setuptools/tests/test_manifest.py
@@ -15,7 +15,6 @@ from setuptools.command.egg_info import FileList, egg_info, translate_pattern
from setuptools.dist import Distribution
from setuptools.extern import six
from setuptools.tests.textwrap import DALS
-from . import py3_only
import pytest
@@ -74,7 +73,9 @@ translate_specs = [
# Glob matching
('*.txt', ['foo.txt', 'bar.txt'], ['foo/foo.txt']),
- ('dir/*.txt', ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']),
+ (
+ 'dir/*.txt',
+ ['dir/foo.txt', 'dir/bar.txt', 'dir/.txt'], ['notdir/foo.txt']),
('*/*.py', ['bin/start.py'], []),
('docs/page-?.txt', ['docs/page-9.txt'], ['docs/page-10.txt']),
@@ -243,77 +244,77 @@ class TestManifestTest(TempDirTestCase):
def test_exclude(self):
"""Include everything in app/ except the text files"""
- l = make_local_path
+ ml = make_local_path
self.make_manifest(
"""
include app/*
exclude app/*.txt
""")
- files = default_files | set([l('app/c.rst')])
+ files = default_files | set([ml('app/c.rst')])
assert files == self.get_files()
def test_include_multiple(self):
"""Include with multiple patterns."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest("include app/*.txt app/static/*")
files = default_files | set([
- l('app/a.txt'), l('app/b.txt'),
- l('app/static/app.js'), l('app/static/app.js.map'),
- l('app/static/app.css'), l('app/static/app.css.map')])
+ ml('app/a.txt'), ml('app/b.txt'),
+ ml('app/static/app.js'), ml('app/static/app.js.map'),
+ ml('app/static/app.css'), ml('app/static/app.css.map')])
assert files == self.get_files()
def test_graft(self):
"""Include the whole app/static/ directory."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest("graft app/static")
files = default_files | set([
- l('app/static/app.js'), l('app/static/app.js.map'),
- l('app/static/app.css'), l('app/static/app.css.map')])
+ ml('app/static/app.js'), ml('app/static/app.js.map'),
+ ml('app/static/app.css'), ml('app/static/app.css.map')])
assert files == self.get_files()
def test_graft_glob_syntax(self):
"""Include the whole app/static/ directory."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest("graft */static")
files = default_files | set([
- l('app/static/app.js'), l('app/static/app.js.map'),
- l('app/static/app.css'), l('app/static/app.css.map')])
+ ml('app/static/app.js'), ml('app/static/app.js.map'),
+ ml('app/static/app.css'), ml('app/static/app.css.map')])
assert files == self.get_files()
def test_graft_global_exclude(self):
"""Exclude all *.map files in the project."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest(
"""
graft app/static
global-exclude *.map
""")
files = default_files | set([
- l('app/static/app.js'), l('app/static/app.css')])
+ ml('app/static/app.js'), ml('app/static/app.css')])
assert files == self.get_files()
def test_global_include(self):
"""Include all *.rst, *.js, and *.css files in the whole tree."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest(
"""
global-include *.rst *.js *.css
""")
files = default_files | set([
- '.hidden.rst', 'testing.rst', l('app/c.rst'),
- l('app/static/app.js'), l('app/static/app.css')])
+ '.hidden.rst', 'testing.rst', ml('app/c.rst'),
+ ml('app/static/app.js'), ml('app/static/app.css')])
assert files == self.get_files()
def test_graft_prune(self):
"""Include all files in app/, except for the whole app/static/ dir."""
- l = make_local_path
+ ml = make_local_path
self.make_manifest(
"""
graft app
prune app/static
""")
files = default_files | set([
- l('app/a.txt'), l('app/b.txt'), l('app/c.rst')])
+ ml('app/a.txt'), ml('app/b.txt'), ml('app/c.rst')])
assert files == self.get_files()
@@ -369,7 +370,7 @@ class TestFileListTest(TempDirTestCase):
def test_process_template_line(self):
# testing all MANIFEST.in template patterns
file_list = FileList()
- l = make_local_path
+ ml = make_local_path
# simulated file list
self.make_files([
@@ -377,16 +378,16 @@ class TestFileListTest(TempDirTestCase):
'buildout.cfg',
# filelist does not filter out VCS directories,
# it's sdist that does
- l('.hg/last-message.txt'),
- l('global/one.txt'),
- l('global/two.txt'),
- l('global/files.x'),
- l('global/here.tmp'),
- l('f/o/f.oo'),
- l('dir/graft-one'),
- l('dir/dir2/graft2'),
- l('dir3/ok'),
- l('dir3/sub/ok.txt'),
+ ml('.hg/last-message.txt'),
+ ml('global/one.txt'),
+ ml('global/two.txt'),
+ ml('global/files.x'),
+ ml('global/here.tmp'),
+ ml('f/o/f.oo'),
+ ml('dir/graft-one'),
+ ml('dir/dir2/graft2'),
+ ml('dir3/ok'),
+ ml('dir3/sub/ok.txt'),
])
MANIFEST_IN = DALS("""\
@@ -413,12 +414,12 @@ class TestFileListTest(TempDirTestCase):
'buildout.cfg',
'four.txt',
'ok',
- l('.hg/last-message.txt'),
- l('dir/graft-one'),
- l('dir/dir2/graft2'),
- l('f/o/f.oo'),
- l('global/one.txt'),
- l('global/two.txt'),
+ ml('.hg/last-message.txt'),
+ ml('dir/graft-one'),
+ ml('dir/dir2/graft2'),
+ ml('f/o/f.oo'),
+ ml('global/one.txt'),
+ ml('global/two.txt'),
]
file_list.sort()
@@ -475,10 +476,10 @@ class TestFileListTest(TempDirTestCase):
assert False, "Should have thrown an error"
def test_include(self):
- l = make_local_path
+ ml = make_local_path
# include
file_list = FileList()
- self.make_files(['a.py', 'b.txt', l('d/c.py')])
+ self.make_files(['a.py', 'b.txt', ml('d/c.py')])
file_list.process_template_line('include *.py')
file_list.sort()
@@ -491,42 +492,42 @@ class TestFileListTest(TempDirTestCase):
self.assertWarnings()
def test_exclude(self):
- l = make_local_path
+ ml = make_local_path
# exclude
file_list = FileList()
- file_list.files = ['a.py', 'b.txt', l('d/c.py')]
+ file_list.files = ['a.py', 'b.txt', ml('d/c.py')]
file_list.process_template_line('exclude *.py')
file_list.sort()
- assert file_list.files == ['b.txt', l('d/c.py')]
+ assert file_list.files == ['b.txt', ml('d/c.py')]
self.assertNoWarnings()
file_list.process_template_line('exclude *.rb')
file_list.sort()
- assert file_list.files == ['b.txt', l('d/c.py')]
+ assert file_list.files == ['b.txt', ml('d/c.py')]
self.assertWarnings()
def test_global_include(self):
- l = make_local_path
+ ml = make_local_path
# global-include
file_list = FileList()
- self.make_files(['a.py', 'b.txt', l('d/c.py')])
+ self.make_files(['a.py', 'b.txt', ml('d/c.py')])
file_list.process_template_line('global-include *.py')
file_list.sort()
- assert file_list.files == ['a.py', l('d/c.py')]
+ assert file_list.files == ['a.py', ml('d/c.py')]
self.assertNoWarnings()
file_list.process_template_line('global-include *.rb')
file_list.sort()
- assert file_list.files == ['a.py', l('d/c.py')]
+ assert file_list.files == ['a.py', ml('d/c.py')]
self.assertWarnings()
def test_global_exclude(self):
- l = make_local_path
+ ml = make_local_path
# global-exclude
file_list = FileList()
- file_list.files = ['a.py', 'b.txt', l('d/c.py')]
+ file_list.files = ['a.py', 'b.txt', ml('d/c.py')]
file_list.process_template_line('global-exclude *.py')
file_list.sort()
@@ -539,65 +540,65 @@ class TestFileListTest(TempDirTestCase):
self.assertWarnings()
def test_recursive_include(self):
- l = make_local_path
+ ml = make_local_path
# recursive-include
file_list = FileList()
- self.make_files(['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')])
+ self.make_files(['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')])
file_list.process_template_line('recursive-include d *.py')
file_list.sort()
- assert file_list.files == [l('d/b.py'), l('d/d/e.py')]
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
self.assertNoWarnings()
file_list.process_template_line('recursive-include e *.py')
file_list.sort()
- assert file_list.files == [l('d/b.py'), l('d/d/e.py')]
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
self.assertWarnings()
def test_recursive_exclude(self):
- l = make_local_path
+ ml = make_local_path
# recursive-exclude
file_list = FileList()
- file_list.files = ['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')]
+ file_list.files = ['a.py', ml('d/b.py'), ml('d/c.txt'), ml('d/d/e.py')]
file_list.process_template_line('recursive-exclude d *.py')
file_list.sort()
- assert file_list.files == ['a.py', l('d/c.txt')]
+ assert file_list.files == ['a.py', ml('d/c.txt')]
self.assertNoWarnings()
file_list.process_template_line('recursive-exclude e *.py')
file_list.sort()
- assert file_list.files == ['a.py', l('d/c.txt')]
+ assert file_list.files == ['a.py', ml('d/c.txt')]
self.assertWarnings()
def test_graft(self):
- l = make_local_path
+ ml = make_local_path
# graft
file_list = FileList()
- self.make_files(['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')])
+ self.make_files(['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')])
file_list.process_template_line('graft d')
file_list.sort()
- assert file_list.files == [l('d/b.py'), l('d/d/e.py')]
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
self.assertNoWarnings()
file_list.process_template_line('graft e')
file_list.sort()
- assert file_list.files == [l('d/b.py'), l('d/d/e.py')]
+ assert file_list.files == [ml('d/b.py'), ml('d/d/e.py')]
self.assertWarnings()
def test_prune(self):
- l = make_local_path
+ ml = make_local_path
# prune
file_list = FileList()
- file_list.files = ['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')]
+ file_list.files = ['a.py', ml('d/b.py'), ml('d/d/e.py'), ml('f/f.py')]
file_list.process_template_line('prune d')
file_list.sort()
- assert file_list.files == ['a.py', l('f/f.py')]
+ assert file_list.files == ['a.py', ml('f/f.py')]
self.assertNoWarnings()
file_list.process_template_line('prune e')
file_list.sort()
- assert file_list.files == ['a.py', l('f/f.py')]
+ assert file_list.files == ['a.py', ml('f/f.py')]
self.assertWarnings()
diff --git a/setuptools/tests/test_msvc.py b/setuptools/tests/test_msvc.py
index 32d7a907..24e38ea8 100644
--- a/setuptools/tests/test_msvc.py
+++ b/setuptools/tests/test_msvc.py
@@ -49,7 +49,8 @@ def mock_reg(hkcu=None, hklm=None):
for k in hive if k.startswith(key.lower())
)
- return mock.patch.multiple(distutils.msvc9compiler.Reg,
+ return mock.patch.multiple(
+ distutils.msvc9compiler.Reg,
read_keys=read_keys, read_values=read_values)
@@ -61,7 +62,7 @@ class TestModulePatch:
"""
key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir'
- key_64 = r'software\wow6432node\microsoft\devdiv\vcforpython\9.0\installdir'
+ key_64 = key_32.replace(r'\microsoft', r'\wow6432node\microsoft')
def test_patched(self):
"Test the module is actually patched"
diff --git a/setuptools/tests/test_namespaces.py b/setuptools/tests/test_namespaces.py
index 670ccee9..f937d981 100644
--- a/setuptools/tests/test_namespaces.py
+++ b/setuptools/tests/test_namespaces.py
@@ -1,6 +1,5 @@
from __future__ import absolute_import, unicode_literals
-import os
import sys
import subprocess
diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py
index 13cffb7e..60d968fd 100644
--- a/setuptools/tests/test_packageindex.py
+++ b/setuptools/tests/test_packageindex.py
@@ -44,7 +44,10 @@ class TestPackageIndex:
hosts=('www.example.com',)
)
- url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk'
+ url = (
+ 'url:%20https://svn.plone.org/svn'
+ '/collective/inquant.contentmirror.plone/trunk'
+ )
try:
v = index.open_url(url)
except Exception as v:
@@ -63,9 +66,9 @@ class TestPackageIndex:
index.opener = _urlopen
url = 'http://example.com'
try:
- v = index.open_url(url)
- except Exception as v:
- assert 'line' in str(v)
+ index.open_url(url)
+ except Exception as exc:
+ assert 'line' in str(exc)
else:
raise AssertionError('Should have raise here!')
@@ -83,7 +86,11 @@ class TestPackageIndex:
index.open_url(url)
except distutils.errors.DistutilsError as error:
msg = six.text_type(error)
- assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg
+ assert (
+ 'nonnumeric port' in msg
+ or 'getaddrinfo failed' in msg
+ or 'Name or service not known' in msg
+ )
return
raise RuntimeError("Did not raise")
@@ -242,7 +249,7 @@ class TestPackageIndex:
first_call_args = os_system_mock.call_args_list[0][0]
assert first_call_args == (expected,)
- tmpl = '(cd {expected_dir} && git checkout --quiet master)'
+ tmpl = 'git -C {expected_dir} checkout --quiet master'
expected = tmpl.format(**locals())
assert os_system_mock.call_args_list[1][0] == (expected,)
assert result == expected_dir
diff --git a/setuptools/tests/test_pep425tags.py b/setuptools/tests/test_pep425tags.py
deleted file mode 100644
index f558a0d8..00000000
--- a/setuptools/tests/test_pep425tags.py
+++ /dev/null
@@ -1,168 +0,0 @@
-import sys
-
-import pytest
-from mock import patch
-
-from setuptools import pep425tags
-
-__metaclass__ = type
-
-
-class TestPEP425Tags:
-
- def mock_get_config_var(self, **kwd):
- """
- Patch sysconfig.get_config_var for arbitrary keys.
- """
- get_config_var = pep425tags.sysconfig.get_config_var
-
- def _mock_get_config_var(var):
- if var in kwd:
- return kwd[var]
- return get_config_var(var)
- return _mock_get_config_var
-
- def abi_tag_unicode(self, flags, config_vars):
- """
- Used to test ABI tags, verify correct use of the `u` flag
- """
- config_vars.update({'SOABI': None})
- base = pep425tags.get_abbr_impl() + pep425tags.get_impl_ver()
-
- if sys.version_info < (3, 3):
- config_vars.update({'Py_UNICODE_SIZE': 2})
- mock_gcf = self.mock_get_config_var(**config_vars)
- with patch('setuptools.pep425tags.sysconfig.get_config_var', mock_gcf):
- abi_tag = pep425tags.get_abi_tag()
- assert abi_tag == base + flags
-
- config_vars.update({'Py_UNICODE_SIZE': 4})
- mock_gcf = self.mock_get_config_var(**config_vars)
- with patch('setuptools.pep425tags.sysconfig.get_config_var',
- mock_gcf):
- abi_tag = pep425tags.get_abi_tag()
- assert abi_tag == base + flags + 'u'
-
- else:
- # On Python >= 3.3, UCS-4 is essentially permanently enabled, and
- # Py_UNICODE_SIZE is None. SOABI on these builds does not include
- # the 'u' so manual SOABI detection should not do so either.
- config_vars.update({'Py_UNICODE_SIZE': None})
- mock_gcf = self.mock_get_config_var(**config_vars)
- with patch('setuptools.pep425tags.sysconfig.get_config_var',
- mock_gcf):
- abi_tag = pep425tags.get_abi_tag()
- assert abi_tag == base + flags
-
- def test_broken_sysconfig(self):
- """
- Test that pep425tags still works when sysconfig is broken.
- Can be a problem on Python 2.7
- Issue #1074.
- """
- def raises_ioerror(var):
- raise IOError("I have the wrong path!")
-
- with patch('setuptools.pep425tags.sysconfig.get_config_var',
- raises_ioerror):
- with pytest.warns(RuntimeWarning):
- assert len(pep425tags.get_supported())
-
- def test_no_hyphen_tag(self):
- """
- Test that no tag contains a hyphen.
- """
- mock_gcf = self.mock_get_config_var(SOABI='cpython-35m-darwin')
-
- with patch('setuptools.pep425tags.sysconfig.get_config_var',
- mock_gcf):
- supported = pep425tags.get_supported()
-
- for (py, abi, plat) in supported:
- assert '-' not in py
- assert '-' not in abi
- assert '-' not in plat
-
- def test_manual_abi_noflags(self):
- """
- Test that no flags are set on a non-PyDebug, non-Pymalloc ABI tag.
- """
- self.abi_tag_unicode('', {'Py_DEBUG': False, 'WITH_PYMALLOC': False})
-
- def test_manual_abi_d_flag(self):
- """
- Test that the `d` flag is set on a PyDebug, non-Pymalloc ABI tag.
- """
- self.abi_tag_unicode('d', {'Py_DEBUG': True, 'WITH_PYMALLOC': False})
-
- def test_manual_abi_m_flag(self):
- """
- Test that the `m` flag is set on a non-PyDebug, Pymalloc ABI tag.
- """
- self.abi_tag_unicode('m', {'Py_DEBUG': False, 'WITH_PYMALLOC': True})
-
- def test_manual_abi_dm_flags(self):
- """
- Test that the `dm` flags are set on a PyDebug, Pymalloc ABI tag.
- """
- self.abi_tag_unicode('dm', {'Py_DEBUG': True, 'WITH_PYMALLOC': True})
-
-
-class TestManylinux1Tags:
-
- @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64')
- @patch('setuptools.glibc.have_compatible_glibc',
- lambda major, minor: True)
- def test_manylinux1_compatible_on_linux_x86_64(self):
- """
- Test that manylinux1 is enabled on linux_x86_64
- """
- assert pep425tags.is_manylinux1_compatible()
-
- @patch('setuptools.pep425tags.get_platform', lambda: 'linux_i686')
- @patch('setuptools.glibc.have_compatible_glibc',
- lambda major, minor: True)
- def test_manylinux1_compatible_on_linux_i686(self):
- """
- Test that manylinux1 is enabled on linux_i686
- """
- assert pep425tags.is_manylinux1_compatible()
-
- @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64')
- @patch('setuptools.glibc.have_compatible_glibc',
- lambda major, minor: False)
- def test_manylinux1_2(self):
- """
- Test that manylinux1 is disabled with incompatible glibc
- """
- assert not pep425tags.is_manylinux1_compatible()
-
- @patch('setuptools.pep425tags.get_platform', lambda: 'arm6vl')
- @patch('setuptools.glibc.have_compatible_glibc',
- lambda major, minor: True)
- def test_manylinux1_3(self):
- """
- Test that manylinux1 is disabled on arm6vl
- """
- assert not pep425tags.is_manylinux1_compatible()
-
- @patch('setuptools.pep425tags.get_platform', lambda: 'linux_x86_64')
- @patch('setuptools.glibc.have_compatible_glibc',
- lambda major, minor: True)
- @patch('sys.platform', 'linux2')
- def test_manylinux1_tag_is_first(self):
- """
- Test that the more specific tag manylinux1 comes first.
- """
- groups = {}
- for pyimpl, abi, arch in pep425tags.get_supported():
- groups.setdefault((pyimpl, abi), []).append(arch)
-
- for arches in groups.values():
- if arches == ['any']:
- continue
- # Expect the most specific arch first:
- if len(arches) == 3:
- assert arches == ['manylinux1_x86_64', 'linux_x86_64', 'any']
- else:
- assert arches == ['manylinux1_x86_64', 'linux_x86_64']
diff --git a/setuptools/tests/test_register.py b/setuptools/tests/test_register.py
index 96114595..98605806 100644
--- a/setuptools/tests/test_register.py
+++ b/setuptools/tests/test_register.py
@@ -1,43 +1,22 @@
-import mock
-from distutils import log
-
-import pytest
-
from setuptools.command.register import register
from setuptools.dist import Distribution
+from setuptools.errors import RemovedCommandError
+try:
+ from unittest import mock
+except ImportError:
+ import mock
-class TestRegisterTest:
- def test_warns_deprecation(self):
- dist = Distribution()
-
- cmd = register(dist)
- cmd.run_command = mock.Mock()
- cmd.send_metadata = mock.Mock()
- cmd.announce = mock.Mock()
-
- cmd.run()
+import pytest
- cmd.announce.assert_called_with(
- "WARNING: Registering is deprecated, use twine to upload instead "
- "(https://pypi.org/p/twine/)",
- log.WARN
- )
- def test_warns_deprecation_when_raising(self):
+class TestRegister:
+ def test_register_exception(self):
+ """Ensure that the register command has been properly removed."""
dist = Distribution()
+ dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
cmd = register(dist)
- cmd.run_command = mock.Mock()
- cmd.send_metadata = mock.Mock()
- cmd.send_metadata.side_effect = Exception
- cmd.announce = mock.Mock()
- with pytest.raises(Exception):
+ with pytest.raises(RemovedCommandError):
cmd.run()
-
- cmd.announce.assert_called_with(
- "WARNING: Registering is deprecated, use twine to upload instead "
- "(https://pypi.org/p/twine/)",
- log.WARN
- )
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
index d8675422..99398cdb 100644
--- a/setuptools/tests/test_sandbox.py
+++ b/setuptools/tests/test_sandbox.py
@@ -26,7 +26,8 @@ class TestSandbox:
"""
It should be possible to execute a setup.py with a Byte Order Mark
"""
- target = pkg_resources.resource_filename(__name__,
+ target = pkg_resources.resource_filename(
+ __name__,
'script-with-bom.py')
namespace = types.ModuleType('namespace')
setuptools.sandbox._execfile(target, vars(namespace))
diff --git a/setuptools/tests/test_setopt.py b/setuptools/tests/test_setopt.py
new file mode 100644
index 00000000..3fb04fb4
--- /dev/null
+++ b/setuptools/tests/test_setopt.py
@@ -0,0 +1,36 @@
+# coding: utf-8
+
+from __future__ import unicode_literals
+
+import io
+
+import six
+
+from setuptools.command import setopt
+from setuptools.extern.six.moves import configparser
+
+
+class TestEdit:
+ @staticmethod
+ def parse_config(filename):
+ parser = configparser.ConfigParser()
+ with io.open(filename, encoding='utf-8') as reader:
+ (parser.read_file if six.PY3 else parser.readfp)(reader)
+ return parser
+
+ @staticmethod
+ def write_text(file, content):
+ with io.open(file, 'wb') as strm:
+ strm.write(content.encode('utf-8'))
+
+ def test_utf8_encoding_retained(self, tmpdir):
+ """
+ When editing a file, non-ASCII characters encoded in
+ UTF-8 should be retained.
+ """
+ config = tmpdir.join('setup.cfg')
+ self.write_text(str(config), '[names]\njaraco=джарако')
+ setopt.edit_config(str(config), dict(names=dict(other='yes')))
+ parser = self.parse_config(str(config))
+ assert parser.get('names', 'jaraco') == 'джарако'
+ assert parser.get('names', 'other') == 'yes'
diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py
index 7aae3a16..5896a69a 100644
--- a/setuptools/tests/test_setuptools.py
+++ b/setuptools/tests/test_setuptools.py
@@ -77,7 +77,8 @@ class TestDepends:
from json import __version__
assert dep.get_module_constant('json', '__version__') == __version__
assert dep.get_module_constant('sys', 'version') == sys.version
- assert dep.get_module_constant('setuptools.tests.test_setuptools', '__doc__') == __doc__
+ assert dep.get_module_constant(
+ 'setuptools.tests.test_setuptools', '__doc__') == __doc__
@needs_bytecode
def testRequire(self):
@@ -216,7 +217,8 @@ class TestFeatures:
self.req = Require('Distutils', '1.0.3', 'distutils')
self.dist = makeSetup(
features={
- 'foo': Feature("foo", standard=True, require_features=['baz', self.req]),
+ 'foo': Feature(
+ "foo", standard=True, require_features=['baz', self.req]),
'bar': Feature("bar", standard=True, packages=['pkg.bar'],
py_modules=['bar_et'], remove=['bar.ext'],
),
@@ -252,7 +254,8 @@ class TestFeatures:
('with-dwim', None, 'include DWIM') in dist.feature_options
)
assert (
- ('without-dwim', None, 'exclude DWIM (default)') in dist.feature_options
+ ('without-dwim', None, 'exclude DWIM (default)')
+ in dist.feature_options
)
assert (
('with-bar', None, 'include bar (default)') in dist.feature_options
diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py
index 8d1425e1..6242a018 100644
--- a/setuptools/tests/test_test.py
+++ b/setuptools/tests/test_test.py
@@ -1,10 +1,10 @@
-# -*- coding: UTF-8 -*-
+# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import mock
from distutils import log
import os
-import sys
import pytest
@@ -86,9 +86,7 @@ def test_test(capfd):
dist.script_name = 'setup.py'
cmd = test(dist)
cmd.ensure_finalized()
- # The test runner calls sys.exit
- with contexts.suppress_exceptions(SystemExit):
- cmd.run()
+ cmd.run()
out, err = capfd.readouterr()
assert out == 'Foo\n'
@@ -120,8 +118,55 @@ def test_tests_are_run_once(capfd):
dist.script_name = 'setup.py'
cmd = test(dist)
cmd.ensure_finalized()
- # The test runner calls sys.exit
- with contexts.suppress_exceptions(SystemExit):
- cmd.run()
+ cmd.run()
out, err = capfd.readouterr()
assert out == 'Foo\n'
+
+
+@pytest.mark.usefixtures('sample_test')
+def test_warns_deprecation(capfd):
+ params = dict(
+ name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+ use_2to3=True
+ )
+ dist = Distribution(params)
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.ensure_finalized()
+ cmd.announce = mock.Mock()
+ cmd.run()
+ capfd.readouterr()
+ msg = (
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox."
+ )
+ cmd.announce.assert_any_call(msg, log.WARN)
+
+
+@pytest.mark.usefixtures('sample_test')
+def test_deprecation_stderr(capfd):
+ params = dict(
+ name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+ use_2to3=True
+ )
+ dist = Distribution(params)
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.ensure_finalized()
+ cmd.run()
+ out, err = capfd.readouterr()
+ msg = (
+ "WARNING: Testing via this command is deprecated and will be "
+ "removed in a future version. Users looking for a generic test "
+ "entry point independent of test runner are encouraged to use "
+ "tox.\n"
+ )
+ assert msg in err
diff --git a/setuptools/tests/test_upload.py b/setuptools/tests/test_upload.py
index 320c6959..7586cb26 100644
--- a/setuptools/tests/test_upload.py
+++ b/setuptools/tests/test_upload.py
@@ -1,213 +1,22 @@
-import mock
-import os
-import re
-
-from distutils import log
-from distutils.errors import DistutilsError
-
-import pytest
-
from setuptools.command.upload import upload
from setuptools.dist import Distribution
-from setuptools.extern import six
-
-
-def _parse_upload_body(body):
- boundary = u'\r\n----------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- entries = []
- name_re = re.compile(u'^Content-Disposition: form-data; name="([^\"]+)"')
-
- for entry in body.split(boundary):
- pair = entry.split(u'\r\n\r\n')
- if not len(pair) == 2:
- continue
-
- key, value = map(six.text_type.strip, pair)
- m = name_re.match(key)
- if m is not None:
- key = m.group(1)
-
- entries.append((key, value))
-
- return entries
-
-
-@pytest.fixture
-def patched_upload(tmpdir):
- class Fix:
- def __init__(self, cmd, urlopen):
- self.cmd = cmd
- self.urlopen = urlopen
-
- def __iter__(self):
- return iter((self.cmd, self.urlopen))
-
- def get_uploaded_metadata(self):
- request = self.urlopen.call_args_list[0][0][0]
- body = request.data.decode('utf-8')
- entries = dict(_parse_upload_body(body))
-
- return entries
+from setuptools.errors import RemovedCommandError
- class ResponseMock(mock.Mock):
- def getheader(self, name, default=None):
- """Mocked getheader method for response object"""
- return {
- 'content-type': 'text/plain; charset=utf-8',
- }.get(name.lower(), default)
+try:
+ from unittest import mock
+except ImportError:
+ import mock
- with mock.patch('setuptools.command.upload.urlopen') as urlopen:
- urlopen.return_value = ResponseMock()
- urlopen.return_value.getcode.return_value = 200
- urlopen.return_value.read.return_value = b''
-
- content = os.path.join(str(tmpdir), "content_data")
-
- with open(content, 'w') as f:
- f.write("Some content")
-
- dist = Distribution()
- dist.dist_files = [('sdist', '3.7.0', content)]
-
- cmd = upload(dist)
- cmd.announce = mock.Mock()
- cmd.username = 'user'
- cmd.password = 'hunter2'
-
- yield Fix(cmd, urlopen)
-
-
-class TestUploadTest:
- def test_upload_metadata(self, patched_upload):
- cmd, patch = patched_upload
-
- # Set the metadata version to 2.1
- cmd.distribution.metadata.metadata_version = '2.1'
-
- # Run the command
- cmd.ensure_finalized()
- cmd.run()
-
- # Make sure we did the upload
- patch.assert_called_once()
-
- # Make sure the metadata version is correct in the headers
- entries = patched_upload.get_uploaded_metadata()
- assert entries['metadata_version'] == '2.1'
-
- def test_warns_deprecation(self):
- dist = Distribution()
- dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
-
- cmd = upload(dist)
- cmd.upload_file = mock.Mock()
- cmd.announce = mock.Mock()
-
- cmd.run()
+import pytest
- cmd.announce.assert_called_once_with(
- "WARNING: Uploading via this command is deprecated, use twine to "
- "upload instead (https://pypi.org/p/twine/)",
- log.WARN
- )
- def test_warns_deprecation_when_raising(self):
+class TestUpload:
+ def test_upload_exception(self):
+ """Ensure that the register command has been properly removed."""
dist = Distribution()
dist.dist_files = [(mock.Mock(), mock.Mock(), mock.Mock())]
cmd = upload(dist)
- cmd.upload_file = mock.Mock()
- cmd.upload_file.side_effect = Exception
- cmd.announce = mock.Mock()
-
- with pytest.raises(Exception):
- cmd.run()
-
- cmd.announce.assert_called_once_with(
- "WARNING: Uploading via this command is deprecated, use twine to "
- "upload instead (https://pypi.org/p/twine/)",
- log.WARN
- )
-
- @pytest.mark.parametrize('url', [
- 'https://example.com/a;parameter', # Has parameters
- 'https://example.com/a?query', # Has query
- 'https://example.com/a#fragment', # Has fragment
- 'ftp://example.com', # Invalid scheme
-
- ])
- def test_upload_file_invalid_url(self, url, patched_upload):
- patched_upload.urlopen.side_effect = Exception("Should not be reached")
-
- cmd = patched_upload.cmd
- cmd.repository = url
-
- cmd.ensure_finalized()
- with pytest.raises(AssertionError):
- cmd.run()
-
- def test_upload_file_http_error(self, patched_upload):
- patched_upload.urlopen.side_effect = six.moves.urllib.error.HTTPError(
- 'https://example.com',
- 404,
- 'File not found',
- None,
- None
- )
-
- cmd = patched_upload.cmd
- cmd.ensure_finalized()
- with pytest.raises(DistutilsError):
+ with pytest.raises(RemovedCommandError):
cmd.run()
-
- cmd.announce.assert_any_call(
- 'Upload failed (404): File not found',
- log.ERROR)
-
- def test_upload_file_os_error(self, patched_upload):
- patched_upload.urlopen.side_effect = OSError("Invalid")
-
- cmd = patched_upload.cmd
- cmd.ensure_finalized()
-
- with pytest.raises(OSError):
- cmd.run()
-
- cmd.announce.assert_any_call('Invalid', log.ERROR)
-
- @mock.patch('setuptools.command.upload.spawn')
- def test_upload_file_gpg(self, spawn, patched_upload):
- cmd, urlopen = patched_upload
-
- cmd.sign = True
- cmd.identity = "Alice"
- cmd.dry_run = True
- content_fname = cmd.distribution.dist_files[0][2]
- signed_file = content_fname + '.asc'
-
- with open(signed_file, 'wb') as f:
- f.write("signed-data".encode('utf-8'))
-
- cmd.ensure_finalized()
- cmd.run()
-
- # Make sure that GPG was called
- spawn.assert_called_once_with([
- "gpg", "--detach-sign", "--local-user", "Alice", "-a",
- content_fname
- ], dry_run=True)
-
- # Read the 'signed' data that was transmitted
- entries = patched_upload.get_uploaded_metadata()
- assert entries['gpg_signature'] == 'signed-data'
-
- def test_show_response_no_error(self, patched_upload):
- # This test is just that show_response doesn't throw an error
- # It is not really important what the printed response looks like
- # in a deprecated command, but we don't want to introduce new
- # errors when importing this function from distutils
-
- patched_upload.cmd.show_response = True
- patched_upload.cmd.ensure_finalized()
- patched_upload.cmd.run()
diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py
index 7b5fea17..cd3d9313 100644
--- a/setuptools/tests/test_virtualenv.py
+++ b/setuptools/tests/test_virtualenv.py
@@ -1,4 +1,3 @@
-import distutils.command
import glob
import os
import sys
@@ -9,6 +8,8 @@ from pytest_fixture_config import yield_requires_config
import pytest_virtualenv
+from setuptools.extern import six
+
from .textwrap import DALS
from .test_easy_install import make_nspkg_sdist
@@ -53,10 +54,58 @@ def test_clean_env_install(bare_virtualenv):
)).format(source=SOURCE_DIR))
-def test_pip_upgrade_from_source(virtualenv):
+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
+ 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
+
+ network_versions = [
+ 'pip==9.0.3',
+ 'pip==10.0.1',
+ 'pip==18.1',
+ 'pip==19.0.1',
+ ]
+
+ # Pip's master dropped support for 3.4.
+ if not six.PY34:
+ network_versions.append('https://github.com/pypa/pip/archive/master.zip')
+
+ versions = [None] + [
+ pytest.param(v, **({} if network else {'marks': pytest.mark.skip}))
+ for v in network_versions
+ ]
+
+ return versions
+
+
+@pytest.mark.parametrize('pip_version', _get_pip_versions())
+def test_pip_upgrade_from_source(pip_version, virtualenv):
"""
Check pip can upgrade setuptools from source.
"""
+ # Install pip/wheel, and remove setuptools (as it
+ # should not be needed for bootstraping from source)
+ if pip_version is None:
+ upgrade_pip = ()
+ else:
+ upgrade_pip = ('python -m pip install -U {pip_version} --retries=1',)
+ virtualenv.run(' && '.join((
+ 'pip uninstall -y setuptools',
+ 'pip install -U wheel',
+ ) + upgrade_pip).format(pip_version=pip_version))
dist_dir = virtualenv.workspace
# Generate source distribution / wheel.
virtualenv.run(' && '.join((
@@ -72,14 +121,12 @@ def test_pip_upgrade_from_source(virtualenv):
virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist)
-def test_test_command_install_requirements(bare_virtualenv, tmpdir):
+def _check_test_command_install_requirements(virtualenv, tmpdir):
"""
Check the test command will install all required dependencies.
"""
- bare_virtualenv.run(' && '.join((
- 'cd {source}',
- 'python setup.py develop',
- )).format(source=SOURCE_DIR))
+ # Install setuptools.
+ virtualenv.run('python setup.py develop', cd=SOURCE_DIR)
def sdist(distname, version):
dist_path = tmpdir.join('%s-%s.tar.gz' % (distname, version))
@@ -130,18 +177,26 @@ def test_test_command_install_requirements(bare_virtualenv, tmpdir):
open('success', 'w').close()
'''))
# Run test command for test package.
- bare_virtualenv.run(' && '.join((
+ virtualenv.run(' && '.join((
'cd {tmpdir}',
'python setup.py test -s test',
)).format(tmpdir=tmpdir))
assert tmpdir.join('success').check()
+def test_test_command_install_requirements(virtualenv, tmpdir):
+ # Ensure pip/wheel packages are installed.
+ virtualenv.run("python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"")
+ _check_test_command_install_requirements(virtualenv, tmpdir)
+
+def test_test_command_install_requirements_when_using_easy_install(bare_virtualenv, tmpdir):
+ _check_test_command_install_requirements(bare_virtualenv, tmpdir)
+
def test_no_missing_dependencies(bare_virtualenv):
"""
Quick and dirty test to ensure all external dependencies are vendored.
"""
- for command in ('upload',):#sorted(distutils.command.__all__):
+ for command in ('upload',): # sorted(distutils.command.__all__):
bare_virtualenv.run(' && '.join((
'cd {source}',
'python setup.py {command} -h',
diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py
index 6db5fa11..55d346c6 100644
--- a/setuptools/tests/test_wheel.py
+++ b/setuptools/tests/test_wheel.py
@@ -18,6 +18,7 @@ import pytest
from pkg_resources import Distribution, PathMetadata, PY_MAJOR
from setuptools.extern.packaging.utils import canonicalize_name
+from setuptools.extern.packaging.tags import parse_tag
from setuptools.wheel import Wheel
from .contexts import tempdir
@@ -63,6 +64,7 @@ WHEEL_INFO_TESTS = (
}),
)
+
@pytest.mark.parametrize(
('filename', 'info'), WHEEL_INFO_TESTS,
ids=[t[0] for t in WHEEL_INFO_TESTS]
@@ -450,6 +452,34 @@ WHEEL_INSTALL_TESTS = (
),
dict(
+ id='empty_namespace_package',
+ file_defs={
+ 'foobar': {
+ '__init__.py': "__import__('pkg_resources').declare_namespace(__name__)",
+ },
+ },
+ setup_kwargs=dict(
+ namespace_packages=['foobar'],
+ packages=['foobar'],
+ ),
+ install_tree=flatten_tree({
+ 'foo-1.0-py{py_version}.egg': [
+ 'foo-1.0-py{py_version}-nspkg.pth',
+ {'EGG-INFO': [
+ 'PKG-INFO',
+ 'RECORD',
+ 'WHEEL',
+ 'namespace_packages.txt',
+ 'top_level.txt',
+ ]},
+ {'foobar': [
+ '__init__.py',
+ ]},
+ ]
+ }),
+ ),
+
+ dict(
id='data_in_package',
file_defs={
'foo': {
@@ -487,6 +517,7 @@ WHEEL_INSTALL_TESTS = (
)
+
@pytest.mark.parametrize(
'params', WHEEL_INSTALL_TESTS,
ids=list(params['id'] for params in WHEEL_INSTALL_TESTS),
@@ -541,3 +572,11 @@ def test_wheel_no_dist_dir():
_check_wheel_install(wheel_path, install_dir, None,
project_name,
version, None)
+
+
+def test_wheel_is_compatible(monkeypatch):
+ def sys_tags():
+ for t in parse_tag('cp36-cp36m-manylinux1_x86_64'):
+ yield t
+ monkeypatch.setattr('setuptools.wheel.sys_tags', sys_tags)
+ assert Wheel('onnxruntime-0.1.2-cp36-cp36m-manylinux1_x86_64.whl').is_compatible()
diff --git a/setuptools/tests/test_windows_wrappers.py b/setuptools/tests/test_windows_wrappers.py
index d2871c0f..2553394a 100644
--- a/setuptools/tests/test_windows_wrappers.py
+++ b/setuptools/tests/test_windows_wrappers.py
@@ -97,7 +97,8 @@ class TestCLI(WrapperTester):
'arg 4\\',
'arg5 a\\\\b',
]
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ proc = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii'))
actual = stdout.decode('ascii').replace('\r\n', '\n')
expected = textwrap.dedent(r"""
@@ -134,7 +135,11 @@ class TestCLI(WrapperTester):
with (tmpdir / 'foo-script.py').open('w') as f:
f.write(self.prep_script(tmpl))
cmd = [str(tmpdir / 'foo.exe')]
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
+ proc = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
stdout, stderr = proc.communicate()
actual = stdout.decode('ascii').replace('\r\n', '\n')
expected = textwrap.dedent(r"""
@@ -172,7 +177,9 @@ class TestGUI(WrapperTester):
str(tmpdir / 'test_output.txt'),
'Test Argument',
]
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
+ proc = subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
stdout, stderr = proc.communicate()
assert not stdout
assert not stderr
diff --git a/setuptools/wheel.py b/setuptools/wheel.py
index e11f0a1d..025aaa82 100644
--- a/setuptools/wheel.py
+++ b/setuptools/wheel.py
@@ -1,6 +1,7 @@
"""Wheels support."""
from distutils.util import get_platform
+from distutils import log
import email
import itertools
import os
@@ -11,9 +12,9 @@ import zipfile
import pkg_resources
import setuptools
from pkg_resources import parse_version
+from setuptools.extern.packaging.tags import sys_tags
from setuptools.extern.packaging.utils import canonicalize_name
from setuptools.extern.six import PY3
-from setuptools import pep425tags
from setuptools.command.egg_info import write_requirements
@@ -76,7 +77,7 @@ class Wheel:
def is_compatible(self):
'''Is the wheel is compatible with the current platform?'''
- supported_tags = pep425tags.get_supported()
+ supported_tags = set((t.interpreter, t.abi, t.platform) for t in sys_tags())
return next((True for t in self.tags() if t in supported_tags), False)
def egg_name(self):
@@ -162,11 +163,17 @@ class Wheel:
extras_require=extras_require,
),
)
- write_requirements(
- setup_dist.get_command_obj('egg_info'),
- None,
- os.path.join(egg_info, 'requires.txt'),
- )
+ # Temporarily disable info traces.
+ log_threshold = log._global_log.threshold
+ log.set_threshold(log.WARN)
+ try:
+ write_requirements(
+ setup_dist.get_command_obj('egg_info'),
+ None,
+ os.path.join(egg_info, 'requires.txt'),
+ )
+ finally:
+ log.set_threshold(log_threshold)
@staticmethod
def _move_data_entries(destination_eggdir, dist_data):
@@ -206,6 +213,8 @@ class Wheel:
for mod in namespace_packages:
mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
mod_init = os.path.join(mod_dir, '__init__.py')
- if os.path.exists(mod_dir) and not os.path.exists(mod_init):
+ if not os.path.exists(mod_dir):
+ os.mkdir(mod_dir)
+ if not os.path.exists(mod_init):
with open(mod_init, 'w') as fp:
fp.write(NAMESPACE_PACKAGE_INIT)
diff --git a/tests/manual_test.py b/tests/manual_test.py
deleted file mode 100644
index 99db4b01..00000000
--- a/tests/manual_test.py
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-import os
-import shutil
-import tempfile
-import subprocess
-from distutils.command.install import INSTALL_SCHEMES
-from string import Template
-
-from setuptools.extern.six.moves import urllib
-
-
-def _system_call(*args):
- assert subprocess.call(args) == 0
-
-
-def tempdir(func):
- def _tempdir(*args, **kwargs):
- test_dir = tempfile.mkdtemp()
- old_dir = os.getcwd()
- os.chdir(test_dir)
- try:
- return func(*args, **kwargs)
- finally:
- os.chdir(old_dir)
- shutil.rmtree(test_dir)
-
- return _tempdir
-
-
-SIMPLE_BUILDOUT = """\
-[buildout]
-
-parts = eggs
-
-[eggs]
-recipe = zc.recipe.egg
-
-eggs =
- extensions
-"""
-
-BOOTSTRAP = 'http://downloads.buildout.org/1/bootstrap.py'
-PYVER = sys.version.split()[0][:3]
-
-_VARS = {'base': '.',
- 'py_version_short': PYVER}
-
-scheme = 'nt' if sys.platform == 'win32' else 'unix_prefix'
-PURELIB = INSTALL_SCHEMES[scheme]['purelib']
-
-
-@tempdir
-def test_virtualenv():
- """virtualenv with setuptools"""
- purelib = os.path.abspath(Template(PURELIB).substitute(**_VARS))
- _system_call('virtualenv', '--no-site-packages', '.')
- _system_call('bin/easy_install', 'setuptools==dev')
- # linux specific
- site_pkg = os.listdir(purelib)
- site_pkg.sort()
- assert 'setuptools' in site_pkg[0]
- easy_install = os.path.join(purelib, 'easy-install.pth')
- with open(easy_install) as f:
- res = f.read()
- assert 'setuptools' in res
-
-
-@tempdir
-def test_full():
- """virtualenv + pip + buildout"""
- _system_call('virtualenv', '--no-site-packages', '.')
- _system_call('bin/easy_install', '-q', 'setuptools==dev')
- _system_call('bin/easy_install', '-qU', 'setuptools==dev')
- _system_call('bin/easy_install', '-q', 'pip')
- _system_call('bin/pip', 'install', '-q', 'zc.buildout')
-
- with open('buildout.cfg', 'w') as f:
- f.write(SIMPLE_BUILDOUT)
-
- with open('bootstrap.py', 'w') as f:
- f.write(urllib.request.urlopen(BOOTSTRAP).read())
-
- _system_call('bin/python', 'bootstrap.py')
- _system_call('bin/buildout', '-q')
- eggs = os.listdir('eggs')
- eggs.sort()
- assert len(eggs) == 3
- assert eggs[1].startswith('setuptools')
- del eggs[1]
- assert eggs == [
- 'extensions-0.3-py2.6.egg',
- 'zc.recipe.egg-1.2.2-py2.6.egg',
- ]
-
-
-if __name__ == '__main__':
- test_virtualenv()
- test_full()
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 0c6c3e59..1f8bd19d 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -3,9 +3,10 @@ pytest-flake8; python_version!="3.4"
pytest-flake8<=1.0.0; python_version=="3.4"
virtualenv>=13.0.0
pytest-virtualenv>=1.2.7
-pytest>=3.0.2
+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.
diff --git a/tools/tox_pip.py b/tools/tox_pip.py
new file mode 100644
index 00000000..63518f92
--- /dev/null
+++ b/tools/tox_pip.py
@@ -0,0 +1,32 @@
+import os
+import shutil
+import subprocess
+import sys
+from glob import glob
+
+VIRTUAL_ENV = os.environ['VIRTUAL_ENV']
+TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, 'pip')
+
+
+def pip(args):
+ # First things first, get a recent (stable) version of pip.
+ if not os.path.exists(TOX_PIP_DIR):
+ subprocess.check_call([sys.executable, '-m', 'pip',
+ '--disable-pip-version-check',
+ 'install', '-t', TOX_PIP_DIR,
+ 'pip'])
+ shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, 'pip-*.dist-info'))[0])
+ # And use that version.
+ pypath = os.environ.get('PYTHONPATH')
+ pypath = pypath.split(os.pathsep) if pypath is not None else []
+ pypath.insert(0, TOX_PIP_DIR)
+ os.environ['PYTHONPATH'] = os.pathsep.join(pypath)
+ # Fix call for setuptools editable install.
+ for n, a in enumerate(args):
+ if a == '.':
+ args[n] = os.getcwd()
+ subprocess.check_call([sys.executable, '-m', 'pip'] + args, cwd=TOX_PIP_DIR)
+
+
+if __name__ == '__main__':
+ pip(sys.argv[1:])
diff --git a/tox.ini b/tox.ini
index a2f850df..6d3b9a9b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -7,18 +7,20 @@
[tox]
envlist=python
+[helpers]
+# Wrapper for calls to pip that make sure the version being used is a
+# up-to-date, and to prevent the current working directory from being
+# added to `sys.path`.
+pip = python {toxinidir}/tools/tox_pip.py
+
[testenv]
-deps=-rtests/requirements.txt
-# Changed from default (`python -m pip ...`)
-# to prevent the current working directory
-# from being added to `sys.path`.
-install_command={envbindir}/pip install {opts} {packages}
-# Same as above.
-list_dependencies_command={envbindir}/pip freeze
+deps=-r{toxinidir}/tests/requirements.txt
+install_command = {[helpers]pip} install {opts} {packages}
+list_dependencies_command = {[helpers]pip} freeze --all
setenv=COVERAGE_FILE={toxworkdir}/.coverage.{envname}
# TODO: The passed environment variables came from copying other tox.ini files
# These should probably be individually annotated to explain what needs them.
-passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_*
+passenv=APPDATA HOMEDRIVE HOMEPATH windir APPVEYOR APPVEYOR_* CI CODECOV_* TRAVIS TRAVIS_* NETWORK_REQUIRED
commands=pytest --cov-config={toxinidir}/tox.ini --cov-report= {posargs}
usedevelop=True
@@ -53,3 +55,22 @@ source=
setuptools
omit=
*/_vendor/*
+
+[testenv:release]
+skip_install = True
+deps =
+ wheel
+ twine[keyring]>=1.13
+ path
+ jaraco.tidelift
+passenv =
+ TWINE_PASSWORD
+ TIDELIFT_TOKEN
+setenv =
+ TWINE_USERNAME = {env:TWINE_USERNAME:__token__}
+commands =
+ python -m bootstrap
+ python -c "import path; path.Path('dist').rmtree_p()"
+ python setup.py release
+ python -m twine upload dist/*
+ python -m jaraco.tidelift.publish-release-notes